diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..a7ec3b3c --- /dev/null +++ b/404.html @@ -0,0 +1 @@ + gNMIc

404 - Not found

\ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..030df928 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +gnmic.openconfig.net \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 00000000..1cf13b9f Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.f758a944.min.js b/assets/javascripts/bundle.f758a944.min.js new file mode 100644 index 00000000..d8a7ef9e --- /dev/null +++ b/assets/javascripts/bundle.f758a944.min.js @@ -0,0 +1,29 @@ +(()=>{var ra=Object.create;var xr=Object.defineProperty;var na=Object.getOwnPropertyDescriptor;var oa=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,ia=Object.getPrototypeOf,Sr=Object.prototype.hasOwnProperty,sn=Object.prototype.propertyIsEnumerable;var an=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,U=(e,t)=>{for(var r in t||(t={}))Sr.call(t,r)&&an(e,r,t[r]);if(kt)for(var r of kt(t))sn.call(t,r)&&an(e,r,t[r]);return e};var cn=(e,t)=>{var r={};for(var n in e)Sr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&sn.call(e,n)&&(r[n]=e[n]);return r};var gt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var aa=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of oa(t))!Sr.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=na(t,o))||n.enumerable});return e};var Ye=(e,t,r)=>(r=e!=null?ra(ia(e)):{},aa(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var un=gt((wr,fn)=>{(function(e,t){typeof wr=="object"&&typeof fn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(w){return!!(w&&w!==document&&w.nodeName!=="HTML"&&w.nodeName!=="BODY"&&"classList"in w&&"contains"in w.classList)}function c(w){var Ue=w.type,He=w.tagName;return!!(He==="INPUT"&&a[Ue]&&!w.readOnly||He==="TEXTAREA"&&!w.readOnly||w.isContentEditable)}function f(w){w.classList.contains("focus-visible")||(w.classList.add("focus-visible"),w.setAttribute("data-focus-visible-added",""))}function u(w){!w.hasAttribute("data-focus-visible-added")||(w.classList.remove("focus-visible"),w.removeAttribute("data-focus-visible-added"))}function p(w){w.metaKey||w.altKey||w.ctrlKey||(s(r.activeElement)&&f(r.activeElement),n=!0)}function l(w){n=!1}function d(w){!s(w.target)||(n||c(w.target))&&f(w.target)}function h(w){!s(w.target)||(w.target.classList.contains("focus-visible")||w.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(w.target))}function b(w){document.visibilityState==="hidden"&&(o&&(n=!0),F())}function F(){document.addEventListener("mousemove",W),document.addEventListener("mousedown",W),document.addEventListener("mouseup",W),document.addEventListener("pointermove",W),document.addEventListener("pointerdown",W),document.addEventListener("pointerup",W),document.addEventListener("touchmove",W),document.addEventListener("touchstart",W),document.addEventListener("touchend",W)}function G(){document.removeEventListener("mousemove",W),document.removeEventListener("mousedown",W),document.removeEventListener("mouseup",W),document.removeEventListener("pointermove",W),document.removeEventListener("pointerdown",W),document.removeEventListener("pointerup",W),document.removeEventListener("touchmove",W),document.removeEventListener("touchstart",W),document.removeEventListener("touchend",W)}function W(w){w.target.nodeName&&w.target.nodeName.toLowerCase()==="html"||(n=!1,G())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",l,!0),document.addEventListener("pointerdown",l,!0),document.addEventListener("touchstart",l,!0),document.addEventListener("visibilitychange",b,!0),F(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var pn=gt(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(f){return!1}},r=t(),n=function(f){var u={next:function(){var p=f.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(f){return encodeURIComponent(f).replace(/%20/g,"+")},i=function(f){return decodeURIComponent(String(f).replace(/\+/g," "))},a=function(){var f=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var l=typeof p;if(l!=="undefined")if(l==="string")p!==""&&this._fromString(p);else if(p instanceof f){var d=this;p.forEach(function(G,W){d.append(W,G)})}else if(p!==null&&l==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),f._entries&&(f._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(c,f){typeof c!="string"&&(c=String(c)),f&&typeof f!="string"&&(f=String(f));var u=document,p;if(f&&(e.location===void 0||f!==e.location.href)){f=f.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=f,u.head.appendChild(p);try{if(p.href.indexOf(f)!==0)throw new Error(p.href)}catch(w){throw new Error("URL unable to set base "+f+" due to "+w)}}var l=u.createElement("a");l.href=c,p&&(u.body.appendChild(l),l.href=l.href);var d=u.createElement("input");if(d.type="url",d.value=c,l.protocol===":"||!/:/.test(l.href)||!d.checkValidity()&&!f)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:l});var h=new e.URLSearchParams(this.search),b=!0,F=!0,G=this;["append","delete","set"].forEach(function(w){var Ue=h[w];h[w]=function(){Ue.apply(h,arguments),b&&(F=!1,G.search=h.toString(),F=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var W=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==W&&(W=this.search,F&&(b=!1,this.searchParams._fromString(this.search),b=!0))}})},a=i.prototype,s=function(c){Object.defineProperty(a,c,{get:function(){return this._anchorElement[c]},set:function(f){this._anchorElement[c]=f},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(c){s(c)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(c){this._anchorElement.search=c,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var c=this;return function(){return c.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(c){this._anchorElement.href=c,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(c){this._anchorElement.pathname=c},enumerable:!0},origin:{get:function(){var c={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],f=this._anchorElement.port!=c&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(f?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(c){},enumerable:!0},username:{get:function(){return""},set:function(c){},enumerable:!0}}),i.createObjectURL=function(c){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(c){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var kn=gt((Ds,It)=>{/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */var ln,mn,dn,hn,bn,vn,gn,yn,xn,Ht,Or,Sn,wn,En,tt,On,_n,Tn,Mn,Ln,An,Cn,Rn,Pt;(function(e){var t=typeof global=="object"?global:typeof self=="object"?self:typeof this=="object"?this:{};typeof define=="function"&&define.amd?define("tslib",["exports"],function(n){e(r(t,r(n)))}):typeof It=="object"&&typeof It.exports=="object"?e(r(t,r(It.exports))):e(r(t));function r(n,o){return n!==t&&(typeof Object.create=="function"?Object.defineProperty(n,"__esModule",{value:!0}):n.__esModule=!0),function(i,a){return n[i]=o?o(i,a):a}}})(function(e){var t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,o){n.__proto__=o}||function(n,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(n[i]=o[i])};ln=function(n,o){if(typeof o!="function"&&o!==null)throw new TypeError("Class extends value "+String(o)+" is not a constructor or null");t(n,o);function i(){this.constructor=n}n.prototype=o===null?Object.create(o):(i.prototype=o.prototype,new i)},mn=Object.assign||function(n){for(var o,i=1,a=arguments.length;i=0;u--)(f=n[u])&&(c=(s<3?f(c):s>3?f(o,i,c):f(o,i))||c);return s>3&&c&&Object.defineProperty(o,i,c),c},bn=function(n,o){return function(i,a){o(i,a,n)}},vn=function(n,o){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(n,o)},gn=function(n,o,i,a){function s(c){return c instanceof i?c:new i(function(f){f(c)})}return new(i||(i=Promise))(function(c,f){function u(d){try{l(a.next(d))}catch(h){f(h)}}function p(d){try{l(a.throw(d))}catch(h){f(h)}}function l(d){d.done?c(d.value):s(d.value).then(u,p)}l((a=a.apply(n,o||[])).next())})},yn=function(n,o){var i={label:0,sent:function(){if(c[0]&1)throw c[1];return c[1]},trys:[],ops:[]},a,s,c,f;return f={next:u(0),throw:u(1),return:u(2)},typeof Symbol=="function"&&(f[Symbol.iterator]=function(){return this}),f;function u(l){return function(d){return p([l,d])}}function p(l){if(a)throw new TypeError("Generator is already executing.");for(;i;)try{if(a=1,s&&(c=l[0]&2?s.return:l[0]?s.throw||((c=s.return)&&c.call(s),0):s.next)&&!(c=c.call(s,l[1])).done)return c;switch(s=0,c&&(l=[l[0]&2,c.value]),l[0]){case 0:case 1:c=l;break;case 4:return i.label++,{value:l[1],done:!1};case 5:i.label++,s=l[1],l=[0];continue;case 7:l=i.ops.pop(),i.trys.pop();continue;default:if(c=i.trys,!(c=c.length>0&&c[c.length-1])&&(l[0]===6||l[0]===2)){i=0;continue}if(l[0]===3&&(!c||l[1]>c[0]&&l[1]=n.length&&(n=void 0),{value:n&&n[a++],done:!n}}};throw new TypeError(o?"Object is not iterable.":"Symbol.iterator is not defined.")},Or=function(n,o){var i=typeof Symbol=="function"&&n[Symbol.iterator];if(!i)return n;var a=i.call(n),s,c=[],f;try{for(;(o===void 0||o-- >0)&&!(s=a.next()).done;)c.push(s.value)}catch(u){f={error:u}}finally{try{s&&!s.done&&(i=a.return)&&i.call(a)}finally{if(f)throw f.error}}return c},Sn=function(){for(var n=[],o=0;o1||u(b,F)})})}function u(b,F){try{p(a[b](F))}catch(G){h(c[0][3],G)}}function p(b){b.value instanceof tt?Promise.resolve(b.value.v).then(l,d):h(c[0][2],b)}function l(b){u("next",b)}function d(b){u("throw",b)}function h(b,F){b(F),c.shift(),c.length&&u(c[0][0],c[0][1])}},_n=function(n){var o,i;return o={},a("next"),a("throw",function(s){throw s}),a("return"),o[Symbol.iterator]=function(){return this},o;function a(s,c){o[s]=n[s]?function(f){return(i=!i)?{value:tt(n[s](f)),done:s==="return"}:c?c(f):f}:c}},Tn=function(n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var o=n[Symbol.asyncIterator],i;return o?o.call(n):(n=typeof Ht=="function"?Ht(n):n[Symbol.iterator](),i={},a("next"),a("throw"),a("return"),i[Symbol.asyncIterator]=function(){return this},i);function a(c){i[c]=n[c]&&function(f){return new Promise(function(u,p){f=n[c](f),s(u,p,f.done,f.value)})}}function s(c,f,u,p){Promise.resolve(p).then(function(l){c({value:l,done:u})},f)}},Mn=function(n,o){return Object.defineProperty?Object.defineProperty(n,"raw",{value:o}):n.raw=o,n};var r=Object.create?function(n,o){Object.defineProperty(n,"default",{enumerable:!0,value:o})}:function(n,o){n.default=o};Ln=function(n){if(n&&n.__esModule)return n;var o={};if(n!=null)for(var i in n)i!=="default"&&Object.prototype.hasOwnProperty.call(n,i)&&Pt(o,n,i);return r(o,n),o},An=function(n){return n&&n.__esModule?n:{default:n}},Cn=function(n,o,i,a){if(i==="a"&&!a)throw new TypeError("Private accessor was defined without a getter");if(typeof o=="function"?n!==o||!a:!o.has(n))throw new TypeError("Cannot read private member from an object whose class did not declare it");return i==="m"?a:i==="a"?a.call(n):a?a.value:o.get(n)},Rn=function(n,o,i,a,s){if(a==="m")throw new TypeError("Private method is not writable");if(a==="a"&&!s)throw new TypeError("Private accessor was defined without a setter");if(typeof o=="function"?n!==o||!s:!o.has(n))throw new TypeError("Cannot write private member to an object whose class did not declare it");return a==="a"?s.call(n,i):s?s.value=i:o.set(n,i),i},e("__extends",ln),e("__assign",mn),e("__rest",dn),e("__decorate",hn),e("__param",bn),e("__metadata",vn),e("__awaiter",gn),e("__generator",yn),e("__exportStar",xn),e("__createBinding",Pt),e("__values",Ht),e("__read",Or),e("__spread",Sn),e("__spreadArrays",wn),e("__spreadArray",En),e("__await",tt),e("__asyncGenerator",On),e("__asyncDelegator",_n),e("__asyncValues",Tn),e("__makeTemplateObject",Mn),e("__importStar",Ln),e("__importDefault",An),e("__classPrivateFieldGet",Cn),e("__classPrivateFieldSet",Rn)})});var Kr=gt((Lt,Yr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Lt=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ta}});var a=i(279),s=i.n(a),c=i(370),f=i.n(c),u=i(817),p=i.n(u);function l(I){try{return document.execCommand(I)}catch(E){return!1}}var d=function(E){var S=p()(E);return l("cut"),S},h=d;function b(I){var E=document.documentElement.getAttribute("dir")==="rtl",S=document.createElement("textarea");S.style.fontSize="12pt",S.style.border="0",S.style.padding="0",S.style.margin="0",S.style.position="absolute",S.style[E?"right":"left"]="-9999px";var R=window.pageYOffset||document.documentElement.scrollTop;return S.style.top="".concat(R,"px"),S.setAttribute("readonly",""),S.value=I,S}var F=function(E,S){var R=b(E);S.container.appendChild(R);var H=p()(R);return l("copy"),R.remove(),H},G=function(E){var S=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},R="";return typeof E=="string"?R=F(E,S):E instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(E==null?void 0:E.type)?R=F(E.value,S):(R=p()(E),l("copy")),R},W=G;function w(I){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?w=function(S){return typeof S}:w=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},w(I)}var Ue=function(){var E=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},S=E.action,R=S===void 0?"copy":S,H=E.container,z=E.target,Ee=E.text;if(R!=="copy"&&R!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(z!==void 0)if(z&&w(z)==="object"&&z.nodeType===1){if(R==="copy"&&z.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(R==="cut"&&(z.hasAttribute("readonly")||z.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Ee)return W(Ee,{container:H});if(z)return R==="cut"?h(z):W(z,{container:H})},He=Ue;function Ce(I){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ce=function(S){return typeof S}:Ce=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},Ce(I)}function Yi(I,E){if(!(I instanceof E))throw new TypeError("Cannot call a class as a function")}function on(I,E){for(var S=0;S0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof H.action=="function"?H.action:this.defaultAction,this.target=typeof H.target=="function"?H.target:this.defaultTarget,this.text=typeof H.text=="function"?H.text:this.defaultText,this.container=Ce(H.container)==="object"?H.container:document.body}},{key:"listenClick",value:function(H){var z=this;this.listener=f()(H,"click",function(Ee){return z.onClick(Ee)})}},{key:"onClick",value:function(H){var z=H.delegateTarget||H.currentTarget,Ee=this.action(z)||"copy",Rt=He({action:Ee,container:this.container,target:this.target(z),text:this.text(z)});this.emit(Rt?"success":"error",{action:Ee,text:Rt,trigger:z,clearSelection:function(){z&&z.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(H){return yr("action",H)}},{key:"defaultTarget",value:function(H){var z=yr("target",H);if(z)return document.querySelector(z)}},{key:"defaultText",value:function(H){return yr("text",H)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(H){var z=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return W(H,z)}},{key:"cut",value:function(H){return h(H)}},{key:"isSupported",value:function(){var H=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],z=typeof H=="string"?[H]:H,Ee=!!document.queryCommandSupported;return z.forEach(function(Rt){Ee=Ee&&!!document.queryCommandSupported(Rt)}),Ee}}]),S}(s()),ta=ea},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,l,d,h){var b=f.apply(this,arguments);return u.addEventListener(l,b,h),{destroy:function(){u.removeEventListener(l,b,h)}}}function c(u,p,l,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof l=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(b){return s(b,p,l,d,h)}))}function f(u,p,l,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function c(l,d,h){if(!l&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(l))return f(l,d,h);if(a.nodeList(l))return u(l,d,h);if(a.string(l))return p(l,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function f(l,d,h){return l.addEventListener(d,h),{destroy:function(){l.removeEventListener(d,h)}}}function u(l,d,h){return Array.prototype.forEach.call(l,function(b){b.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(l,function(b){b.removeEventListener(d,h)})}}}function p(l,d,h){return s(document.body,l,d,h)}n.exports=c},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),f=document.createRange();f.selectNodeContents(i),c.removeAllRanges(),c.addRange(f),a=c.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function f(){c.off(i,f),a.apply(s,arguments)}return f._=a,this.on(i,f,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,f=s.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var Ss=/["'&<>]/;yi.exports=ws;function ws(e){var t=""+e,r=Ss.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?_r:(this.currentObservers=null,s.push(r),new Re(function(){n.currentObservers=null,Pe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new $;return r.source=this,r},t.create=function(r,n){return new qn(r,n)},t}($);var qn=function(e){te(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:_r},t}(_);var xt={now:function(){return(xt.delegate||Date).now()},delegate:void 0};var St=function(e){te(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=xt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,c=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=at.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);r.actions.some(function(i){return i.id===n})||(at.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Nt);var Kn=function(e){te(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(zt);var _e=new Kn(Yn);var k=new $(function(e){return e.complete()});function qt(e){return e&&T(e.schedule)}function kr(e){return e[e.length-1]}function De(e){return T(kr(e))?e.pop():void 0}function ge(e){return qt(kr(e))?e.pop():void 0}function Qt(e,t){return typeof kr(e)=="number"?e.pop():t}var st=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Yt(e){return T(e==null?void 0:e.then)}function Kt(e){return T(e[it])}function Bt(e){return Symbol.asyncIterator&&T(e==null?void 0:e[Symbol.asyncIterator])}function Gt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function ha(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Jt=ha();function Xt(e){return T(e==null?void 0:e[Jt])}function Zt(e){return In(this,arguments,function(){var r,n,o,i;return $t(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,jt(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,jt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,jt(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function er(e){return T(e==null?void 0:e.getReader)}function N(e){if(e instanceof $)return e;if(e!=null){if(Kt(e))return ba(e);if(st(e))return va(e);if(Yt(e))return ga(e);if(Bt(e))return Bn(e);if(Xt(e))return ya(e);if(er(e))return xa(e)}throw Gt(e)}function ba(e){return new $(function(t){var r=e[it]();if(T(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function va(e){return new $(function(t){for(var r=0;r=2,!0))}function ne(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new _}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s;return function(f){var u=null,p=null,l=null,d=0,h=!1,b=!1,F=function(){p==null||p.unsubscribe(),p=null},G=function(){F(),u=l=null,h=b=!1},W=function(){var w=u;G(),w==null||w.unsubscribe()};return v(function(w,Ue){d++,!b&&!h&&F();var He=l=l!=null?l:r();Ue.add(function(){d--,d===0&&!b&&!h&&(p=Ur(W,c))}),He.subscribe(Ue),u||(u=new ot({next:function(Ce){return He.next(Ce)},error:function(Ce){b=!0,F(),p=Ur(G,o,Ce),He.error(Ce)},complete:function(){h=!0,F(),p=Ur(G,a),He.complete()}}),ie(w).subscribe(u))})(f)}}function Ur(e,t){for(var r=[],n=2;ne.next(document)),e}function B(e,t=document){return Array.from(t.querySelectorAll(e))}function Q(e,t=document){let r=pe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function pe(e,t=document){return t.querySelector(e)||void 0}function Ne(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function nr(e){return A(g(document.body,"focusin"),g(document.body,"focusout")).pipe(Xe(1),m(()=>{let t=Ne();return typeof t!="undefined"?e.contains(t):!1}),q(e===Ne()),K())}function ze(e){return{x:e.offsetLeft,y:e.offsetTop}}function vo(e){return A(g(window,"load"),g(window,"resize")).pipe($e(0,_e),m(()=>ze(e)),q(ze(e)))}function or(e){return{x:e.scrollLeft,y:e.scrollTop}}function pt(e){return A(g(e,"scroll"),g(window,"resize")).pipe($e(0,_e),m(()=>or(e)),q(or(e)))}var yo=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),Va?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=Wa.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),xo=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),wo=typeof WeakMap!="undefined"?new WeakMap:new yo,Eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Na.getInstance(),n=new Za(t,r,this);wo.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Eo.prototype[e]=function(){var t;return(t=wo.get(this))[e].apply(t,arguments)}});var es=function(){return typeof ir.ResizeObserver!="undefined"?ir.ResizeObserver:Eo}(),Oo=es;var _o=new _,ts=j(()=>P(new Oo(e=>{for(let t of e)_o.next(t)}))).pipe(x(e=>A(ye,P(e)).pipe(C(()=>e.disconnect()))),X(1));function Ae(e){return{width:e.offsetWidth,height:e.offsetHeight}}function de(e){return ts.pipe(O(t=>t.observe(e)),x(t=>_o.pipe(M(({target:r})=>r===e),C(()=>t.unobserve(e)),m(()=>Ae(e)))),q(Ae(e)))}function mt(e){return{width:e.scrollWidth,height:e.scrollHeight}}var To=new _,rs=j(()=>P(new IntersectionObserver(e=>{for(let t of e)To.next(t)},{threshold:0}))).pipe(x(e=>A(ye,P(e)).pipe(C(()=>e.disconnect()))),X(1));function cr(e){return rs.pipe(O(t=>t.observe(e)),x(t=>To.pipe(M(({target:r})=>r===e),C(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function Mo(e,t=16){return pt(e).pipe(m(({y:r})=>{let n=Ae(e),o=mt(e);return r>=o.height-n.height-t}),K())}var fr={drawer:Q("[data-md-toggle=drawer]"),search:Q("[data-md-toggle=search]")};function Lo(e){return fr[e].checked}function qe(e,t){fr[e].checked!==t&&fr[e].click()}function dt(e){let t=fr[e];return g(t,"change").pipe(m(()=>t.checked),q(t.checked))}function ns(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ao(){return g(window,"keydown").pipe(M(e=>!(e.metaKey||e.ctrlKey)),m(e=>({mode:Lo("search")?"search":"global",type:e.key,claim(){e.preventDefault(),e.stopPropagation()}})),M(({mode:e,type:t})=>{if(e==="global"){let r=Ne();if(typeof r!="undefined")return!ns(r,t)}return!0}),ne())}function xe(){return new URL(location.href)}function ur(e){location.href=e.href}function Co(){return new _}function Ro(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Ro(e,r)}function L(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)Ro(n,o);return n}function ko(e,t){let r=t;if(e.length>r){for(;e[r]!==" "&&--r>0;);return`${e.substring(0,r)}...`}return e}function pr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Ho(){return location.hash.substring(1)}function Po(e){let t=L("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function os(){return g(window,"hashchange").pipe(m(Ho),q(Ho()),M(e=>e.length>0),X(1))}function Io(){return os().pipe(m(e=>pe(`[id="${e}"]`)),M(e=>typeof e!="undefined"))}function qr(e){let t=matchMedia(e);return rr(r=>t.addListener(()=>r(t.matches))).pipe(q(t.matches))}function $o(){let e=matchMedia("print");return A(g(window,"beforeprint").pipe(m(()=>!0)),g(window,"afterprint").pipe(m(()=>!1))).pipe(q(e.matches))}function Qr(e,t){return e.pipe(x(r=>r?t():k))}function lr(e,t={credentials:"same-origin"}){return ie(fetch(`${e}`,t)).pipe(ce(()=>k),x(r=>r.status!==200?Et(()=>new Error(r.statusText)):P(r)))}function ke(e,t){return lr(e,t).pipe(x(r=>r.json()),X(1))}function jo(e,t){let r=new DOMParser;return lr(e,t).pipe(x(n=>n.text()),m(n=>r.parseFromString(n,"text/xml")),X(1))}function Fo(e){let t=L("script",{src:e});return j(()=>(document.head.appendChild(t),A(g(t,"load"),g(t,"error").pipe(x(()=>Et(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),C(()=>document.head.removeChild(t)),re(1))))}function Uo(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function Do(){return A(g(window,"scroll",{passive:!0}),g(window,"resize",{passive:!0})).pipe(m(Uo),q(Uo()))}function Wo(){return{width:innerWidth,height:innerHeight}}function Vo(){return g(window,"resize",{passive:!0}).pipe(m(Wo),q(Wo()))}function No(){return Y([Do(),Vo()]).pipe(m(([e,t])=>({offset:e,size:t})),X(1))}function mr(e,{viewport$:t,header$:r}){let n=t.pipe(J("size")),o=Y([n,r]).pipe(m(()=>ze(e)));return Y([r,t,o]).pipe(m(([{height:i},{offset:a,size:s},{x:c,y:f}])=>({offset:{x:a.x-c,y:a.y-f+i},size:s})))}function zo(e,{tx$:t}){let r=g(e,"message").pipe(m(({data:n})=>n));return t.pipe(Mt(()=>r,{leading:!0,trailing:!0}),O(n=>e.postMessage(n)),x(()=>r),ne())}var is=Q("#__config"),ht=JSON.parse(is.textContent);ht.base=`${new URL(ht.base,xe())}`;function he(){return ht}function oe(e){return ht.features.includes(e)}function ee(e,t){return typeof t!="undefined"?ht.translations[e].replace("#",t.toString()):ht.translations[e]}function Se(e,t=document){return Q(`[data-md-component=${e}]`,t)}function ae(e,t=document){return B(`[data-md-component=${e}]`,t)}var ti=Ye(Kr());function qo(e){return L("aside",{class:"md-annotation",tabIndex:0},L("div",{class:"md-annotation__inner md-tooltip"},L("div",{class:"md-tooltip__inner md-typeset"})),L("span",{class:"md-annotation__index"},L("span",{"data-md-annotation-id":e})))}function Qo(e){return L("button",{class:"md-clipboard md-icon",title:ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function Br(e,t){let r=t&2,n=t&1,o=Object.keys(e.terms).filter(a=>!e.terms[a]).reduce((a,s)=>[...a,L("del",null,s)," "],[]).slice(0,-1),i=new URL(e.location);return oe("search.highlight")&&i.searchParams.set("h",Object.entries(e.terms).filter(([,a])=>a).reduce((a,[s])=>`${a} ${s}`.trim(),"")),L("a",{href:`${i}`,class:"md-search-result__link",tabIndex:-1},L("article",{class:["md-search-result__article",...r?["md-search-result__article--document"]:[]].join(" "),"data-md-score":e.score.toFixed(2)},r>0&&L("div",{class:"md-search-result__icon md-icon"}),L("h1",{class:"md-search-result__title"},e.title),n>0&&e.text.length>0&&L("p",{class:"md-search-result__teaser"},ko(e.text,320)),e.tags&&e.tags.map(a=>L("span",{class:"md-tag"},a)),n>0&&o.length>0&&L("p",{class:"md-search-result__terms"},ee("search.result.term.missing"),": ",...o)))}function Yo(e){let t=e[0].score,r=[...e],n=r.findIndex(f=>!f.location.includes("#")),[o]=r.splice(n,1),i=r.findIndex(f=>f.scoreBr(f,1)),...s.length?[L("details",{class:"md-search-result__more"},L("summary",{tabIndex:-1},s.length>0&&s.length===1?ee("search.result.more.one"):ee("search.result.more.other",s.length)),...s.map(f=>Br(f,1)))]:[]];return L("li",{class:"md-search-result__item"},c)}function Ko(e){return L("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>L("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?pr(r):r)))}function Gr(e){let t=`tabbed-control tabbed-control--${e}`;return L("div",{class:t,hidden:!0},L("button",{class:"tabbed-button",tabIndex:-1}))}function Bo(e){return L("div",{class:"md-typeset__scrollwrap"},L("div",{class:"md-typeset__table"},e))}function as(e){let t=he(),r=new URL(`../${e.version}/`,t.base);return L("li",{class:"md-version__item"},L("a",{href:`${r}`,class:"md-version__link"},e.title))}function Go(e,t){return L("div",{class:"md-version"},L("button",{class:"md-version__current","aria-label":ee("select.version.title")},t.title),L("ul",{class:"md-version__list"},e.map(as)))}function ss(e,t){let r=j(()=>Y([vo(e),pt(t)])).pipe(m(([{x:n,y:o},i])=>{let{width:a}=Ae(e);return{x:n-i.x+a/2,y:o-i.y}}));return nr(e).pipe(x(n=>r.pipe(m(o=>({active:n,offset:o})),re(+!n||1/0))))}function Jo(e,t){return j(()=>{let r=new _;r.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}});let n=r.pipe(fe(1));cr(e).pipe(Z(n)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),r.pipe(Vr(500,_e),m(()=>t.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}});let o=Q(":scope > :last-child",e),i=g(o,"mousedown",{once:!0});return r.pipe(x(({active:a})=>a?i:k),O(a=>a.preventDefault())).subscribe(()=>e.blur()),ss(e,t).pipe(O(a=>r.next(a)),C(()=>r.complete()),m(a=>U({ref:e},a)))})}function cs(e){let t=[];for(let r of B(".c, .c1, .cm",e)){let n,o=r.firstChild;if(o instanceof Text)for(;n=/\((\d+)\)/.exec(o.textContent);){let i=o.splitText(n.index);o=i.splitText(n[0].length),t.push(i)}}return t}function Xo(e,t){t.append(...Array.from(e.childNodes))}function Zo(e,t,{print$:r}){let n=new Map;for(let o of cs(t)){let[,i]=o.textContent.match(/\((\d+)\)/);pe(`li:nth-child(${i})`,e)&&(n.set(+i,qo(+i)),o.replaceWith(n.get(+i)))}return n.size===0?k:j(()=>{let o=new _;return r.pipe(Z(o.pipe(fe(1)))).subscribe(i=>{e.hidden=!i;for(let[a,s]of n){let c=Q(".md-typeset",s),f=Q(`li:nth-child(${a})`,e);i?Xo(c,f):Xo(f,c)}}),A(...[...n].map(([,i])=>Jo(i,t))).pipe(C(()=>o.complete()),ne())})}var fs=0;function ri(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return ri(t)}}function ei(e){return de(e).pipe(m(({width:t})=>({scrollable:mt(e).width>t})),J("scrollable"))}function ni(e,t){let{matches:r}=matchMedia("(hover)"),n=j(()=>{let o=new _;if(o.subscribe(({scrollable:a})=>{a&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")}),ti.default.isSupported()){let a=e.closest("pre");a.id=`__code_${++fs}`,a.insertBefore(Qo(a.id),e)}let i=e.closest(".highlight");if(i instanceof HTMLElement){let a=ri(i);if(typeof a!="undefined"&&(i.classList.contains("annotate")||oe("content.code.annotate"))){let s=Zo(a,e,t);return ei(e).pipe(O(c=>o.next(c)),C(()=>o.complete()),m(c=>U({ref:e},c)),Ze(de(i).pipe(Z(o.pipe(fe(1))),m(({width:c,height:f})=>c&&f),K(),x(c=>c?s:k))))}}return ei(e).pipe(O(a=>o.next(a)),C(()=>o.complete()),m(a=>U({ref:e},a)))});return cr(e).pipe(M(o=>o),re(1),x(()=>n))}var oi=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:transparent}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}defs #flowchart-circleEnd,defs #flowchart-circleStart,defs #flowchart-crossEnd,defs #flowchart-crossStart,defs #flowchart-pointEnd,defs #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}.actor,defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{stroke:var(--md-mermaid-node-fg-color)}text.actor>tspan{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-default-fg-color--lighter)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-edge-color)}.loopText>tspan,.messageText{font-family:var(--md-mermaid-font-family)!important}#arrowhead path,.loopText>tspan,.messageText{fill:var(--md-mermaid-edge-color);stroke:none}.loopLine{stroke:var(--md-mermaid-node-fg-color)}.labelBox,.loopLine{fill:var(--md-mermaid-node-bg-color)}.labelBox{stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-node-fg-color);font-family:var(--md-mermaid-font-family)}";var Jr,ps=0;function ls(){return typeof mermaid=="undefined"||mermaid instanceof Element?Fo("https://unpkg.com/mermaid@9.0.1/dist/mermaid.min.js"):P(void 0)}function ii(e){return e.classList.remove("mermaid"),Jr||(Jr=ls().pipe(O(()=>mermaid.initialize({startOnLoad:!1,themeCSS:oi})),m(()=>{}),X(1))),Jr.subscribe(()=>{e.classList.add("mermaid");let t=`__mermaid_${ps++}`,r=L("div",{class:"mermaid"});mermaid.mermaidAPI.render(t,e.textContent,n=>{let o=r.attachShadow({mode:"closed"});o.innerHTML=n,e.replaceWith(r)})}),Jr.pipe(m(()=>({ref:e})))}function ms(e,{target$:t,print$:r}){let n=!0;return A(t.pipe(m(o=>o.closest("details:not([open])")),M(o=>e===o),m(()=>({action:"open",reveal:!0}))),r.pipe(M(o=>o||!n),O(()=>n=e.open),m(o=>({action:o?"open":"close"}))))}function ai(e,t){return j(()=>{let r=new _;return r.subscribe(({action:n,reveal:o})=>{n==="open"?e.setAttribute("open",""):e.removeAttribute("open"),o&&e.scrollIntoView()}),ms(e,t).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))})}var si=L("table");function ci(e){return e.replaceWith(si),si.replaceWith(Bo(e)),P({ref:e})}function ds(e){let t=B(":scope > input",e),r=t.find(n=>n.checked)||t[0];return A(...t.map(n=>g(n,"change").pipe(m(()=>Q(`label[for=${n.id}]`))))).pipe(q(Q(`label[for=${r.id}]`)),m(n=>({active:n})))}function fi(e){let t=Gr("prev");e.append(t);let r=Gr("next");e.append(r);let n=Q(".tabbed-labels",e);return j(()=>{let o=new _,i=o.pipe(fe(1));return Y([o,de(e)]).pipe($e(1,_e),Z(i)).subscribe({next([{active:a},s]){let c=ze(a),{width:f}=Ae(a);e.style.setProperty("--md-indicator-x",`${c.x}px`),e.style.setProperty("--md-indicator-width",`${f}px`);let u=or(n);(c.xu.x+s.width)&&n.scrollTo({left:Math.max(0,c.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),Y([pt(n),de(n)]).pipe(Z(i)).subscribe(([a,s])=>{let c=mt(n);t.hidden=a.x<16,r.hidden=a.x>c.width-s.width-16}),A(g(t,"click").pipe(m(()=>-1)),g(r,"click").pipe(m(()=>1))).pipe(Z(i)).subscribe(a=>{let{width:s}=Ae(n);n.scrollBy({left:s*a,behavior:"smooth"})}),oe("content.tabs.link")&&o.pipe(Me(1)).subscribe(({active:a})=>{let s=a.innerText.trim();for(let f of B("[data-tabs]"))for(let u of B(":scope > input",f))if(Q(`label[for=${u.id}]`).innerText.trim()===s){u.click();break}let c=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([s,...c])])}),ds(e).pipe(O(a=>o.next(a)),C(()=>o.complete()),m(a=>U({ref:e},a)))}).pipe(Be(ue))}function ui(e,{target$:t,print$:r}){return A(...B("pre:not(.mermaid) > code",e).map(n=>ni(n,{print$:r})),...B("pre.mermaid",e).map(n=>ii(n)),...B("table:not([class])",e).map(n=>ci(n)),...B("details",e).map(n=>ai(n,{target$:t,print$:r})),...B("[data-tabs]",e).map(n=>fi(n)))}function hs(e,{alert$:t}){return t.pipe(x(r=>A(P(!0),P(!1).pipe(Fe(2e3))).pipe(m(n=>({message:r,active:n})))))}function pi(e,t){let r=Q(".md-typeset",e);return j(()=>{let n=new _;return n.subscribe(({message:o,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=o}),hs(e,t).pipe(O(o=>n.next(o)),C(()=>n.complete()),m(o=>U({ref:e},o)))})}function bs({viewport$:e}){if(!oe("header.autohide"))return P(!1);let t=e.pipe(m(({offset:{y:o}})=>o),Te(2,1),m(([o,i])=>[oMath.abs(i-o.y)>100),m(([,[o]])=>o),K()),n=dt("search");return Y([e,n]).pipe(m(([{offset:o},i])=>o.y>400&&!i),K(),x(o=>o?r:P(!1)),q(!1))}function li(e,t){return j(()=>Y([de(e),bs(t)])).pipe(m(([{height:r},n])=>({height:r,hidden:n})),K((r,n)=>r.height===n.height&&r.hidden===n.hidden),X(1))}function mi(e,{header$:t,main$:r}){return j(()=>{let n=new _,o=n.pipe(fe(1));return n.pipe(J("active"),Je(t)).subscribe(([{active:i},{hidden:a}])=>{e.classList.toggle("md-header--shadow",i&&!a),e.hidden=a}),r.subscribe(n),t.pipe(Z(o),m(i=>U({ref:e},i)))})}function vs(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:n}})=>{let{height:o}=Ae(e);return{active:n>=o}}),J("active"))}function di(e,t){return j(()=>{let r=new _;r.subscribe(({active:o})=>{e.classList.toggle("md-header__title--active",o)});let n=pe("article h1");return typeof n=="undefined"?k:vs(n,t).pipe(O(o=>r.next(o)),C(()=>r.complete()),m(o=>U({ref:e},o)))})}function hi(e,{viewport$:t,header$:r}){let n=r.pipe(m(({height:i})=>i),K()),o=n.pipe(x(()=>de(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),J("bottom"))));return Y([n,o,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:c},size:{height:f}}])=>(f=Math.max(0,f-Math.max(0,a-c,i)-Math.max(0,f+c-s)),{offset:a-i,height:f,active:a-i<=c})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function gs(e){let t=__md_get("__palette")||{index:e.findIndex(r=>matchMedia(r.getAttribute("data-md-color-media")).matches)};return P(...e).pipe(se(r=>g(r,"change").pipe(m(()=>r))),q(e[Math.max(0,t.index)]),m(r=>({index:e.indexOf(r),color:{scheme:r.getAttribute("data-md-color-scheme"),primary:r.getAttribute("data-md-color-primary"),accent:r.getAttribute("data-md-color-accent")}})),X(1))}function bi(e){return j(()=>{let t=new _;t.subscribe(n=>{document.body.setAttribute("data-md-color-switching","");for(let[o,i]of Object.entries(n.color))document.body.setAttribute(`data-md-color-${o}`,i);for(let o=0;o{document.body.removeAttribute("data-md-color-switching")});let r=B("input",e);return gs(r).pipe(O(n=>t.next(n)),C(()=>t.complete()),m(n=>U({ref:e},n)))})}var Xr=Ye(Kr());function ys(e){e.setAttribute("data-md-copying","");let t=e.innerText;return e.removeAttribute("data-md-copying"),t}function vi({alert$:e}){Xr.default.isSupported()&&new $(t=>{new Xr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ys(Q(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(O(t=>{t.trigger.focus()}),m(()=>ee("clipboard.copied"))).subscribe(e)}function xs(e){if(e.length<2)return[""];let[t,r]=[...e].sort((o,i)=>o.length-i.length).map(o=>o.replace(/[^/]+$/,"")),n=0;if(t===r)n=t.length;else for(;t.charCodeAt(n)===r.charCodeAt(n);)n++;return e.map(o=>o.replace(t.slice(0,n),""))}function dr(e){let t=__md_get("__sitemap",sessionStorage,e);if(t)return P(t);{let r=he();return jo(new URL("sitemap.xml",e||r.base)).pipe(m(n=>xs(B("loc",n).map(o=>o.textContent))),ce(()=>k),je([]),O(n=>__md_set("__sitemap",n,sessionStorage,e)))}}function gi({document$:e,location$:t,viewport$:r}){let n=he();if(location.protocol==="file:")return;"scrollRestoration"in history&&(history.scrollRestoration="manual",g(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}));let o=pe("link[rel=icon]");typeof o!="undefined"&&(o.href=o.href);let i=dr().pipe(m(f=>f.map(u=>`${new URL(u,n.base)}`)),x(f=>g(document.body,"click").pipe(M(u=>!u.metaKey&&!u.ctrlKey),x(u=>{if(u.target instanceof Element){let p=u.target.closest("a");if(p&&!p.target){let l=new URL(p.href);if(l.search="",l.hash="",l.pathname!==location.pathname&&f.includes(l.toString()))return u.preventDefault(),P({url:new URL(p.href)})}}return ye}))),ne()),a=g(window,"popstate").pipe(M(f=>f.state!==null),m(f=>({url:new URL(location.href),offset:f.state})),ne());A(i,a).pipe(K((f,u)=>f.url.href===u.url.href),m(({url:f})=>f)).subscribe(t);let s=t.pipe(J("pathname"),x(f=>lr(f.href).pipe(ce(()=>(ur(f),ye)))),ne());i.pipe(ut(s)).subscribe(({url:f})=>{history.pushState({},"",`${f}`)});let c=new DOMParser;s.pipe(x(f=>f.text()),m(f=>c.parseFromString(f,"text/html"))).subscribe(e),e.pipe(Me(1)).subscribe(f=>{for(let u of["title","link[rel=canonical]","meta[name=author]","meta[name=description]","[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...oe("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let p=pe(u),l=pe(u,f);typeof p!="undefined"&&typeof l!="undefined"&&p.replaceWith(l)}}),e.pipe(Me(1),m(()=>Se("container")),x(f=>B("script",f)),Ir(f=>{let u=L("script");if(f.src){for(let p of f.getAttributeNames())u.setAttribute(p,f.getAttribute(p));return f.replaceWith(u),new $(p=>{u.onload=()=>p.complete()})}else return u.textContent=f.textContent,f.replaceWith(u),k})).subscribe(),A(i,a).pipe(ut(e)).subscribe(({url:f,offset:u})=>{f.hash&&!u?Po(f.hash):window.scrollTo(0,(u==null?void 0:u.y)||0)}),r.pipe(Tt(i),Xe(250),J("offset")).subscribe(({offset:f})=>{history.replaceState(f,"")}),A(i,a).pipe(Te(2,1),M(([f,u])=>f.url.pathname===u.url.pathname),m(([,f])=>f)).subscribe(({offset:f})=>{window.scrollTo(0,(f==null?void 0:f.y)||0)})}var Es=Ye(Zr());var xi=Ye(Zr());function en(e,t){let r=new RegExp(e.separator,"img"),n=(o,i,a)=>`${i}${a}`;return o=>{o=o.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator})(${o.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(t?(0,xi.default)(a):a).replace(i,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function Si(e){return e.split(/"([^"]+)"/g).map((t,r)=>r&1?t.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):t).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").trim()}function bt(e){return e.type===1}function wi(e){return e.type===2}function vt(e){return e.type===3}function _s({config:e,docs:t}){e.lang.length===1&&e.lang[0]==="en"&&(e.lang=[ee("search.config.lang")]),e.separator==="[\\s\\-]+"&&(e.separator=ee("search.config.separator"));let n={pipeline:ee("search.config.pipeline").split(/\s*,\s*/).filter(Boolean),suggestions:oe("search.suggest")};return{config:e,docs:t,options:n}}function Ei(e,t){let r=he(),n=new Worker(e),o=new _,i=zo(n,{tx$:o}).pipe(m(a=>{if(vt(a))for(let s of a.data.items)for(let c of s)c.location=`${new URL(c.location,r.base)}`;return a}),ne());return ie(t).pipe(m(a=>({type:0,data:_s(a)}))).subscribe(o.next.bind(o)),{tx$:o,rx$:i}}function Oi({document$:e}){let t=he(),r=ke(new URL("../versions.json",t.base)).pipe(ce(()=>k)),n=r.pipe(m(o=>{let[,i]=t.base.match(/([^/]+)\/?$/);return o.find(({version:a,aliases:s})=>a===i||s.includes(i))||o[0]}));Y([r,n]).pipe(m(([o,i])=>new Map(o.filter(a=>a!==i).map(a=>[`${new URL(`../${a.version}/`,t.base)}`,a]))),x(o=>g(document.body,"click").pipe(M(i=>!i.metaKey&&!i.ctrlKey),x(i=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&o.has(a.href))return i.preventDefault(),P(a.href)}return k}),x(i=>{let{version:a}=o.get(i);return dr(new URL(i)).pipe(m(s=>{let f=xe().href.replace(t.base,"");return s.includes(f)?new URL(`../${a}/${f}`,t.base):new URL(i)}))})))).subscribe(o=>ur(o)),Y([r,n]).subscribe(([o,i])=>{Q(".md-header__topic").appendChild(Go(o,i))}),e.pipe(x(()=>n)).subscribe(o=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){let s=((a=t.version)==null?void 0:a.default)||"latest";i=!o.aliases.includes(s),__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function Ts(e,{rx$:t}){let r=(__search==null?void 0:__search.transform)||Si,{searchParams:n}=xe();n.has("q")&&qe("search",!0);let o=t.pipe(M(bt),re(1),m(()=>n.get("q")||""));dt("search").pipe(M(s=>!s),re(1)).subscribe(()=>{let s=new URL(location.href);s.searchParams.delete("q"),history.replaceState({},"",`${s}`)}),o.subscribe(s=>{s&&(e.value=s,e.focus())});let i=nr(e),a=A(g(e,"keyup"),g(e,"focus").pipe(Fe(1)),o).pipe(m(()=>r(e.value)),q(""),K());return Y([a,i]).pipe(m(([s,c])=>({value:s,focus:c})),X(1))}function _i(e,{tx$:t,rx$:r}){let n=new _,o=n.pipe(fe(1));return n.pipe(J("value"),m(({value:i})=>({type:2,data:i}))).subscribe(t.next.bind(t)),n.pipe(J("focus")).subscribe(({focus:i})=>{i?(qe("search",i),e.placeholder=""):e.placeholder=ee("search.placeholder")}),g(e.form,"reset").pipe(Z(o)).subscribe(()=>e.focus()),Ts(e,{tx$:t,rx$:r}).pipe(O(i=>n.next(i)),C(()=>n.complete()),m(i=>U({ref:e},i)),ne())}function Ti(e,{rx$:t},{query$:r}){let n=new _,o=Mo(e.parentElement).pipe(M(Boolean)),i=Q(":scope > :first-child",e),a=Q(":scope > :last-child",e),s=t.pipe(M(bt),re(1));return n.pipe(Le(r),Tt(s)).subscribe(([{items:f},{value:u}])=>{if(u)switch(f.length){case 0:i.textContent=ee("search.result.none");break;case 1:i.textContent=ee("search.result.one");break;default:i.textContent=ee("search.result.other",pr(f.length))}else i.textContent=ee("search.result.placeholder")}),n.pipe(O(()=>a.innerHTML=""),x(({items:f})=>A(P(...f.slice(0,10)),P(...f.slice(10)).pipe(Te(4),Nr(o),x(([u])=>u))))).subscribe(f=>a.appendChild(Yo(f))),t.pipe(M(vt),m(({data:f})=>f)).pipe(O(f=>n.next(f)),C(()=>n.complete()),m(f=>U({ref:e},f)))}function Ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let n=xe();return n.hash="",n.searchParams.delete("h"),n.searchParams.set("q",r),{url:n}}))}function Mi(e,t){let r=new _;return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),g(e,"click").subscribe(n=>n.preventDefault()),Ms(e,t).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))}function Li(e,{rx$:t},{keyboard$:r}){let n=new _,o=Se("search-query"),i=A(g(o,"keydown"),g(o,"focus")).pipe(Ie(ue),m(()=>o.value),K());return n.pipe(Je(i),m(([{suggestions:s},c])=>{let f=c.split(/([\s-]+)/);if((s==null?void 0:s.length)&&f[f.length-1]){let u=s[s.length-1];u.startsWith(f[f.length-1])&&(f[f.length-1]=u)}else f.length=0;return f})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(M(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&o.selectionStart===o.value.length&&(o.value=e.innerText);break}}),t.pipe(M(vt),m(({data:s})=>s)).pipe(O(s=>n.next(s)),C(()=>n.complete()),m(()=>({ref:e})))}function Ai(e,{index$:t,keyboard$:r}){let n=he();try{let o=(__search==null?void 0:__search.worker)||n.search,i=Ei(o,t),a=Se("search-query",e),s=Se("search-result",e),{tx$:c,rx$:f}=i;c.pipe(M(wi),ut(f.pipe(M(bt))),re(1)).subscribe(c.next.bind(c)),r.pipe(M(({mode:l})=>l==="search")).subscribe(l=>{let d=Ne();switch(l.type){case"Enter":if(d===a){let h=new Map;for(let b of B(":first-child [href]",s)){let F=b.firstElementChild;h.set(b,parseFloat(F.getAttribute("data-md-score")))}if(h.size){let[[b]]=[...h].sort(([,F],[,G])=>G-F);b.click()}l.claim()}break;case"Escape":case"Tab":qe("search",!1),a.blur();break;case"ArrowUp":case"ArrowDown":if(typeof d=="undefined")a.focus();else{let h=[a,...B(":not(details) > [href], summary, details[open] [href]",s)],b=Math.max(0,(Math.max(0,h.indexOf(d))+h.length+(l.type==="ArrowUp"?-1:1))%h.length);h[b].focus()}l.claim();break;default:a!==Ne()&&a.focus()}}),r.pipe(M(({mode:l})=>l==="global")).subscribe(l=>{switch(l.type){case"f":case"s":case"/":a.focus(),a.select(),l.claim();break}});let u=_i(a,i),p=Ti(s,i,{query$:u});return A(u,p).pipe(Ze(...ae("search-share",e).map(l=>Mi(l,{query$:u})),...ae("search-suggest",e).map(l=>Li(l,i,{keyboard$:r}))))}catch(o){return e.hidden=!0,ye}}function Ci(e,{index$:t,location$:r}){return Y([t,r.pipe(q(xe()),M(n=>!!n.searchParams.get("h")))]).pipe(m(([n,o])=>en(n.config,!0)(o.searchParams.get("h"))),m(n=>{var a;let o=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let c=s.textContent,f=n(c);f.length>c.length&&o.set(s,f)}for(let[s,c]of o){let{childNodes:f}=L("span",null,c);s.replaceWith(...Array.from(f))}return{ref:e,nodes:o}}))}function Ls(e,{viewport$:t,main$:r}){let n=e.parentElement,o=n.offsetTop-n.parentElement.offsetTop;return Y([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(o,Math.max(0,s-i))-o,{height:a,locked:s>=i+o})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function tn(e,n){var o=n,{header$:t}=o,r=cn(o,["header$"]);let i=Q(".md-sidebar__scrollwrap",e),{y:a}=ze(i);return j(()=>{let s=new _;return s.pipe($e(0,_e),Le(t)).subscribe({next([{height:c},{height:f}]){i.style.height=`${c-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),Ls(e,r).pipe(O(c=>s.next(c)),C(()=>s.complete()),m(c=>U({ref:e},c)))})}function Ri(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Ot(ke(`${r}/releases/latest`).pipe(ce(()=>k),m(n=>({version:n.tag_name})),je({})),ke(r).pipe(ce(()=>k),m(n=>({stars:n.stargazers_count,forks:n.forks_count})),je({}))).pipe(m(([n,o])=>U(U({},n),o)))}else{let r=`https://api.github.com/users/${e}`;return ke(r).pipe(m(n=>({repositories:n.public_repos})),je({}))}}function ki(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return ke(r).pipe(ce(()=>k),m(({star_count:n,forks_count:o})=>({stars:n,forks:o})),je({}))}function Hi(e){let[t]=e.match(/(git(?:hub|lab))/i)||[];switch(t.toLowerCase()){case"github":let[,r,n]=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);return Ri(r,n);case"gitlab":let[,o,i]=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i);return ki(o,i);default:return k}}var As;function Cs(e){return As||(As=j(()=>{let t=__md_get("__source",sessionStorage);return t?P(t):Hi(e.href).pipe(O(r=>__md_set("__source",r,sessionStorage)))}).pipe(ce(()=>k),M(t=>Object.keys(t).length>0),m(t=>({facts:t})),X(1)))}function Pi(e){let t=Q(":scope > :last-child",e);return j(()=>{let r=new _;return r.subscribe(({facts:n})=>{t.appendChild(Ko(n)),t.classList.add("md-source__repository--active")}),Cs(e).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))})}function Rs(e,{viewport$:t,header$:r}){return de(document.body).pipe(x(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:n}})=>({hidden:n>=10})),J("hidden"))}function Ii(e,t){return j(()=>{let r=new _;return r.subscribe({next({hidden:n}){e.hidden=n},complete(){e.hidden=!1}}),(oe("navigation.tabs.sticky")?P({hidden:!1}):Rs(e,t)).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))})}function ks(e,{viewport$:t,header$:r}){let n=new Map,o=B("[href^=\\#]",e);for(let s of o){let c=decodeURIComponent(s.hash.substring(1)),f=pe(`[id="${c}"]`);typeof f!="undefined"&&n.set(s,f)}let i=r.pipe(J("height"),m(({height:s})=>{let c=Se("main"),f=Q(":scope > :first-child",c);return s+.8*(f.offsetTop-c.offsetTop)}),ne());return de(document.body).pipe(J("height"),x(s=>j(()=>{let c=[];return P([...n].reduce((f,[u,p])=>{for(;c.length&&n.get(c[c.length-1]).tagName>=p.tagName;)c.pop();let l=p.offsetTop;for(;!l&&p.parentElement;)p=p.parentElement,l=p.offsetTop;return f.set([...c=[...c,u]].reverse(),l)},new Map))}).pipe(m(c=>new Map([...c].sort(([,f],[,u])=>f-u))),Je(i),x(([c,f])=>t.pipe(Fr(([u,p],{offset:{y:l},size:d})=>{let h=l+d.height>=Math.floor(s.height);for(;p.length;){let[,b]=p[0];if(b-f=l&&!h)p=[u.pop(),...p];else break}return[u,p]},[[],[...c]]),K((u,p)=>u[0]===p[0]&&u[1]===p[1])))))).pipe(m(([s,c])=>({prev:s.map(([f])=>f),next:c.map(([f])=>f)})),q({prev:[],next:[]}),Te(2,1),m(([s,c])=>s.prev.length{let o=new _,i=o.pipe(fe(1));return o.subscribe(({prev:a,next:s})=>{for(let[c]of s)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[f]]of a.entries())f.classList.add("md-nav__link--passed"),f.classList.toggle("md-nav__link--active",c===a.length-1)}),oe("navigation.tracking")&&t.pipe(Z(i),J("offset"),Xe(250),Me(1),Z(n.pipe(Me(1))),_t({delay:250}),Le(o)).subscribe(([,{prev:a}])=>{let s=xe(),c=a[a.length-1];if(c&&c.length){let[f]=c,{hash:u}=new URL(f.href);s.hash!==u&&(s.hash=u,history.replaceState({},"",`${s}`))}else s.hash="",history.replaceState({},"",`${s}`)}),ks(e,{viewport$:t,header$:r}).pipe(O(a=>o.next(a)),C(()=>o.complete()),m(a=>U({ref:e},a)))})}function Hs(e,{viewport$:t,main$:r,target$:n}){let o=t.pipe(m(({offset:{y:a}})=>a),Te(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return Y([i,o]).pipe(m(([a,s])=>!(a&&s)),K(),Z(n.pipe(Me(1))),jr(!0),_t({delay:250}),m(a=>({hidden:a})))}function ji(e,{viewport$:t,header$:r,main$:n,target$:o}){let i=new _,a=i.pipe(fe(1));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Z(a),J("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),Hs(e,{viewport$:t,main$:n,target$:o}).pipe(O(s=>i.next(s)),C(()=>i.complete()),m(s=>U({ref:e},s)))}function Fi({document$:e,tablet$:t}){e.pipe(x(()=>B(".md-toggle--indeterminate, [data-md-state=indeterminate]")),O(r=>{r.indeterminate=!0,r.checked=!1}),se(r=>g(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),Le(t)).subscribe(([r,n])=>{r.classList.remove("md-toggle--indeterminate"),n&&(r.checked=!1)})}function Ps(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Ui({document$:e}){e.pipe(x(()=>B("[data-md-scrollfix]")),O(t=>t.removeAttribute("data-md-scrollfix")),M(Ps),se(t=>g(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Di({viewport$:e,tablet$:t}){Y([dt("search"),t]).pipe(m(([r,n])=>r&&!n),x(r=>P(r).pipe(Fe(r?400:100))),Le(e)).subscribe(([r,{offset:{y:n}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${n}px`;else{let o=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",o&&window.scrollTo(0,o)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let n=e[r];typeof n!="object"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?t.insertBefore(this.previousSibling,n):t.replaceChild(n,this)}}}));document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var et=bo(),br=Co(),At=Io(),rn=Ao(),we=No(),vr=qr("(min-width: 960px)"),Vi=qr("(min-width: 1220px)"),Ni=$o(),zi=he(),qi=document.forms.namedItem("search")?(__search==null?void 0:__search.index)||ke(new URL("search/search_index.json",zi.base)):ye,nn=new _;vi({alert$:nn});oe("navigation.instant")&&gi({document$:et,location$:br,viewport$:we});var Wi;((Wi=zi.version)==null?void 0:Wi.provider)==="mike"&&Oi({document$:et});A(br,At).pipe(Fe(125)).subscribe(()=>{qe("drawer",!1),qe("search",!1)});rn.pipe(M(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=pe("[href][rel=prev]");typeof t!="undefined"&&t.click();break;case"n":case".":let r=pe("[href][rel=next]");typeof r!="undefined"&&r.click();break}});Fi({document$:et,tablet$:vr});Ui({document$:et});Di({viewport$:we,tablet$:vr});var Qe=li(Se("header"),{viewport$:we}),hr=et.pipe(m(()=>Se("main")),x(e=>hi(e,{viewport$:we,header$:Qe})),X(1)),Is=A(...ae("dialog").map(e=>pi(e,{alert$:nn})),...ae("header").map(e=>mi(e,{viewport$:we,header$:Qe,main$:hr})),...ae("palette").map(e=>bi(e)),...ae("search").map(e=>Ai(e,{index$:qi,keyboard$:rn})),...ae("source").map(e=>Pi(e))),$s=j(()=>A(...ae("content").map(e=>ui(e,{target$:At,print$:Ni})),...ae("content").map(e=>oe("search.highlight")?Ci(e,{index$:qi,location$:br}):k),...ae("header-title").map(e=>di(e,{viewport$:we,header$:Qe})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Qr(Vi,()=>tn(e,{viewport$:we,header$:Qe,main$:hr})):Qr(vr,()=>tn(e,{viewport$:we,header$:Qe,main$:hr}))),...ae("tabs").map(e=>Ii(e,{viewport$:we,header$:Qe})),...ae("toc").map(e=>$i(e,{viewport$:we,header$:Qe,target$:At})),...ae("top").map(e=>ji(e,{viewport$:we,header$:Qe,main$:hr,target$:At})))),Qi=et.pipe(x(()=>$s),Ze(Is),X(1));Qi.subscribe();window.document$=et;window.location$=br;window.target$=At;window.keyboard$=rn;window.viewport$=we;window.tablet$=vr;window.screen$=Vi;window.print$=Ni;window.alert$=nn;window.component$=Qi;})(); +//# sourceMappingURL=bundle.f758a944.min.js.map + diff --git a/assets/javascripts/bundle.f758a944.min.js.map b/assets/javascripts/bundle.f758a944.min.js.map new file mode 100644 index 00000000..28eef05d --- /dev/null +++ b/assets/javascripts/bundle.f758a944.min.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/url-polyfill/url-polyfill.js", "node_modules/rxjs/node_modules/tslib/tslib.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "node_modules/array-flat-polyfill/index.mjs", "src/assets/javascripts/bundle.ts", "node_modules/unfetch/polyfill/index.js", "node_modules/rxjs/node_modules/tslib/modules/index.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/concatMap.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/assets/javascripts/browser/document/index.ts", "src/assets/javascripts/browser/element/_/index.ts", "src/assets/javascripts/browser/element/focus/index.ts", "src/assets/javascripts/browser/element/offset/_/index.ts", "src/assets/javascripts/browser/element/offset/content/index.ts", "node_modules/resize-observer-polyfill/dist/ResizeObserver.es.js", "src/assets/javascripts/browser/element/size/_/index.ts", "src/assets/javascripts/browser/element/size/content/index.ts", "src/assets/javascripts/browser/element/visibility/index.ts", "src/assets/javascripts/browser/toggle/index.ts", "src/assets/javascripts/browser/keyboard/index.ts", "src/assets/javascripts/browser/location/_/index.ts", "src/assets/javascripts/utilities/h/index.ts", "src/assets/javascripts/utilities/string/index.ts", "src/assets/javascripts/browser/location/hash/index.ts", "src/assets/javascripts/browser/media/index.ts", "src/assets/javascripts/browser/request/index.ts", "src/assets/javascripts/browser/script/index.ts", "src/assets/javascripts/browser/viewport/offset/index.ts", "src/assets/javascripts/browser/viewport/size/index.ts", "src/assets/javascripts/browser/viewport/_/index.ts", "src/assets/javascripts/browser/viewport/at/index.ts", "src/assets/javascripts/browser/worker/index.ts", "src/assets/javascripts/_/index.ts", "src/assets/javascripts/components/_/index.ts", "src/assets/javascripts/components/content/code/_/index.ts", "src/assets/javascripts/templates/annotation/index.tsx", "src/assets/javascripts/templates/clipboard/index.tsx", "src/assets/javascripts/templates/search/index.tsx", "src/assets/javascripts/templates/source/index.tsx", "src/assets/javascripts/templates/tabbed/index.tsx", "src/assets/javascripts/templates/table/index.tsx", "src/assets/javascripts/templates/version/index.tsx", "src/assets/javascripts/components/content/annotation/_/index.ts", "src/assets/javascripts/components/content/annotation/list/index.ts", "src/assets/javascripts/components/content/code/mermaid/index.ts", "src/assets/javascripts/components/content/details/index.ts", "src/assets/javascripts/components/content/table/index.ts", "src/assets/javascripts/components/content/tabs/index.ts", "src/assets/javascripts/components/content/_/index.ts", "src/assets/javascripts/components/dialog/index.ts", "src/assets/javascripts/components/header/_/index.ts", "src/assets/javascripts/components/header/title/index.ts", "src/assets/javascripts/components/main/index.ts", "src/assets/javascripts/components/palette/index.ts", "src/assets/javascripts/integrations/clipboard/index.ts", "src/assets/javascripts/integrations/sitemap/index.ts", "src/assets/javascripts/integrations/instant/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/transform/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts", "src/assets/javascripts/integrations/search/worker/_/index.ts", "src/assets/javascripts/integrations/version/index.ts", "src/assets/javascripts/components/search/query/index.ts", "src/assets/javascripts/components/search/result/index.ts", "src/assets/javascripts/components/search/share/index.ts", "src/assets/javascripts/components/search/suggest/index.ts", "src/assets/javascripts/components/search/_/index.ts", "src/assets/javascripts/components/search/highlight/index.ts", "src/assets/javascripts/components/sidebar/index.ts", "src/assets/javascripts/components/source/facts/github/index.ts", "src/assets/javascripts/components/source/facts/gitlab/index.ts", "src/assets/javascripts/components/source/facts/_/index.ts", "src/assets/javascripts/components/source/_/index.ts", "src/assets/javascripts/components/tabs/index.ts", "src/assets/javascripts/components/toc/index.ts", "src/assets/javascripts/components/top/index.ts", "src/assets/javascripts/patches/indeterminate/index.ts", "src/assets/javascripts/patches/scrollfix/index.ts", "src/assets/javascripts/patches/scrolllock/index.ts", "src/assets/javascripts/polyfills/index.ts"], + "sourceRoot": "../../../..", + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "(function(global) {\r\n /**\r\n * Polyfill URLSearchParams\r\n *\r\n * Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js\r\n */\r\n\r\n var checkIfIteratorIsSupported = function() {\r\n try {\r\n return !!Symbol.iterator;\r\n } catch (error) {\r\n return false;\r\n }\r\n };\r\n\r\n\r\n var iteratorSupported = checkIfIteratorIsSupported();\r\n\r\n var createIterator = function(items) {\r\n var iterator = {\r\n next: function() {\r\n var value = items.shift();\r\n return { done: value === void 0, value: value };\r\n }\r\n };\r\n\r\n if (iteratorSupported) {\r\n iterator[Symbol.iterator] = function() {\r\n return iterator;\r\n };\r\n }\r\n\r\n return iterator;\r\n };\r\n\r\n /**\r\n * Search param name and values should be encoded according to https://url.spec.whatwg.org/#urlencoded-serializing\r\n * encodeURIComponent() produces the same result except encoding spaces as `%20` instead of `+`.\r\n */\r\n var serializeParam = function(value) {\r\n return encodeURIComponent(value).replace(/%20/g, '+');\r\n };\r\n\r\n var deserializeParam = function(value) {\r\n return decodeURIComponent(String(value).replace(/\\+/g, ' '));\r\n };\r\n\r\n var polyfillURLSearchParams = function() {\r\n\r\n var URLSearchParams = function(searchString) {\r\n Object.defineProperty(this, '_entries', { writable: true, value: {} });\r\n var typeofSearchString = typeof searchString;\r\n\r\n if (typeofSearchString === 'undefined') {\r\n // do nothing\r\n } else if (typeofSearchString === 'string') {\r\n if (searchString !== '') {\r\n this._fromString(searchString);\r\n }\r\n } else if (searchString instanceof URLSearchParams) {\r\n var _this = this;\r\n searchString.forEach(function(value, name) {\r\n _this.append(name, value);\r\n });\r\n } else if ((searchString !== null) && (typeofSearchString === 'object')) {\r\n if (Object.prototype.toString.call(searchString) === '[object Array]') {\r\n for (var i = 0; i < searchString.length; i++) {\r\n var entry = searchString[i];\r\n if ((Object.prototype.toString.call(entry) === '[object Array]') || (entry.length !== 2)) {\r\n this.append(entry[0], entry[1]);\r\n } else {\r\n throw new TypeError('Expected [string, any] as entry at index ' + i + ' of URLSearchParams\\'s input');\r\n }\r\n }\r\n } else {\r\n for (var key in searchString) {\r\n if (searchString.hasOwnProperty(key)) {\r\n this.append(key, searchString[key]);\r\n }\r\n }\r\n }\r\n } else {\r\n throw new TypeError('Unsupported input\\'s type for URLSearchParams');\r\n }\r\n };\r\n\r\n var proto = URLSearchParams.prototype;\r\n\r\n proto.append = function(name, value) {\r\n if (name in this._entries) {\r\n this._entries[name].push(String(value));\r\n } else {\r\n this._entries[name] = [String(value)];\r\n }\r\n };\r\n\r\n proto.delete = function(name) {\r\n delete this._entries[name];\r\n };\r\n\r\n proto.get = function(name) {\r\n return (name in this._entries) ? this._entries[name][0] : null;\r\n };\r\n\r\n proto.getAll = function(name) {\r\n return (name in this._entries) ? this._entries[name].slice(0) : [];\r\n };\r\n\r\n proto.has = function(name) {\r\n return (name in this._entries);\r\n };\r\n\r\n proto.set = function(name, value) {\r\n this._entries[name] = [String(value)];\r\n };\r\n\r\n proto.forEach = function(callback, thisArg) {\r\n var entries;\r\n for (var name in this._entries) {\r\n if (this._entries.hasOwnProperty(name)) {\r\n entries = this._entries[name];\r\n for (var i = 0; i < entries.length; i++) {\r\n callback.call(thisArg, entries[i], name, this);\r\n }\r\n }\r\n }\r\n };\r\n\r\n proto.keys = function() {\r\n var items = [];\r\n this.forEach(function(value, name) {\r\n items.push(name);\r\n });\r\n return createIterator(items);\r\n };\r\n\r\n proto.values = function() {\r\n var items = [];\r\n this.forEach(function(value) {\r\n items.push(value);\r\n });\r\n return createIterator(items);\r\n };\r\n\r\n proto.entries = function() {\r\n var items = [];\r\n this.forEach(function(value, name) {\r\n items.push([name, value]);\r\n });\r\n return createIterator(items);\r\n };\r\n\r\n if (iteratorSupported) {\r\n proto[Symbol.iterator] = proto.entries;\r\n }\r\n\r\n proto.toString = function() {\r\n var searchArray = [];\r\n this.forEach(function(value, name) {\r\n searchArray.push(serializeParam(name) + '=' + serializeParam(value));\r\n });\r\n return searchArray.join('&');\r\n };\r\n\r\n\r\n global.URLSearchParams = URLSearchParams;\r\n };\r\n\r\n var checkIfURLSearchParamsSupported = function() {\r\n try {\r\n var URLSearchParams = global.URLSearchParams;\r\n\r\n return (\r\n (new URLSearchParams('?a=1').toString() === 'a=1') &&\r\n (typeof URLSearchParams.prototype.set === 'function') &&\r\n (typeof URLSearchParams.prototype.entries === 'function')\r\n );\r\n } catch (e) {\r\n return false;\r\n }\r\n };\r\n\r\n if (!checkIfURLSearchParamsSupported()) {\r\n polyfillURLSearchParams();\r\n }\r\n\r\n var proto = global.URLSearchParams.prototype;\r\n\r\n if (typeof proto.sort !== 'function') {\r\n proto.sort = function() {\r\n var _this = this;\r\n var items = [];\r\n this.forEach(function(value, name) {\r\n items.push([name, value]);\r\n if (!_this._entries) {\r\n _this.delete(name);\r\n }\r\n });\r\n items.sort(function(a, b) {\r\n if (a[0] < b[0]) {\r\n return -1;\r\n } else if (a[0] > b[0]) {\r\n return +1;\r\n } else {\r\n return 0;\r\n }\r\n });\r\n if (_this._entries) { // force reset because IE keeps keys index\r\n _this._entries = {};\r\n }\r\n for (var i = 0; i < items.length; i++) {\r\n this.append(items[i][0], items[i][1]);\r\n }\r\n };\r\n }\r\n\r\n if (typeof proto._fromString !== 'function') {\r\n Object.defineProperty(proto, '_fromString', {\r\n enumerable: false,\r\n configurable: false,\r\n writable: false,\r\n value: function(searchString) {\r\n if (this._entries) {\r\n this._entries = {};\r\n } else {\r\n var keys = [];\r\n this.forEach(function(value, name) {\r\n keys.push(name);\r\n });\r\n for (var i = 0; i < keys.length; i++) {\r\n this.delete(keys[i]);\r\n }\r\n }\r\n\r\n searchString = searchString.replace(/^\\?/, '');\r\n var attributes = searchString.split('&');\r\n var attribute;\r\n for (var i = 0; i < attributes.length; i++) {\r\n attribute = attributes[i].split('=');\r\n this.append(\r\n deserializeParam(attribute[0]),\r\n (attribute.length > 1) ? deserializeParam(attribute[1]) : ''\r\n );\r\n }\r\n }\r\n });\r\n }\r\n\r\n // HTMLAnchorElement\r\n\r\n})(\r\n (typeof global !== 'undefined') ? global\r\n : ((typeof window !== 'undefined') ? window\r\n : ((typeof self !== 'undefined') ? self : this))\r\n);\r\n\r\n(function(global) {\r\n /**\r\n * Polyfill URL\r\n *\r\n * Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js\r\n */\r\n\r\n var checkIfURLIsSupported = function() {\r\n try {\r\n var u = new global.URL('b', 'http://a');\r\n u.pathname = 'c d';\r\n return (u.href === 'http://a/c%20d') && u.searchParams;\r\n } catch (e) {\r\n return false;\r\n }\r\n };\r\n\r\n\r\n var polyfillURL = function() {\r\n var _URL = global.URL;\r\n\r\n var URL = function(url, base) {\r\n if (typeof url !== 'string') url = String(url);\r\n if (base && typeof base !== 'string') base = String(base);\r\n\r\n // Only create another document if the base is different from current location.\r\n var doc = document, baseElement;\r\n if (base && (global.location === void 0 || base !== global.location.href)) {\r\n base = base.toLowerCase();\r\n doc = document.implementation.createHTMLDocument('');\r\n baseElement = doc.createElement('base');\r\n baseElement.href = base;\r\n doc.head.appendChild(baseElement);\r\n try {\r\n if (baseElement.href.indexOf(base) !== 0) throw new Error(baseElement.href);\r\n } catch (err) {\r\n throw new Error('URL unable to set base ' + base + ' due to ' + err);\r\n }\r\n }\r\n\r\n var anchorElement = doc.createElement('a');\r\n anchorElement.href = url;\r\n if (baseElement) {\r\n doc.body.appendChild(anchorElement);\r\n anchorElement.href = anchorElement.href; // force href to refresh\r\n }\r\n\r\n var inputElement = doc.createElement('input');\r\n inputElement.type = 'url';\r\n inputElement.value = url;\r\n\r\n if (anchorElement.protocol === ':' || !/:/.test(anchorElement.href) || (!inputElement.checkValidity() && !base)) {\r\n throw new TypeError('Invalid URL');\r\n }\r\n\r\n Object.defineProperty(this, '_anchorElement', {\r\n value: anchorElement\r\n });\r\n\r\n\r\n // create a linked searchParams which reflect its changes on URL\r\n var searchParams = new global.URLSearchParams(this.search);\r\n var enableSearchUpdate = true;\r\n var enableSearchParamsUpdate = true;\r\n var _this = this;\r\n ['append', 'delete', 'set'].forEach(function(methodName) {\r\n var method = searchParams[methodName];\r\n searchParams[methodName] = function() {\r\n method.apply(searchParams, arguments);\r\n if (enableSearchUpdate) {\r\n enableSearchParamsUpdate = false;\r\n _this.search = searchParams.toString();\r\n enableSearchParamsUpdate = true;\r\n }\r\n };\r\n });\r\n\r\n Object.defineProperty(this, 'searchParams', {\r\n value: searchParams,\r\n enumerable: true\r\n });\r\n\r\n var search = void 0;\r\n Object.defineProperty(this, '_updateSearchParams', {\r\n enumerable: false,\r\n configurable: false,\r\n writable: false,\r\n value: function() {\r\n if (this.search !== search) {\r\n search = this.search;\r\n if (enableSearchParamsUpdate) {\r\n enableSearchUpdate = false;\r\n this.searchParams._fromString(this.search);\r\n enableSearchUpdate = true;\r\n }\r\n }\r\n }\r\n });\r\n };\r\n\r\n var proto = URL.prototype;\r\n\r\n var linkURLWithAnchorAttribute = function(attributeName) {\r\n Object.defineProperty(proto, attributeName, {\r\n get: function() {\r\n return this._anchorElement[attributeName];\r\n },\r\n set: function(value) {\r\n this._anchorElement[attributeName] = value;\r\n },\r\n enumerable: true\r\n });\r\n };\r\n\r\n ['hash', 'host', 'hostname', 'port', 'protocol']\r\n .forEach(function(attributeName) {\r\n linkURLWithAnchorAttribute(attributeName);\r\n });\r\n\r\n Object.defineProperty(proto, 'search', {\r\n get: function() {\r\n return this._anchorElement['search'];\r\n },\r\n set: function(value) {\r\n this._anchorElement['search'] = value;\r\n this._updateSearchParams();\r\n },\r\n enumerable: true\r\n });\r\n\r\n Object.defineProperties(proto, {\r\n\r\n 'toString': {\r\n get: function() {\r\n var _this = this;\r\n return function() {\r\n return _this.href;\r\n };\r\n }\r\n },\r\n\r\n 'href': {\r\n get: function() {\r\n return this._anchorElement.href.replace(/\\?$/, '');\r\n },\r\n set: function(value) {\r\n this._anchorElement.href = value;\r\n this._updateSearchParams();\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'pathname': {\r\n get: function() {\r\n return this._anchorElement.pathname.replace(/(^\\/?)/, '/');\r\n },\r\n set: function(value) {\r\n this._anchorElement.pathname = value;\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'origin': {\r\n get: function() {\r\n // get expected port from protocol\r\n var expectedPort = { 'http:': 80, 'https:': 443, 'ftp:': 21 }[this._anchorElement.protocol];\r\n // add port to origin if, expected port is different than actual port\r\n // and it is not empty f.e http://foo:8080\r\n // 8080 != 80 && 8080 != ''\r\n var addPortToOrigin = this._anchorElement.port != expectedPort &&\r\n this._anchorElement.port !== '';\r\n\r\n return this._anchorElement.protocol +\r\n '//' +\r\n this._anchorElement.hostname +\r\n (addPortToOrigin ? (':' + this._anchorElement.port) : '');\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'password': { // TODO\r\n get: function() {\r\n return '';\r\n },\r\n set: function(value) {\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'username': { // TODO\r\n get: function() {\r\n return '';\r\n },\r\n set: function(value) {\r\n },\r\n enumerable: true\r\n },\r\n });\r\n\r\n URL.createObjectURL = function(blob) {\r\n return _URL.createObjectURL.apply(_URL, arguments);\r\n };\r\n\r\n URL.revokeObjectURL = function(url) {\r\n return _URL.revokeObjectURL.apply(_URL, arguments);\r\n };\r\n\r\n global.URL = URL;\r\n\r\n };\r\n\r\n if (!checkIfURLIsSupported()) {\r\n polyfillURL();\r\n }\r\n\r\n if ((global.location !== void 0) && !('origin' in global.location)) {\r\n var getOrigin = function() {\r\n return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : '');\r\n };\r\n\r\n try {\r\n Object.defineProperty(global.location, 'origin', {\r\n get: getOrigin,\r\n enumerable: true\r\n });\r\n } catch (e) {\r\n setInterval(function() {\r\n global.location.origin = getOrigin();\r\n }, 100);\r\n }\r\n }\r\n\r\n})(\r\n (typeof global !== 'undefined') ? global\r\n : ((typeof window !== 'undefined') ? window\r\n : ((typeof self !== 'undefined') ? self : this))\r\n);\r\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global global, define, System, Reflect, Promise */\r\nvar __extends;\r\nvar __assign;\r\nvar __rest;\r\nvar __decorate;\r\nvar __param;\r\nvar __metadata;\r\nvar __awaiter;\r\nvar __generator;\r\nvar __exportStar;\r\nvar __values;\r\nvar __read;\r\nvar __spread;\r\nvar __spreadArrays;\r\nvar __spreadArray;\r\nvar __await;\r\nvar __asyncGenerator;\r\nvar __asyncDelegator;\r\nvar __asyncValues;\r\nvar __makeTemplateObject;\r\nvar __importStar;\r\nvar __importDefault;\r\nvar __classPrivateFieldGet;\r\nvar __classPrivateFieldSet;\r\nvar __createBinding;\r\n(function (factory) {\r\n var root = typeof global === \"object\" ? global : typeof self === \"object\" ? self : typeof this === \"object\" ? this : {};\r\n if (typeof define === \"function\" && define.amd) {\r\n define(\"tslib\", [\"exports\"], function (exports) { factory(createExporter(root, createExporter(exports))); });\r\n }\r\n else if (typeof module === \"object\" && typeof module.exports === \"object\") {\r\n factory(createExporter(root, createExporter(module.exports)));\r\n }\r\n else {\r\n factory(createExporter(root));\r\n }\r\n function createExporter(exports, previous) {\r\n if (exports !== root) {\r\n if (typeof Object.create === \"function\") {\r\n Object.defineProperty(exports, \"__esModule\", { value: true });\r\n }\r\n else {\r\n exports.__esModule = true;\r\n }\r\n }\r\n return function (id, v) { return exports[id] = previous ? previous(id, v) : v; };\r\n }\r\n})\r\n(function (exporter) {\r\n var extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n\r\n __extends = function (d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n };\r\n\r\n __assign = Object.assign || function (t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n };\r\n\r\n __rest = function (s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n };\r\n\r\n __decorate = function (decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n };\r\n\r\n __param = function (paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n };\r\n\r\n __metadata = function (metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n };\r\n\r\n __awaiter = function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n };\r\n\r\n __generator = function (thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n };\r\n\r\n __exportStar = function(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n };\r\n\r\n __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n }) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n });\r\n\r\n __values = function (o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n };\r\n\r\n __read = function (o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n };\r\n\r\n /** @deprecated */\r\n __spread = function () {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n };\r\n\r\n /** @deprecated */\r\n __spreadArrays = function () {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n };\r\n\r\n __spreadArray = function (to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n };\r\n\r\n __await = function (v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n };\r\n\r\n __asyncGenerator = function (thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n };\r\n\r\n __asyncDelegator = function (o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n };\r\n\r\n __asyncValues = function (o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n };\r\n\r\n __makeTemplateObject = function (cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n };\r\n\r\n var __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n }) : function(o, v) {\r\n o[\"default\"] = v;\r\n };\r\n\r\n __importStar = function (mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n };\r\n\r\n __importDefault = function (mod) {\r\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\r\n };\r\n\r\n __classPrivateFieldGet = function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n };\r\n\r\n __classPrivateFieldSet = function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n };\r\n\r\n exporter(\"__extends\", __extends);\r\n exporter(\"__assign\", __assign);\r\n exporter(\"__rest\", __rest);\r\n exporter(\"__decorate\", __decorate);\r\n exporter(\"__param\", __param);\r\n exporter(\"__metadata\", __metadata);\r\n exporter(\"__awaiter\", __awaiter);\r\n exporter(\"__generator\", __generator);\r\n exporter(\"__exportStar\", __exportStar);\r\n exporter(\"__createBinding\", __createBinding);\r\n exporter(\"__values\", __values);\r\n exporter(\"__read\", __read);\r\n exporter(\"__spread\", __spread);\r\n exporter(\"__spreadArrays\", __spreadArrays);\r\n exporter(\"__spreadArray\", __spreadArray);\r\n exporter(\"__await\", __await);\r\n exporter(\"__asyncGenerator\", __asyncGenerator);\r\n exporter(\"__asyncDelegator\", __asyncDelegator);\r\n exporter(\"__asyncValues\", __asyncValues);\r\n exporter(\"__makeTemplateObject\", __makeTemplateObject);\r\n exporter(\"__importStar\", __importStar);\r\n exporter(\"__importDefault\", __importDefault);\r\n exporter(\"__classPrivateFieldGet\", __classPrivateFieldGet);\r\n exporter(\"__classPrivateFieldSet\", __classPrivateFieldSet);\r\n});\r\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "Array.prototype.flat||Object.defineProperty(Array.prototype,\"flat\",{configurable:!0,value:function r(){var t=isNaN(arguments[0])?1:Number(arguments[0]);return t?Array.prototype.reduce.call(this,function(a,e){return Array.isArray(e)?a.push.apply(a,r.call(e,t-1)):a.push(e),a},[]):Array.prototype.slice.call(this)},writable:!0}),Array.prototype.flatMap||Object.defineProperty(Array.prototype,\"flatMap\",{configurable:!0,value:function(r){return Array.prototype.map.apply(this,arguments).flat()},writable:!0})\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"array-flat-polyfill\"\nimport \"focus-visible\"\nimport \"unfetch/polyfill\"\nimport \"url-polyfill\"\n\nimport {\n EMPTY,\n NEVER,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getOptionalElement,\n requestJSON,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountBackToTop,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantLoading,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget()\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? __search?.index || requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantLoading({ document$, location$, viewport$ })\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"[href][rel=prev]\")\n if (typeof prev !== \"undefined\")\n prev.click()\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"[href][rel=next]\")\n if (typeof next !== \"undefined\")\n next.click()\n break\n }\n })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, { viewport$, header$, target$ })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.component$ = component$ /* Component observable */\n", "self.fetch||(self.fetch=function(e,n){return n=n||{},new Promise(function(t,s){var r=new XMLHttpRequest,o=[],u=[],i={},a=function(){return{ok:2==(r.status/100|0),statusText:r.statusText,status:r.status,url:r.responseURL,text:function(){return Promise.resolve(r.responseText)},json:function(){return Promise.resolve(r.responseText).then(JSON.parse)},blob:function(){return Promise.resolve(new Blob([r.response]))},clone:a,headers:{keys:function(){return o},entries:function(){return u},get:function(e){return i[e.toLowerCase()]},has:function(e){return e.toLowerCase()in i}}}};for(var c in r.open(n.method||\"get\",e,!0),r.onload=function(){r.getAllResponseHeaders().replace(/^(.*?):[^\\S\\n]*([\\s\\S]*?)$/gm,function(e,n,t){o.push(n=n.toLowerCase()),u.push([n,t]),i[n]=i[n]?i[n]+\",\"+t:t}),t(a())},r.onerror=s,r.withCredentials=\"include\"==n.credentials,n.headers)r.setRequestHeader(c,n.headers[c]);r.send(n.body||null)})});\n", "import tslib from '../tslib.js';\r\nconst {\r\n __extends,\r\n __assign,\r\n __rest,\r\n __decorate,\r\n __param,\r\n __metadata,\r\n __awaiter,\r\n __generator,\r\n __exportStar,\r\n __createBinding,\r\n __values,\r\n __read,\r\n __spread,\r\n __spreadArrays,\r\n __spreadArray,\r\n __await,\r\n __asyncGenerator,\r\n __asyncDelegator,\r\n __asyncValues,\r\n __makeTemplateObject,\r\n __importStar,\r\n __importDefault,\r\n __classPrivateFieldGet,\r\n __classPrivateFieldSet,\r\n} = tslib;\r\nexport {\r\n __extends,\r\n __assign,\r\n __rest,\r\n __decorate,\r\n __param,\r\n __metadata,\r\n __awaiter,\r\n __generator,\r\n __exportStar,\r\n __createBinding,\r\n __values,\r\n __read,\r\n __spread,\r\n __spreadArrays,\r\n __spreadArray,\r\n __await,\r\n __asyncGenerator,\r\n __asyncDelegator,\r\n __asyncValues,\r\n __makeTemplateObject,\r\n __importStar,\r\n __importDefault,\r\n __classPrivateFieldGet,\r\n __classPrivateFieldSet,\r\n};\r\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n ReplaySubject,\n Subject,\n fromEvent\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch document\n *\n * Documents are implemented as subjects, so all downstream observables are\n * automatically updated when a new document is emitted.\n *\n * @returns Document subject\n */\nexport function watchDocument(): Subject {\n const document$ = new ReplaySubject(1)\n fromEvent(document, \"DOMContentLoaded\", { once: true })\n .subscribe(() => document$.next(document))\n\n /* Return document */\n return document$\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements(\n selector: string, node?: ParentNode\n): T[]\n\nexport function getElements(\n selector: string, node: ParentNode = document\n): T[] {\n return Array.from(node.querySelectorAll(selector))\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * Note that this function assumes that the element is present. If unsure if an\n * element is existent, use the `getOptionalElement` function instead.\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement(\n selector: string, node?: ParentNode\n): T\n\nexport function getElement(\n selector: string, node: ParentNode = document\n): T {\n const el = getOptionalElement(selector, node)\n if (typeof el === \"undefined\")\n throw new ReferenceError(\n `Missing element: expected \"${selector}\" to be present`\n )\n\n /* Return element */\n return el\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an optional element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getOptionalElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T] | undefined\n\nexport function getOptionalElement(\n selector: string, node?: ParentNode\n): T | undefined\n\nexport function getOptionalElement(\n selector: string, node: ParentNode = document\n): T | undefined {\n return node.querySelector(selector) || undefined\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n return document.activeElement instanceof HTMLElement\n ? document.activeElement || undefined\n : undefined\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n debounceTime,\n distinctUntilChanged,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\nimport { getActiveElement } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch element focus\n *\n * Previously, this function used `focus` and `blur` events to determine whether\n * an element is focused, but this doesn't work if there are focusable elements\n * within the elements itself. A better solutions are `focusin` and `focusout`\n * events, which bubble up the tree and allow for more fine-grained control.\n *\n * `debounceTime` is necessary, because when a focus change happens inside an\n * element, the observable would first emit `false` and then `true` again.\n *\n * @param el - Element\n *\n * @returns Element focus observable\n */\nexport function watchElementFocus(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(document.body, \"focusin\"),\n fromEvent(document.body, \"focusout\")\n )\n .pipe(\n debounceTime(1),\n map(() => {\n const active = getActiveElement()\n return typeof active !== \"undefined\"\n ? el.contains(active)\n : false\n }),\n startWith(el === getActiveElement()),\n distinctUntilChanged()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n animationFrameScheduler,\n auditTime,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementOffset {\n x: number /* Horizontal offset */\n y: number /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element offset\n *\n * @param el - Element\n *\n * @returns Element offset\n */\nexport function getElementOffset(\n el: HTMLElement\n): ElementOffset {\n return {\n x: el.offsetLeft,\n y: el.offsetTop\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element offset\n *\n * @param el - Element\n *\n * @returns Element offset observable\n */\nexport function watchElementOffset(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(window, \"load\"),\n fromEvent(window, \"resize\")\n )\n .pipe(\n auditTime(0, animationFrameScheduler),\n map(() => getElementOffset(el)),\n startWith(getElementOffset(el))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n animationFrameScheduler,\n auditTime,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\nimport { ElementOffset } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element content offset (= scroll offset)\n *\n * @param el - Element\n *\n * @returns Element content offset\n */\nexport function getElementContentOffset(\n el: HTMLElement\n): ElementOffset {\n return {\n x: el.scrollLeft,\n y: el.scrollTop\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element content offset\n *\n * @param el - Element\n *\n * @returns Element content offset observable\n */\nexport function watchElementContentOffset(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(el, \"scroll\"),\n fromEvent(window, \"resize\")\n )\n .pipe(\n auditTime(0, animationFrameScheduler),\n map(() => getElementContentOffset(el)),\n startWith(getElementContentOffset(el))\n )\n}\n", "/**\r\n * A collection of shims that provide minimal functionality of the ES6 collections.\r\n *\r\n * These implementations are not meant to be used outside of the ResizeObserver\r\n * modules as they cover only a limited range of use cases.\r\n */\r\n/* eslint-disable require-jsdoc, valid-jsdoc */\r\nvar MapShim = (function () {\r\n if (typeof Map !== 'undefined') {\r\n return Map;\r\n }\r\n /**\r\n * Returns index in provided array that matches the specified key.\r\n *\r\n * @param {Array} arr\r\n * @param {*} key\r\n * @returns {number}\r\n */\r\n function getIndex(arr, key) {\r\n var result = -1;\r\n arr.some(function (entry, index) {\r\n if (entry[0] === key) {\r\n result = index;\r\n return true;\r\n }\r\n return false;\r\n });\r\n return result;\r\n }\r\n return /** @class */ (function () {\r\n function class_1() {\r\n this.__entries__ = [];\r\n }\r\n Object.defineProperty(class_1.prototype, \"size\", {\r\n /**\r\n * @returns {boolean}\r\n */\r\n get: function () {\r\n return this.__entries__.length;\r\n },\r\n enumerable: true,\r\n configurable: true\r\n });\r\n /**\r\n * @param {*} key\r\n * @returns {*}\r\n */\r\n class_1.prototype.get = function (key) {\r\n var index = getIndex(this.__entries__, key);\r\n var entry = this.__entries__[index];\r\n return entry && entry[1];\r\n };\r\n /**\r\n * @param {*} key\r\n * @param {*} value\r\n * @returns {void}\r\n */\r\n class_1.prototype.set = function (key, value) {\r\n var index = getIndex(this.__entries__, key);\r\n if (~index) {\r\n this.__entries__[index][1] = value;\r\n }\r\n else {\r\n this.__entries__.push([key, value]);\r\n }\r\n };\r\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\r\n class_1.prototype.delete = function (key) {\r\n var entries = this.__entries__;\r\n var index = getIndex(entries, key);\r\n if (~index) {\r\n entries.splice(index, 1);\r\n }\r\n };\r\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\r\n class_1.prototype.has = function (key) {\r\n return !!~getIndex(this.__entries__, key);\r\n };\r\n /**\r\n * @returns {void}\r\n */\r\n class_1.prototype.clear = function () {\r\n this.__entries__.splice(0);\r\n };\r\n /**\r\n * @param {Function} callback\r\n * @param {*} [ctx=null]\r\n * @returns {void}\r\n */\r\n class_1.prototype.forEach = function (callback, ctx) {\r\n if (ctx === void 0) { ctx = null; }\r\n for (var _i = 0, _a = this.__entries__; _i < _a.length; _i++) {\r\n var entry = _a[_i];\r\n callback.call(ctx, entry[1], entry[0]);\r\n }\r\n };\r\n return class_1;\r\n }());\r\n})();\n\n/**\r\n * Detects whether window and document objects are available in current environment.\r\n */\r\nvar isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document;\n\n// Returns global object of a current environment.\r\nvar global$1 = (function () {\r\n if (typeof global !== 'undefined' && global.Math === Math) {\r\n return global;\r\n }\r\n if (typeof self !== 'undefined' && self.Math === Math) {\r\n return self;\r\n }\r\n if (typeof window !== 'undefined' && window.Math === Math) {\r\n return window;\r\n }\r\n // eslint-disable-next-line no-new-func\r\n return Function('return this')();\r\n})();\n\n/**\r\n * A shim for the requestAnimationFrame which falls back to the setTimeout if\r\n * first one is not supported.\r\n *\r\n * @returns {number} Requests' identifier.\r\n */\r\nvar requestAnimationFrame$1 = (function () {\r\n if (typeof requestAnimationFrame === 'function') {\r\n // It's required to use a bounded function because IE sometimes throws\r\n // an \"Invalid calling object\" error if rAF is invoked without the global\r\n // object on the left hand side.\r\n return requestAnimationFrame.bind(global$1);\r\n }\r\n return function (callback) { return setTimeout(function () { return callback(Date.now()); }, 1000 / 60); };\r\n})();\n\n// Defines minimum timeout before adding a trailing call.\r\nvar trailingTimeout = 2;\r\n/**\r\n * Creates a wrapper function which ensures that provided callback will be\r\n * invoked only once during the specified delay period.\r\n *\r\n * @param {Function} callback - Function to be invoked after the delay period.\r\n * @param {number} delay - Delay after which to invoke callback.\r\n * @returns {Function}\r\n */\r\nfunction throttle (callback, delay) {\r\n var leadingCall = false, trailingCall = false, lastCallTime = 0;\r\n /**\r\n * Invokes the original callback function and schedules new invocation if\r\n * the \"proxy\" was called during current request.\r\n *\r\n * @returns {void}\r\n */\r\n function resolvePending() {\r\n if (leadingCall) {\r\n leadingCall = false;\r\n callback();\r\n }\r\n if (trailingCall) {\r\n proxy();\r\n }\r\n }\r\n /**\r\n * Callback invoked after the specified delay. It will further postpone\r\n * invocation of the original function delegating it to the\r\n * requestAnimationFrame.\r\n *\r\n * @returns {void}\r\n */\r\n function timeoutCallback() {\r\n requestAnimationFrame$1(resolvePending);\r\n }\r\n /**\r\n * Schedules invocation of the original function.\r\n *\r\n * @returns {void}\r\n */\r\n function proxy() {\r\n var timeStamp = Date.now();\r\n if (leadingCall) {\r\n // Reject immediately following calls.\r\n if (timeStamp - lastCallTime < trailingTimeout) {\r\n return;\r\n }\r\n // Schedule new call to be in invoked when the pending one is resolved.\r\n // This is important for \"transitions\" which never actually start\r\n // immediately so there is a chance that we might miss one if change\r\n // happens amids the pending invocation.\r\n trailingCall = true;\r\n }\r\n else {\r\n leadingCall = true;\r\n trailingCall = false;\r\n setTimeout(timeoutCallback, delay);\r\n }\r\n lastCallTime = timeStamp;\r\n }\r\n return proxy;\r\n}\n\n// Minimum delay before invoking the update of observers.\r\nvar REFRESH_DELAY = 20;\r\n// A list of substrings of CSS properties used to find transition events that\r\n// might affect dimensions of observed elements.\r\nvar transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];\r\n// Check if MutationObserver is available.\r\nvar mutationObserverSupported = typeof MutationObserver !== 'undefined';\r\n/**\r\n * Singleton controller class which handles updates of ResizeObserver instances.\r\n */\r\nvar ResizeObserverController = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserverController.\r\n *\r\n * @private\r\n */\r\n function ResizeObserverController() {\r\n /**\r\n * Indicates whether DOM listeners have been added.\r\n *\r\n * @private {boolean}\r\n */\r\n this.connected_ = false;\r\n /**\r\n * Tells that controller has subscribed for Mutation Events.\r\n *\r\n * @private {boolean}\r\n */\r\n this.mutationEventsAdded_ = false;\r\n /**\r\n * Keeps reference to the instance of MutationObserver.\r\n *\r\n * @private {MutationObserver}\r\n */\r\n this.mutationsObserver_ = null;\r\n /**\r\n * A list of connected observers.\r\n *\r\n * @private {Array}\r\n */\r\n this.observers_ = [];\r\n this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);\r\n this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);\r\n }\r\n /**\r\n * Adds observer to observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be added.\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.addObserver = function (observer) {\r\n if (!~this.observers_.indexOf(observer)) {\r\n this.observers_.push(observer);\r\n }\r\n // Add listeners if they haven't been added yet.\r\n if (!this.connected_) {\r\n this.connect_();\r\n }\r\n };\r\n /**\r\n * Removes observer from observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be removed.\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.removeObserver = function (observer) {\r\n var observers = this.observers_;\r\n var index = observers.indexOf(observer);\r\n // Remove observer if it's present in registry.\r\n if (~index) {\r\n observers.splice(index, 1);\r\n }\r\n // Remove listeners if controller has no connected observers.\r\n if (!observers.length && this.connected_) {\r\n this.disconnect_();\r\n }\r\n };\r\n /**\r\n * Invokes the update of observers. It will continue running updates insofar\r\n * it detects changes.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.refresh = function () {\r\n var changesDetected = this.updateObservers_();\r\n // Continue running updates if changes have been detected as there might\r\n // be future ones caused by CSS transitions.\r\n if (changesDetected) {\r\n this.refresh();\r\n }\r\n };\r\n /**\r\n * Updates every observer from observers list and notifies them of queued\r\n * entries.\r\n *\r\n * @private\r\n * @returns {boolean} Returns \"true\" if any observer has detected changes in\r\n * dimensions of it's elements.\r\n */\r\n ResizeObserverController.prototype.updateObservers_ = function () {\r\n // Collect observers that have active observations.\r\n var activeObservers = this.observers_.filter(function (observer) {\r\n return observer.gatherActive(), observer.hasActive();\r\n });\r\n // Deliver notifications in a separate cycle in order to avoid any\r\n // collisions between observers, e.g. when multiple instances of\r\n // ResizeObserver are tracking the same element and the callback of one\r\n // of them changes content dimensions of the observed target. Sometimes\r\n // this may result in notifications being blocked for the rest of observers.\r\n activeObservers.forEach(function (observer) { return observer.broadcastActive(); });\r\n return activeObservers.length > 0;\r\n };\r\n /**\r\n * Initializes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.connect_ = function () {\r\n // Do nothing if running in a non-browser environment or if listeners\r\n // have been already added.\r\n if (!isBrowser || this.connected_) {\r\n return;\r\n }\r\n // Subscription to the \"Transitionend\" event is used as a workaround for\r\n // delayed transitions. This way it's possible to capture at least the\r\n // final state of an element.\r\n document.addEventListener('transitionend', this.onTransitionEnd_);\r\n window.addEventListener('resize', this.refresh);\r\n if (mutationObserverSupported) {\r\n this.mutationsObserver_ = new MutationObserver(this.refresh);\r\n this.mutationsObserver_.observe(document, {\r\n attributes: true,\r\n childList: true,\r\n characterData: true,\r\n subtree: true\r\n });\r\n }\r\n else {\r\n document.addEventListener('DOMSubtreeModified', this.refresh);\r\n this.mutationEventsAdded_ = true;\r\n }\r\n this.connected_ = true;\r\n };\r\n /**\r\n * Removes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.disconnect_ = function () {\r\n // Do nothing if running in a non-browser environment or if listeners\r\n // have been already removed.\r\n if (!isBrowser || !this.connected_) {\r\n return;\r\n }\r\n document.removeEventListener('transitionend', this.onTransitionEnd_);\r\n window.removeEventListener('resize', this.refresh);\r\n if (this.mutationsObserver_) {\r\n this.mutationsObserver_.disconnect();\r\n }\r\n if (this.mutationEventsAdded_) {\r\n document.removeEventListener('DOMSubtreeModified', this.refresh);\r\n }\r\n this.mutationsObserver_ = null;\r\n this.mutationEventsAdded_ = false;\r\n this.connected_ = false;\r\n };\r\n /**\r\n * \"Transitionend\" event handler.\r\n *\r\n * @private\r\n * @param {TransitionEvent} event\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.onTransitionEnd_ = function (_a) {\r\n var _b = _a.propertyName, propertyName = _b === void 0 ? '' : _b;\r\n // Detect whether transition may affect dimensions of an element.\r\n var isReflowProperty = transitionKeys.some(function (key) {\r\n return !!~propertyName.indexOf(key);\r\n });\r\n if (isReflowProperty) {\r\n this.refresh();\r\n }\r\n };\r\n /**\r\n * Returns instance of the ResizeObserverController.\r\n *\r\n * @returns {ResizeObserverController}\r\n */\r\n ResizeObserverController.getInstance = function () {\r\n if (!this.instance_) {\r\n this.instance_ = new ResizeObserverController();\r\n }\r\n return this.instance_;\r\n };\r\n /**\r\n * Holds reference to the controller's instance.\r\n *\r\n * @private {ResizeObserverController}\r\n */\r\n ResizeObserverController.instance_ = null;\r\n return ResizeObserverController;\r\n}());\n\n/**\r\n * Defines non-writable/enumerable properties of the provided target object.\r\n *\r\n * @param {Object} target - Object for which to define properties.\r\n * @param {Object} props - Properties to be defined.\r\n * @returns {Object} Target object.\r\n */\r\nvar defineConfigurable = (function (target, props) {\r\n for (var _i = 0, _a = Object.keys(props); _i < _a.length; _i++) {\r\n var key = _a[_i];\r\n Object.defineProperty(target, key, {\r\n value: props[key],\r\n enumerable: false,\r\n writable: false,\r\n configurable: true\r\n });\r\n }\r\n return target;\r\n});\n\n/**\r\n * Returns the global object associated with provided element.\r\n *\r\n * @param {Object} target\r\n * @returns {Object}\r\n */\r\nvar getWindowOf = (function (target) {\r\n // Assume that the element is an instance of Node, which means that it\r\n // has the \"ownerDocument\" property from which we can retrieve a\r\n // corresponding global object.\r\n var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;\r\n // Return the local global object if it's not possible extract one from\r\n // provided element.\r\n return ownerGlobal || global$1;\r\n});\n\n// Placeholder of an empty content rectangle.\r\nvar emptyRect = createRectInit(0, 0, 0, 0);\r\n/**\r\n * Converts provided string to a number.\r\n *\r\n * @param {number|string} value\r\n * @returns {number}\r\n */\r\nfunction toFloat(value) {\r\n return parseFloat(value) || 0;\r\n}\r\n/**\r\n * Extracts borders size from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @param {...string} positions - Borders positions (top, right, ...)\r\n * @returns {number}\r\n */\r\nfunction getBordersSize(styles) {\r\n var positions = [];\r\n for (var _i = 1; _i < arguments.length; _i++) {\r\n positions[_i - 1] = arguments[_i];\r\n }\r\n return positions.reduce(function (size, position) {\r\n var value = styles['border-' + position + '-width'];\r\n return size + toFloat(value);\r\n }, 0);\r\n}\r\n/**\r\n * Extracts paddings sizes from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @returns {Object} Paddings box.\r\n */\r\nfunction getPaddings(styles) {\r\n var positions = ['top', 'right', 'bottom', 'left'];\r\n var paddings = {};\r\n for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {\r\n var position = positions_1[_i];\r\n var value = styles['padding-' + position];\r\n paddings[position] = toFloat(value);\r\n }\r\n return paddings;\r\n}\r\n/**\r\n * Calculates content rectangle of provided SVG element.\r\n *\r\n * @param {SVGGraphicsElement} target - Element content rectangle of which needs\r\n * to be calculated.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getSVGContentRect(target) {\r\n var bbox = target.getBBox();\r\n return createRectInit(0, 0, bbox.width, bbox.height);\r\n}\r\n/**\r\n * Calculates content rectangle of provided HTMLElement.\r\n *\r\n * @param {HTMLElement} target - Element for which to calculate the content rectangle.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getHTMLElementContentRect(target) {\r\n // Client width & height properties can't be\r\n // used exclusively as they provide rounded values.\r\n var clientWidth = target.clientWidth, clientHeight = target.clientHeight;\r\n // By this condition we can catch all non-replaced inline, hidden and\r\n // detached elements. Though elements with width & height properties less\r\n // than 0.5 will be discarded as well.\r\n //\r\n // Without it we would need to implement separate methods for each of\r\n // those cases and it's not possible to perform a precise and performance\r\n // effective test for hidden elements. E.g. even jQuery's ':visible' filter\r\n // gives wrong results for elements with width & height less than 0.5.\r\n if (!clientWidth && !clientHeight) {\r\n return emptyRect;\r\n }\r\n var styles = getWindowOf(target).getComputedStyle(target);\r\n var paddings = getPaddings(styles);\r\n var horizPad = paddings.left + paddings.right;\r\n var vertPad = paddings.top + paddings.bottom;\r\n // Computed styles of width & height are being used because they are the\r\n // only dimensions available to JS that contain non-rounded values. It could\r\n // be possible to utilize the getBoundingClientRect if only it's data wasn't\r\n // affected by CSS transformations let alone paddings, borders and scroll bars.\r\n var width = toFloat(styles.width), height = toFloat(styles.height);\r\n // Width & height include paddings and borders when the 'border-box' box\r\n // model is applied (except for IE).\r\n if (styles.boxSizing === 'border-box') {\r\n // Following conditions are required to handle Internet Explorer which\r\n // doesn't include paddings and borders to computed CSS dimensions.\r\n //\r\n // We can say that if CSS dimensions + paddings are equal to the \"client\"\r\n // properties then it's either IE, and thus we don't need to subtract\r\n // anything, or an element merely doesn't have paddings/borders styles.\r\n if (Math.round(width + horizPad) !== clientWidth) {\r\n width -= getBordersSize(styles, 'left', 'right') + horizPad;\r\n }\r\n if (Math.round(height + vertPad) !== clientHeight) {\r\n height -= getBordersSize(styles, 'top', 'bottom') + vertPad;\r\n }\r\n }\r\n // Following steps can't be applied to the document's root element as its\r\n // client[Width/Height] properties represent viewport area of the window.\r\n // Besides, it's as well not necessary as the itself neither has\r\n // rendered scroll bars nor it can be clipped.\r\n if (!isDocumentElement(target)) {\r\n // In some browsers (only in Firefox, actually) CSS width & height\r\n // include scroll bars size which can be removed at this step as scroll\r\n // bars are the only difference between rounded dimensions + paddings\r\n // and \"client\" properties, though that is not always true in Chrome.\r\n var vertScrollbar = Math.round(width + horizPad) - clientWidth;\r\n var horizScrollbar = Math.round(height + vertPad) - clientHeight;\r\n // Chrome has a rather weird rounding of \"client\" properties.\r\n // E.g. for an element with content width of 314.2px it sometimes gives\r\n // the client width of 315px and for the width of 314.7px it may give\r\n // 314px. And it doesn't happen all the time. So just ignore this delta\r\n // as a non-relevant.\r\n if (Math.abs(vertScrollbar) !== 1) {\r\n width -= vertScrollbar;\r\n }\r\n if (Math.abs(horizScrollbar) !== 1) {\r\n height -= horizScrollbar;\r\n }\r\n }\r\n return createRectInit(paddings.left, paddings.top, width, height);\r\n}\r\n/**\r\n * Checks whether provided element is an instance of the SVGGraphicsElement.\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\r\nvar isSVGGraphicsElement = (function () {\r\n // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement\r\n // interface.\r\n if (typeof SVGGraphicsElement !== 'undefined') {\r\n return function (target) { return target instanceof getWindowOf(target).SVGGraphicsElement; };\r\n }\r\n // If it's so, then check that element is at least an instance of the\r\n // SVGElement and that it has the \"getBBox\" method.\r\n // eslint-disable-next-line no-extra-parens\r\n return function (target) { return (target instanceof getWindowOf(target).SVGElement &&\r\n typeof target.getBBox === 'function'); };\r\n})();\r\n/**\r\n * Checks whether provided element is a document element ().\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\r\nfunction isDocumentElement(target) {\r\n return target === getWindowOf(target).document.documentElement;\r\n}\r\n/**\r\n * Calculates an appropriate content rectangle for provided html or svg element.\r\n *\r\n * @param {Element} target - Element content rectangle of which needs to be calculated.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getContentRect(target) {\r\n if (!isBrowser) {\r\n return emptyRect;\r\n }\r\n if (isSVGGraphicsElement(target)) {\r\n return getSVGContentRect(target);\r\n }\r\n return getHTMLElementContentRect(target);\r\n}\r\n/**\r\n * Creates rectangle with an interface of the DOMRectReadOnly.\r\n * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly\r\n *\r\n * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions.\r\n * @returns {DOMRectReadOnly}\r\n */\r\nfunction createReadOnlyRect(_a) {\r\n var x = _a.x, y = _a.y, width = _a.width, height = _a.height;\r\n // If DOMRectReadOnly is available use it as a prototype for the rectangle.\r\n var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object;\r\n var rect = Object.create(Constr.prototype);\r\n // Rectangle's properties are not writable and non-enumerable.\r\n defineConfigurable(rect, {\r\n x: x, y: y, width: width, height: height,\r\n top: y,\r\n right: x + width,\r\n bottom: height + y,\r\n left: x\r\n });\r\n return rect;\r\n}\r\n/**\r\n * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates.\r\n * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit\r\n *\r\n * @param {number} x - X coordinate.\r\n * @param {number} y - Y coordinate.\r\n * @param {number} width - Rectangle's width.\r\n * @param {number} height - Rectangle's height.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction createRectInit(x, y, width, height) {\r\n return { x: x, y: y, width: width, height: height };\r\n}\n\n/**\r\n * Class that is responsible for computations of the content rectangle of\r\n * provided DOM element and for keeping track of it's changes.\r\n */\r\nvar ResizeObservation = /** @class */ (function () {\r\n /**\r\n * Creates an instance of ResizeObservation.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n */\r\n function ResizeObservation(target) {\r\n /**\r\n * Broadcasted width of content rectangle.\r\n *\r\n * @type {number}\r\n */\r\n this.broadcastWidth = 0;\r\n /**\r\n * Broadcasted height of content rectangle.\r\n *\r\n * @type {number}\r\n */\r\n this.broadcastHeight = 0;\r\n /**\r\n * Reference to the last observed content rectangle.\r\n *\r\n * @private {DOMRectInit}\r\n */\r\n this.contentRect_ = createRectInit(0, 0, 0, 0);\r\n this.target = target;\r\n }\r\n /**\r\n * Updates content rectangle and tells whether it's width or height properties\r\n * have changed since the last broadcast.\r\n *\r\n * @returns {boolean}\r\n */\r\n ResizeObservation.prototype.isActive = function () {\r\n var rect = getContentRect(this.target);\r\n this.contentRect_ = rect;\r\n return (rect.width !== this.broadcastWidth ||\r\n rect.height !== this.broadcastHeight);\r\n };\r\n /**\r\n * Updates 'broadcastWidth' and 'broadcastHeight' properties with a data\r\n * from the corresponding properties of the last observed content rectangle.\r\n *\r\n * @returns {DOMRectInit} Last observed content rectangle.\r\n */\r\n ResizeObservation.prototype.broadcastRect = function () {\r\n var rect = this.contentRect_;\r\n this.broadcastWidth = rect.width;\r\n this.broadcastHeight = rect.height;\r\n return rect;\r\n };\r\n return ResizeObservation;\r\n}());\n\nvar ResizeObserverEntry = /** @class */ (function () {\r\n /**\r\n * Creates an instance of ResizeObserverEntry.\r\n *\r\n * @param {Element} target - Element that is being observed.\r\n * @param {DOMRectInit} rectInit - Data of the element's content rectangle.\r\n */\r\n function ResizeObserverEntry(target, rectInit) {\r\n var contentRect = createReadOnlyRect(rectInit);\r\n // According to the specification following properties are not writable\r\n // and are also not enumerable in the native implementation.\r\n //\r\n // Property accessors are not being used as they'd require to define a\r\n // private WeakMap storage which may cause memory leaks in browsers that\r\n // don't support this type of collections.\r\n defineConfigurable(this, { target: target, contentRect: contentRect });\r\n }\r\n return ResizeObserverEntry;\r\n}());\n\nvar ResizeObserverSPI = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback function that is invoked\r\n * when one of the observed elements changes it's content dimensions.\r\n * @param {ResizeObserverController} controller - Controller instance which\r\n * is responsible for the updates of observer.\r\n * @param {ResizeObserver} callbackCtx - Reference to the public\r\n * ResizeObserver instance which will be passed to callback function.\r\n */\r\n function ResizeObserverSPI(callback, controller, callbackCtx) {\r\n /**\r\n * Collection of resize observations that have detected changes in dimensions\r\n * of elements.\r\n *\r\n * @private {Array}\r\n */\r\n this.activeObservations_ = [];\r\n /**\r\n * Registry of the ResizeObservation instances.\r\n *\r\n * @private {Map}\r\n */\r\n this.observations_ = new MapShim();\r\n if (typeof callback !== 'function') {\r\n throw new TypeError('The callback provided as parameter 1 is not a function.');\r\n }\r\n this.callback_ = callback;\r\n this.controller_ = controller;\r\n this.callbackCtx_ = callbackCtx;\r\n }\r\n /**\r\n * Starts observing provided element.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.observe = function (target) {\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n // Do nothing if current environment doesn't have the Element interface.\r\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\r\n return;\r\n }\r\n if (!(target instanceof getWindowOf(target).Element)) {\r\n throw new TypeError('parameter 1 is not of type \"Element\".');\r\n }\r\n var observations = this.observations_;\r\n // Do nothing if element is already being observed.\r\n if (observations.has(target)) {\r\n return;\r\n }\r\n observations.set(target, new ResizeObservation(target));\r\n this.controller_.addObserver(this);\r\n // Force the update of observations.\r\n this.controller_.refresh();\r\n };\r\n /**\r\n * Stops observing provided element.\r\n *\r\n * @param {Element} target - Element to stop observing.\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.unobserve = function (target) {\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n // Do nothing if current environment doesn't have the Element interface.\r\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\r\n return;\r\n }\r\n if (!(target instanceof getWindowOf(target).Element)) {\r\n throw new TypeError('parameter 1 is not of type \"Element\".');\r\n }\r\n var observations = this.observations_;\r\n // Do nothing if element is not being observed.\r\n if (!observations.has(target)) {\r\n return;\r\n }\r\n observations.delete(target);\r\n if (!observations.size) {\r\n this.controller_.removeObserver(this);\r\n }\r\n };\r\n /**\r\n * Stops observing all elements.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.disconnect = function () {\r\n this.clearActive();\r\n this.observations_.clear();\r\n this.controller_.removeObserver(this);\r\n };\r\n /**\r\n * Collects observation instances the associated element of which has changed\r\n * it's content rectangle.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.gatherActive = function () {\r\n var _this = this;\r\n this.clearActive();\r\n this.observations_.forEach(function (observation) {\r\n if (observation.isActive()) {\r\n _this.activeObservations_.push(observation);\r\n }\r\n });\r\n };\r\n /**\r\n * Invokes initial callback function with a list of ResizeObserverEntry\r\n * instances collected from active resize observations.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.broadcastActive = function () {\r\n // Do nothing if observer doesn't have active observations.\r\n if (!this.hasActive()) {\r\n return;\r\n }\r\n var ctx = this.callbackCtx_;\r\n // Create ResizeObserverEntry instance for every active observation.\r\n var entries = this.activeObservations_.map(function (observation) {\r\n return new ResizeObserverEntry(observation.target, observation.broadcastRect());\r\n });\r\n this.callback_.call(ctx, entries, ctx);\r\n this.clearActive();\r\n };\r\n /**\r\n * Clears the collection of active observations.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.clearActive = function () {\r\n this.activeObservations_.splice(0);\r\n };\r\n /**\r\n * Tells whether observer has active observations.\r\n *\r\n * @returns {boolean}\r\n */\r\n ResizeObserverSPI.prototype.hasActive = function () {\r\n return this.activeObservations_.length > 0;\r\n };\r\n return ResizeObserverSPI;\r\n}());\n\n// Registry of internal observers. If WeakMap is not available use current shim\r\n// for the Map collection as it has all required methods and because WeakMap\r\n// can't be fully polyfilled anyway.\r\nvar observers = typeof WeakMap !== 'undefined' ? new WeakMap() : new MapShim();\r\n/**\r\n * ResizeObserver API. Encapsulates the ResizeObserver SPI implementation\r\n * exposing only those methods and properties that are defined in the spec.\r\n */\r\nvar ResizeObserver = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback that is invoked when\r\n * dimensions of the observed elements change.\r\n */\r\n function ResizeObserver(callback) {\r\n if (!(this instanceof ResizeObserver)) {\r\n throw new TypeError('Cannot call a class as a function.');\r\n }\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n var controller = ResizeObserverController.getInstance();\r\n var observer = new ResizeObserverSPI(callback, controller, this);\r\n observers.set(this, observer);\r\n }\r\n return ResizeObserver;\r\n}());\r\n// Expose public methods of ResizeObserver.\r\n[\r\n 'observe',\r\n 'unobserve',\r\n 'disconnect'\r\n].forEach(function (method) {\r\n ResizeObserver.prototype[method] = function () {\r\n var _a;\r\n return (_a = observers.get(this))[method].apply(_a, arguments);\r\n };\r\n});\n\nvar index = (function () {\r\n // Export existing implementation if available.\r\n if (typeof global$1.ResizeObserver !== 'undefined') {\r\n return global$1.ResizeObserver;\r\n }\r\n return ResizeObserver;\r\n})();\n\nexport default index;\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ResizeObserver from \"resize-observer-polyfill\"\nimport {\n NEVER,\n Observable,\n Subject,\n defer,\n filter,\n finalize,\n map,\n merge,\n of,\n shareReplay,\n startWith,\n switchMap,\n tap\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementSize {\n width: number /* Element width */\n height: number /* Element height */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Resize observer entry subject\n */\nconst entry$ = new Subject()\n\n/**\n * Resize observer observable\n *\n * This observable will create a `ResizeObserver` on the first subscription\n * and will automatically terminate it when there are no more subscribers.\n * It's quite important to centralize observation in a single `ResizeObserver`,\n * as the performance difference can be quite dramatic, as the link shows.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n new ResizeObserver(entries => {\n for (const entry of entries)\n entry$.next(entry)\n })\n))\n .pipe(\n switchMap(observer => merge(NEVER, of(observer))\n .pipe(\n finalize(() => observer.disconnect())\n )\n ),\n shareReplay(1)\n )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element size\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementSize(\n el: HTMLElement\n): ElementSize {\n return {\n width: el.offsetWidth,\n height: el.offsetHeight\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element size\n *\n * This function returns an observable that subscribes to a single internal\n * instance of `ResizeObserver` upon subscription, and emit resize events until\n * termination. Note that this function should not be called with the same\n * element twice, as the first unsubscription will terminate observation.\n *\n * Sadly, we can't use the `DOMRect` objects returned by the observer, because\n * we need the emitted values to be consistent with `getElementSize`, which will\n * return the used values (rounded) and not actual values (unrounded). Thus, we\n * use the `offset*` properties. See the linked GitHub issue.\n *\n * @see https://bit.ly/3m0k3he - GitHub issue\n *\n * @param el - Element\n *\n * @returns Element size observable\n */\nexport function watchElementSize(\n el: HTMLElement\n): Observable {\n return observer$\n .pipe(\n tap(observer => observer.observe(el)),\n switchMap(observer => entry$\n .pipe(\n filter(({ target }) => target === el),\n finalize(() => observer.unobserve(el)),\n map(() => getElementSize(el))\n )\n ),\n startWith(getElementSize(el))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ElementSize } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element content size (= scroll width and height)\n *\n * @param el - Element\n *\n * @returns Element content size\n */\nexport function getElementContentSize(\n el: HTMLElement\n): ElementSize {\n return {\n width: el.scrollWidth,\n height: el.scrollHeight\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n NEVER,\n Observable,\n Subject,\n defer,\n distinctUntilChanged,\n filter,\n finalize,\n map,\n merge,\n of,\n shareReplay,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport {\n getElementContentSize,\n getElementSize,\n watchElementContentOffset\n} from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Intersection observer entry subject\n */\nconst entry$ = new Subject()\n\n/**\n * Intersection observer observable\n *\n * This observable will create an `IntersectionObserver` on first subscription\n * and will automatically terminate it when there are no more subscribers.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n new IntersectionObserver(entries => {\n for (const entry of entries)\n entry$.next(entry)\n }, {\n threshold: 0\n })\n))\n .pipe(\n switchMap(observer => merge(NEVER, of(observer))\n .pipe(\n finalize(() => observer.disconnect())\n )\n ),\n shareReplay(1)\n )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch element visibility\n *\n * @param el - Element\n *\n * @returns Element visibility observable\n */\nexport function watchElementVisibility(\n el: HTMLElement\n): Observable {\n return observer$\n .pipe(\n tap(observer => observer.observe(el)),\n switchMap(observer => entry$\n .pipe(\n filter(({ target }) => target === el),\n finalize(() => observer.unobserve(el)),\n map(({ isIntersecting }) => isIntersecting)\n )\n )\n )\n}\n\n/**\n * Watch element boundary\n *\n * This function returns an observable which emits whether the bottom content\n * boundary (= scroll offset) of an element is within a certain threshold.\n *\n * @param el - Element\n * @param threshold - Threshold\n *\n * @returns Element boundary observable\n */\nexport function watchElementBoundary(\n el: HTMLElement, threshold = 16\n): Observable {\n return watchElementContentOffset(el)\n .pipe(\n map(({ y }) => {\n const visible = getElementSize(el)\n const content = getElementContentSize(el)\n return y >= (\n content.height - visible.height - threshold\n )\n }),\n distinctUntilChanged()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n startWith\n} from \"rxjs\"\n\nimport { getElement } from \"../element\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle\n */\nexport type Toggle =\n | \"drawer\" /* Toggle for drawer */\n | \"search\" /* Toggle for search */\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle map\n */\nconst toggles: Record = {\n drawer: getElement(\"[data-md-toggle=drawer]\"),\n search: getElement(\"[data-md-toggle=search]\")\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the value of a toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value\n */\nexport function getToggle(name: Toggle): boolean {\n return toggles[name].checked\n}\n\n/**\n * Set toggle\n *\n * Simulating a click event seems to be the most cross-browser compatible way\n * of changing the value while also emitting a `change` event. Before, Material\n * used `CustomEvent` to programmatically change the value of a toggle, but this\n * is a much simpler and cleaner solution which doesn't require a polyfill.\n *\n * @param name - Toggle\n * @param value - Toggle value\n */\nexport function setToggle(name: Toggle, value: boolean): void {\n if (toggles[name].checked !== value)\n toggles[name].click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value observable\n */\nexport function watchToggle(name: Toggle): Observable {\n const el = toggles[name]\n return fromEvent(el, \"change\")\n .pipe(\n map(() => el.checked),\n startWith(el.checked)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n filter,\n fromEvent,\n map,\n share\n} from \"rxjs\"\n\nimport { getActiveElement } from \"../element\"\nimport { getToggle } from \"../toggle\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Keyboard mode\n */\nexport type KeyboardMode =\n | \"global\" /* Global */\n | \"search\" /* Search is open */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Keyboard\n */\nexport interface Keyboard {\n mode: KeyboardMode /* Keyboard mode */\n type: string /* Key type */\n claim(): void /* Key claim */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether an element may receive keyboard input\n *\n * @param el - Element\n * @param type - Key type\n *\n * @returns Test result\n */\nfunction isSusceptibleToKeyboard(\n el: HTMLElement, type: string\n): boolean {\n switch (el.constructor) {\n\n /* Input elements */\n case HTMLInputElement:\n /* @ts-expect-error - omit unnecessary type cast */\n if (el.type === \"radio\")\n return /^Arrow/.test(type)\n else\n return true\n\n /* Select element and textarea */\n case HTMLSelectElement:\n case HTMLTextAreaElement:\n return true\n\n /* Everything else */\n default:\n return el.isContentEditable\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch keyboard\n *\n * @returns Keyboard observable\n */\nexport function watchKeyboard(): Observable {\n return fromEvent(window, \"keydown\")\n .pipe(\n filter(ev => !(ev.metaKey || ev.ctrlKey)),\n map(ev => ({\n mode: getToggle(\"search\") ? \"search\" : \"global\",\n type: ev.key,\n claim() {\n ev.preventDefault()\n ev.stopPropagation()\n }\n } as Keyboard)),\n filter(({ mode, type }) => {\n if (mode === \"global\") {\n const active = getActiveElement()\n if (typeof active !== \"undefined\")\n return !isSusceptibleToKeyboard(active, type)\n }\n return true\n }),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Subject } from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location\n *\n * This function returns a `URL` object (and not `Location`) to normalize the\n * typings across the application. Furthermore, locations need to be tracked\n * without setting them and `Location` is a singleton which represents the\n * current location.\n *\n * @returns URL\n */\nexport function getLocation(): URL {\n return new URL(location.href)\n}\n\n/**\n * Set location\n *\n * @param url - URL to change to\n */\nexport function setLocation(url: URL): void {\n location.href = url.href\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location\n *\n * @returns Location subject\n */\nexport function watchLocation(): Subject {\n return new Subject()\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { JSX as JSXInternal } from \"preact\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * HTML attributes\n */\ntype Attributes =\n & JSXInternal.HTMLAttributes\n & JSXInternal.SVGAttributes\n & Record\n\n/**\n * Child element\n */\ntype Child =\n | HTMLElement\n | Text\n | string\n | number\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Append a child node to an element\n *\n * @param el - Element\n * @param child - Child node(s)\n */\nfunction appendChild(el: HTMLElement, child: Child | Child[]): void {\n\n /* Handle primitive types (including raw HTML) */\n if (typeof child === \"string\" || typeof child === \"number\") {\n el.innerHTML += child.toString()\n\n /* Handle nodes */\n } else if (child instanceof Node) {\n el.appendChild(child)\n\n /* Handle nested children */\n } else if (Array.isArray(child)) {\n for (const node of child)\n appendChild(el, node)\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * JSX factory\n *\n * @template T - Element type\n *\n * @param tag - HTML tag\n * @param attributes - HTML attributes\n * @param children - Child elements\n *\n * @returns Element\n */\nexport function h(\n tag: T, attributes?: Attributes | null, ...children: Child[]\n): HTMLElementTagNameMap[T]\n\nexport function h(\n tag: string, attributes?: Attributes | null, ...children: Child[]\n): T\n\nexport function h(\n tag: string, attributes?: Attributes | null, ...children: Child[]\n): T {\n const el = document.createElement(tag)\n\n /* Set attributes, if any */\n if (attributes)\n for (const attr of Object.keys(attributes)) {\n if (typeof attributes[attr] === \"undefined\")\n continue\n\n /* Set default attribute or boolean */\n if (typeof attributes[attr] !== \"boolean\")\n el.setAttribute(attr, attributes[attr])\n else\n el.setAttribute(attr, \"\")\n }\n\n /* Append child nodes */\n for (const child of children)\n appendChild(el, child)\n\n /* Return element */\n return el as T\n}\n\n/* ----------------------------------------------------------------------------\n * Namespace\n * ------------------------------------------------------------------------- */\n\nexport declare namespace h {\n namespace JSX {\n type Element = HTMLElement\n type IntrinsicElements = JSXInternal.IntrinsicElements\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Truncate a string after the given number of characters\n *\n * This is not a very reasonable approach, since the summaries kind of suck.\n * It would be better to create something more intelligent, highlighting the\n * search occurrences and making a better summary out of it, but this note was\n * written three years ago, so who knows if we'll ever fix it.\n *\n * @param value - Value to be truncated\n * @param n - Number of characters\n *\n * @returns Truncated value\n */\nexport function truncate(value: string, n: number): string {\n let i = n\n if (value.length > i) {\n while (value[i] !== \" \" && --i > 0) { /* keep eating */ }\n return `${value.substring(0, i)}...`\n }\n return value\n}\n\n/**\n * Round a number for display with repository facts\n *\n * This is a reverse-engineered version of GitHub's weird rounding algorithm\n * for stars, forks and all other numbers. While all numbers below `1,000` are\n * returned as-is, bigger numbers are converted to fixed numbers:\n *\n * - `1,049` => `1k`\n * - `1,050` => `1.1k`\n * - `1,949` => `1.9k`\n * - `1,950` => `2k`\n *\n * @param value - Original value\n *\n * @returns Rounded value\n */\nexport function round(value: number): string {\n if (value > 999) {\n const digits = +((value - 950) % 1000 > 99)\n return `${((value + 0.000001) / 1000).toFixed(digits)}k`\n } else {\n return value.toString()\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n filter,\n fromEvent,\n map,\n shareReplay,\n startWith\n} from \"rxjs\"\n\nimport { getOptionalElement } from \"~/browser\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location hash\n *\n * @returns Location hash\n */\nexport function getLocationHash(): string {\n return location.hash.substring(1)\n}\n\n/**\n * Set location hash\n *\n * Setting a new fragment identifier via `location.hash` will have no effect\n * if the value doesn't change. When a new fragment identifier is set, we want\n * the browser to target the respective element at all times, which is why we\n * use this dirty little trick.\n *\n * @param hash - Location hash\n */\nexport function setLocationHash(hash: string): void {\n const el = h(\"a\", { href: hash })\n el.addEventListener(\"click\", ev => ev.stopPropagation())\n el.click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location hash\n *\n * @returns Location hash observable\n */\nexport function watchLocationHash(): Observable {\n return fromEvent(window, \"hashchange\")\n .pipe(\n map(getLocationHash),\n startWith(getLocationHash()),\n filter(hash => hash.length > 0),\n shareReplay(1)\n )\n}\n\n/**\n * Watch location target\n *\n * @returns Location target observable\n */\nexport function watchLocationTarget(): Observable {\n return watchLocationHash()\n .pipe(\n map(id => getOptionalElement(`[id=\"${id}\"]`)!),\n filter(el => typeof el !== \"undefined\")\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n fromEvent,\n fromEventPattern,\n map,\n merge,\n startWith,\n switchMap\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch media query\n *\n * Note that although `MediaQueryList.addListener` is deprecated we have to\n * use it, because it's the only way to ensure proper downward compatibility.\n *\n * @see https://bit.ly/3dUBH2m - GitHub issue\n *\n * @param query - Media query\n *\n * @returns Media observable\n */\nexport function watchMedia(query: string): Observable {\n const media = matchMedia(query)\n return fromEventPattern(next => (\n media.addListener(() => next(media.matches))\n ))\n .pipe(\n startWith(media.matches)\n )\n}\n\n/**\n * Watch print mode\n *\n * @returns Print observable\n */\nexport function watchPrint(): Observable {\n const media = matchMedia(\"print\")\n return merge(\n fromEvent(window, \"beforeprint\").pipe(map(() => true)),\n fromEvent(window, \"afterprint\").pipe(map(() => false))\n )\n .pipe(\n startWith(media.matches)\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Toggle an observable with a media observable\n *\n * @template T - Data type\n *\n * @param query$ - Media observable\n * @param factory - Observable factory\n *\n * @returns Toggled observable\n */\nexport function at(\n query$: Observable, factory: () => Observable\n): Observable {\n return query$\n .pipe(\n switchMap(active => active ? factory() : EMPTY)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n catchError,\n from,\n map,\n of,\n shareReplay,\n switchMap,\n throwError\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the given URL\n *\n * If the request fails (e.g. when dispatched from `file://` locations), the\n * observable will complete without emitting a value.\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Response observable\n */\nexport function request(\n url: URL | string, options: RequestInit = { credentials: \"same-origin\" }\n): Observable {\n return from(fetch(`${url}`, options))\n .pipe(\n catchError(() => EMPTY),\n switchMap(res => res.status !== 200\n ? throwError(() => new Error(res.statusText))\n : of(res)\n )\n )\n}\n\n/**\n * Fetch JSON from the given URL\n *\n * @template T - Data type\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestJSON(\n url: URL | string, options?: RequestInit\n): Observable {\n return request(url, options)\n .pipe(\n switchMap(res => res.json()),\n shareReplay(1)\n )\n}\n\n/**\n * Fetch XML from the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestXML(\n url: URL | string, options?: RequestInit\n): Observable {\n const dom = new DOMParser()\n return request(url, options)\n .pipe(\n switchMap(res => res.text()),\n map(res => dom.parseFromString(res, \"text/xml\")),\n shareReplay(1)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n defer,\n finalize,\n fromEvent,\n map,\n merge,\n switchMap,\n take,\n throwError\n} from \"rxjs\"\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create and load a `script` element\n *\n * This function returns an observable that will emit when the script was\n * successfully loaded, or throw an error if it didn't.\n *\n * @param src - Script URL\n *\n * @returns Script observable\n */\nexport function watchScript(src: string): Observable {\n const script = h(\"script\", { src })\n return defer(() => {\n document.head.appendChild(script)\n return merge(\n fromEvent(script, \"load\"),\n fromEvent(script, \"error\")\n .pipe(\n switchMap(() => (\n throwError(() => new ReferenceError(`Invalid script: ${src}`))\n ))\n )\n )\n .pipe(\n map(() => undefined),\n finalize(() => document.head.removeChild(script)),\n take(1)\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport offset\n */\nexport interface ViewportOffset {\n x: number /* Horizontal offset */\n y: number /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport offset\n *\n * On iOS Safari, viewport offset can be negative due to overflow scrolling.\n * As this may induce strange behaviors downstream, we'll just limit it to 0.\n *\n * @returns Viewport offset\n */\nexport function getViewportOffset(): ViewportOffset {\n return {\n x: Math.max(0, scrollX),\n y: Math.max(0, scrollY)\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport offset\n *\n * @returns Viewport offset observable\n */\nexport function watchViewportOffset(): Observable {\n return merge(\n fromEvent(window, \"scroll\", { passive: true }),\n fromEvent(window, \"resize\", { passive: true })\n )\n .pipe(\n map(getViewportOffset),\n startWith(getViewportOffset())\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n startWith\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport size\n */\nexport interface ViewportSize {\n width: number /* Viewport width */\n height: number /* Viewport height */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport size\n *\n * @returns Viewport size\n */\nexport function getViewportSize(): ViewportSize {\n return {\n width: innerWidth,\n height: innerHeight\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport size\n *\n * @returns Viewport size observable\n */\nexport function watchViewportSize(): Observable {\n return fromEvent(window, \"resize\", { passive: true })\n .pipe(\n map(getViewportSize),\n startWith(getViewportSize())\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n map,\n shareReplay\n} from \"rxjs\"\n\nimport {\n ViewportOffset,\n watchViewportOffset\n} from \"../offset\"\nimport {\n ViewportSize,\n watchViewportSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport\n */\nexport interface Viewport {\n offset: ViewportOffset /* Viewport offset */\n size: ViewportSize /* Viewport size */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport\n *\n * @returns Viewport observable\n */\nexport function watchViewport(): Observable {\n return combineLatest([\n watchViewportOffset(),\n watchViewportSize()\n ])\n .pipe(\n map(([offset, size]) => ({ offset, size })),\n shareReplay(1)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n distinctUntilKeyChanged,\n map\n} from \"rxjs\"\n\nimport { Header } from \"~/components\"\n\nimport { getElementOffset } from \"../../element\"\nimport { Viewport } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
/* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport relative to element\n *\n * @param el - Element\n * @param options - Options\n *\n * @returns Viewport observable\n */\nexport function watchViewportAt(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n const size$ = viewport$\n .pipe(\n distinctUntilKeyChanged(\"size\")\n )\n\n /* Compute element offset */\n const offset$ = combineLatest([size$, header$])\n .pipe(\n map(() => getElementOffset(el))\n )\n\n /* Compute relative viewport, return hot observable */\n return combineLatest([header$, viewport$, offset$])\n .pipe(\n map(([{ height }, { offset, size }, { x, y }]) => ({\n offset: {\n x: offset.x - x,\n y: offset.y - y + height\n },\n size\n }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n fromEvent,\n map,\n share,\n switchMap,\n tap,\n throttle\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Worker message\n */\nexport interface WorkerMessage {\n type: unknown /* Message type */\n data?: unknown /* Message data */\n}\n\n/**\n * Worker handler\n *\n * @template T - Message type\n */\nexport interface WorkerHandler<\n T extends WorkerMessage\n> {\n tx$: Subject /* Message transmission subject */\n rx$: Observable /* Message receive observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n *\n * @template T - Worker message type\n */\ninterface WatchOptions {\n tx$: Observable /* Message transmission observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch a web worker\n *\n * This function returns an observable that sends all values emitted by the\n * message observable to the web worker. Web worker communication is expected\n * to be bidirectional (request-response) and synchronous. Messages that are\n * emitted during a pending request are throttled, the last one is emitted.\n *\n * @param worker - Web worker\n * @param options - Options\n *\n * @returns Worker message observable\n */\nexport function watchWorker(\n worker: Worker, { tx$ }: WatchOptions\n): Observable {\n\n /* Intercept messages from worker-like objects */\n const rx$ = fromEvent(worker, \"message\")\n .pipe(\n map(({ data }) => data as T)\n )\n\n /* Send and receive messages, return hot observable */\n return tx$\n .pipe(\n throttle(() => rx$, { leading: true, trailing: true }),\n tap(message => worker.postMessage(message)),\n switchMap(() => rx$),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElement, getLocation } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Feature flag\n */\nexport type Flag =\n | \"content.code.annotate\" /* Code annotations */\n | \"content.tabs.link\" /* Link content tabs */\n | \"header.autohide\" /* Hide header */\n | \"navigation.expand\" /* Automatic expansion */\n | \"navigation.indexes\" /* Section pages */\n | \"navigation.instant\" /* Instant loading */\n | \"navigation.sections\" /* Section navigation */\n | \"navigation.tabs\" /* Tabs navigation */\n | \"navigation.tabs.sticky\" /* Tabs navigation (sticky) */\n | \"navigation.top\" /* Back-to-top button */\n | \"navigation.tracking\" /* Anchor tracking */\n | \"search.highlight\" /* Search highlighting */\n | \"search.share\" /* Search sharing */\n | \"search.suggest\" /* Search suggestions */\n | \"toc.integrate\" /* Integrated table of contents */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Translation\n */\nexport type Translation =\n | \"clipboard.copy\" /* Copy to clipboard */\n | \"clipboard.copied\" /* Copied to clipboard */\n | \"search.config.lang\" /* Search language */\n | \"search.config.pipeline\" /* Search pipeline */\n | \"search.config.separator\" /* Search separator */\n | \"search.placeholder\" /* Search */\n | \"search.result.placeholder\" /* Type to start searching */\n | \"search.result.none\" /* No matching documents */\n | \"search.result.one\" /* 1 matching document */\n | \"search.result.other\" /* # matching documents */\n | \"search.result.more.one\" /* 1 more on this page */\n | \"search.result.more.other\" /* # more on this page */\n | \"search.result.term.missing\" /* Missing */\n | \"select.version.title\" /* Version selector */\n\n/**\n * Translations\n */\nexport type Translations = Record\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Versioning\n */\nexport interface Versioning {\n provider: \"mike\" /* Version provider */\n default?: string /* Default version */\n}\n\n/**\n * Configuration\n */\nexport interface Config {\n base: string /* Base URL */\n features: Flag[] /* Feature flags */\n translations: Translations /* Translations */\n search: string /* Search worker URL */\n version?: Versioning /* Versioning */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration and make base URL absolute\n */\nconst script = getElement(\"#__config\")\nconst config: Config = JSON.parse(script.textContent!)\nconfig.base = `${new URL(config.base, getLocation())}`\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration\n *\n * @returns Global configuration\n */\nexport function configuration(): Config {\n return config\n}\n\n/**\n * Check whether a feature flag is enabled\n *\n * @param flag - Feature flag\n *\n * @returns Test result\n */\nexport function feature(flag: Flag): boolean {\n return config.features.includes(flag)\n}\n\n/**\n * Retrieve the translation for the given key\n *\n * @param key - Key to be translated\n * @param value - Positional value, if any\n *\n * @returns Translation\n */\nexport function translation(\n key: Translation, value?: string | number\n): string {\n return typeof value !== \"undefined\"\n ? config.translations[key].replace(\"#\", value.toString())\n : config.translations[key]\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElement, getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type\n */\nexport type ComponentType =\n | \"announce\" /* Announcement bar */\n | \"container\" /* Container */\n | \"content\" /* Content */\n | \"dialog\" /* Dialog */\n | \"header\" /* Header */\n | \"header-title\" /* Header title */\n | \"header-topic\" /* Header topic */\n | \"main\" /* Main area */\n | \"outdated\" /* Version warning */\n | \"palette\" /* Color palette */\n | \"search\" /* Search */\n | \"search-query\" /* Search input */\n | \"search-result\" /* Search results */\n | \"search-share\" /* Search sharing */\n | \"search-suggest\" /* Search suggestions */\n | \"sidebar\" /* Sidebar */\n | \"skip\" /* Skip link */\n | \"source\" /* Repository information */\n | \"tabs\" /* Navigation tabs */\n | \"toc\" /* Table of contents */\n | \"top\" /* Back-to-top button */\n\n/**\n * Component\n *\n * @template T - Component type\n * @template U - Reference type\n */\nexport type Component<\n T extends {} = {},\n U extends HTMLElement = HTMLElement\n> =\n T & {\n ref: U /* Component reference */\n }\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type map\n */\ninterface ComponentTypeMap {\n \"announce\": HTMLElement /* Announcement bar */\n \"container\": HTMLElement /* Container */\n \"content\": HTMLElement /* Content */\n \"dialog\": HTMLElement /* Dialog */\n \"header\": HTMLElement /* Header */\n \"header-title\": HTMLElement /* Header title */\n \"header-topic\": HTMLElement /* Header topic */\n \"main\": HTMLElement /* Main area */\n \"outdated\": HTMLElement /* Version warning */\n \"palette\": HTMLElement /* Color palette */\n \"search\": HTMLElement /* Search */\n \"search-query\": HTMLInputElement /* Search input */\n \"search-result\": HTMLElement /* Search results */\n \"search-share\": HTMLAnchorElement /* Search sharing */\n \"search-suggest\": HTMLElement /* Search suggestions */\n \"sidebar\": HTMLElement /* Sidebar */\n \"skip\": HTMLAnchorElement /* Skip link */\n \"source\": HTMLAnchorElement /* Repository information */\n \"tabs\": HTMLElement /* Navigation tabs */\n \"toc\": HTMLElement /* Table of contents */\n \"top\": HTMLAnchorElement /* Back-to-top button */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the element for a given component or throw a reference error\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getComponentElement(\n type: T, node: ParentNode = document\n): ComponentTypeMap[T] {\n return getElement(`[data-md-component=${type}]`, node)\n}\n\n/**\n * Retrieve all elements for a given component\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getComponentElements(\n type: T, node: ParentNode = document\n): ComponentTypeMap[T][] {\n return getElements(`[data-md-component=${type}]`, node)\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n EMPTY,\n Observable,\n Subject,\n defer,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n finalize,\n map,\n mergeWith,\n switchMap,\n take,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n getElementContentSize,\n watchElementSize,\n watchElementVisibility\n} from \"~/browser\"\nimport { renderClipboardButton } from \"~/templates\"\n\nimport { Component } from \"../../../_\"\nimport {\n Annotation,\n mountAnnotationList\n} from \"../../annotation\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Code block\n */\nexport interface CodeBlock {\n scrollable: boolean /* Code block overflows */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Global sequence number for Clipboard.js integration\n */\nlet sequence = 0\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Find candidate list element directly following a code block\n *\n * @param el - Code block element\n *\n * @returns List element or nothing\n */\nfunction findCandidateList(el: HTMLElement): HTMLElement | undefined {\n if (el.nextElementSibling) {\n const sibling = el.nextElementSibling as HTMLElement\n if (sibling.tagName === \"OL\")\n return sibling\n\n /* Skip empty paragraphs - see https://bit.ly/3r4ZJ2O */\n else if (sibling.tagName === \"P\" && !sibling.children.length)\n return findCandidateList(sibling)\n }\n\n /* Everything else */\n return undefined\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch code block\n *\n * This function monitors size changes of the viewport, as well as switches of\n * content tabs with embedded code blocks, as both may trigger overflow.\n *\n * @param el - Code block element\n *\n * @returns Code block observable\n */\nexport function watchCodeBlock(\n el: HTMLElement\n): Observable {\n return watchElementSize(el)\n .pipe(\n map(({ width }) => {\n const content = getElementContentSize(el)\n return {\n scrollable: content.width > width\n }\n }),\n distinctUntilKeyChanged(\"scrollable\")\n )\n}\n\n/**\n * Mount code block\n *\n * This function ensures that an overflowing code block is focusable through\n * keyboard, so it can be scrolled without a mouse to improve on accessibility.\n * Furthermore, if code annotations are enabled, they are mounted if and only\n * if the code block is currently visible, e.g., not in a hidden content tab.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block and annotation component observable\n */\nexport function mountCodeBlock(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const { matches: hover } = matchMedia(\"(hover)\")\n\n /* Defer mounting of code block - see https://bit.ly/3vHVoVD */\n const factory$ = defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ scrollable }) => {\n if (scrollable && hover)\n el.setAttribute(\"tabindex\", \"0\")\n else\n el.removeAttribute(\"tabindex\")\n })\n\n /* Render button for Clipboard.js integration */\n if (ClipboardJS.isSupported()) {\n const parent = el.closest(\"pre\")!\n parent.id = `__code_${++sequence}`\n parent.insertBefore(\n renderClipboardButton(parent.id),\n el\n )\n }\n\n /* Handle code annotations */\n const container = el.closest(\".highlight\")\n if (container instanceof HTMLElement) {\n const list = findCandidateList(container)\n\n /* Mount code annotations, if enabled */\n if (typeof list !== \"undefined\" && (\n container.classList.contains(\"annotate\") ||\n feature(\"content.code.annotate\")\n )) {\n const annotations$ = mountAnnotationList(list, el, options)\n\n /* Create and return component */\n return watchCodeBlock(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state })),\n mergeWith(\n watchElementSize(container)\n .pipe(\n takeUntil(push$.pipe(takeLast(1))),\n map(({ width, height }) => width && height),\n distinctUntilChanged(),\n switchMap(active => active ? annotations$ : EMPTY)\n )\n )\n )\n }\n }\n\n /* Create and return component */\n return watchCodeBlock(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n\n /* Mount code block on first sight */\n return watchElementVisibility(el)\n .pipe(\n filter(visible => visible),\n take(1),\n switchMap(() => factory$)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render an empty annotation\n *\n * @param id - Annotation identifier\n *\n * @returns Element\n */\nexport function renderAnnotation(id: number): HTMLElement {\n return (\n \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a 'copy-to-clipboard' button\n *\n * @param id - Unique identifier\n *\n * @returns Element\n */\nexport function renderClipboardButton(id: string): HTMLElement {\n return (\n code`}\n >\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ComponentChild } from \"preact\"\n\nimport { feature, translation } from \"~/_\"\nimport {\n SearchDocument,\n SearchMetadata,\n SearchResultItem\n} from \"~/integrations/search\"\nimport { h, truncate } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Render flag\n */\nconst enum Flag {\n TEASER = 1, /* Render teaser */\n PARENT = 2 /* Render as parent */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper function\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search document\n *\n * @param document - Search document\n * @param flag - Render flags\n *\n * @returns Element\n */\nfunction renderSearchDocument(\n document: SearchDocument & SearchMetadata, flag: Flag\n): HTMLElement {\n const parent = flag & Flag.PARENT\n const teaser = flag & Flag.TEASER\n\n /* Render missing query terms */\n const missing = Object.keys(document.terms)\n .filter(key => !document.terms[key])\n .reduce((list, key) => [\n ...list, {key}, \" \"\n ], [])\n .slice(0, -1)\n\n /* Assemble query string for highlighting */\n const url = new URL(document.location)\n if (feature(\"search.highlight\"))\n url.searchParams.set(\"h\", Object.entries(document.terms)\n .filter(([, match]) => match)\n .reduce((highlight, [value]) => `${highlight} ${value}`.trim(), \"\")\n )\n\n /* Render article or section, depending on flags */\n return (\n \n \n {parent > 0 &&
}\n

{document.title}

\n {teaser > 0 && document.text.length > 0 &&\n

\n {truncate(document.text, 320)}\n

\n }\n {document.tags && document.tags.map(tag => (\n {tag}\n ))}\n {teaser > 0 && missing.length > 0 &&\n

\n {translation(\"search.result.term.missing\")}: {...missing}\n

\n }\n \n
\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search result\n *\n * @param result - Search result\n *\n * @returns Element\n */\nexport function renderSearchResultItem(\n result: SearchResultItem\n): HTMLElement {\n const threshold = result[0].score\n const docs = [...result]\n\n /* Find and extract parent article */\n const parent = docs.findIndex(doc => !doc.location.includes(\"#\"))\n const [article] = docs.splice(parent, 1)\n\n /* Determine last index above threshold */\n let index = docs.findIndex(doc => doc.score < threshold)\n if (index === -1)\n index = docs.length\n\n /* Partition sections */\n const best = docs.slice(0, index)\n const more = docs.slice(index)\n\n /* Render children */\n const children = [\n renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),\n ...best.map(section => renderSearchDocument(section, Flag.TEASER)),\n ...more.length ? [\n
\n \n {more.length > 0 && more.length === 1\n ? translation(\"search.result.more.one\")\n : translation(\"search.result.more.other\", more.length)\n }\n \n {...more.map(section => renderSearchDocument(section, Flag.TEASER))}\n
\n ] : []\n ]\n\n /* Render search result */\n return (\n
  • \n {children}\n
  • \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SourceFacts } from \"~/components\"\nimport { h, round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render repository facts\n *\n * @param facts - Repository facts\n *\n * @returns Element\n */\nexport function renderSourceFacts(facts: SourceFacts): HTMLElement {\n return (\n
      \n {Object.entries(facts).map(([key, value]) => (\n
    • \n {typeof value === \"number\" ? round(value) : value}\n
    • \n ))}\n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Tabbed control type\n */\ntype TabbedControlType =\n | \"prev\"\n | \"next\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render control for content tabs\n *\n * @param type - Control type\n *\n * @returns Element\n */\nexport function renderTabbedControl(\n type: TabbedControlType\n): HTMLElement {\n const classes = `tabbed-control tabbed-control--${type}`\n return (\n \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a table inside a wrapper to improve scrolling on mobile\n *\n * @param table - Table element\n *\n * @returns Element\n */\nexport function renderTable(table: HTMLElement): HTMLElement {\n return (\n
    \n
    \n {table}\n
    \n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration, translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Version\n */\nexport interface Version {\n version: string /* Version identifier */\n title: string /* Version title */\n aliases: string[] /* Version aliases */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version\n *\n * @param version - Version\n *\n * @returns Element\n */\nfunction renderVersion(version: Version): HTMLElement {\n const config = configuration()\n\n /* Ensure trailing slash, see https://bit.ly/3rL5u3f */\n const url = new URL(`../${version.version}/`, config.base)\n return (\n
  • \n \n {version.title}\n \n
  • \n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version selector\n *\n * @param versions - Versions\n * @param active - Active version\n *\n * @returns Element\n */\nexport function renderVersionSelector(\n versions: Version[], active: Version\n): HTMLElement {\n return (\n
    \n \n {active.title}\n \n
      \n {versions.map(renderVersion)}\n
    \n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n animationFrameScheduler,\n combineLatest,\n defer,\n finalize,\n fromEvent,\n map,\n switchMap,\n take,\n takeLast,\n takeUntil,\n tap,\n throttleTime\n} from \"rxjs\"\n\nimport {\n ElementOffset,\n getElement,\n getElementSize,\n watchElementContentOffset,\n watchElementFocus,\n watchElementOffset,\n watchElementVisibility\n} from \"~/browser\"\n\nimport { Component } from \"../../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Annotation\n */\nexport interface Annotation {\n active: boolean /* Annotation is active */\n offset: ElementOffset /* Annotation offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch annotation\n *\n * @param el - Annotation element\n * @param container - Containing element\n *\n * @returns Annotation observable\n */\nexport function watchAnnotation(\n el: HTMLElement, container: HTMLElement\n): Observable {\n const offset$ = defer(() => combineLatest([\n watchElementOffset(el),\n watchElementContentOffset(container)\n ]))\n .pipe(\n map(([{ x, y }, scroll]) => {\n const { width } = getElementSize(el)\n return ({\n x: x - scroll.x + width / 2,\n y: y - scroll.y\n })\n })\n )\n\n /* Actively watch annotation on focus */\n return watchElementFocus(el)\n .pipe(\n switchMap(active => offset$\n .pipe(\n map(offset => ({ active, offset })),\n take(+!active || Infinity)\n )\n )\n )\n}\n\n/**\n * Mount annotation\n *\n * @param el - Annotation element\n * @param container - Containing element\n *\n * @returns Annotation component observable\n */\nexport function mountAnnotation(\n el: HTMLElement, container: HTMLElement\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe({\n\n /* Handle emission */\n next({ offset }) {\n el.style.setProperty(\"--md-tooltip-x\", `${offset.x}px`)\n el.style.setProperty(\"--md-tooltip-y\", `${offset.y}px`)\n },\n\n /* Handle complete */\n complete() {\n el.style.removeProperty(\"--md-tooltip-x\")\n el.style.removeProperty(\"--md-tooltip-y\")\n }\n })\n\n /* Start animation only when annotation is visible */\n const done$ = push$.pipe(takeLast(1))\n watchElementVisibility(el)\n .pipe(\n takeUntil(done$)\n )\n .subscribe(visible => {\n el.toggleAttribute(\"data-md-visible\", visible)\n })\n\n /* Track relative origin of tooltip */\n push$\n .pipe(\n throttleTime(500, animationFrameScheduler),\n map(() => container.getBoundingClientRect()),\n map(({ x }) => x)\n )\n .subscribe({\n\n /* Handle emission */\n next(origin) {\n if (origin)\n el.style.setProperty(\"--md-tooltip-0\", `${-origin}px`)\n else\n el.style.removeProperty(\"--md-tooltip-0\")\n },\n\n /* Handle complete */\n complete() {\n el.style.removeProperty(\"--md-tooltip-0\")\n }\n })\n\n /* Close open annotation on click */\n const index = getElement(\":scope > :last-child\", el)\n const blur$ = fromEvent(index, \"mousedown\", { once: true })\n push$\n .pipe(\n switchMap(({ active }) => active ? blur$ : EMPTY),\n tap(ev => ev.preventDefault())\n )\n .subscribe(() => el.blur())\n\n /* Create and return component */\n return watchAnnotation(el, container)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n defer,\n finalize,\n merge,\n share,\n takeLast,\n takeUntil\n} from \"rxjs\"\n\nimport {\n getElement,\n getElements,\n getOptionalElement\n} from \"~/browser\"\nimport { renderAnnotation } from \"~/templates\"\n\nimport { Component } from \"../../../_\"\nimport {\n Annotation,\n mountAnnotation\n} from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Find all annotation markers in the given code block\n *\n * @param container - Containing element\n *\n * @returns Annotation markers\n */\nfunction findAnnotationMarkers(container: HTMLElement): Text[] {\n const markers: Text[] = []\n for (const comment of getElements(\".c, .c1, .cm\", container)) {\n let match: RegExpExecArray | null\n\n /* Split text at marker and add to list */\n let text = comment.firstChild as Text\n if (text instanceof Text)\n while ((match = /\\((\\d+)\\)/.exec(text.textContent!))) {\n const marker = text.splitText(match.index)\n text = marker.splitText(match[0].length)\n markers.push(marker)\n }\n }\n return markers\n}\n\n/**\n * Swap the child nodes of two elements\n *\n * @param source - Source element\n * @param target - Target element\n */\nfunction swap(source: HTMLElement, target: HTMLElement): void {\n target.append(...Array.from(source.childNodes))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount annotation list\n *\n * This function analyzes the containing code block and checks for markers\n * referring to elements in the given annotation list. If no markers are found,\n * the list is left untouched. Otherwise, list elements are rendered as\n * annotations inside the code block.\n *\n * @param el - Annotation list element\n * @param container - Containing element\n * @param options - Options\n *\n * @returns Annotation component observable\n */\nexport function mountAnnotationList(\n el: HTMLElement, container: HTMLElement, { print$ }: MountOptions\n): Observable> {\n\n /* Find and replace all markers with empty annotations */\n const annotations = new Map()\n for (const marker of findAnnotationMarkers(container)) {\n const [, id] = marker.textContent!.match(/\\((\\d+)\\)/)!\n if (getOptionalElement(`li:nth-child(${id})`, el)) {\n annotations.set(+id, renderAnnotation(+id))\n marker.replaceWith(annotations.get(+id)!)\n }\n }\n\n /* Keep list if there are no annotations to render */\n if (annotations.size === 0)\n return EMPTY\n\n /* Create and return component */\n return defer(() => {\n const done$ = new Subject()\n\n /* Handle print mode - see https://bit.ly/3rgPdpt */\n print$\n .pipe(\n takeUntil(done$.pipe(takeLast(1)))\n )\n .subscribe(active => {\n el.hidden = !active\n\n /* Show annotations in code block or list (print) */\n for (const [id, annotation] of annotations) {\n const inner = getElement(\".md-typeset\", annotation)\n const child = getElement(`li:nth-child(${id})`, el)\n if (!active)\n swap(child, inner)\n else\n swap(inner, child)\n }\n })\n\n /* Create and return component */\n return merge(...[...annotations]\n .map(([, annotation]) => (\n mountAnnotation(annotation, container)\n ))\n )\n .pipe(\n finalize(() => done$.complete()),\n share()\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n map,\n of,\n shareReplay,\n tap\n} from \"rxjs\"\n\nimport { watchScript } from \"~/browser\"\nimport { h } from \"~/utilities\"\n\nimport { Component } from \"../../../_\"\n\nimport themeCSS from \"./index.css\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mermaid diagram\n */\nexport interface Mermaid {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Mermaid instance observable\n */\nlet mermaid$: Observable\n\n/**\n * Global sequence number for diagrams\n */\nlet sequence = 0\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch Mermaid script\n *\n * @returns Mermaid scripts observable\n */\nfunction fetchScripts(): Observable {\n return typeof mermaid === \"undefined\" || mermaid instanceof Element\n ? watchScript(\"https://unpkg.com/mermaid@9.0.1/dist/mermaid.min.js\")\n : of(undefined)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount Mermaid diagram\n *\n * @param el - Code block element\n *\n * @returns Mermaid diagram component observable\n */\nexport function mountMermaid(\n el: HTMLElement\n): Observable> {\n el.classList.remove(\"mermaid\") // Hack: mitigate https://bit.ly/3CiN6Du\n mermaid$ ||= fetchScripts()\n .pipe(\n tap(() => mermaid.initialize({\n startOnLoad: false,\n themeCSS\n })),\n map(() => undefined),\n shareReplay(1)\n )\n\n /* Render diagram */\n mermaid$.subscribe(() => {\n el.classList.add(\"mermaid\") // Hack: mitigate https://bit.ly/3CiN6Du\n const id = `__mermaid_${sequence++}`\n const host = h(\"div\", { class: \"mermaid\" })\n mermaid.mermaidAPI.render(id, el.textContent, (svg: string) => {\n\n /* Create a shadow root and inject diagram */\n const shadow = host.attachShadow({ mode: \"closed\" })\n shadow.innerHTML = svg\n\n /* Replace code block with diagram */\n el.replaceWith(host)\n })\n })\n\n /* Create and return component */\n return mermaid$\n .pipe(\n map(() => ({ ref: el }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n defer,\n filter,\n finalize,\n map,\n merge,\n tap\n} from \"rxjs\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Details\n */\nexport interface Details {\n action: \"open\" | \"close\" /* Details state */\n reveal?: boolean /* Details is revealed */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Media print observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch details\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details observable\n */\nexport function watchDetails(\n el: HTMLDetailsElement, { target$, print$ }: WatchOptions\n): Observable
    {\n let open = true\n return merge(\n\n /* Open and focus details on location target */\n target$\n .pipe(\n map(target => target.closest(\"details:not([open])\")!),\n filter(details => el === details),\n map(() => ({\n action: \"open\", reveal: true\n }) as Details)\n ),\n\n /* Open details on print and close afterwards */\n print$\n .pipe(\n filter(active => active || !open),\n tap(() => open = el.open),\n map(active => ({\n action: active ? \"open\" : \"close\"\n }) as Details)\n )\n )\n}\n\n/**\n * Mount details\n *\n * This function ensures that `details` tags are opened on anchor jumps and\n * prior to printing, so the whole content of the page is visible.\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details component observable\n */\nexport function mountDetails(\n el: HTMLDetailsElement, options: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject
    ()\n push$.subscribe(({ action, reveal }) => {\n if (action === \"open\")\n el.setAttribute(\"open\", \"\")\n else\n el.removeAttribute(\"open\")\n if (reveal)\n el.scrollIntoView()\n })\n\n /* Create and return component */\n return watchDetails(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, of } from \"rxjs\"\n\nimport { renderTable } from \"~/templates\"\nimport { h } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Data table\n */\nexport interface DataTable {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Sentinel for replacement\n */\nconst sentinel = h(\"table\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount data table\n *\n * This function wraps a data table in another scrollable container, so it can\n * be smoothly scrolled on smaller screen sizes and won't break the layout.\n *\n * @param el - Data table element\n *\n * @returns Data table component observable\n */\nexport function mountDataTable(\n el: HTMLElement\n): Observable> {\n el.replaceWith(sentinel)\n sentinel.replaceWith(renderTable(el))\n\n /* Create and return component */\n return of({ ref: el })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n asyncScheduler,\n auditTime,\n combineLatest,\n defer,\n finalize,\n fromEvent,\n map,\n merge,\n skip,\n startWith,\n subscribeOn,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n getElement,\n getElementContentOffset,\n getElementContentSize,\n getElementOffset,\n getElementSize,\n getElements,\n watchElementContentOffset,\n watchElementSize\n} from \"~/browser\"\nimport { renderTabbedControl } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content tabs\n */\nexport interface ContentTabs {\n active: HTMLLabelElement /* Active tab label */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch content tabs\n *\n * @param el - Content tabs element\n *\n * @returns Content tabs observable\n */\nexport function watchContentTabs(\n el: HTMLElement\n): Observable {\n const inputs = getElements(\":scope > input\", el)\n const initial = inputs.find(input => input.checked) || inputs[0]\n return merge(...inputs.map(input => fromEvent(input, \"change\")\n .pipe(\n map(() => getElement(`label[for=${input.id}]`))\n )\n ))\n .pipe(\n startWith(getElement(`label[for=${initial.id}]`)),\n map(active => ({ active }))\n )\n}\n\n/**\n * Mount content tabs\n *\n * This function scrolls the active tab into view. While this functionality is\n * provided by browsers as part of `scrollInfoView`, browsers will always also\n * scroll the vertical axis, which we do not want. Thus, we decided to provide\n * this functionality ourselves.\n *\n * @param el - Content tabs element\n *\n * @returns Content tabs component observable\n */\nexport function mountContentTabs(\n el: HTMLElement\n): Observable> {\n\n /* Render content tab previous button for pagination */\n const prev = renderTabbedControl(\"prev\")\n el.append(prev)\n\n /* Render content tab next button for pagination */\n const next = renderTabbedControl(\"next\")\n el.append(next)\n\n /* Mount component on subscription */\n const container = getElement(\".tabbed-labels\", el)\n return defer(() => {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n combineLatest([push$, watchElementSize(el)])\n .pipe(\n auditTime(1, animationFrameScheduler),\n takeUntil(done$)\n )\n .subscribe({\n\n /* Handle emission */\n next([{ active }, size]) {\n const offset = getElementOffset(active)\n const { width } = getElementSize(active)\n\n /* Set tab indicator offset and width */\n el.style.setProperty(\"--md-indicator-x\", `${offset.x}px`)\n el.style.setProperty(\"--md-indicator-width\", `${width}px`)\n\n /* Scroll container to active content tab */\n const content = getElementContentOffset(container)\n if (\n offset.x < content.x ||\n offset.x + width > content.x + size.width\n )\n container.scrollTo({\n left: Math.max(0, offset.x - 16),\n behavior: \"smooth\"\n })\n },\n\n /* Handle complete */\n complete() {\n el.style.removeProperty(\"--md-indicator-x\")\n el.style.removeProperty(\"--md-indicator-width\")\n }\n })\n\n /* Hide content tab buttons on borders */\n combineLatest([\n watchElementContentOffset(container),\n watchElementSize(container)\n ])\n .pipe(\n takeUntil(done$)\n )\n .subscribe(([offset, size]) => {\n const content = getElementContentSize(container)\n prev.hidden = offset.x < 16\n next.hidden = offset.x > content.width - size.width - 16\n })\n\n /* Paginate content tab container on click */\n merge(\n fromEvent(prev, \"click\").pipe(map(() => -1)),\n fromEvent(next, \"click\").pipe(map(() => +1))\n )\n .pipe(\n takeUntil(done$)\n )\n .subscribe(direction => {\n const { width } = getElementSize(container)\n container.scrollBy({\n left: width * direction,\n behavior: \"smooth\"\n })\n })\n\n /* Set up linking of content tabs, if enabled */\n if (feature(\"content.tabs.link\"))\n push$.pipe(skip(1))\n .subscribe(({ active }) => {\n const tab = active.innerText.trim()\n for (const set of getElements(\"[data-tabs]\"))\n for (const input of getElements(\n \":scope > input\", set\n )) {\n const label = getElement(`label[for=${input.id}]`)\n if (label.innerText.trim() === tab) {\n input.click()\n break\n }\n }\n\n /* Persist active tabs in local storage */\n const tabs = __md_get(\"__tabs\") || []\n __md_set(\"__tabs\", [...new Set([tab, ...tabs])])\n })\n\n /* Create and return component */\n return watchContentTabs(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n .pipe(\n subscribeOn(asyncScheduler)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, merge } from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Annotation } from \"../annotation\"\nimport {\n CodeBlock,\n Mermaid,\n mountCodeBlock,\n mountMermaid\n} from \"../code\"\nimport {\n Details,\n mountDetails\n} from \"../details\"\nimport {\n DataTable,\n mountDataTable\n} from \"../table\"\nimport {\n ContentTabs,\n mountContentTabs\n} from \"../tabs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content\n */\nexport type Content =\n | Annotation\n | ContentTabs\n | CodeBlock\n | Mermaid\n | DataTable\n | Details\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount content\n *\n * This function mounts all components that are found in the content of the\n * actual article, including code blocks, data tables and details.\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Content component observable\n */\nexport function mountContent(\n el: HTMLElement, { target$, print$ }: MountOptions\n): Observable> {\n return merge(\n\n /* Code blocks */\n ...getElements(\"pre:not(.mermaid) > code\", el)\n .map(child => mountCodeBlock(child, { print$ })),\n\n /* Mermaid diagrams */\n ...getElements(\"pre.mermaid\", el)\n .map(child => mountMermaid(child)),\n\n /* Data tables */\n ...getElements(\"table:not([class])\", el)\n .map(child => mountDataTable(child)),\n\n /* Details */\n ...getElements(\"details\", el)\n .map(child => mountDetails(child, { target$, print$ })),\n\n /* Content tabs */\n ...getElements(\"[data-tabs]\", el)\n .map(child => mountContentTabs(child))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n defer,\n delay,\n finalize,\n map,\n merge,\n of,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport { getElement } from \"~/browser\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Dialog\n */\nexport interface Dialog {\n message: string /* Dialog message */\n active: boolean /* Dialog is active */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n alert$: Subject /* Alert subject */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n alert$: Subject /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch dialog\n *\n * @param _el - Dialog element\n * @param options - Options\n *\n * @returns Dialog observable\n */\nexport function watchDialog(\n _el: HTMLElement, { alert$ }: WatchOptions\n): Observable {\n return alert$\n .pipe(\n switchMap(message => merge(\n of(true),\n of(false).pipe(delay(2000))\n )\n .pipe(\n map(active => ({ message, active }))\n )\n )\n )\n}\n\n/**\n * Mount dialog\n *\n * This function reveals the dialog in the right corner when a new alert is\n * emitted through the subject that is passed as part of the options.\n *\n * @param el - Dialog element\n * @param options - Options\n *\n * @returns Dialog component observable\n */\nexport function mountDialog(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const inner = getElement(\".md-typeset\", el)\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ message, active }) => {\n el.classList.toggle(\"md-dialog--active\", active)\n inner.textContent = message\n })\n\n /* Create and return component */\n return watchDialog(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n combineLatest,\n combineLatestWith,\n defer,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n map,\n of,\n shareReplay,\n startWith,\n switchMap,\n takeLast,\n takeUntil\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n Viewport,\n watchElementSize,\n watchToggle\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Main } from \"../../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface Header {\n height: number /* Header visible height */\n hidden: boolean /* Header is hidden */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute whether the header is hidden\n *\n * If the user scrolls past a certain threshold, the header can be hidden when\n * scrolling down, and shown when scrolling up.\n *\n * @param options - Options\n *\n * @returns Toggle observable\n */\nfunction isHidden({ viewport$ }: WatchOptions): Observable {\n if (!feature(\"header.autohide\"))\n return of(false)\n\n /* Compute direction and turning point */\n const direction$ = viewport$\n .pipe(\n map(({ offset: { y } }) => y),\n bufferCount(2, 1),\n map(([a, b]) => [a < b, b] as const),\n distinctUntilKeyChanged(0)\n )\n\n /* Compute whether header should be hidden */\n const hidden$ = combineLatest([viewport$, direction$])\n .pipe(\n filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),\n map(([, [direction]]) => direction),\n distinctUntilChanged()\n )\n\n /* Compute threshold for hiding */\n const search$ = watchToggle(\"search\")\n return combineLatest([viewport$, search$])\n .pipe(\n map(([{ offset }, search]) => offset.y > 400 && !search),\n distinctUntilChanged(),\n switchMap(active => active ? hidden$ : of(false)),\n startWith(false)\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header observable\n */\nexport function watchHeader(\n el: HTMLElement, options: WatchOptions\n): Observable
    {\n return defer(() => combineLatest([\n watchElementSize(el),\n isHidden(options)\n ]))\n .pipe(\n map(([{ height }, hidden]) => ({\n height,\n hidden\n })),\n distinctUntilChanged((a, b) => (\n a.height === b.height &&\n a.hidden === b.hidden\n )),\n shareReplay(1)\n )\n}\n\n/**\n * Mount header\n *\n * This function manages the different states of the header, i.e. whether it's\n * hidden or rendered with a shadow. This depends heavily on the main area.\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header component observable\n */\nexport function mountHeader(\n el: HTMLElement, { header$, main$ }: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject
    ()\n const done$ = push$.pipe(takeLast(1))\n push$\n .pipe(\n distinctUntilKeyChanged(\"active\"),\n combineLatestWith(header$)\n )\n .subscribe(([{ active }, { hidden }]) => {\n el.classList.toggle(\"md-header--shadow\", active && !hidden)\n el.hidden = hidden\n })\n\n /* Link to main area */\n main$.subscribe(push$)\n\n /* Create and return component */\n return header$\n .pipe(\n takeUntil(done$),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n defer,\n distinctUntilKeyChanged,\n finalize,\n map,\n tap\n} from \"rxjs\"\n\nimport {\n Viewport,\n getElementSize,\n getOptionalElement,\n watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Header } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface HeaderTitle {\n active: boolean /* Header title is active */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header title\n *\n * @param el - Heading element\n * @param options - Options\n *\n * @returns Header title observable\n */\nexport function watchHeaderTitle(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n return watchViewportAt(el, { viewport$, header$ })\n .pipe(\n map(({ offset: { y } }) => {\n const { height } = getElementSize(el)\n return {\n active: y >= height\n }\n }),\n distinctUntilKeyChanged(\"active\")\n )\n}\n\n/**\n * Mount header title\n *\n * This function swaps the header title from the site title to the title of the\n * current page when the user scrolls past the first headline.\n *\n * @param el - Header title element\n * @param options - Options\n *\n * @returns Header title component observable\n */\nexport function mountHeaderTitle(\n el: HTMLElement, options: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ active }) => {\n el.classList.toggle(\"md-header__title--active\", active)\n })\n\n /* Obtain headline, if any */\n const heading = getOptionalElement(\"article h1\")\n if (typeof heading === \"undefined\")\n return EMPTY\n\n /* Create and return component */\n return watchHeaderTitle(heading, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n map,\n switchMap\n} from \"rxjs\"\n\nimport {\n Viewport,\n watchElementSize\n} from \"~/browser\"\n\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Main area\n */\nexport interface Main {\n offset: number /* Main area top offset */\n height: number /* Main area visible height */\n active: boolean /* Main area is active */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch main area\n *\n * This function returns an observable that computes the visual parameters of\n * the main area which depends on the viewport vertical offset and height, as\n * well as the height of the header element, if the header is fixed.\n *\n * @param el - Main area element\n * @param options - Options\n *\n * @returns Main area observable\n */\nexport function watchMain(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable
    {\n\n /* Compute necessary adjustment for header */\n const adjust$ = header$\n .pipe(\n map(({ height }) => height),\n distinctUntilChanged()\n )\n\n /* Compute the main area's top and bottom borders */\n const border$ = adjust$\n .pipe(\n switchMap(() => watchElementSize(el)\n .pipe(\n map(({ height }) => ({\n top: el.offsetTop,\n bottom: el.offsetTop + height\n })),\n distinctUntilKeyChanged(\"bottom\")\n )\n )\n )\n\n /* Compute the main area's offset, visible height and if we scrolled past */\n return combineLatest([adjust$, border$, viewport$])\n .pipe(\n map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {\n height = Math.max(0, height\n - Math.max(0, top - y, header)\n - Math.max(0, height + y - bottom)\n )\n return {\n offset: top - header,\n height,\n active: top - header <= y\n }\n }),\n distinctUntilChanged((a, b) => (\n a.offset === b.offset &&\n a.height === b.height &&\n a.active === b.active\n ))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n asyncScheduler,\n defer,\n finalize,\n fromEvent,\n map,\n mergeMap,\n observeOn,\n of,\n shareReplay,\n startWith,\n tap\n} from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Palette colors\n */\nexport interface PaletteColor {\n scheme?: string /* Color scheme */\n primary?: string /* Primary color */\n accent?: string /* Accent color */\n}\n\n/**\n * Palette\n */\nexport interface Palette {\n index: number /* Palette index */\n color: PaletteColor /* Palette colors */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch color palette\n *\n * @param inputs - Color palette element\n *\n * @returns Color palette observable\n */\nexport function watchPalette(\n inputs: HTMLInputElement[]\n): Observable {\n const current = __md_get(\"__palette\") || {\n index: inputs.findIndex(input => matchMedia(\n input.getAttribute(\"data-md-color-media\")!\n ).matches)\n }\n\n /* Emit changes in color palette */\n return of(...inputs)\n .pipe(\n mergeMap(input => fromEvent(input, \"change\")\n .pipe(\n map(() => input)\n )\n ),\n startWith(inputs[Math.max(0, current.index)]),\n map(input => ({\n index: inputs.indexOf(input),\n color: {\n scheme: input.getAttribute(\"data-md-color-scheme\"),\n primary: input.getAttribute(\"data-md-color-primary\"),\n accent: input.getAttribute(\"data-md-color-accent\")\n }\n } as Palette)),\n shareReplay(1)\n )\n}\n\n/**\n * Mount color palette\n *\n * @param el - Color palette element\n *\n * @returns Color palette component observable\n */\nexport function mountPalette(\n el: HTMLElement\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(palette => {\n document.body.setAttribute(\"data-md-color-switching\", \"\")\n\n /* Set color palette */\n for (const [key, value] of Object.entries(palette.color))\n document.body.setAttribute(`data-md-color-${key}`, value)\n\n /* Toggle visibility */\n for (let index = 0; index < inputs.length; index++) {\n const label = inputs[index].nextElementSibling\n if (label instanceof HTMLElement)\n label.hidden = palette.index !== index\n }\n\n /* Persist preference in local storage */\n __md_set(\"__palette\", palette)\n })\n\n /* Revert transition durations after color switch */\n push$.pipe(observeOn(asyncScheduler))\n .subscribe(() => {\n document.body.removeAttribute(\"data-md-color-switching\")\n })\n\n /* Create and return component */\n const inputs = getElements(\"input\", el)\n return watchPalette(inputs)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n Observable,\n Subject,\n map,\n tap\n} from \"rxjs\"\n\nimport { translation } from \"~/_\"\nimport { getElement } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n alert$: Subject /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Extract text to copy\n *\n * @param el - HTML element\n *\n * @returns Extracted text\n */\nfunction extract(el: HTMLElement): string {\n el.setAttribute(\"data-md-copying\", \"\")\n const text = el.innerText\n el.removeAttribute(\"data-md-copying\")\n return text\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up Clipboard.js integration\n *\n * @param options - Options\n */\nexport function setupClipboardJS(\n { alert$ }: SetupOptions\n): void {\n if (ClipboardJS.isSupported()) {\n new Observable(subscriber => {\n new ClipboardJS(\"[data-clipboard-target], [data-clipboard-text]\", {\n text: el => (\n el.getAttribute(\"data-clipboard-text\")! ||\n extract(getElement(\n el.getAttribute(\"data-clipboard-target\")!\n ))\n )\n })\n .on(\"success\", ev => subscriber.next(ev))\n })\n .pipe(\n tap(ev => {\n const trigger = ev.trigger as HTMLElement\n trigger.focus()\n }),\n map(() => translation(\"clipboard.copied\"))\n )\n .subscribe(alert$)\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n catchError,\n defaultIfEmpty,\n map,\n of,\n tap\n} from \"rxjs\"\n\nimport { configuration } from \"~/_\"\nimport { getElements, requestXML } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sitemap, i.e. a list of URLs\n */\nexport type Sitemap = string[]\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Preprocess a list of URLs\n *\n * This function replaces the `site_url` in the sitemap with the actual base\n * URL, to allow instant loading to work in occasions like Netlify previews.\n *\n * @param urls - URLs\n *\n * @returns URL path parts\n */\nfunction preprocess(urls: Sitemap): Sitemap {\n if (urls.length < 2)\n return [\"\"]\n\n /* Take the first two URLs and remove everything after the last slash */\n const [root, next] = [...urls]\n .sort((a, b) => a.length - b.length)\n .map(url => url.replace(/[^/]+$/, \"\"))\n\n /* Compute common prefix */\n let index = 0\n if (root === next)\n index = root.length\n else\n while (root.charCodeAt(index) === next.charCodeAt(index))\n index++\n\n /* Remove common prefix and return in original order */\n return urls.map(url => url.replace(root.slice(0, index), \"\"))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the sitemap for the given base URL\n *\n * @param base - Base URL\n *\n * @returns Sitemap observable\n */\nexport function fetchSitemap(base?: URL): Observable {\n const cached = __md_get(\"__sitemap\", sessionStorage, base)\n if (cached) {\n return of(cached)\n } else {\n const config = configuration()\n return requestXML(new URL(\"sitemap.xml\", base || config.base))\n .pipe(\n map(sitemap => preprocess(getElements(\"loc\", sitemap)\n .map(node => node.textContent!)\n )),\n catchError(() => EMPTY), // @todo refactor instant loading\n defaultIfEmpty([]),\n tap(sitemap => __md_set(\"__sitemap\", sitemap, sessionStorage, base))\n )\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n bufferCount,\n catchError,\n concatMap,\n debounceTime,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n fromEvent,\n map,\n merge,\n of,\n sample,\n share,\n skip,\n skipUntil,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"~/_\"\nimport {\n Viewport,\n ViewportOffset,\n getElements,\n getOptionalElement,\n request,\n setLocation,\n setLocationHash\n} from \"~/browser\"\nimport { getComponentElement } from \"~/components\"\nimport { h } from \"~/utilities\"\n\nimport { fetchSitemap } from \"../sitemap\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * History state\n */\nexport interface HistoryState {\n url: URL /* State URL */\n offset?: ViewportOffset /* State viewport offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n document$: Subject /* Document subject */\n location$: Subject /* Location subject */\n viewport$: Observable /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up instant loading\n *\n * When fetching, theoretically, we could use `responseType: \"document\"`, but\n * since all MkDocs links are relative, we need to make sure that the current\n * location matches the document we just loaded. Otherwise any relative links\n * in the document could use the old location.\n *\n * This is the reason why we need to synchronize history events and the process\n * of fetching the document for navigation changes (except `popstate` events):\n *\n * 1. Fetch document via `XMLHTTPRequest`\n * 2. Set new location via `history.pushState`\n * 3. Parse and emit fetched document\n *\n * For `popstate` events, we must not use `history.pushState`, or the forward\n * history will be irreversibly overwritten. In case the request fails, the\n * location change is dispatched regularly.\n *\n * @param options - Options\n */\nexport function setupInstantLoading(\n { document$, location$, viewport$ }: SetupOptions\n): void {\n const config = configuration()\n if (location.protocol === \"file:\")\n return\n\n /* Disable automatic scroll restoration */\n if (\"scrollRestoration\" in history) {\n history.scrollRestoration = \"manual\"\n\n /* Hack: ensure that reloads restore viewport offset */\n fromEvent(window, \"beforeunload\")\n .subscribe(() => {\n history.scrollRestoration = \"auto\"\n })\n }\n\n /* Hack: ensure absolute favicon link to omit 404s when switching */\n const favicon = getOptionalElement(\"link[rel=icon]\")\n if (typeof favicon !== \"undefined\")\n favicon.href = favicon.href\n\n /* Intercept internal navigation */\n const push$ = fetchSitemap()\n .pipe(\n map(paths => paths.map(path => `${new URL(path, config.base)}`)),\n switchMap(urls => fromEvent(document.body, \"click\")\n .pipe(\n filter(ev => !ev.metaKey && !ev.ctrlKey),\n switchMap(ev => {\n if (ev.target instanceof Element) {\n const el = ev.target.closest(\"a\")\n if (el && !el.target) {\n const url = new URL(el.href)\n\n /* Canonicalize URL */\n url.search = \"\"\n url.hash = \"\"\n\n /* Check if URL should be intercepted */\n if (\n url.pathname !== location.pathname &&\n urls.includes(url.toString())\n ) {\n ev.preventDefault()\n return of({\n url: new URL(el.href)\n })\n }\n }\n }\n return NEVER\n })\n )\n ),\n share()\n )\n\n /* Intercept history back and forward */\n const pop$ = fromEvent(window, \"popstate\")\n .pipe(\n filter(ev => ev.state !== null),\n map(ev => ({\n url: new URL(location.href),\n offset: ev.state\n })),\n share()\n )\n\n /* Emit location change */\n merge(push$, pop$)\n .pipe(\n distinctUntilChanged((a, b) => a.url.href === b.url.href),\n map(({ url }) => url)\n )\n .subscribe(location$)\n\n /* Fetch document via `XMLHTTPRequest` */\n const response$ = location$\n .pipe(\n distinctUntilKeyChanged(\"pathname\"),\n switchMap(url => request(url.href)\n .pipe(\n catchError(() => {\n setLocation(url)\n return NEVER\n })\n )\n ),\n share()\n )\n\n /* Set new location via `history.pushState` */\n push$\n .pipe(\n sample(response$)\n )\n .subscribe(({ url }) => {\n history.pushState({}, \"\", `${url}`)\n })\n\n /* Parse and emit fetched document */\n const dom = new DOMParser()\n response$\n .pipe(\n switchMap(res => res.text()),\n map(res => dom.parseFromString(res, \"text/html\"))\n )\n .subscribe(document$)\n\n /* Replace meta tags and components */\n document$\n .pipe(\n skip(1)\n )\n .subscribe(replacement => {\n for (const selector of [\n\n /* Meta tags */\n \"title\",\n \"link[rel=canonical]\",\n \"meta[name=author]\",\n \"meta[name=description]\",\n\n /* Components */\n \"[data-md-component=announce]\",\n \"[data-md-component=container]\",\n \"[data-md-component=header-topic]\",\n \"[data-md-component=outdated]\",\n \"[data-md-component=logo]\",\n \"[data-md-component=skip]\",\n ...feature(\"navigation.tabs.sticky\")\n ? [\"[data-md-component=tabs]\"]\n : []\n ]) {\n const source = getOptionalElement(selector)\n const target = getOptionalElement(selector, replacement)\n if (\n typeof source !== \"undefined\" &&\n typeof target !== \"undefined\"\n ) {\n source.replaceWith(target)\n }\n }\n })\n\n /* Re-evaluate scripts */\n document$\n .pipe(\n skip(1),\n map(() => getComponentElement(\"container\")),\n switchMap(el => getElements(\"script\", el)),\n concatMap(el => {\n const script = h(\"script\")\n if (el.src) {\n for (const name of el.getAttributeNames())\n script.setAttribute(name, el.getAttribute(name)!)\n el.replaceWith(script)\n\n /* Complete when script is loaded */\n return new Observable(observer => {\n script.onload = () => observer.complete()\n })\n\n /* Complete immediately */\n } else {\n script.textContent = el.textContent\n el.replaceWith(script)\n return EMPTY\n }\n })\n )\n .subscribe()\n\n /* Emit history state change */\n merge(push$, pop$)\n .pipe(\n sample(document$)\n )\n .subscribe(({ url, offset }) => {\n if (url.hash && !offset) {\n setLocationHash(url.hash)\n } else {\n window.scrollTo(0, offset?.y || 0)\n }\n })\n\n /* Debounce update of viewport offset */\n viewport$\n .pipe(\n skipUntil(push$),\n debounceTime(250),\n distinctUntilKeyChanged(\"offset\")\n )\n .subscribe(({ offset }) => {\n history.replaceState(offset, \"\")\n })\n\n /* Set viewport offset from history */\n merge(push$, pop$)\n .pipe(\n bufferCount(2, 1),\n filter(([a, b]) => a.url.pathname === b.url.pathname),\n map(([, state]) => state)\n )\n .subscribe(({ offset }) => {\n window.scrollTo(0, offset?.y || 0)\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n parent?: SearchIndexDocument /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchIndexDocument[]\n): SearchDocumentMap {\n const documents = new Map()\n const parents = new Set()\n for (const doc of docs) {\n const [path, hash] = doc.location.split(\"#\")\n\n /* Extract location, title and tags */\n const location = doc.location\n const title = doc.title\n const tags = doc.tags\n\n /* Escape and cleanup text */\n const text = escapeHTML(doc.text)\n .replace(/\\s+(?=[,.:;!?])/g, \"\")\n .replace(/\\s+/g, \" \")\n\n /* Handle section */\n if (hash) {\n const parent = documents.get(path)!\n\n /* Ignore first section, override article */\n if (!parents.has(parent)) {\n parent.title = doc.title\n parent.text = text\n\n /* Remember that we processed the article */\n parents.add(parent)\n\n /* Add subsequent section */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n parent\n })\n }\n\n /* Add article */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n ...tags && { tags }\n })\n }\n }\n return documents\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n * @param escape - Whether to escape HTML\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n config: SearchIndexConfig, escape: boolean\n): SearchHighlightFactoryFn {\n const separator = new RegExp(config.separator, \"img\")\n const highlight = (_: unknown, data: string, term: string) => {\n return `${data}${term}`\n }\n\n /* Return factory function */\n return (query: string) => {\n query = query\n .replace(/[\\s*+\\-:~^]+/g, \" \")\n .trim()\n\n /* Create search term match expression */\n const match = new RegExp(`(^|${config.separator})(${\n query\n .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n .replace(separator, \"|\")\n })`, \"img\")\n\n /* Highlight string value */\n return value => (\n escape\n ? escapeHTML(value)\n : value\n )\n .replace(match, highlight)\n .replace(/<\\/mark>(\\s+)]*>/img, \"$1\")\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search transformation function\n *\n * @param value - Query value\n *\n * @returns Transformed query value\n */\nexport type SearchTransformFn = (value: string) => string\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Search for terms in quotation marks and prepend a `+` modifier to denote\n * that the resulting document must contain all terms, converting the query\n * to an `AND` query (as opposed to the default `OR` behavior). While users\n * may expect terms enclosed in quotation marks to map to span queries, i.e.\n * for which order is important, Lunr.js doesn't support them, so the best\n * we can do is to convert the terms to an `AND` query.\n *\n * 2. Replace control characters which are not located at the beginning of the\n * query or preceded by white space, or are not followed by a non-whitespace\n * character or are at the end of the query string. Furthermore, filter\n * unmatched quotation marks.\n *\n * 3. Trim excess whitespace from left and right.\n *\n * @param query - Query value\n *\n * @returns Transformed query value\n */\nexport function defaultTransform(query: string): string {\n return query\n .split(/\"([^\"]+)\"/g) /* => 1 */\n .map((terms, index) => index & 1\n ? terms.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n : terms\n )\n .join(\"\")\n .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\") /* => 2 */\n .trim() /* => 3 */\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n SETUP, /* Search index setup */\n READY, /* Search index ready */\n QUERY, /* Search query */\n RESULT /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n type: SearchMessageType.SETUP /* Message type */\n data: SearchIndex /* Message data */\n}\n\n/**\n * Message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n type: SearchMessageType.READY /* Message type */\n}\n\n/**\n * Message containing a search query\n */\nexport interface SearchQueryMessage {\n type: SearchMessageType.QUERY /* Message type */\n data: string /* Message data */\n}\n\n/**\n * Message containing results for a search query\n */\nexport interface SearchResultMessage {\n type: SearchMessageType.RESULT /* Message type */\n data: SearchResult /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Message exchanged with the search worker\n */\nexport type SearchMessage =\n | SearchSetupMessage\n | SearchReadyMessage\n | SearchQueryMessage\n | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n message: SearchMessage\n): message is SearchSetupMessage {\n return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n message: SearchMessage\n): message is SearchReadyMessage {\n return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n message: SearchMessage\n): message is SearchQueryMessage {\n return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n message: SearchMessage\n): message is SearchResultMessage {\n return message.type === SearchMessageType.RESULT\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n ObservableInput,\n Subject,\n from,\n map,\n share\n} from \"rxjs\"\n\nimport { configuration, feature, translation } from \"~/_\"\nimport { WorkerHandler, watchWorker } from \"~/browser\"\n\nimport { SearchIndex } from \"../../_\"\nimport {\n SearchOptions,\n SearchPipeline\n} from \"../../options\"\nimport {\n SearchMessage,\n SearchMessageType,\n SearchSetupMessage,\n isSearchResultMessage\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search worker\n */\nexport type SearchWorker = WorkerHandler\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search index\n *\n * @param data - Search index\n *\n * @returns Search index\n */\nfunction setupSearchIndex({ config, docs }: SearchIndex): SearchIndex {\n\n /* Override default language with value from translation */\n if (config.lang.length === 1 && config.lang[0] === \"en\")\n config.lang = [\n translation(\"search.config.lang\")\n ]\n\n /* Override default separator with value from translation */\n if (config.separator === \"[\\\\s\\\\-]+\")\n config.separator = translation(\"search.config.separator\")\n\n /* Set pipeline from translation */\n const pipeline = translation(\"search.config.pipeline\")\n .split(/\\s*,\\s*/)\n .filter(Boolean) as SearchPipeline\n\n /* Determine search options */\n const options: SearchOptions = {\n pipeline,\n suggestions: feature(\"search.suggest\")\n }\n\n /* Return search index after defaulting */\n return { config, docs, options }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search worker\n *\n * This function creates a web worker to set up and query the search index,\n * which is done using Lunr.js. The index must be passed as an observable to\n * enable hacks like _localsearch_ via search index embedding as JSON.\n *\n * @param url - Worker URL\n * @param index - Search index observable input\n *\n * @returns Search worker\n */\nexport function setupSearchWorker(\n url: string, index: ObservableInput\n): SearchWorker {\n const config = configuration()\n const worker = new Worker(url)\n\n /* Create communication channels and resolve relative links */\n const tx$ = new Subject()\n const rx$ = watchWorker(worker, { tx$ })\n .pipe(\n map(message => {\n if (isSearchResultMessage(message)) {\n for (const result of message.data.items)\n for (const document of result)\n document.location = `${new URL(document.location, config.base)}`\n }\n return message\n }),\n share()\n )\n\n /* Set up search index */\n from(index)\n .pipe(\n map(data => ({\n type: SearchMessageType.SETUP,\n data: setupSearchIndex(data)\n } as SearchSetupMessage))\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Return search worker */\n return { tx$, rx$ }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Subject,\n catchError,\n combineLatest,\n filter,\n fromEvent,\n map,\n of,\n switchMap\n} from \"rxjs\"\n\nimport { configuration } from \"~/_\"\nimport {\n getElement,\n getLocation,\n requestJSON,\n setLocation\n} from \"~/browser\"\nimport { getComponentElements } from \"~/components\"\nimport {\n Version,\n renderVersionSelector\n} from \"~/templates\"\n\nimport { fetchSitemap } from \"../sitemap\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n document$: Subject /* Document subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up version selector\n *\n * @param options - Options\n */\nexport function setupVersionSelector(\n { document$ }: SetupOptions\n): void {\n const config = configuration()\n const versions$ = requestJSON(\n new URL(\"../versions.json\", config.base)\n )\n .pipe(\n catchError(() => EMPTY) // @todo refactor instant loading\n )\n\n /* Determine current version */\n const current$ = versions$\n .pipe(\n map(versions => {\n const [, current] = config.base.match(/([^/]+)\\/?$/)!\n return versions.find(({ version, aliases }) => (\n version === current || aliases.includes(current)\n )) || versions[0]\n })\n )\n\n /* Intercept inter-version navigation */\n combineLatest([versions$, current$])\n .pipe(\n map(([versions, current]) => new Map(versions\n .filter(version => version !== current)\n .map(version => [\n `${new URL(`../${version.version}/`, config.base)}`,\n version\n ])\n )),\n switchMap(urls => fromEvent(document.body, \"click\")\n .pipe(\n filter(ev => !ev.metaKey && !ev.ctrlKey),\n switchMap(ev => {\n if (ev.target instanceof Element) {\n const el = ev.target.closest(\"a\")\n if (el && !el.target && urls.has(el.href)) {\n ev.preventDefault()\n return of(el.href)\n }\n }\n return EMPTY\n }),\n switchMap(url => {\n const { version } = urls.get(url)!\n return fetchSitemap(new URL(url))\n .pipe(\n map(sitemap => {\n const location = getLocation()\n const path = location.href.replace(config.base, \"\")\n return sitemap.includes(path)\n ? new URL(`../${version}/${path}`, config.base)\n : new URL(url)\n })\n )\n })\n )\n )\n )\n .subscribe(url => setLocation(url))\n\n /* Render version selector and warning */\n combineLatest([versions$, current$])\n .subscribe(([versions, current]) => {\n const topic = getElement(\".md-header__topic\")\n topic.appendChild(renderVersionSelector(versions, current))\n })\n\n /* Integrate outdated version banner with instant loading */\n document$.pipe(switchMap(() => current$))\n .subscribe(current => {\n\n /* Check if version state was already determined */\n let outdated = __md_get(\"__outdated\", sessionStorage)\n if (outdated === null) {\n const latest = config.version?.default || \"latest\"\n outdated = !current.aliases.includes(latest)\n\n /* Persist version state in session storage */\n __md_set(\"__outdated\", outdated, sessionStorage)\n }\n\n /* Unhide outdated version banner */\n if (outdated)\n for (const warning of getComponentElements(\"outdated\"))\n warning.hidden = false\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n combineLatest,\n delay,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n finalize,\n fromEvent,\n map,\n merge,\n share,\n shareReplay,\n startWith,\n take,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { translation } from \"~/_\"\nimport {\n getLocation,\n setToggle,\n watchElementFocus,\n watchToggle\n} from \"~/browser\"\nimport {\n SearchMessageType,\n SearchQueryMessage,\n SearchWorker,\n defaultTransform,\n isSearchReadyMessage\n} from \"~/integrations\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query\n */\nexport interface SearchQuery {\n value: string /* Query value */\n focus: boolean /* Query focus */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch search query\n *\n * Note that the focus event which triggers re-reading the current query value\n * is delayed by `1ms` so the input's empty state is allowed to propagate.\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query observable\n */\nexport function watchSearchQuery(\n el: HTMLInputElement, { rx$ }: SearchWorker\n): Observable {\n const fn = __search?.transform || defaultTransform\n\n /* Immediately show search dialog */\n const { searchParams } = getLocation()\n if (searchParams.has(\"q\"))\n setToggle(\"search\", true)\n\n /* Intercept query parameter (deep link) */\n const param$ = rx$\n .pipe(\n filter(isSearchReadyMessage),\n take(1),\n map(() => searchParams.get(\"q\") || \"\")\n )\n\n /* Remove query parameter when search is closed */\n watchToggle(\"search\")\n .pipe(\n filter(active => !active),\n take(1)\n )\n .subscribe(() => {\n const url = new URL(location.href)\n url.searchParams.delete(\"q\")\n history.replaceState({}, \"\", `${url}`)\n })\n\n /* Set query from parameter */\n param$.subscribe(value => { // TODO: not ideal - find a better way\n if (value) {\n el.value = value\n el.focus()\n }\n })\n\n /* Intercept focus and input events */\n const focus$ = watchElementFocus(el)\n const value$ = merge(\n fromEvent(el, \"keyup\"),\n fromEvent(el, \"focus\").pipe(delay(1)),\n param$\n )\n .pipe(\n map(() => fn(el.value)),\n startWith(\"\"),\n distinctUntilChanged(),\n )\n\n /* Combine into single observable */\n return combineLatest([value$, focus$])\n .pipe(\n map(([value, focus]) => ({ value, focus })),\n shareReplay(1)\n )\n}\n\n/**\n * Mount search query\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query component observable\n */\nexport function mountSearchQuery(\n el: HTMLInputElement, { tx$, rx$ }: SearchWorker\n): Observable> {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n\n /* Handle value changes */\n push$\n .pipe(\n distinctUntilKeyChanged(\"value\"),\n map(({ value }): SearchQueryMessage => ({\n type: SearchMessageType.QUERY,\n data: value\n }))\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Handle focus changes */\n push$\n .pipe(\n distinctUntilKeyChanged(\"focus\")\n )\n .subscribe(({ focus }) => {\n if (focus) {\n setToggle(\"search\", focus)\n el.placeholder = \"\"\n } else {\n el.placeholder = translation(\"search.placeholder\")\n }\n })\n\n /* Handle reset */\n fromEvent(el.form!, \"reset\")\n .pipe(\n takeUntil(done$)\n )\n .subscribe(() => el.focus())\n\n /* Create and return component */\n return watchSearchQuery(el, { tx$, rx$ })\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state })),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n filter,\n finalize,\n map,\n merge,\n of,\n skipUntil,\n switchMap,\n take,\n tap,\n withLatestFrom,\n zipWith\n} from \"rxjs\"\n\nimport { translation } from \"~/_\"\nimport {\n getElement,\n watchElementBoundary\n} from \"~/browser\"\nimport {\n SearchResult,\n SearchWorker,\n isSearchReadyMessage,\n isSearchResultMessage\n} from \"~/integrations\"\nimport { renderSearchResultItem } from \"~/templates\"\nimport { round } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n query$: Observable /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search result list\n *\n * This function performs a lazy rendering of the search results, depending on\n * the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchResult(\n el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions\n): Observable> {\n const push$ = new Subject()\n const boundary$ = watchElementBoundary(el.parentElement!)\n .pipe(\n filter(Boolean)\n )\n\n /* Retrieve nested components */\n const meta = getElement(\":scope > :first-child\", el)\n const list = getElement(\":scope > :last-child\", el)\n\n /* Wait until search is ready */\n const ready$ = rx$\n .pipe(\n filter(isSearchReadyMessage),\n take(1)\n )\n\n /* Update search result metadata */\n push$\n .pipe(\n withLatestFrom(query$),\n skipUntil(ready$)\n )\n .subscribe(([{ items }, { value }]) => {\n if (value) {\n switch (items.length) {\n\n /* No results */\n case 0:\n meta.textContent = translation(\"search.result.none\")\n break\n\n /* One result */\n case 1:\n meta.textContent = translation(\"search.result.one\")\n break\n\n /* Multiple result */\n default:\n meta.textContent = translation(\n \"search.result.other\",\n round(items.length)\n )\n }\n } else {\n meta.textContent = translation(\"search.result.placeholder\")\n }\n })\n\n /* Update search result list */\n push$\n .pipe(\n tap(() => list.innerHTML = \"\"),\n switchMap(({ items }) => merge(\n of(...items.slice(0, 10)),\n of(...items.slice(10))\n .pipe(\n bufferCount(4),\n zipWith(boundary$),\n switchMap(([chunk]) => chunk)\n )\n ))\n )\n .subscribe(result => list.appendChild(\n renderSearchResultItem(result)\n ))\n\n /* Filter search result message */\n const result$ = rx$\n .pipe(\n filter(isSearchResultMessage),\n map(({ data }) => data)\n )\n\n /* Create and return component */\n return result$\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n finalize,\n fromEvent,\n map,\n tap\n} from \"rxjs\"\n\nimport { getLocation } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search sharing\n */\nexport interface SearchShare {\n url: URL /* Deep link for sharing */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n query$: Observable /* Search query observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n query$: Observable /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search sharing\n *\n * @param _el - Search sharing element\n * @param options - Options\n *\n * @returns Search sharing observable\n */\nexport function watchSearchShare(\n _el: HTMLElement, { query$ }: WatchOptions\n): Observable {\n return query$\n .pipe(\n map(({ value }) => {\n const url = getLocation()\n url.hash = \"\"\n url.searchParams.delete(\"h\")\n url.searchParams.set(\"q\", value)\n return { url }\n })\n )\n}\n\n/**\n * Mount search sharing\n *\n * @param el - Search sharing element\n * @param options - Options\n *\n * @returns Search sharing component observable\n */\nexport function mountSearchShare(\n el: HTMLAnchorElement, options: MountOptions\n): Observable> {\n const push$ = new Subject()\n push$.subscribe(({ url }) => {\n el.setAttribute(\"data-clipboard-text\", el.href)\n el.href = `${url}`\n })\n\n /* Prevent following of link */\n fromEvent(el, \"click\")\n .subscribe(ev => ev.preventDefault())\n\n /* Create and return component */\n return watchSearchShare(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n asyncScheduler,\n combineLatestWith,\n distinctUntilChanged,\n filter,\n finalize,\n fromEvent,\n map,\n merge,\n observeOn,\n tap\n} from \"rxjs\"\n\nimport { Keyboard } from \"~/browser\"\nimport {\n SearchResult,\n SearchWorker,\n isSearchResultMessage\n} from \"~/integrations\"\n\nimport { Component, getComponentElement } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search suggestions\n */\nexport interface SearchSuggest {}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n keyboard$: Observable /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search suggestions\n *\n * This function will perform a lazy rendering of the search results, depending\n * on the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchSuggest(\n el: HTMLElement, { rx$ }: SearchWorker, { keyboard$ }: MountOptions\n): Observable> {\n const push$ = new Subject()\n\n /* Retrieve query component and track all changes */\n const query = getComponentElement(\"search-query\")\n const query$ = merge(\n fromEvent(query, \"keydown\"),\n fromEvent(query, \"focus\")\n )\n .pipe(\n observeOn(asyncScheduler),\n map(() => query.value),\n distinctUntilChanged(),\n )\n\n /* Update search suggestions */\n push$\n .pipe(\n combineLatestWith(query$),\n map(([{ suggestions }, value]) => {\n const words = value.split(/([\\s-]+)/)\n if (suggestions?.length && words[words.length - 1]) {\n const last = suggestions[suggestions.length - 1]\n if (last.startsWith(words[words.length - 1]))\n words[words.length - 1] = last\n } else {\n words.length = 0\n }\n return words\n })\n )\n .subscribe(words => el.innerHTML = words\n .join(\"\")\n .replace(/\\s/g, \" \")\n )\n\n /* Set up search keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"search\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Right arrow: accept current suggestion */\n case \"ArrowRight\":\n if (\n el.innerText.length &&\n query.selectionStart === query.value.length\n )\n query.value = el.innerText\n break\n }\n })\n\n /* Filter search result message */\n const result$ = rx$\n .pipe(\n filter(isSearchResultMessage),\n map(({ data }) => data)\n )\n\n /* Create and return component */\n return result$\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(() => ({ ref: el }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n NEVER,\n Observable,\n ObservableInput,\n filter,\n merge,\n mergeWith,\n sample,\n take\n} from \"rxjs\"\n\nimport { configuration } from \"~/_\"\nimport {\n Keyboard,\n getActiveElement,\n getElements,\n setToggle\n} from \"~/browser\"\nimport {\n SearchIndex,\n SearchResult,\n isSearchQueryMessage,\n isSearchReadyMessage,\n setupSearchWorker\n} from \"~/integrations\"\n\nimport {\n Component,\n getComponentElement,\n getComponentElements\n} from \"../../_\"\nimport {\n SearchQuery,\n mountSearchQuery\n} from \"../query\"\nimport { mountSearchResult } from \"../result\"\nimport {\n SearchShare,\n mountSearchShare\n} from \"../share\"\nimport {\n SearchSuggest,\n mountSearchSuggest\n} from \"../suggest\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search\n */\nexport type Search =\n | SearchQuery\n | SearchResult\n | SearchShare\n | SearchSuggest\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n index$: ObservableInput /* Search index observable */\n keyboard$: Observable /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search\n *\n * This function sets up the search functionality, including the underlying\n * web worker and all keyboard bindings.\n *\n * @param el - Search element\n * @param options - Options\n *\n * @returns Search component observable\n */\nexport function mountSearch(\n el: HTMLElement, { index$, keyboard$ }: MountOptions\n): Observable> {\n const config = configuration()\n try {\n const url = __search?.worker || config.search\n const worker = setupSearchWorker(url, index$)\n\n /* Retrieve query and result components */\n const query = getComponentElement(\"search-query\", el)\n const result = getComponentElement(\"search-result\", el)\n\n /* Re-emit query when search is ready */\n const { tx$, rx$ } = worker\n tx$\n .pipe(\n filter(isSearchQueryMessage),\n sample(rx$.pipe(filter(isSearchReadyMessage))),\n take(1)\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Set up search keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"search\")\n )\n .subscribe(key => {\n const active = getActiveElement()\n switch (key.type) {\n\n /* Enter: go to first (best) result */\n case \"Enter\":\n if (active === query) {\n const anchors = new Map()\n for (const anchor of getElements(\n \":first-child [href]\", result\n )) {\n const article = anchor.firstElementChild!\n anchors.set(anchor, parseFloat(\n article.getAttribute(\"data-md-score\")!\n ))\n }\n\n /* Go to result with highest score, if any */\n if (anchors.size) {\n const [[best]] = [...anchors].sort(([, a], [, b]) => b - a)\n best.click()\n }\n\n /* Otherwise omit form submission */\n key.claim()\n }\n break\n\n /* Escape or Tab: close search */\n case \"Escape\":\n case \"Tab\":\n setToggle(\"search\", false)\n query.blur()\n break\n\n /* Vertical arrows: select previous or next search result */\n case \"ArrowUp\":\n case \"ArrowDown\":\n if (typeof active === \"undefined\") {\n query.focus()\n } else {\n const els = [query, ...getElements(\n \":not(details) > [href], summary, details[open] [href]\",\n result\n )]\n const i = Math.max(0, (\n Math.max(0, els.indexOf(active)) + els.length + (\n key.type === \"ArrowUp\" ? -1 : +1\n )\n ) % els.length)\n els[i].focus()\n }\n\n /* Prevent scrolling of page */\n key.claim()\n break\n\n /* All other keys: hand to search query */\n default:\n if (query !== getActiveElement())\n query.focus()\n }\n })\n\n /* Set up global keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\"),\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Open search and select query */\n case \"f\":\n case \"s\":\n case \"/\":\n query.focus()\n query.select()\n\n /* Prevent scrolling of page */\n key.claim()\n break\n }\n })\n\n /* Create and return component */\n const query$ = mountSearchQuery(query, worker)\n const result$ = mountSearchResult(result, worker, { query$ })\n return merge(query$, result$)\n .pipe(\n mergeWith(\n\n /* Search sharing */\n ...getComponentElements(\"search-share\", el)\n .map(child => mountSearchShare(child, { query$ })),\n\n /* Search suggestions */\n ...getComponentElements(\"search-suggest\", el)\n .map(child => mountSearchSuggest(child, worker, { keyboard$ }))\n )\n )\n\n /* Gracefully handle broken search */\n } catch (err) {\n el.hidden = true\n return NEVER\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n ObservableInput,\n combineLatest,\n filter,\n map,\n startWith\n} from \"rxjs\"\n\nimport { getLocation } from \"~/browser\"\nimport {\n SearchIndex,\n setupSearchHighlighter\n} from \"~/integrations\"\nimport { h } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlighting\n */\nexport interface SearchHighlight {\n nodes: Map /* Map of replacements */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n index$: ObservableInput /* Search index observable */\n location$: Observable /* Location observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search highlighting\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Search highlighting component observable\n */\nexport function mountSearchHiglight(\n el: HTMLElement, { index$, location$ }: MountOptions\n): Observable> {\n return combineLatest([\n index$,\n location$\n .pipe(\n startWith(getLocation()),\n filter(url => !!url.searchParams.get(\"h\"))\n )\n ])\n .pipe(\n map(([index, url]) => setupSearchHighlighter(index.config, true)(\n url.searchParams.get(\"h\")!\n )),\n map(fn => {\n const nodes = new Map()\n\n /* Traverse text nodes and collect matches */\n const it = document.createNodeIterator(el, NodeFilter.SHOW_TEXT)\n for (let node = it.nextNode(); node; node = it.nextNode()) {\n if (node.parentElement?.offsetHeight) {\n const original = node.textContent!\n const replaced = fn(original)\n if (replaced.length > original.length)\n nodes.set(node as ChildNode, replaced)\n }\n }\n\n /* Replace original nodes with matches */\n for (const [node, text] of nodes) {\n const { childNodes } = h(\"span\", null, text)\n node.replaceWith(...Array.from(childNodes))\n }\n\n /* Return component */\n return { ref: el, nodes }\n })\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n auditTime,\n combineLatest,\n defer,\n distinctUntilChanged,\n finalize,\n map,\n tap,\n withLatestFrom\n} from \"rxjs\"\n\nimport {\n Viewport,\n getElement,\n getElementOffset\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sidebar\n */\nexport interface Sidebar {\n height: number /* Sidebar height */\n locked: boolean /* Sidebar is locked */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n main$: Observable
    /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch sidebar\n *\n * This function returns an observable that computes the visual parameters of\n * the sidebar which depends on the vertical viewport offset, as well as the\n * height of the main area. When the page is scrolled beyond the header, the\n * sidebar is locked and fills the remaining space.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar observable\n */\nexport function watchSidebar(\n el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable {\n const parent = el.parentElement!\n const adjust =\n parent.offsetTop -\n parent.parentElement!.offsetTop\n\n /* Compute the sidebar's available height and if it should be locked */\n return combineLatest([main$, viewport$])\n .pipe(\n map(([{ offset, height }, { offset: { y } }]) => {\n height = height\n + Math.min(adjust, Math.max(0, y - offset))\n - adjust\n return {\n height,\n locked: y >= offset + adjust\n }\n }),\n distinctUntilChanged((a, b) => (\n a.height === b.height &&\n a.locked === b.locked\n ))\n )\n}\n\n/**\n * Mount sidebar\n *\n * This function doesn't set the height of the actual sidebar, but of its first\n * child \u2013 the `.md-sidebar__scrollwrap` element in order to mitigiate jittery\n * sidebars when the footer is scrolled into view. At some point we switched\n * from `absolute` / `fixed` positioning to `sticky` positioning, significantly\n * reducing jitter in some browsers (respectively Firefox and Safari) when\n * scrolling from the top. However, top-aligned sticky positioning means that\n * the sidebar snaps to the bottom when the end of the container is reached.\n * This is what leads to the mentioned jitter, as the sidebar's height may be\n * updated too slowly.\n *\n * This behaviour can be mitigiated by setting the height of the sidebar to `0`\n * while preserving the padding, and the height on its first element.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar component observable\n */\nexport function mountSidebar(\n el: HTMLElement, { header$, ...options }: MountOptions\n): Observable> {\n const inner = getElement(\".md-sidebar__scrollwrap\", el)\n const { y } = getElementOffset(inner)\n return defer(() => {\n const push$ = new Subject()\n push$\n .pipe(\n auditTime(0, animationFrameScheduler),\n withLatestFrom(header$)\n )\n .subscribe({\n\n /* Handle emission */\n next([{ height }, { height: offset }]) {\n inner.style.height = `${height - 2 * y}px`\n el.style.top = `${offset}px`\n },\n\n /* Handle complete */\n complete() {\n inner.style.height = \"\"\n el.style.top = \"\"\n }\n })\n\n /* Create and return component */\n return watchSidebar(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Repo, User } from \"github-types\"\nimport {\n EMPTY,\n Observable,\n catchError,\n defaultIfEmpty,\n map,\n zip\n} from \"rxjs\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * GitHub release (partial)\n */\ninterface Release {\n tag_name: string /* Tag name */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitHub repository facts\n *\n * @param user - GitHub user or organization\n * @param repo - GitHub repository\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitHub(\n user: string, repo?: string\n): Observable {\n if (typeof repo !== \"undefined\") {\n const url = `https://api.github.com/repos/${user}/${repo}`\n return zip(\n\n /* Fetch version */\n requestJSON(`${url}/releases/latest`)\n .pipe(\n catchError(() => EMPTY), // @todo refactor instant loading\n map(release => ({\n version: release.tag_name\n })),\n defaultIfEmpty({})\n ),\n\n /* Fetch stars and forks */\n requestJSON(url)\n .pipe(\n catchError(() => EMPTY), // @todo refactor instant loading\n map(info => ({\n stars: info.stargazers_count,\n forks: info.forks_count\n })),\n defaultIfEmpty({})\n )\n )\n .pipe(\n map(([release, info]) => ({ ...release, ...info }))\n )\n\n /* User or organization */\n } else {\n const url = `https://api.github.com/users/${user}`\n return requestJSON(url)\n .pipe(\n map(info => ({\n repositories: info.public_repos\n })),\n defaultIfEmpty({})\n )\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ProjectSchema } from \"gitlab\"\nimport {\n EMPTY,\n Observable,\n catchError,\n defaultIfEmpty,\n map\n} from \"rxjs\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitLab repository facts\n *\n * @param base - GitLab base\n * @param project - GitLab project\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitLab(\n base: string, project: string\n): Observable {\n const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`\n return requestJSON(url)\n .pipe(\n catchError(() => EMPTY), // @todo refactor instant loading\n map(({ star_count, forks_count }) => ({\n stars: star_count,\n forks: forks_count\n })),\n defaultIfEmpty({})\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { EMPTY, Observable } from \"rxjs\"\n\nimport { fetchSourceFactsFromGitHub } from \"../github\"\nimport { fetchSourceFactsFromGitLab } from \"../gitlab\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository facts for repositories\n */\nexport interface RepositoryFacts {\n stars?: number /* Number of stars */\n forks?: number /* Number of forks */\n version?: string /* Latest version */\n}\n\n/**\n * Repository facts for organizations\n */\nexport interface OrganizationFacts {\n repositories?: number /* Number of repositories */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Repository facts\n */\nexport type SourceFacts =\n | RepositoryFacts\n | OrganizationFacts\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch repository facts\n *\n * @param url - Repository URL\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFacts(\n url: string\n): Observable {\n const [type] = url.match(/(git(?:hub|lab))/i) || []\n switch (type.toLowerCase()) {\n\n /* GitHub repository */\n case \"github\":\n const [, user, repo] = url.match(/^.+github\\.com\\/([^/]+)\\/?([^/]+)?/i)!\n return fetchSourceFactsFromGitHub(user, repo)\n\n /* GitLab repository */\n case \"gitlab\":\n const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\\/(.+?)\\/?$/i)!\n return fetchSourceFactsFromGitLab(base, slug)\n\n /* Everything else */\n default:\n return EMPTY\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n catchError,\n defer,\n filter,\n finalize,\n map,\n of,\n shareReplay,\n tap\n} from \"rxjs\"\n\nimport { getElement } from \"~/browser\"\nimport { renderSourceFacts } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport {\n SourceFacts,\n fetchSourceFacts\n} from \"../facts\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information\n */\nexport interface Source {\n facts: SourceFacts /* Repository facts */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information observable\n */\nlet fetch$: Observable\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch repository information\n *\n * This function tries to read the repository facts from session storage, and\n * if unsuccessful, fetches them from the underlying provider.\n *\n * @param el - Repository information element\n *\n * @returns Repository information observable\n */\nexport function watchSource(\n el: HTMLAnchorElement\n): Observable {\n return fetch$ ||= defer(() => {\n const cached = __md_get(\"__source\", sessionStorage)\n if (cached)\n return of(cached)\n else\n return fetchSourceFacts(el.href)\n .pipe(\n tap(facts => __md_set(\"__source\", facts, sessionStorage))\n )\n })\n .pipe(\n catchError(() => EMPTY),\n filter(facts => Object.keys(facts).length > 0),\n map(facts => ({ facts })),\n shareReplay(1)\n )\n}\n\n/**\n * Mount repository information\n *\n * @param el - Repository information element\n *\n * @returns Repository information component observable\n */\nexport function mountSource(\n el: HTMLAnchorElement\n): Observable> {\n const inner = getElement(\":scope > :last-child\", el)\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ facts }) => {\n inner.appendChild(renderSourceFacts(facts))\n inner.classList.add(\"md-source__repository--active\")\n })\n\n /* Create and return component */\n return watchSource(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n defer,\n distinctUntilKeyChanged,\n finalize,\n map,\n of,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n Viewport,\n watchElementSize,\n watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Navigation tabs\n */\nexport interface Tabs {\n hidden: boolean /* Navigation tabs are hidden */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch navigation tabs\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs observable\n */\nexport function watchTabs(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n return watchElementSize(document.body)\n .pipe(\n switchMap(() => watchViewportAt(el, { header$, viewport$ })),\n map(({ offset: { y } }) => {\n return {\n hidden: y >= 10\n }\n }),\n distinctUntilKeyChanged(\"hidden\")\n )\n}\n\n/**\n * Mount navigation tabs\n *\n * This function hides the navigation tabs when scrolling past the threshold\n * and makes them reappear in a nice CSS animation when scrolling back up.\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs component observable\n */\nexport function mountTabs(\n el: HTMLElement, options: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe({\n\n /* Handle emission */\n next({ hidden }) {\n el.hidden = hidden\n },\n\n /* Handle complete */\n complete() {\n el.hidden = false\n }\n })\n\n /* Create and return component */\n return (\n feature(\"navigation.tabs.sticky\")\n ? of({ hidden: false })\n : watchTabs(el, options)\n )\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n combineLatestWith,\n debounceTime,\n defer,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n finalize,\n map,\n of,\n repeat,\n scan,\n share,\n skip,\n startWith,\n switchMap,\n takeLast,\n takeUntil,\n tap,\n withLatestFrom\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n Viewport,\n getElement,\n getElements,\n getLocation,\n getOptionalElement,\n watchElementSize\n} from \"~/browser\"\n\nimport {\n Component,\n getComponentElement\n} from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Table of contents\n */\nexport interface TableOfContents {\n prev: HTMLAnchorElement[][] /* Anchors (previous) */\n next: HTMLAnchorElement[][] /* Anchors (next) */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n target$: Observable /* Location target observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch table of contents\n *\n * This is effectively a scroll spy implementation which will account for the\n * fixed header and automatically re-calculate anchor offsets when the viewport\n * is resized. The returned observable will only emit if the table of contents\n * needs to be repainted.\n *\n * This implementation tracks an anchor element's entire path starting from its\n * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the\n * Material theme currently doesn't make use of this information, it enables\n * the styling of the entire hierarchy through customization.\n *\n * Note that the current anchor is the last item of the `prev` anchor list.\n *\n * @param el - Table of contents element\n * @param options - Options\n *\n * @returns Table of contents observable\n */\nexport function watchTableOfContents(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n const table = new Map()\n\n /* Compute anchor-to-target mapping */\n const anchors = getElements(\"[href^=\\\\#]\", el)\n for (const anchor of anchors) {\n const id = decodeURIComponent(anchor.hash.substring(1))\n const target = getOptionalElement(`[id=\"${id}\"]`)\n if (typeof target !== \"undefined\")\n table.set(anchor, target)\n }\n\n /* Compute necessary adjustment for header */\n const adjust$ = header$\n .pipe(\n distinctUntilKeyChanged(\"height\"),\n map(({ height }) => {\n const main = getComponentElement(\"main\")\n const grid = getElement(\":scope > :first-child\", main)\n return height + 0.8 * (\n grid.offsetTop -\n main.offsetTop\n )\n }),\n share()\n )\n\n /* Compute partition of previous and next anchors */\n const partition$ = watchElementSize(document.body)\n .pipe(\n distinctUntilKeyChanged(\"height\"),\n\n /* Build index to map anchor paths to vertical offsets */\n switchMap(body => defer(() => {\n let path: HTMLAnchorElement[] = []\n return of([...table].reduce((index, [anchor, target]) => {\n while (path.length) {\n const last = table.get(path[path.length - 1])!\n if (last.tagName >= target.tagName) {\n path.pop()\n } else {\n break\n }\n }\n\n /* If the current anchor is hidden, continue with its parent */\n let offset = target.offsetTop\n while (!offset && target.parentElement) {\n target = target.parentElement\n offset = target.offsetTop\n }\n\n /* Map reversed anchor path to vertical offset */\n return index.set(\n [...path = [...path, anchor]].reverse(),\n offset\n )\n }, new Map()))\n })\n .pipe(\n\n /* Sort index by vertical offset (see https://bit.ly/30z6QSO) */\n map(index => new Map([...index].sort(([, a], [, b]) => a - b))),\n combineLatestWith(adjust$),\n\n /* Re-compute partition when viewport offset changes */\n switchMap(([index, adjust]) => viewport$\n .pipe(\n scan(([prev, next], { offset: { y }, size }) => {\n const last = y + size.height >= Math.floor(body.height)\n\n /* Look forward */\n while (next.length) {\n const [, offset] = next[0]\n if (offset - adjust < y || last) {\n prev = [...prev, next.shift()!]\n } else {\n break\n }\n }\n\n /* Look backward */\n while (prev.length) {\n const [, offset] = prev[prev.length - 1]\n if (offset - adjust >= y && !last) {\n next = [prev.pop()!, ...next]\n } else {\n break\n }\n }\n\n /* Return partition */\n return [prev, next]\n }, [[], [...index]]),\n distinctUntilChanged((a, b) => (\n a[0] === b[0] &&\n a[1] === b[1]\n ))\n )\n )\n )\n )\n )\n\n /* Compute and return anchor list migrations */\n return partition$\n .pipe(\n map(([prev, next]) => ({\n prev: prev.map(([path]) => path),\n next: next.map(([path]) => path)\n })),\n\n /* Extract anchor list migrations */\n startWith({ prev: [], next: [] }),\n bufferCount(2, 1),\n map(([a, b]) => {\n\n /* Moving down */\n if (a.prev.length < b.prev.length) {\n return {\n prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),\n next: []\n }\n\n /* Moving up */\n } else {\n return {\n prev: b.prev.slice(-1),\n next: b.next.slice(0, b.next.length - a.next.length)\n }\n }\n })\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount table of contents\n *\n * @param el - Table of contents element\n * @param options - Options\n *\n * @returns Table of contents component observable\n */\nexport function mountTableOfContents(\n el: HTMLElement, { viewport$, header$, target$ }: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n push$.subscribe(({ prev, next }) => {\n\n /* Look forward */\n for (const [anchor] of next) {\n anchor.classList.remove(\"md-nav__link--passed\")\n anchor.classList.remove(\"md-nav__link--active\")\n }\n\n /* Look backward */\n for (const [index, [anchor]] of prev.entries()) {\n anchor.classList.add(\"md-nav__link--passed\")\n anchor.classList.toggle(\n \"md-nav__link--active\",\n index === prev.length - 1\n )\n }\n })\n\n /* Set up anchor tracking, if enabled */\n if (feature(\"navigation.tracking\"))\n viewport$\n .pipe(\n takeUntil(done$),\n distinctUntilKeyChanged(\"offset\"),\n debounceTime(250),\n skip(1),\n takeUntil(target$.pipe(skip(1))),\n repeat({ delay: 250 }),\n withLatestFrom(push$)\n )\n .subscribe(([, { prev }]) => {\n const url = getLocation()\n\n /* Set hash fragment to active anchor */\n const anchor = prev[prev.length - 1]\n if (anchor && anchor.length) {\n const [active] = anchor\n const { hash } = new URL(active.href)\n if (url.hash !== hash) {\n url.hash = hash\n history.replaceState({}, \"\", `${url}`)\n }\n\n /* Reset anchor when at the top */\n } else {\n url.hash = \"\"\n history.replaceState({}, \"\", `${url}`)\n }\n })\n\n /* Create and return component */\n return watchTableOfContents(el, { viewport$, header$ })\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n combineLatest,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n endWith,\n finalize,\n map,\n repeat,\n skip,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Back-to-top button\n */\nexport interface BackToTop {\n hidden: boolean /* Back-to-top button is hidden */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n main$: Observable
    /* Main area observable */\n target$: Observable /* Location target observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n target$: Observable /* Location target observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch back-to-top\n *\n * @param _el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top observable\n */\nexport function watchBackToTop(\n _el: HTMLElement, { viewport$, main$, target$ }: WatchOptions\n): Observable {\n\n /* Compute direction */\n const direction$ = viewport$\n .pipe(\n map(({ offset: { y } }) => y),\n bufferCount(2, 1),\n map(([a, b]) => a > b && b > 0),\n distinctUntilChanged()\n )\n\n /* Compute whether main area is active */\n const active$ = main$\n .pipe(\n map(({ active }) => active)\n )\n\n /* Compute threshold for hiding */\n return combineLatest([active$, direction$])\n .pipe(\n map(([active, direction]) => !(active && direction)),\n distinctUntilChanged(),\n takeUntil(target$.pipe(skip(1))),\n endWith(true),\n repeat({ delay: 250 }),\n map(hidden => ({ hidden }))\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount back-to-top\n *\n * @param el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top component observable\n */\nexport function mountBackToTop(\n el: HTMLElement, { viewport$, header$, main$, target$ }: MountOptions\n): Observable> {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n push$.subscribe({\n\n /* Handle emission */\n next({ hidden }) {\n el.hidden = hidden\n if (hidden) {\n el.setAttribute(\"tabindex\", \"-1\")\n el.blur()\n } else {\n el.removeAttribute(\"tabindex\")\n }\n },\n\n /* Handle complete */\n complete() {\n el.style.top = \"\"\n el.hidden = true\n el.removeAttribute(\"tabindex\")\n }\n })\n\n /* Watch header height */\n header$\n .pipe(\n takeUntil(done$),\n distinctUntilKeyChanged(\"height\")\n )\n .subscribe(({ height }) => {\n el.style.top = `${height + 16}px`\n })\n\n /* Create and return component */\n return watchBackToTop(el, { viewport$, main$, target$ })\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n mergeMap,\n switchMap,\n takeWhile,\n tap,\n withLatestFrom\n} from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n document$: Observable /* Document observable */\n tablet$: Observable /* Media tablet observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch indeterminate checkboxes\n *\n * This function replaces the indeterminate \"pseudo state\" with the actual\n * indeterminate state, which is used to keep navigation always expanded.\n *\n * @param options - Options\n */\nexport function patchIndeterminate(\n { document$, tablet$ }: PatchOptions\n): void {\n document$\n .pipe(\n switchMap(() => getElements(\n // @todo `data-md-state` is deprecated and removed in v9\n \".md-toggle--indeterminate, [data-md-state=indeterminate]\"\n )),\n tap(el => {\n el.indeterminate = true\n el.checked = false\n }),\n mergeMap(el => fromEvent(el, \"change\")\n .pipe(\n takeWhile(() => el.classList.contains(\"md-toggle--indeterminate\")),\n map(() => el)\n )\n ),\n withLatestFrom(tablet$)\n )\n .subscribe(([el, tablet]) => {\n el.classList.remove(\"md-toggle--indeterminate\")\n if (tablet)\n el.checked = false\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n filter,\n fromEvent,\n map,\n mergeMap,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n document$: Observable /* Document observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether the given device is an Apple device\n *\n * @returns Test result\n */\nfunction isAppleDevice(): boolean {\n return /(iPad|iPhone|iPod)/.test(navigator.userAgent)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch all elements with `data-md-scrollfix` attributes\n *\n * This is a year-old patch which ensures that overflow scrolling works at the\n * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon\n * the start of a touch event.\n *\n * @see https://bit.ly/2SCtAOO - Original source\n *\n * @param options - Options\n */\nexport function patchScrollfix(\n { document$ }: PatchOptions\n): void {\n document$\n .pipe(\n switchMap(() => getElements(\"[data-md-scrollfix]\")),\n tap(el => el.removeAttribute(\"data-md-scrollfix\")),\n filter(isAppleDevice),\n mergeMap(el => fromEvent(el, \"touchstart\")\n .pipe(\n map(() => el)\n )\n )\n )\n .subscribe(el => {\n const top = el.scrollTop\n\n /* We're at the top of the container */\n if (top === 0) {\n el.scrollTop = 1\n\n /* We're at the bottom of the container */\n } else if (top + el.offsetHeight === el.scrollHeight) {\n el.scrollTop = top - 1\n }\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n delay,\n map,\n of,\n switchMap,\n withLatestFrom\n} from \"rxjs\"\n\nimport {\n Viewport,\n watchToggle\n} from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n viewport$: Observable /* Viewport observable */\n tablet$: Observable /* Media tablet observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch the document body to lock when search is open\n *\n * For mobile and tablet viewports, the search is rendered full screen, which\n * leads to scroll leaking when at the top or bottom of the search result. This\n * function locks the body when the search is in full screen mode, and restores\n * the scroll position when leaving.\n *\n * @param options - Options\n */\nexport function patchScrolllock(\n { viewport$, tablet$ }: PatchOptions\n): void {\n combineLatest([watchToggle(\"search\"), tablet$])\n .pipe(\n map(([active, tablet]) => active && !tablet),\n switchMap(active => of(active)\n .pipe(\n delay(active ? 400 : 100)\n )\n ),\n withLatestFrom(viewport$)\n )\n .subscribe(([active, { offset: { y }}]) => {\n if (active) {\n document.body.setAttribute(\"data-md-scrolllock\", \"\")\n document.body.style.top = `-${y}px`\n } else {\n const value = -1 * parseInt(document.body.style.top, 10)\n document.body.removeAttribute(\"data-md-scrolllock\")\n document.body.style.top = \"\"\n if (value)\n window.scrollTo(0, value)\n }\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Polyfills\n * ------------------------------------------------------------------------- */\n\n/* Polyfill `Object.entries` */\nif (!Object.entries)\n Object.entries = function (obj: object) {\n const data: [string, string][] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push([key, obj[key]])\n\n /* Return entries */\n return data\n }\n\n/* Polyfill `Object.values` */\nif (!Object.values)\n Object.values = function (obj: object) {\n const data: string[] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push(obj[key])\n\n /* Return values */\n return data\n }\n\n/* ------------------------------------------------------------------------- */\n\n/* Polyfills for `Element` */\nif (typeof Element !== \"undefined\") {\n\n /* Polyfill `Element.scrollTo` */\n if (!Element.prototype.scrollTo)\n Element.prototype.scrollTo = function (\n x?: ScrollToOptions | number, y?: number\n ): void {\n if (typeof x === \"object\") {\n this.scrollLeft = x.left!\n this.scrollTop = x.top!\n } else {\n this.scrollLeft = x!\n this.scrollTop = y!\n }\n }\n\n /* Polyfill `Element.replaceWith` */\n if (!Element.prototype.replaceWith)\n Element.prototype.replaceWith = function (\n ...nodes: Array\n ): void {\n const parent = this.parentNode\n if (parent) {\n if (nodes.length === 0)\n parent.removeChild(this)\n\n /* Replace children and create text nodes */\n for (let i = nodes.length - 1; i >= 0; i--) {\n let node = nodes[i]\n if (typeof node !== \"object\")\n node = document.createTextNode(node)\n else if (node.parentNode)\n node.parentNode.removeChild(node)\n\n /* Replace child or insert before previous sibling */\n if (!i)\n parent.replaceChild(node, this)\n else\n parent.insertBefore(this.previousSibling!, node)\n }\n }\n }\n}\n"], + "mappings": "g+BAAA,oBAAC,UAAU,EAAQ,EAAS,CAC1B,MAAO,KAAY,UAAY,MAAO,KAAW,YAAc,EAAQ,EACvE,MAAO,SAAW,YAAc,OAAO,IAAM,OAAO,CAAO,EAC1D,EAAQ,CACX,GAAE,GAAO,UAAY,CAAE,aASrB,WAAmC,EAAO,CACxC,GAAI,GAAmB,GACnB,EAA0B,GAC1B,EAAiC,KAEjC,EAAsB,CACxB,KAAM,GACN,OAAQ,GACR,IAAK,GACL,IAAK,GACL,MAAO,GACP,SAAU,GACV,OAAQ,GACR,KAAM,GACN,MAAO,GACP,KAAM,GACN,KAAM,GACN,SAAU,GACV,iBAAkB,EACpB,EAOA,WAA4B,EAAI,CAC9B,MACE,MACA,IAAO,UACP,EAAG,WAAa,QAChB,EAAG,WAAa,QAChB,aAAe,IACf,YAAc,GAAG,UAKrB,CASA,WAAuC,EAAI,CACzC,GAAI,IAAO,EAAG,KACV,GAAU,EAAG,QAUjB,MARI,QAAY,SAAW,EAAoB,KAAS,CAAC,EAAG,UAIxD,KAAY,YAAc,CAAC,EAAG,UAI9B,EAAG,kBAKT,CAOA,WAA8B,EAAI,CAChC,AAAI,EAAG,UAAU,SAAS,eAAe,GAGzC,GAAG,UAAU,IAAI,eAAe,EAChC,EAAG,aAAa,2BAA4B,EAAE,EAChD,CAOA,WAAiC,EAAI,CACnC,AAAI,CAAC,EAAG,aAAa,0BAA0B,GAG/C,GAAG,UAAU,OAAO,eAAe,EACnC,EAAG,gBAAgB,0BAA0B,EAC/C,CAUA,WAAmB,EAAG,CACpB,AAAI,EAAE,SAAW,EAAE,QAAU,EAAE,SAI3B,GAAmB,EAAM,aAAa,GACxC,EAAqB,EAAM,aAAa,EAG1C,EAAmB,GACrB,CAUA,WAAuB,EAAG,CACxB,EAAmB,EACrB,CASA,WAAiB,EAAG,CAElB,AAAI,CAAC,EAAmB,EAAE,MAAM,GAI5B,IAAoB,EAA8B,EAAE,MAAM,IAC5D,EAAqB,EAAE,MAAM,CAEjC,CAMA,WAAgB,EAAG,CACjB,AAAI,CAAC,EAAmB,EAAE,MAAM,GAK9B,GAAE,OAAO,UAAU,SAAS,eAAe,GAC3C,EAAE,OAAO,aAAa,0BAA0B,IAMhD,GAA0B,GAC1B,OAAO,aAAa,CAA8B,EAClD,EAAiC,OAAO,WAAW,UAAW,CAC5D,EAA0B,EAC5B,EAAG,GAAG,EACN,EAAwB,EAAE,MAAM,EAEpC,CAOA,WAA4B,EAAG,CAC7B,AAAI,SAAS,kBAAoB,UAK3B,IACF,GAAmB,IAErB,EAA+B,EAEnC,CAQA,YAA0C,CACxC,SAAS,iBAAiB,YAAa,CAAoB,EAC3D,SAAS,iBAAiB,YAAa,CAAoB,EAC3D,SAAS,iBAAiB,UAAW,CAAoB,EACzD,SAAS,iBAAiB,cAAe,CAAoB,EAC7D,SAAS,iBAAiB,cAAe,CAAoB,EAC7D,SAAS,iBAAiB,YAAa,CAAoB,EAC3D,SAAS,iBAAiB,YAAa,CAAoB,EAC3D,SAAS,iBAAiB,aAAc,CAAoB,EAC5D,SAAS,iBAAiB,WAAY,CAAoB,CAC5D,CAEA,YAA6C,CAC3C,SAAS,oBAAoB,YAAa,CAAoB,EAC9D,SAAS,oBAAoB,YAAa,CAAoB,EAC9D,SAAS,oBAAoB,UAAW,CAAoB,EAC5D,SAAS,oBAAoB,cAAe,CAAoB,EAChE,SAAS,oBAAoB,cAAe,CAAoB,EAChE,SAAS,oBAAoB,YAAa,CAAoB,EAC9D,SAAS,oBAAoB,YAAa,CAAoB,EAC9D,SAAS,oBAAoB,aAAc,CAAoB,EAC/D,SAAS,oBAAoB,WAAY,CAAoB,CAC/D,CASA,WAA8B,EAAG,CAG/B,AAAI,EAAE,OAAO,UAAY,EAAE,OAAO,SAAS,YAAY,IAAM,QAI7D,GAAmB,GACnB,EAAkC,EACpC,CAKA,SAAS,iBAAiB,UAAW,EAAW,EAAI,EACpD,SAAS,iBAAiB,YAAa,EAAe,EAAI,EAC1D,SAAS,iBAAiB,cAAe,EAAe,EAAI,EAC5D,SAAS,iBAAiB,aAAc,EAAe,EAAI,EAC3D,SAAS,iBAAiB,mBAAoB,EAAoB,EAAI,EAEtE,EAA+B,EAM/B,EAAM,iBAAiB,QAAS,EAAS,EAAI,EAC7C,EAAM,iBAAiB,OAAQ,EAAQ,EAAI,EAO3C,AAAI,EAAM,WAAa,KAAK,wBAA0B,EAAM,KAI1D,EAAM,KAAK,aAAa,wBAAyB,EAAE,EAC1C,EAAM,WAAa,KAAK,eACjC,UAAS,gBAAgB,UAAU,IAAI,kBAAkB,EACzD,SAAS,gBAAgB,aAAa,wBAAyB,EAAE,EAErE,CAKA,GAAI,MAAO,SAAW,aAAe,MAAO,WAAa,YAAa,CAIpE,OAAO,0BAA4B,EAInC,GAAI,GAEJ,GAAI,CACF,EAAQ,GAAI,aAAY,8BAA8B,CACxD,OAAS,EAAP,CAEA,EAAQ,SAAS,YAAY,aAAa,EAC1C,EAAM,gBAAgB,+BAAgC,GAAO,GAAO,CAAC,CAAC,CACxE,CAEA,OAAO,cAAc,CAAK,CAC5B,CAEA,AAAI,MAAO,WAAa,aAGtB,EAA0B,QAAQ,CAGtC,CAAE,ICvTF,eAAC,UAAS,EAAQ,CAOhB,GAAI,GAA6B,UAAW,CAC1C,GAAI,CACF,MAAO,CAAC,CAAC,OAAO,QAClB,OAAS,EAAP,CACA,MAAO,EACT,CACF,EAGI,EAAoB,EAA2B,EAE/C,EAAiB,SAAS,EAAO,CACnC,GAAI,GAAW,CACb,KAAM,UAAW,CACf,GAAI,GAAQ,EAAM,MAAM,EACxB,MAAO,CAAE,KAAM,IAAU,OAAQ,MAAO,CAAM,CAChD,CACF,EAEA,MAAI,IACF,GAAS,OAAO,UAAY,UAAW,CACrC,MAAO,EACT,GAGK,CACT,EAMI,EAAiB,SAAS,EAAO,CACnC,MAAO,oBAAmB,CAAK,EAAE,QAAQ,OAAQ,GAAG,CACtD,EAEI,EAAmB,SAAS,EAAO,CACrC,MAAO,oBAAmB,OAAO,CAAK,EAAE,QAAQ,MAAO,GAAG,CAAC,CAC7D,EAEI,EAA0B,UAAW,CAEvC,GAAI,GAAkB,SAAS,EAAc,CAC3C,OAAO,eAAe,KAAM,WAAY,CAAE,SAAU,GAAM,MAAO,CAAC,CAAE,CAAC,EACrE,GAAI,GAAqB,MAAO,GAEhC,GAAI,IAAuB,YAEpB,GAAI,IAAuB,SAChC,AAAI,IAAiB,IACnB,KAAK,YAAY,CAAY,UAEtB,YAAwB,GAAiB,CAClD,GAAI,GAAQ,KACZ,EAAa,QAAQ,SAAS,EAAO,EAAM,CACzC,EAAM,OAAO,EAAM,CAAK,CAC1B,CAAC,CACH,SAAY,IAAiB,MAAU,IAAuB,SAC5D,GAAI,OAAO,UAAU,SAAS,KAAK,CAAY,IAAM,iBACnD,OAAS,GAAI,EAAG,EAAI,EAAa,OAAQ,IAAK,CAC5C,GAAI,GAAQ,EAAa,GACzB,GAAK,OAAO,UAAU,SAAS,KAAK,CAAK,IAAM,kBAAsB,EAAM,SAAW,EACpF,KAAK,OAAO,EAAM,GAAI,EAAM,EAAE,MAE9B,MAAM,IAAI,WAAU,4CAA8C,EAAI,6BAA8B,CAExG,KAEA,QAAS,KAAO,GACd,AAAI,EAAa,eAAe,CAAG,GACjC,KAAK,OAAO,EAAK,EAAa,EAAI,MAKxC,MAAM,IAAI,WAAU,8CAA+C,CAEvE,EAEI,EAAQ,EAAgB,UAE5B,EAAM,OAAS,SAAS,EAAM,EAAO,CACnC,AAAI,IAAQ,MAAK,SACf,KAAK,SAAS,GAAM,KAAK,OAAO,CAAK,CAAC,EAEtC,KAAK,SAAS,GAAQ,CAAC,OAAO,CAAK,CAAC,CAExC,EAEA,EAAM,OAAS,SAAS,EAAM,CAC5B,MAAO,MAAK,SAAS,EACvB,EAEA,EAAM,IAAM,SAAS,EAAM,CACzB,MAAQ,KAAQ,MAAK,SAAY,KAAK,SAAS,GAAM,GAAK,IAC5D,EAEA,EAAM,OAAS,SAAS,EAAM,CAC5B,MAAQ,KAAQ,MAAK,SAAY,KAAK,SAAS,GAAM,MAAM,CAAC,EAAI,CAAC,CACnE,EAEA,EAAM,IAAM,SAAS,EAAM,CACzB,MAAQ,KAAQ,MAAK,QACvB,EAEA,EAAM,IAAM,SAAS,EAAM,EAAO,CAChC,KAAK,SAAS,GAAQ,CAAC,OAAO,CAAK,CAAC,CACtC,EAEA,EAAM,QAAU,SAAS,EAAU,EAAS,CAC1C,GAAI,GACJ,OAAS,KAAQ,MAAK,SACpB,GAAI,KAAK,SAAS,eAAe,CAAI,EAAG,CACtC,EAAU,KAAK,SAAS,GACxB,OAAS,GAAI,EAAG,EAAI,EAAQ,OAAQ,IAClC,EAAS,KAAK,EAAS,EAAQ,GAAI,EAAM,IAAI,CAEjD,CAEJ,EAEA,EAAM,KAAO,UAAW,CACtB,GAAI,GAAQ,CAAC,EACb,YAAK,QAAQ,SAAS,EAAO,EAAM,CACjC,EAAM,KAAK,CAAI,CACjB,CAAC,EACM,EAAe,CAAK,CAC7B,EAEA,EAAM,OAAS,UAAW,CACxB,GAAI,GAAQ,CAAC,EACb,YAAK,QAAQ,SAAS,EAAO,CAC3B,EAAM,KAAK,CAAK,CAClB,CAAC,EACM,EAAe,CAAK,CAC7B,EAEA,EAAM,QAAU,UAAW,CACzB,GAAI,GAAQ,CAAC,EACb,YAAK,QAAQ,SAAS,EAAO,EAAM,CACjC,EAAM,KAAK,CAAC,EAAM,CAAK,CAAC,CAC1B,CAAC,EACM,EAAe,CAAK,CAC7B,EAEI,GACF,GAAM,OAAO,UAAY,EAAM,SAGjC,EAAM,SAAW,UAAW,CAC1B,GAAI,GAAc,CAAC,EACnB,YAAK,QAAQ,SAAS,EAAO,EAAM,CACjC,EAAY,KAAK,EAAe,CAAI,EAAI,IAAM,EAAe,CAAK,CAAC,CACrE,CAAC,EACM,EAAY,KAAK,GAAG,CAC7B,EAGA,EAAO,gBAAkB,CAC3B,EAEI,EAAkC,UAAW,CAC/C,GAAI,CACF,GAAI,GAAkB,EAAO,gBAE7B,MACG,IAAI,GAAgB,MAAM,EAAE,SAAS,IAAM,OAC3C,MAAO,GAAgB,UAAU,KAAQ,YACzC,MAAO,GAAgB,UAAU,SAAY,UAElD,OAAS,EAAP,CACA,MAAO,EACT,CACF,EAEA,AAAK,EAAgC,GACnC,EAAwB,EAG1B,GAAI,GAAQ,EAAO,gBAAgB,UAEnC,AAAI,MAAO,GAAM,MAAS,YACxB,GAAM,KAAO,UAAW,CACtB,GAAI,GAAQ,KACR,EAAQ,CAAC,EACb,KAAK,QAAQ,SAAS,EAAO,EAAM,CACjC,EAAM,KAAK,CAAC,EAAM,CAAK,CAAC,EACnB,EAAM,UACT,EAAM,OAAO,CAAI,CAErB,CAAC,EACD,EAAM,KAAK,SAAS,EAAG,EAAG,CACxB,MAAI,GAAE,GAAK,EAAE,GACJ,GACE,EAAE,GAAK,EAAE,GACX,EAEA,CAEX,CAAC,EACG,EAAM,UACR,GAAM,SAAW,CAAC,GAEpB,OAAS,GAAI,EAAG,EAAI,EAAM,OAAQ,IAChC,KAAK,OAAO,EAAM,GAAG,GAAI,EAAM,GAAG,EAAE,CAExC,GAGE,MAAO,GAAM,aAAgB,YAC/B,OAAO,eAAe,EAAO,cAAe,CAC1C,WAAY,GACZ,aAAc,GACd,SAAU,GACV,MAAO,SAAS,EAAc,CAC5B,GAAI,KAAK,SACP,KAAK,SAAW,CAAC,MACZ,CACL,GAAI,GAAO,CAAC,EACZ,KAAK,QAAQ,SAAS,EAAO,EAAM,CACjC,EAAK,KAAK,CAAI,CAChB,CAAC,EACD,OAAS,GAAI,EAAG,EAAI,EAAK,OAAQ,IAC/B,KAAK,OAAO,EAAK,EAAE,CAEvB,CAEA,EAAe,EAAa,QAAQ,MAAO,EAAE,EAG7C,OAFI,GAAa,EAAa,MAAM,GAAG,EACnC,EACK,EAAI,EAAG,EAAI,EAAW,OAAQ,IACrC,EAAY,EAAW,GAAG,MAAM,GAAG,EACnC,KAAK,OACH,EAAiB,EAAU,EAAE,EAC5B,EAAU,OAAS,EAAK,EAAiB,EAAU,EAAE,EAAI,EAC5D,CAEJ,CACF,CAAC,CAKL,GACG,MAAO,SAAW,YAAe,OAC5B,MAAO,SAAW,YAAe,OACjC,MAAO,OAAS,YAAe,KAAO,EAC9C,EAEA,AAAC,UAAS,EAAQ,CAOhB,GAAI,GAAwB,UAAW,CACrC,GAAI,CACF,GAAI,GAAI,GAAI,GAAO,IAAI,IAAK,UAAU,EACtC,SAAE,SAAW,MACL,EAAE,OAAS,kBAAqB,EAAE,YAC5C,OAAS,EAAP,CACA,MAAO,EACT,CACF,EAGI,EAAc,UAAW,CAC3B,GAAI,GAAO,EAAO,IAEd,EAAM,SAAS,EAAK,EAAM,CAC5B,AAAI,MAAO,IAAQ,UAAU,GAAM,OAAO,CAAG,GACzC,GAAQ,MAAO,IAAS,UAAU,GAAO,OAAO,CAAI,GAGxD,GAAI,GAAM,SAAU,EACpB,GAAI,GAAS,GAAO,WAAa,QAAU,IAAS,EAAO,SAAS,MAAO,CACzE,EAAO,EAAK,YAAY,EACxB,EAAM,SAAS,eAAe,mBAAmB,EAAE,EACnD,EAAc,EAAI,cAAc,MAAM,EACtC,EAAY,KAAO,EACnB,EAAI,KAAK,YAAY,CAAW,EAChC,GAAI,CACF,GAAI,EAAY,KAAK,QAAQ,CAAI,IAAM,EAAG,KAAM,IAAI,OAAM,EAAY,IAAI,CAC5E,OAAS,EAAP,CACA,KAAM,IAAI,OAAM,0BAA4B,EAAO,WAAa,CAAG,CACrE,CACF,CAEA,GAAI,GAAgB,EAAI,cAAc,GAAG,EACzC,EAAc,KAAO,EACjB,GACF,GAAI,KAAK,YAAY,CAAa,EAClC,EAAc,KAAO,EAAc,MAGrC,GAAI,GAAe,EAAI,cAAc,OAAO,EAI5C,GAHA,EAAa,KAAO,MACpB,EAAa,MAAQ,EAEjB,EAAc,WAAa,KAAO,CAAC,IAAI,KAAK,EAAc,IAAI,GAAM,CAAC,EAAa,cAAc,GAAK,CAAC,EACxG,KAAM,IAAI,WAAU,aAAa,EAGnC,OAAO,eAAe,KAAM,iBAAkB,CAC5C,MAAO,CACT,CAAC,EAID,GAAI,GAAe,GAAI,GAAO,gBAAgB,KAAK,MAAM,EACrD,EAAqB,GACrB,EAA2B,GAC3B,EAAQ,KACZ,CAAC,SAAU,SAAU,KAAK,EAAE,QAAQ,SAAS,EAAY,CACvD,GAAI,IAAS,EAAa,GAC1B,EAAa,GAAc,UAAW,CACpC,GAAO,MAAM,EAAc,SAAS,EAChC,GACF,GAA2B,GAC3B,EAAM,OAAS,EAAa,SAAS,EACrC,EAA2B,GAE/B,CACF,CAAC,EAED,OAAO,eAAe,KAAM,eAAgB,CAC1C,MAAO,EACP,WAAY,EACd,CAAC,EAED,GAAI,GAAS,OACb,OAAO,eAAe,KAAM,sBAAuB,CACjD,WAAY,GACZ,aAAc,GACd,SAAU,GACV,MAAO,UAAW,CAChB,AAAI,KAAK,SAAW,GAClB,GAAS,KAAK,OACV,GACF,GAAqB,GACrB,KAAK,aAAa,YAAY,KAAK,MAAM,EACzC,EAAqB,IAG3B,CACF,CAAC,CACH,EAEI,EAAQ,EAAI,UAEZ,EAA6B,SAAS,EAAe,CACvD,OAAO,eAAe,EAAO,EAAe,CAC1C,IAAK,UAAW,CACd,MAAO,MAAK,eAAe,EAC7B,EACA,IAAK,SAAS,EAAO,CACnB,KAAK,eAAe,GAAiB,CACvC,EACA,WAAY,EACd,CAAC,CACH,EAEA,CAAC,OAAQ,OAAQ,WAAY,OAAQ,UAAU,EAC5C,QAAQ,SAAS,EAAe,CAC/B,EAA2B,CAAa,CAC1C,CAAC,EAEH,OAAO,eAAe,EAAO,SAAU,CACrC,IAAK,UAAW,CACd,MAAO,MAAK,eAAe,MAC7B,EACA,IAAK,SAAS,EAAO,CACnB,KAAK,eAAe,OAAY,EAChC,KAAK,oBAAoB,CAC3B,EACA,WAAY,EACd,CAAC,EAED,OAAO,iBAAiB,EAAO,CAE7B,SAAY,CACV,IAAK,UAAW,CACd,GAAI,GAAQ,KACZ,MAAO,WAAW,CAChB,MAAO,GAAM,IACf,CACF,CACF,EAEA,KAAQ,CACN,IAAK,UAAW,CACd,MAAO,MAAK,eAAe,KAAK,QAAQ,MAAO,EAAE,CACnD,EACA,IAAK,SAAS,EAAO,CACnB,KAAK,eAAe,KAAO,EAC3B,KAAK,oBAAoB,CAC3B,EACA,WAAY,EACd,EAEA,SAAY,CACV,IAAK,UAAW,CACd,MAAO,MAAK,eAAe,SAAS,QAAQ,SAAU,GAAG,CAC3D,EACA,IAAK,SAAS,EAAO,CACnB,KAAK,eAAe,SAAW,CACjC,EACA,WAAY,EACd,EAEA,OAAU,CACR,IAAK,UAAW,CAEd,GAAI,GAAe,CAAE,QAAS,GAAI,SAAU,IAAK,OAAQ,EAAG,EAAE,KAAK,eAAe,UAI9E,EAAkB,KAAK,eAAe,MAAQ,GAChD,KAAK,eAAe,OAAS,GAE/B,MAAO,MAAK,eAAe,SACzB,KACA,KAAK,eAAe,SACnB,GAAmB,IAAM,KAAK,eAAe,KAAQ,GAC1D,EACA,WAAY,EACd,EAEA,SAAY,CACV,IAAK,UAAW,CACd,MAAO,EACT,EACA,IAAK,SAAS,EAAO,CACrB,EACA,WAAY,EACd,EAEA,SAAY,CACV,IAAK,UAAW,CACd,MAAO,EACT,EACA,IAAK,SAAS,EAAO,CACrB,EACA,WAAY,EACd,CACF,CAAC,EAED,EAAI,gBAAkB,SAAS,EAAM,CACnC,MAAO,GAAK,gBAAgB,MAAM,EAAM,SAAS,CACnD,EAEA,EAAI,gBAAkB,SAAS,EAAK,CAClC,MAAO,GAAK,gBAAgB,MAAM,EAAM,SAAS,CACnD,EAEA,EAAO,IAAM,CAEf,EAMA,GAJK,EAAsB,GACzB,EAAY,EAGT,EAAO,WAAa,QAAW,CAAE,WAAY,GAAO,UAAW,CAClE,GAAI,GAAY,UAAW,CACzB,MAAO,GAAO,SAAS,SAAW,KAAO,EAAO,SAAS,SAAY,GAAO,SAAS,KAAQ,IAAM,EAAO,SAAS,KAAQ,GAC7H,EAEA,GAAI,CACF,OAAO,eAAe,EAAO,SAAU,SAAU,CAC/C,IAAK,EACL,WAAY,EACd,CAAC,CACH,OAAS,EAAP,CACA,YAAY,UAAW,CACrB,EAAO,SAAS,OAAS,EAAU,CACrC,EAAG,GAAG,CACR,CACF,CAEF,GACG,MAAO,SAAW,YAAe,OAC5B,MAAO,SAAW,YAAe,OACjC,MAAO,OAAS,YAAe,KAAO,EAC9C,IC5eA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gFAeA,GAAI,IACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACJ,AAAC,UAAU,EAAS,CAChB,GAAI,GAAO,MAAO,SAAW,SAAW,OAAS,MAAO,OAAS,SAAW,KAAO,MAAO,OAAS,SAAW,KAAO,CAAC,EACtH,AAAI,MAAO,SAAW,YAAc,OAAO,IACvC,OAAO,QAAS,CAAC,SAAS,EAAG,SAAU,EAAS,CAAE,EAAQ,EAAe,EAAM,EAAe,CAAO,CAAC,CAAC,CAAG,CAAC,EAE1G,AAAI,MAAO,KAAW,UAAY,MAAO,IAAO,SAAY,SAC7D,EAAQ,EAAe,EAAM,EAAe,GAAO,OAAO,CAAC,CAAC,EAG5D,EAAQ,EAAe,CAAI,CAAC,EAEhC,WAAwB,EAAS,EAAU,CACvC,MAAI,KAAY,GACZ,CAAI,MAAO,QAAO,QAAW,WACzB,OAAO,eAAe,EAAS,aAAc,CAAE,MAAO,EAAK,CAAC,EAG5D,EAAQ,WAAa,IAGtB,SAAU,EAAI,EAAG,CAAE,MAAO,GAAQ,GAAM,EAAW,EAAS,EAAI,CAAC,EAAI,CAAG,CACnF,CACJ,GACC,SAAU,EAAU,CACjB,GAAI,GAAgB,OAAO,gBACtB,CAAE,UAAW,CAAC,CAAE,WAAa,QAAS,SAAU,EAAG,EAAG,CAAE,EAAE,UAAY,CAAG,GAC1E,SAAU,EAAG,EAAG,CAAE,OAAS,KAAK,GAAG,AAAI,OAAO,UAAU,eAAe,KAAK,EAAG,CAAC,GAAG,GAAE,GAAK,EAAE,GAAI,EAEpG,GAAY,SAAU,EAAG,EAAG,CACxB,GAAI,MAAO,IAAM,YAAc,IAAM,KACjC,KAAM,IAAI,WAAU,uBAAyB,OAAO,CAAC,EAAI,+BAA+B,EAC5F,EAAc,EAAG,CAAC,EAClB,YAAc,CAAE,KAAK,YAAc,CAAG,CACtC,EAAE,UAAY,IAAM,KAAO,OAAO,OAAO,CAAC,EAAK,GAAG,UAAY,EAAE,UAAW,GAAI,GACnF,EAEA,GAAW,OAAO,QAAU,SAAU,EAAG,CACrC,OAAS,GAAG,EAAI,EAAG,EAAI,UAAU,OAAQ,EAAI,EAAG,IAAK,CACjD,EAAI,UAAU,GACd,OAAS,KAAK,GAAG,AAAI,OAAO,UAAU,eAAe,KAAK,EAAG,CAAC,GAAG,GAAE,GAAK,EAAE,GAC9E,CACA,MAAO,EACX,EAEA,GAAS,SAAU,EAAG,EAAG,CACrB,GAAI,GAAI,CAAC,EACT,OAAS,KAAK,GAAG,AAAI,OAAO,UAAU,eAAe,KAAK,EAAG,CAAC,GAAK,EAAE,QAAQ,CAAC,EAAI,GAC9E,GAAE,GAAK,EAAE,IACb,GAAI,GAAK,MAAQ,MAAO,QAAO,uBAA0B,WACrD,OAAS,GAAI,EAAG,EAAI,OAAO,sBAAsB,CAAC,EAAG,EAAI,EAAE,OAAQ,IAC/D,AAAI,EAAE,QAAQ,EAAE,EAAE,EAAI,GAAK,OAAO,UAAU,qBAAqB,KAAK,EAAG,EAAE,EAAE,GACzE,GAAE,EAAE,IAAM,EAAE,EAAE,KAE1B,MAAO,EACX,EAEA,GAAa,SAAU,EAAY,EAAQ,EAAK,EAAM,CAClD,GAAI,GAAI,UAAU,OAAQ,EAAI,EAAI,EAAI,EAAS,IAAS,KAAO,EAAO,OAAO,yBAAyB,EAAQ,CAAG,EAAI,EAAM,EAC3H,GAAI,MAAO,UAAY,UAAY,MAAO,SAAQ,UAAa,WAAY,EAAI,QAAQ,SAAS,EAAY,EAAQ,EAAK,CAAI,MACxH,QAAS,GAAI,EAAW,OAAS,EAAG,GAAK,EAAG,IAAK,AAAI,GAAI,EAAW,KAAI,GAAK,GAAI,EAAI,EAAE,CAAC,EAAI,EAAI,EAAI,EAAE,EAAQ,EAAK,CAAC,EAAI,EAAE,EAAQ,CAAG,IAAM,GAChJ,MAAO,GAAI,GAAK,GAAK,OAAO,eAAe,EAAQ,EAAK,CAAC,EAAG,CAChE,EAEA,GAAU,SAAU,EAAY,EAAW,CACvC,MAAO,UAAU,EAAQ,EAAK,CAAE,EAAU,EAAQ,EAAK,CAAU,CAAG,CACxE,EAEA,GAAa,SAAU,EAAa,EAAe,CAC/C,GAAI,MAAO,UAAY,UAAY,MAAO,SAAQ,UAAa,WAAY,MAAO,SAAQ,SAAS,EAAa,CAAa,CACjI,EAEA,GAAY,SAAU,EAAS,EAAY,EAAG,EAAW,CACrD,WAAe,EAAO,CAAE,MAAO,aAAiB,GAAI,EAAQ,GAAI,GAAE,SAAU,EAAS,CAAE,EAAQ,CAAK,CAAG,CAAC,CAAG,CAC3G,MAAO,IAAK,IAAM,GAAI,UAAU,SAAU,EAAS,EAAQ,CACvD,WAAmB,EAAO,CAAE,GAAI,CAAE,EAAK,EAAU,KAAK,CAAK,CAAC,CAAG,OAAS,EAAP,CAAY,EAAO,CAAC,CAAG,CAAE,CAC1F,WAAkB,EAAO,CAAE,GAAI,CAAE,EAAK,EAAU,MAAS,CAAK,CAAC,CAAG,OAAS,EAAP,CAAY,EAAO,CAAC,CAAG,CAAE,CAC7F,WAAc,EAAQ,CAAE,EAAO,KAAO,EAAQ,EAAO,KAAK,EAAI,EAAM,EAAO,KAAK,EAAE,KAAK,EAAW,CAAQ,CAAG,CAC7G,EAAM,GAAY,EAAU,MAAM,EAAS,GAAc,CAAC,CAAC,GAAG,KAAK,CAAC,CACxE,CAAC,CACL,EAEA,GAAc,SAAU,EAAS,EAAM,CACnC,GAAI,GAAI,CAAE,MAAO,EAAG,KAAM,UAAW,CAAE,GAAI,EAAE,GAAK,EAAG,KAAM,GAAE,GAAI,MAAO,GAAE,EAAI,EAAG,KAAM,CAAC,EAAG,IAAK,CAAC,CAAE,EAAG,EAAG,EAAG,EAAG,EAC/G,MAAO,GAAI,CAAE,KAAM,EAAK,CAAC,EAAG,MAAS,EAAK,CAAC,EAAG,OAAU,EAAK,CAAC,CAAE,EAAG,MAAO,SAAW,YAAe,GAAE,OAAO,UAAY,UAAW,CAAE,MAAO,KAAM,GAAI,EACvJ,WAAc,EAAG,CAAE,MAAO,UAAU,EAAG,CAAE,MAAO,GAAK,CAAC,EAAG,CAAC,CAAC,CAAG,CAAG,CACjE,WAAc,EAAI,CACd,GAAI,EAAG,KAAM,IAAI,WAAU,iCAAiC,EAC5D,KAAO,GAAG,GAAI,CACV,GAAI,EAAI,EAAG,GAAM,GAAI,EAAG,GAAK,EAAI,EAAE,OAAY,EAAG,GAAK,EAAE,OAAc,IAAI,EAAE,SAAc,EAAE,KAAK,CAAC,EAAG,GAAK,EAAE,OAAS,CAAE,GAAI,EAAE,KAAK,EAAG,EAAG,EAAE,GAAG,KAAM,MAAO,GAE3J,OADI,EAAI,EAAG,GAAG,GAAK,CAAC,EAAG,GAAK,EAAG,EAAE,KAAK,GAC9B,EAAG,QACF,OAAQ,GAAG,EAAI,EAAI,UACnB,GAAG,SAAE,QAAgB,CAAE,MAAO,EAAG,GAAI,KAAM,EAAM,MACjD,GAAG,EAAE,QAAS,EAAI,EAAG,GAAI,EAAK,CAAC,CAAC,EAAG,aACnC,GAAG,EAAK,EAAE,IAAI,IAAI,EAAG,EAAE,KAAK,IAAI,EAAG,iBAEpC,GAAM,EAAI,EAAE,KAAM,IAAI,EAAE,OAAS,GAAK,EAAE,EAAE,OAAS,KAAQ,GAAG,KAAO,GAAK,EAAG,KAAO,GAAI,CAAE,EAAI,EAAG,QAAU,CAC3G,GAAI,EAAG,KAAO,GAAM,EAAC,GAAM,EAAG,GAAK,EAAE,IAAM,EAAG,GAAK,EAAE,IAAM,CAAE,EAAE,MAAQ,EAAG,GAAI,KAAO,CACrF,GAAI,EAAG,KAAO,GAAK,EAAE,MAAQ,EAAE,GAAI,CAAE,EAAE,MAAQ,EAAE,GAAI,EAAI,EAAI,KAAO,CACpE,GAAI,GAAK,EAAE,MAAQ,EAAE,GAAI,CAAE,EAAE,MAAQ,EAAE,GAAI,EAAE,IAAI,KAAK,CAAE,EAAG,KAAO,CAClE,AAAI,EAAE,IAAI,EAAE,IAAI,IAAI,EACpB,EAAE,KAAK,IAAI,EAAG,SAEtB,EAAK,EAAK,KAAK,EAAS,CAAC,CAC7B,OAAS,EAAP,CAAY,EAAK,CAAC,EAAG,CAAC,EAAG,EAAI,CAAG,QAAE,CAAU,EAAI,EAAI,CAAG,CACzD,GAAI,EAAG,GAAK,EAAG,KAAM,GAAG,GAAI,MAAO,CAAE,MAAO,EAAG,GAAK,EAAG,GAAK,OAAQ,KAAM,EAAK,CACnF,CACJ,EAEA,GAAe,SAAS,EAAG,EAAG,CAC1B,OAAS,KAAK,GAAG,AAAI,IAAM,WAAa,CAAC,OAAO,UAAU,eAAe,KAAK,EAAG,CAAC,GAAG,GAAgB,EAAG,EAAG,CAAC,CAChH,EAEA,GAAkB,OAAO,OAAU,SAAS,EAAG,EAAG,EAAG,EAAI,CACrD,AAAI,IAAO,QAAW,GAAK,GAC3B,OAAO,eAAe,EAAG,EAAI,CAAE,WAAY,GAAM,IAAK,UAAW,CAAE,MAAO,GAAE,EAAI,CAAE,CAAC,CACvF,EAAM,SAAS,EAAG,EAAG,EAAG,EAAI,CACxB,AAAI,IAAO,QAAW,GAAK,GAC3B,EAAE,GAAM,EAAE,EACd,EAEA,GAAW,SAAU,EAAG,CACpB,GAAI,GAAI,MAAO,SAAW,YAAc,OAAO,SAAU,EAAI,GAAK,EAAE,GAAI,EAAI,EAC5E,GAAI,EAAG,MAAO,GAAE,KAAK,CAAC,EACtB,GAAI,GAAK,MAAO,GAAE,QAAW,SAAU,MAAO,CAC1C,KAAM,UAAY,CACd,MAAI,IAAK,GAAK,EAAE,QAAQ,GAAI,QACrB,CAAE,MAAO,GAAK,EAAE,KAAM,KAAM,CAAC,CAAE,CAC1C,CACJ,EACA,KAAM,IAAI,WAAU,EAAI,0BAA4B,iCAAiC,CACzF,EAEA,GAAS,SAAU,EAAG,EAAG,CACrB,GAAI,GAAI,MAAO,SAAW,YAAc,EAAE,OAAO,UACjD,GAAI,CAAC,EAAG,MAAO,GACf,GAAI,GAAI,EAAE,KAAK,CAAC,EAAG,EAAG,EAAK,CAAC,EAAG,EAC/B,GAAI,CACA,KAAQ,KAAM,QAAU,KAAM,IAAM,CAAE,GAAI,EAAE,KAAK,GAAG,MAAM,EAAG,KAAK,EAAE,KAAK,CAC7E,OACO,EAAP,CAAgB,EAAI,CAAE,MAAO,CAAM,CAAG,QACtC,CACI,GAAI,CACA,AAAI,GAAK,CAAC,EAAE,MAAS,GAAI,EAAE,SAAY,EAAE,KAAK,CAAC,CACnD,QACA,CAAU,GAAI,EAAG,KAAM,GAAE,KAAO,CACpC,CACA,MAAO,EACX,EAGA,GAAW,UAAY,CACnB,OAAS,GAAK,CAAC,EAAG,EAAI,EAAG,EAAI,UAAU,OAAQ,IAC3C,EAAK,EAAG,OAAO,GAAO,UAAU,EAAE,CAAC,EACvC,MAAO,EACX,EAGA,GAAiB,UAAY,CACzB,OAAS,GAAI,EAAG,EAAI,EAAG,EAAK,UAAU,OAAQ,EAAI,EAAI,IAAK,GAAK,UAAU,GAAG,OAC7E,OAAS,GAAI,MAAM,CAAC,EAAG,EAAI,EAAG,EAAI,EAAG,EAAI,EAAI,IACzC,OAAS,GAAI,UAAU,GAAI,EAAI,EAAG,EAAK,EAAE,OAAQ,EAAI,EAAI,IAAK,IAC1D,EAAE,GAAK,EAAE,GACjB,MAAO,EACX,EAEA,GAAgB,SAAU,EAAI,EAAM,EAAM,CACtC,GAAI,GAAQ,UAAU,SAAW,EAAG,OAAS,GAAI,EAAG,EAAI,EAAK,OAAQ,EAAI,EAAI,EAAG,IAC5E,AAAI,IAAM,CAAE,KAAK,MACR,IAAI,GAAK,MAAM,UAAU,MAAM,KAAK,EAAM,EAAG,CAAC,GACnD,EAAG,GAAK,EAAK,IAGrB,MAAO,GAAG,OAAO,GAAM,MAAM,UAAU,MAAM,KAAK,CAAI,CAAC,CAC3D,EAEA,GAAU,SAAU,EAAG,CACnB,MAAO,gBAAgB,IAAW,MAAK,EAAI,EAAG,MAAQ,GAAI,IAAQ,CAAC,CACvE,EAEA,GAAmB,SAAU,EAAS,EAAY,EAAW,CACzD,GAAI,CAAC,OAAO,cAAe,KAAM,IAAI,WAAU,sCAAsC,EACrF,GAAI,GAAI,EAAU,MAAM,EAAS,GAAc,CAAC,CAAC,EAAG,EAAG,EAAI,CAAC,EAC5D,MAAO,GAAI,CAAC,EAAG,EAAK,MAAM,EAAG,EAAK,OAAO,EAAG,EAAK,QAAQ,EAAG,EAAE,OAAO,eAAiB,UAAY,CAAE,MAAO,KAAM,EAAG,EACpH,WAAc,EAAG,CAAE,AAAI,EAAE,IAAI,GAAE,GAAK,SAAU,EAAG,CAAE,MAAO,IAAI,SAAQ,SAAU,EAAG,EAAG,CAAE,EAAE,KAAK,CAAC,EAAG,EAAG,EAAG,CAAC,CAAC,EAAI,GAAK,EAAO,EAAG,CAAC,CAAG,CAAC,CAAG,EAAG,CACzI,WAAgB,EAAG,EAAG,CAAE,GAAI,CAAE,EAAK,EAAE,GAAG,CAAC,CAAC,CAAG,OAAS,EAAP,CAAY,EAAO,EAAE,GAAG,GAAI,CAAC,CAAG,CAAE,CACjF,WAAc,EAAG,CAAE,EAAE,gBAAiB,IAAU,QAAQ,QAAQ,EAAE,MAAM,CAAC,EAAE,KAAK,EAAS,CAAM,EAAI,EAAO,EAAE,GAAG,GAAI,CAAC,CAAI,CACxH,WAAiB,EAAO,CAAE,EAAO,OAAQ,CAAK,CAAG,CACjD,WAAgB,EAAO,CAAE,EAAO,QAAS,CAAK,CAAG,CACjD,WAAgB,EAAG,EAAG,CAAE,AAAI,EAAE,CAAC,EAAG,EAAE,MAAM,EAAG,EAAE,QAAQ,EAAO,EAAE,GAAG,GAAI,EAAE,GAAG,EAAE,CAAG,CACrF,EAEA,GAAmB,SAAU,EAAG,CAC5B,GAAI,GAAG,EACP,MAAO,GAAI,CAAC,EAAG,EAAK,MAAM,EAAG,EAAK,QAAS,SAAU,EAAG,CAAE,KAAM,EAAG,CAAC,EAAG,EAAK,QAAQ,EAAG,EAAE,OAAO,UAAY,UAAY,CAAE,MAAO,KAAM,EAAG,EAC1I,WAAc,EAAG,EAAG,CAAE,EAAE,GAAK,EAAE,GAAK,SAAU,EAAG,CAAE,MAAQ,GAAI,CAAC,GAAK,CAAE,MAAO,GAAQ,EAAE,GAAG,CAAC,CAAC,EAAG,KAAM,IAAM,QAAS,EAAI,EAAI,EAAE,CAAC,EAAI,CAAG,EAAI,CAAG,CAClJ,EAEA,GAAgB,SAAU,EAAG,CACzB,GAAI,CAAC,OAAO,cAAe,KAAM,IAAI,WAAU,sCAAsC,EACrF,GAAI,GAAI,EAAE,OAAO,eAAgB,EACjC,MAAO,GAAI,EAAE,KAAK,CAAC,EAAK,GAAI,MAAO,KAAa,WAAa,GAAS,CAAC,EAAI,EAAE,OAAO,UAAU,EAAG,EAAI,CAAC,EAAG,EAAK,MAAM,EAAG,EAAK,OAAO,EAAG,EAAK,QAAQ,EAAG,EAAE,OAAO,eAAiB,UAAY,CAAE,MAAO,KAAM,EAAG,GAC9M,WAAc,EAAG,CAAE,EAAE,GAAK,EAAE,IAAM,SAAU,EAAG,CAAE,MAAO,IAAI,SAAQ,SAAU,EAAS,EAAQ,CAAE,EAAI,EAAE,GAAG,CAAC,EAAG,EAAO,EAAS,EAAQ,EAAE,KAAM,EAAE,KAAK,CAAG,CAAC,CAAG,CAAG,CAC/J,WAAgB,EAAS,EAAQ,EAAG,EAAG,CAAE,QAAQ,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAG,CAAE,EAAQ,CAAE,MAAO,EAAG,KAAM,CAAE,CAAC,CAAG,EAAG,CAAM,CAAG,CAC/H,EAEA,GAAuB,SAAU,EAAQ,EAAK,CAC1C,MAAI,QAAO,eAAkB,OAAO,eAAe,EAAQ,MAAO,CAAE,MAAO,CAAI,CAAC,EAAY,EAAO,IAAM,EAClG,CACX,EAEA,GAAI,GAAqB,OAAO,OAAU,SAAS,EAAG,EAAG,CACrD,OAAO,eAAe,EAAG,UAAW,CAAE,WAAY,GAAM,MAAO,CAAE,CAAC,CACtE,EAAK,SAAS,EAAG,EAAG,CAChB,EAAE,QAAa,CACnB,EAEA,GAAe,SAAU,EAAK,CAC1B,GAAI,GAAO,EAAI,WAAY,MAAO,GAClC,GAAI,GAAS,CAAC,EACd,GAAI,GAAO,KAAM,OAAS,KAAK,GAAK,AAAI,IAAM,WAAa,OAAO,UAAU,eAAe,KAAK,EAAK,CAAC,GAAG,GAAgB,EAAQ,EAAK,CAAC,EACvI,SAAmB,EAAQ,CAAG,EACvB,CACX,EAEA,GAAkB,SAAU,EAAK,CAC7B,MAAQ,IAAO,EAAI,WAAc,EAAM,CAAE,QAAW,CAAI,CAC5D,EAEA,GAAyB,SAAU,EAAU,EAAO,EAAM,EAAG,CACzD,GAAI,IAAS,KAAO,CAAC,EAAG,KAAM,IAAI,WAAU,+CAA+C,EAC3F,GAAI,MAAO,IAAU,WAAa,IAAa,GAAS,CAAC,EAAI,CAAC,EAAM,IAAI,CAAQ,EAAG,KAAM,IAAI,WAAU,0EAA0E,EACjL,MAAO,KAAS,IAAM,EAAI,IAAS,IAAM,EAAE,KAAK,CAAQ,EAAI,EAAI,EAAE,MAAQ,EAAM,IAAI,CAAQ,CAChG,EAEA,GAAyB,SAAU,EAAU,EAAO,EAAO,EAAM,EAAG,CAChE,GAAI,IAAS,IAAK,KAAM,IAAI,WAAU,gCAAgC,EACtE,GAAI,IAAS,KAAO,CAAC,EAAG,KAAM,IAAI,WAAU,+CAA+C,EAC3F,GAAI,MAAO,IAAU,WAAa,IAAa,GAAS,CAAC,EAAI,CAAC,EAAM,IAAI,CAAQ,EAAG,KAAM,IAAI,WAAU,yEAAyE,EAChL,MAAQ,KAAS,IAAM,EAAE,KAAK,EAAU,CAAK,EAAI,EAAI,EAAE,MAAQ,EAAQ,EAAM,IAAI,EAAU,CAAK,EAAI,CACxG,EAEA,EAAS,YAAa,EAAS,EAC/B,EAAS,WAAY,EAAQ,EAC7B,EAAS,SAAU,EAAM,EACzB,EAAS,aAAc,EAAU,EACjC,EAAS,UAAW,EAAO,EAC3B,EAAS,aAAc,EAAU,EACjC,EAAS,YAAa,EAAS,EAC/B,EAAS,cAAe,EAAW,EACnC,EAAS,eAAgB,EAAY,EACrC,EAAS,kBAAmB,EAAe,EAC3C,EAAS,WAAY,EAAQ,EAC7B,EAAS,SAAU,EAAM,EACzB,EAAS,WAAY,EAAQ,EAC7B,EAAS,iBAAkB,EAAc,EACzC,EAAS,gBAAiB,EAAa,EACvC,EAAS,UAAW,EAAO,EAC3B,EAAS,mBAAoB,EAAgB,EAC7C,EAAS,mBAAoB,EAAgB,EAC7C,EAAS,gBAAiB,EAAa,EACvC,EAAS,uBAAwB,EAAoB,EACrD,EAAS,eAAgB,EAAY,EACrC,EAAS,kBAAmB,EAAe,EAC3C,EAAS,yBAA0B,EAAsB,EACzD,EAAS,yBAA0B,EAAsB,CAC7D,CAAC,ICjTD;AAAA;AAAA;AAAA;AAAA;AAAA,GAMA,AAAC,UAA0C,EAAM,EAAS,CACzD,AAAG,MAAO,KAAY,UAAY,MAAO,KAAW,SACnD,GAAO,QAAU,EAAQ,EACrB,AAAG,MAAO,SAAW,YAAc,OAAO,IAC9C,OAAO,CAAC,EAAG,CAAO,EACd,AAAG,MAAO,KAAY,SAC1B,GAAQ,YAAiB,EAAQ,EAEjC,EAAK,YAAiB,EAAQ,CAChC,GAAG,GAAM,UAAW,CACpB,MAAiB,WAAW,CAClB,GAAI,GAAuB,CAE/B,IACC,SAAS,EAAyB,EAAqB,EAAqB,CAEnF,aAGA,EAAoB,EAAE,EAAqB,CACzC,QAAW,UAAW,CAAE,MAAqB,GAAW,CAC1D,CAAC,EAGD,GAAI,GAAe,EAAoB,GAAG,EACtC,EAAoC,EAAoB,EAAE,CAAY,EAEtE,EAAS,EAAoB,GAAG,EAChC,EAA8B,EAAoB,EAAE,CAAM,EAE1D,EAAa,EAAoB,GAAG,EACpC,EAA8B,EAAoB,EAAE,CAAU,EAOlE,WAAiB,EAAM,CACrB,GAAI,CACF,MAAO,UAAS,YAAY,CAAI,CAClC,OAAS,EAAP,CACA,MAAO,EACT,CACF,CAUA,GAAI,GAAqB,SAA4B,EAAQ,CAC3D,GAAI,GAAe,EAAe,EAAE,CAAM,EAC1C,SAAQ,KAAK,EACN,CACT,EAEiC,EAAe,EAOhD,WAA2B,EAAO,CAChC,GAAI,GAAQ,SAAS,gBAAgB,aAAa,KAAK,IAAM,MACzD,EAAc,SAAS,cAAc,UAAU,EAEnD,EAAY,MAAM,SAAW,OAE7B,EAAY,MAAM,OAAS,IAC3B,EAAY,MAAM,QAAU,IAC5B,EAAY,MAAM,OAAS,IAE3B,EAAY,MAAM,SAAW,WAC7B,EAAY,MAAM,EAAQ,QAAU,QAAU,UAE9C,GAAI,GAAY,OAAO,aAAe,SAAS,gBAAgB,UAC/D,SAAY,MAAM,IAAM,GAAG,OAAO,EAAW,IAAI,EACjD,EAAY,aAAa,WAAY,EAAE,EACvC,EAAY,MAAQ,EACb,CACT,CAYA,GAAI,GAAiB,SAAwB,EAAO,EAAS,CAC3D,GAAI,GAAc,EAAkB,CAAK,EACzC,EAAQ,UAAU,YAAY,CAAW,EACzC,GAAI,GAAe,EAAe,EAAE,CAAW,EAC/C,SAAQ,MAAM,EACd,EAAY,OAAO,EACZ,CACT,EASI,EAAsB,SAA6B,EAAQ,CAC7D,GAAI,GAAU,UAAU,OAAS,GAAK,UAAU,KAAO,OAAY,UAAU,GAAK,CAChF,UAAW,SAAS,IACtB,EACI,EAAe,GAEnB,MAAI,OAAO,IAAW,SACpB,EAAe,EAAe,EAAQ,CAAO,EACxC,AAAI,YAAkB,mBAAoB,CAAC,CAAC,OAAQ,SAAU,MAAO,MAAO,UAAU,EAAE,SAAS,GAAW,KAA4B,OAAS,EAAO,IAAI,EAEjK,EAAe,EAAe,EAAO,MAAO,CAAO,EAEnD,GAAe,EAAe,EAAE,CAAM,EACtC,EAAQ,MAAM,GAGT,CACT,EAEiC,EAAgB,EAEjD,WAAiB,EAAK,CAA6B,MAAI,OAAO,SAAW,YAAc,MAAO,QAAO,UAAa,SAAY,EAAU,SAAiB,EAAK,CAAE,MAAO,OAAO,EAAK,EAAY,EAAU,SAAiB,EAAK,CAAE,MAAO,IAAO,MAAO,SAAW,YAAc,EAAI,cAAgB,QAAU,IAAQ,OAAO,UAAY,SAAW,MAAO,EAAK,EAAY,EAAQ,CAAG,CAAG,CAUzX,GAAI,IAAyB,UAAkC,CAC7D,GAAI,GAAU,UAAU,OAAS,GAAK,UAAU,KAAO,OAAY,UAAU,GAAK,CAAC,EAE/E,EAAkB,EAAQ,OAC1B,EAAS,IAAoB,OAAS,OAAS,EAC/C,EAAY,EAAQ,UACpB,EAAS,EAAQ,OACjB,GAAO,EAAQ,KAEnB,GAAI,IAAW,QAAU,IAAW,MAClC,KAAM,IAAI,OAAM,oDAAoD,EAItE,GAAI,IAAW,OACb,GAAI,GAAU,EAAQ,CAAM,IAAM,UAAY,EAAO,WAAa,EAAG,CACnE,GAAI,IAAW,QAAU,EAAO,aAAa,UAAU,EACrD,KAAM,IAAI,OAAM,mFAAmF,EAGrG,GAAI,IAAW,OAAU,GAAO,aAAa,UAAU,GAAK,EAAO,aAAa,UAAU,GACxF,KAAM,IAAI,OAAM,uGAAwG,CAE5H,KACE,MAAM,IAAI,OAAM,6CAA6C,EAKjE,GAAI,GACF,MAAO,GAAa,GAAM,CACxB,UAAW,CACb,CAAC,EAIH,GAAI,EACF,MAAO,KAAW,MAAQ,EAAY,CAAM,EAAI,EAAa,EAAQ,CACnE,UAAW,CACb,CAAC,CAEL,EAEiC,GAAmB,GAEpD,YAA0B,EAAK,CAA6B,MAAI,OAAO,SAAW,YAAc,MAAO,QAAO,UAAa,SAAY,GAAmB,SAAiB,EAAK,CAAE,MAAO,OAAO,EAAK,EAAY,GAAmB,SAAiB,EAAK,CAAE,MAAO,IAAO,MAAO,SAAW,YAAc,EAAI,cAAgB,QAAU,IAAQ,OAAO,UAAY,SAAW,MAAO,EAAK,EAAY,GAAiB,CAAG,CAAG,CAE7Z,YAAyB,EAAU,EAAa,CAAE,GAAI,CAAE,aAAoB,IAAgB,KAAM,IAAI,WAAU,mCAAmC,CAAK,CAExJ,YAA2B,EAAQ,EAAO,CAAE,OAAS,GAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CAAE,GAAI,GAAa,EAAM,GAAI,EAAW,WAAa,EAAW,YAAc,GAAO,EAAW,aAAe,GAAU,SAAW,IAAY,GAAW,SAAW,IAAM,OAAO,eAAe,EAAQ,EAAW,IAAK,CAAU,CAAG,CAAE,CAE5T,YAAsB,EAAa,EAAY,EAAa,CAAE,MAAI,IAAY,GAAkB,EAAY,UAAW,CAAU,EAAO,GAAa,GAAkB,EAAa,CAAW,EAAU,CAAa,CAEtN,YAAmB,EAAU,EAAY,CAAE,GAAI,MAAO,IAAe,YAAc,IAAe,KAAQ,KAAM,IAAI,WAAU,oDAAoD,EAAK,EAAS,UAAY,OAAO,OAAO,GAAc,EAAW,UAAW,CAAE,YAAa,CAAE,MAAO,EAAU,SAAU,GAAM,aAAc,EAAK,CAAE,CAAC,EAAO,GAAY,GAAgB,EAAU,CAAU,CAAG,CAEhY,YAAyB,EAAG,EAAG,CAAE,UAAkB,OAAO,gBAAkB,SAAyB,EAAG,EAAG,CAAE,SAAE,UAAY,EAAU,CAAG,EAAU,GAAgB,EAAG,CAAC,CAAG,CAEzK,YAAsB,EAAS,CAAE,GAAI,GAA4B,GAA0B,EAAG,MAAO,WAAgC,CAAE,GAAI,GAAQ,GAAgB,CAAO,EAAG,EAAQ,GAAI,EAA2B,CAAE,GAAI,GAAY,GAAgB,IAAI,EAAE,YAAa,EAAS,QAAQ,UAAU,EAAO,UAAW,CAAS,CAAG,KAAS,GAAS,EAAM,MAAM,KAAM,SAAS,EAAK,MAAO,IAA2B,KAAM,CAAM,CAAG,CAAG,CAExa,YAAoC,EAAM,EAAM,CAAE,MAAI,IAAS,IAAiB,CAAI,IAAM,UAAY,MAAO,IAAS,YAAsB,EAAe,GAAuB,CAAI,CAAG,CAEzL,YAAgC,EAAM,CAAE,GAAI,IAAS,OAAU,KAAM,IAAI,gBAAe,2DAA2D,EAAK,MAAO,EAAM,CAErK,aAAqC,CAA0E,GAApE,MAAO,UAAY,aAAe,CAAC,QAAQ,WAA6B,QAAQ,UAAU,KAAM,MAAO,GAAO,GAAI,MAAO,QAAU,WAAY,MAAO,GAAM,GAAI,CAAE,YAAK,UAAU,SAAS,KAAK,QAAQ,UAAU,KAAM,CAAC,EAAG,UAAY,CAAC,CAAC,CAAC,EAAU,EAAM,OAAS,EAAP,CAAY,MAAO,EAAO,CAAE,CAEnU,YAAyB,EAAG,CAAE,UAAkB,OAAO,eAAiB,OAAO,eAAiB,SAAyB,EAAG,CAAE,MAAO,GAAE,WAAa,OAAO,eAAe,CAAC,CAAG,EAAU,GAAgB,CAAC,CAAG,CAa5M,YAA2B,EAAQ,EAAS,CAC1C,GAAI,GAAY,kBAAkB,OAAO,CAAM,EAE/C,GAAI,EAAC,EAAQ,aAAa,CAAS,EAInC,MAAO,GAAQ,aAAa,CAAS,CACvC,CAOA,GAAI,IAAyB,SAAU,EAAU,CAC/C,GAAU,EAAW,CAAQ,EAE7B,GAAI,GAAS,GAAa,CAAS,EAMnC,WAAmB,EAAS,EAAS,CACnC,GAAI,GAEJ,UAAgB,KAAM,CAAS,EAE/B,EAAQ,EAAO,KAAK,IAAI,EAExB,EAAM,eAAe,CAAO,EAE5B,EAAM,YAAY,CAAO,EAElB,CACT,CAQA,UAAa,EAAW,CAAC,CACvB,IAAK,iBACL,MAAO,UAA0B,CAC/B,GAAI,GAAU,UAAU,OAAS,GAAK,UAAU,KAAO,OAAY,UAAU,GAAK,CAAC,EACnF,KAAK,OAAS,MAAO,GAAQ,QAAW,WAAa,EAAQ,OAAS,KAAK,cAC3E,KAAK,OAAS,MAAO,GAAQ,QAAW,WAAa,EAAQ,OAAS,KAAK,cAC3E,KAAK,KAAO,MAAO,GAAQ,MAAS,WAAa,EAAQ,KAAO,KAAK,YACrE,KAAK,UAAY,GAAiB,EAAQ,SAAS,IAAM,SAAW,EAAQ,UAAY,SAAS,IACnG,CAMF,EAAG,CACD,IAAK,cACL,MAAO,SAAqB,EAAS,CACnC,GAAI,GAAS,KAEb,KAAK,SAAW,EAAe,EAAE,EAAS,QAAS,SAAU,GAAG,CAC9D,MAAO,GAAO,QAAQ,EAAC,CACzB,CAAC,CACH,CAMF,EAAG,CACD,IAAK,UACL,MAAO,SAAiB,EAAG,CACzB,GAAI,GAAU,EAAE,gBAAkB,EAAE,cAChC,GAAS,KAAK,OAAO,CAAO,GAAK,OACjC,GAAO,GAAgB,CACzB,OAAQ,GACR,UAAW,KAAK,UAChB,OAAQ,KAAK,OAAO,CAAO,EAC3B,KAAM,KAAK,KAAK,CAAO,CACzB,CAAC,EAED,KAAK,KAAK,GAAO,UAAY,QAAS,CACpC,OAAQ,GACR,KAAM,GACN,QAAS,EACT,eAAgB,UAA0B,CACxC,AAAI,GACF,EAAQ,MAAM,EAGhB,OAAO,aAAa,EAAE,gBAAgB,CACxC,CACF,CAAC,CACH,CAMF,EAAG,CACD,IAAK,gBACL,MAAO,SAAuB,EAAS,CACrC,MAAO,IAAkB,SAAU,CAAO,CAC5C,CAMF,EAAG,CACD,IAAK,gBACL,MAAO,SAAuB,EAAS,CACrC,GAAI,GAAW,GAAkB,SAAU,CAAO,EAElD,GAAI,EACF,MAAO,UAAS,cAAc,CAAQ,CAE1C,CAQF,EAAG,CACD,IAAK,cAML,MAAO,SAAqB,EAAS,CACnC,MAAO,IAAkB,OAAQ,CAAO,CAC1C,CAKF,EAAG,CACD,IAAK,UACL,MAAO,UAAmB,CACxB,KAAK,SAAS,QAAQ,CACxB,CACF,CAAC,EAAG,CAAC,CACH,IAAK,OACL,MAAO,SAAc,EAAQ,CAC3B,GAAI,GAAU,UAAU,OAAS,GAAK,UAAU,KAAO,OAAY,UAAU,GAAK,CAChF,UAAW,SAAS,IACtB,EACA,MAAO,GAAa,EAAQ,CAAO,CACrC,CAOF,EAAG,CACD,IAAK,MACL,MAAO,SAAa,EAAQ,CAC1B,MAAO,GAAY,CAAM,CAC3B,CAOF,EAAG,CACD,IAAK,cACL,MAAO,UAAuB,CAC5B,GAAI,GAAS,UAAU,OAAS,GAAK,UAAU,KAAO,OAAY,UAAU,GAAK,CAAC,OAAQ,KAAK,EAC3F,EAAU,MAAO,IAAW,SAAW,CAAC,CAAM,EAAI,EAClD,GAAU,CAAC,CAAC,SAAS,sBACzB,SAAQ,QAAQ,SAAU,GAAQ,CAChC,GAAU,IAAW,CAAC,CAAC,SAAS,sBAAsB,EAAM,CAC9D,CAAC,EACM,EACT,CACF,CAAC,CAAC,EAEK,CACT,EAAG,EAAqB,CAAE,EAEO,GAAa,EAExC,EAEA,IACC,SAAS,EAAQ,CAExB,GAAI,GAAqB,EAKzB,GAAI,MAAO,UAAY,aAAe,CAAC,QAAQ,UAAU,QAAS,CAC9D,GAAI,GAAQ,QAAQ,UAEpB,EAAM,QAAU,EAAM,iBACN,EAAM,oBACN,EAAM,mBACN,EAAM,kBACN,EAAM,qBAC1B,CASA,WAAkB,EAAS,EAAU,CACjC,KAAO,GAAW,EAAQ,WAAa,GAAoB,CACvD,GAAI,MAAO,GAAQ,SAAY,YAC3B,EAAQ,QAAQ,CAAQ,EAC1B,MAAO,GAET,EAAU,EAAQ,UACtB,CACJ,CAEA,EAAO,QAAU,CAGX,EAEA,IACC,SAAS,EAAQ,EAA0B,EAAqB,CAEvE,GAAI,GAAU,EAAoB,GAAG,EAYrC,WAAmB,EAAS,EAAU,EAAM,EAAU,EAAY,CAC9D,GAAI,GAAa,EAAS,MAAM,KAAM,SAAS,EAE/C,SAAQ,iBAAiB,EAAM,EAAY,CAAU,EAE9C,CACH,QAAS,UAAW,CAChB,EAAQ,oBAAoB,EAAM,EAAY,CAAU,CAC5D,CACJ,CACJ,CAYA,WAAkB,EAAU,EAAU,EAAM,EAAU,EAAY,CAE9D,MAAI,OAAO,GAAS,kBAAqB,WAC9B,EAAU,MAAM,KAAM,SAAS,EAItC,MAAO,IAAS,WAGT,EAAU,KAAK,KAAM,QAAQ,EAAE,MAAM,KAAM,SAAS,EAI3D,OAAO,IAAa,UACpB,GAAW,SAAS,iBAAiB,CAAQ,GAI1C,MAAM,UAAU,IAAI,KAAK,EAAU,SAAU,EAAS,CACzD,MAAO,GAAU,EAAS,EAAU,EAAM,EAAU,CAAU,CAClE,CAAC,EACL,CAWA,WAAkB,EAAS,EAAU,EAAM,EAAU,CACjD,MAAO,UAAS,EAAG,CACf,EAAE,eAAiB,EAAQ,EAAE,OAAQ,CAAQ,EAEzC,EAAE,gBACF,EAAS,KAAK,EAAS,CAAC,CAEhC,CACJ,CAEA,EAAO,QAAU,CAGX,EAEA,IACC,SAAS,EAAyB,EAAS,CAQlD,EAAQ,KAAO,SAAS,EAAO,CAC3B,MAAO,KAAU,QACV,YAAiB,cACjB,EAAM,WAAa,CAC9B,EAQA,EAAQ,SAAW,SAAS,EAAO,CAC/B,GAAI,GAAO,OAAO,UAAU,SAAS,KAAK,CAAK,EAE/C,MAAO,KAAU,QACT,KAAS,qBAAuB,IAAS,4BACzC,UAAY,IACZ,GAAM,SAAW,GAAK,EAAQ,KAAK,EAAM,EAAE,EACvD,EAQA,EAAQ,OAAS,SAAS,EAAO,CAC7B,MAAO,OAAO,IAAU,UACjB,YAAiB,OAC5B,EAQA,EAAQ,GAAK,SAAS,EAAO,CACzB,GAAI,GAAO,OAAO,UAAU,SAAS,KAAK,CAAK,EAE/C,MAAO,KAAS,mBACpB,CAGM,EAEA,IACC,SAAS,EAAQ,EAA0B,EAAqB,CAEvE,GAAI,GAAK,EAAoB,GAAG,EAC5B,EAAW,EAAoB,GAAG,EAWtC,WAAgB,EAAQ,EAAM,EAAU,CACpC,GAAI,CAAC,GAAU,CAAC,GAAQ,CAAC,EACrB,KAAM,IAAI,OAAM,4BAA4B,EAGhD,GAAI,CAAC,EAAG,OAAO,CAAI,EACf,KAAM,IAAI,WAAU,kCAAkC,EAG1D,GAAI,CAAC,EAAG,GAAG,CAAQ,EACf,KAAM,IAAI,WAAU,mCAAmC,EAG3D,GAAI,EAAG,KAAK,CAAM,EACd,MAAO,GAAW,EAAQ,EAAM,CAAQ,EAEvC,GAAI,EAAG,SAAS,CAAM,EACvB,MAAO,GAAe,EAAQ,EAAM,CAAQ,EAE3C,GAAI,EAAG,OAAO,CAAM,EACrB,MAAO,GAAe,EAAQ,EAAM,CAAQ,EAG5C,KAAM,IAAI,WAAU,2EAA2E,CAEvG,CAWA,WAAoB,EAAM,EAAM,EAAU,CACtC,SAAK,iBAAiB,EAAM,CAAQ,EAE7B,CACH,QAAS,UAAW,CAChB,EAAK,oBAAoB,EAAM,CAAQ,CAC3C,CACJ,CACJ,CAWA,WAAwB,EAAU,EAAM,EAAU,CAC9C,aAAM,UAAU,QAAQ,KAAK,EAAU,SAAS,EAAM,CAClD,EAAK,iBAAiB,EAAM,CAAQ,CACxC,CAAC,EAEM,CACH,QAAS,UAAW,CAChB,MAAM,UAAU,QAAQ,KAAK,EAAU,SAAS,EAAM,CAClD,EAAK,oBAAoB,EAAM,CAAQ,CAC3C,CAAC,CACL,CACJ,CACJ,CAWA,WAAwB,EAAU,EAAM,EAAU,CAC9C,MAAO,GAAS,SAAS,KAAM,EAAU,EAAM,CAAQ,CAC3D,CAEA,EAAO,QAAU,CAGX,EAEA,IACC,SAAS,EAAQ,CAExB,WAAgB,EAAS,CACrB,GAAI,GAEJ,GAAI,EAAQ,WAAa,SACrB,EAAQ,MAAM,EAEd,EAAe,EAAQ,cAElB,EAAQ,WAAa,SAAW,EAAQ,WAAa,WAAY,CACtE,GAAI,GAAa,EAAQ,aAAa,UAAU,EAEhD,AAAK,GACD,EAAQ,aAAa,WAAY,EAAE,EAGvC,EAAQ,OAAO,EACf,EAAQ,kBAAkB,EAAG,EAAQ,MAAM,MAAM,EAE5C,GACD,EAAQ,gBAAgB,UAAU,EAGtC,EAAe,EAAQ,KAC3B,KACK,CACD,AAAI,EAAQ,aAAa,iBAAiB,GACtC,EAAQ,MAAM,EAGlB,GAAI,GAAY,OAAO,aAAa,EAChC,EAAQ,SAAS,YAAY,EAEjC,EAAM,mBAAmB,CAAO,EAChC,EAAU,gBAAgB,EAC1B,EAAU,SAAS,CAAK,EAExB,EAAe,EAAU,SAAS,CACtC,CAEA,MAAO,EACX,CAEA,EAAO,QAAU,CAGX,EAEA,IACC,SAAS,EAAQ,CAExB,YAAc,CAGd,CAEA,EAAE,UAAY,CACZ,GAAI,SAAU,EAAM,EAAU,EAAK,CACjC,GAAI,GAAI,KAAK,GAAM,MAAK,EAAI,CAAC,GAE7B,MAAC,GAAE,IAAU,GAAE,GAAQ,CAAC,IAAI,KAAK,CAC/B,GAAI,EACJ,IAAK,CACP,CAAC,EAEM,IACT,EAEA,KAAM,SAAU,EAAM,EAAU,EAAK,CACnC,GAAI,GAAO,KACX,YAAqB,CACnB,EAAK,IAAI,EAAM,CAAQ,EACvB,EAAS,MAAM,EAAK,SAAS,CAC/B,CAEA,SAAS,EAAI,EACN,KAAK,GAAG,EAAM,EAAU,CAAG,CACpC,EAEA,KAAM,SAAU,EAAM,CACpB,GAAI,GAAO,CAAC,EAAE,MAAM,KAAK,UAAW,CAAC,EACjC,EAAW,OAAK,GAAM,MAAK,EAAI,CAAC,IAAI,IAAS,CAAC,GAAG,MAAM,EACvD,EAAI,EACJ,EAAM,EAAO,OAEjB,IAAK,EAAG,EAAI,EAAK,IACf,EAAO,GAAG,GAAG,MAAM,EAAO,GAAG,IAAK,CAAI,EAGxC,MAAO,KACT,EAEA,IAAK,SAAU,EAAM,EAAU,CAC7B,GAAI,GAAI,KAAK,GAAM,MAAK,EAAI,CAAC,GACzB,EAAO,EAAE,GACT,EAAa,CAAC,EAElB,GAAI,GAAQ,EACV,OAAS,GAAI,EAAG,EAAM,EAAK,OAAQ,EAAI,EAAK,IAC1C,AAAI,EAAK,GAAG,KAAO,GAAY,EAAK,GAAG,GAAG,IAAM,GAC9C,EAAW,KAAK,EAAK,EAAE,EAQ7B,MAAC,GAAW,OACR,EAAE,GAAQ,EACV,MAAO,GAAE,GAEN,IACT,CACF,EAEA,EAAO,QAAU,EACjB,EAAO,QAAQ,YAAc,CAGvB,CAEI,EAGI,EAA2B,CAAC,EAGhC,WAA6B,EAAU,CAEtC,GAAG,EAAyB,GAC3B,MAAO,GAAyB,GAAU,QAG3C,GAAI,GAAS,EAAyB,GAAY,CAGjD,QAAS,CAAC,CACX,EAGA,SAAoB,GAAU,EAAQ,EAAO,QAAS,CAAmB,EAGlE,EAAO,OACf,CAIA,MAAC,WAAW,CAEX,EAAoB,EAAI,SAAS,EAAQ,CACxC,GAAI,GAAS,GAAU,EAAO,WAC7B,UAAW,CAAE,MAAO,GAAO,OAAY,EACvC,UAAW,CAAE,MAAO,EAAQ,EAC7B,SAAoB,EAAE,EAAQ,CAAE,EAAG,CAAO,CAAC,EACpC,CACR,CACD,EAAE,EAGD,UAAW,CAEX,EAAoB,EAAI,SAAS,EAAS,EAAY,CACrD,OAAQ,KAAO,GACd,AAAG,EAAoB,EAAE,EAAY,CAAG,GAAK,CAAC,EAAoB,EAAE,EAAS,CAAG,GAC/E,OAAO,eAAe,EAAS,EAAK,CAAE,WAAY,GAAM,IAAK,EAAW,EAAK,CAAC,CAGjF,CACD,EAAE,EAGD,UAAW,CACX,EAAoB,EAAI,SAAS,EAAK,EAAM,CAAE,MAAO,QAAO,UAAU,eAAe,KAAK,EAAK,CAAI,CAAG,CACvG,EAAE,EAMK,EAAoB,GAAG,CAC/B,EAAG,EACX,OACD,CAAC,ICz3BD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAeA,GAAI,IAAkB,UAOtB,GAAO,QAAU,GAUjB,YAAoB,EAAQ,CAC1B,GAAI,GAAM,GAAK,EACX,EAAQ,GAAgB,KAAK,CAAG,EAEpC,GAAI,CAAC,EACH,MAAO,GAGT,GAAI,GACA,EAAO,GACP,EAAQ,EACR,EAAY,EAEhB,IAAK,EAAQ,EAAM,MAAO,EAAQ,EAAI,OAAQ,IAAS,CACrD,OAAQ,EAAI,WAAW,CAAK,OACrB,IACH,EAAS,SACT,UACG,IACH,EAAS,QACT,UACG,IACH,EAAS,QACT,UACG,IACH,EAAS,OACT,UACG,IACH,EAAS,OACT,cAEA,SAGJ,AAAI,IAAc,GAChB,IAAQ,EAAI,UAAU,EAAW,CAAK,GAGxC,EAAY,EAAQ,EACpB,GAAQ,CACV,CAEA,MAAO,KAAc,EACjB,EAAO,EAAI,UAAU,EAAW,CAAK,EACrC,CACN,IC7EA,MAAM,UAAU,MAAM,OAAO,eAAe,MAAM,UAAU,OAAO,CAAC,aAAa,GAAG,MAAM,YAAY,CAAC,GAAI,GAAE,MAAM,UAAU,EAAE,EAAE,EAAE,OAAO,UAAU,EAAE,EAAE,MAAO,GAAE,MAAM,UAAU,OAAO,KAAK,KAAK,SAAS,EAAE,EAAE,CAAC,MAAO,OAAM,QAAQ,CAAC,EAAE,EAAE,KAAK,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,UAAU,MAAM,KAAK,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,eAAe,MAAM,UAAU,UAAU,CAAC,aAAa,GAAG,MAAM,SAAS,EAAE,CAAC,MAAO,OAAM,UAAU,IAAI,MAAM,KAAK,SAAS,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,ECuBxf,OAAO,SCvBP,KAAK,OAAQ,MAAK,MAAM,SAAS,EAAE,EAAE,CAAC,MAAO,GAAE,GAAG,CAAC,EAAE,GAAI,SAAQ,SAAS,EAAE,EAAE,CAAC,GAAI,GAAE,GAAI,gBAAe,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,AAAI,GAAE,OAAO,IAAI,IAAjB,EAAoB,WAAW,EAAE,WAAW,OAAO,EAAE,OAAO,IAAI,EAAE,YAAY,KAAK,UAAU,CAAC,MAAO,SAAQ,QAAQ,EAAE,YAAY,CAAC,EAAE,KAAK,UAAU,CAAC,MAAO,SAAQ,QAAQ,EAAE,YAAY,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,KAAK,UAAU,CAAC,MAAO,SAAQ,QAAQ,GAAI,MAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,UAAU,CAAC,MAAO,EAAC,EAAE,QAAQ,UAAU,CAAC,MAAO,EAAC,EAAE,IAAI,SAAS,EAAE,CAAC,MAAO,GAAE,EAAE,YAAY,EAAE,EAAE,IAAI,SAAS,EAAE,CAAC,MAAO,GAAE,YAAY,GAAI,EAAC,CAAC,CAAC,CAAC,EAAE,OAAQ,KAAK,GAAE,KAAK,EAAE,QAAQ,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,UAAU,CAAC,EAAE,sBAAsB,EAAE,QAAQ,+BAA+B,SAAS,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,gBAAgB,AAAW,EAAE,aAAb,UAAyB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,GDyBj5B,OAAO,SEzBP,OAAkB,WACZ,CACF,aACA,YACA,UACA,cACA,WACA,cACA,aACA,eACA,gBACA,mBACA,YACA,SACA,YACA,kBACA,gBACA,WACA,oBACA,oBACA,iBACA,wBACA,gBACA,mBACA,0BACA,2BACA,WCtBE,WAAqB,EAAU,CACnC,MAAO,OAAO,IAAU,UAC1B,CCGM,YAA8B,EAAgC,CAClE,GAAM,GAAS,SAAC,EAAa,CAC3B,MAAM,KAAK,CAAQ,EACnB,EAAS,MAAQ,GAAI,OAAK,EAAG,KAC/B,EAEM,EAAW,EAAW,CAAM,EAClC,SAAS,UAAY,OAAO,OAAO,MAAM,SAAS,EAClD,EAAS,UAAU,YAAc,EAC1B,CACT,CCDO,GAAM,IAA+C,GAC1D,SAAC,EAAM,CACL,MAAA,UAA4C,EAA0B,CACpE,EAAO,IAAI,EACX,KAAK,QAAU,EACR,EAAO,OAAM;EACxB,EAAO,IAAI,SAAC,EAAK,EAAC,CAAK,MAAG,GAAI,EAAC,KAAK,EAAI,SAAQ,CAAzB,CAA6B,EAAE,KAAK;GAAM,EACzD,GACJ,KAAK,KAAO,sBACZ,KAAK,OAAS,CAChB,CARA,CAQC,ECvBC,YAAuB,EAA6B,EAAO,CAC/D,GAAI,EAAK,CACP,GAAM,GAAQ,EAAI,QAAQ,CAAI,EAC9B,GAAK,GAAS,EAAI,OAAO,EAAO,CAAC,EAErC,CCOA,GAAA,IAAA,UAAA,CAyBE,WAAoB,EAA4B,CAA5B,KAAA,gBAAA,EAdb,KAAA,OAAS,GAER,KAAA,WAAmD,KAMnD,KAAA,YAAqD,IAMV,CAQnD,SAAA,UAAA,YAAA,UAAA,aACM,EAEJ,GAAI,CAAC,KAAK,OAAQ,CAChB,KAAK,OAAS,GAGN,GAAA,GAAe,KAAI,WAC3B,GAAI,EAEF,GADA,KAAK,WAAa,KACd,MAAM,QAAQ,CAAU,MAC1B,OAAqB,GAAA,GAAA,CAAU,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAA5B,GAAM,GAAM,EAAA,MACf,EAAO,OAAO,IAAI,wGAGpB,GAAW,OAAO,IAAI,EAIlB,GAAiB,GAAqB,KAAI,gBAClD,GAAI,EAAW,CAAgB,EAC7B,GAAI,CACF,EAAgB,QACT,EAAP,CACA,EAAS,YAAa,IAAsB,EAAE,OAAS,CAAC,CAAC,EAIrD,GAAA,GAAgB,KAAI,YAC5B,GAAI,EAAa,CACf,KAAK,YAAc,SACnB,OAAwB,GAAA,GAAA,CAAW,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAAhC,GAAM,GAAS,EAAA,MAClB,GAAI,CACF,GAAc,CAAS,QAChB,EAAP,CACA,EAAS,GAAM,KAAN,EAAU,CAAA,EACnB,AAAI,YAAe,IACjB,EAAM,EAAA,EAAA,CAAA,EAAA,EAAO,CAAM,CAAA,EAAA,EAAK,EAAI,MAAM,CAAA,EAElC,EAAO,KAAK,CAAG,sGAMvB,GAAI,EACF,KAAM,IAAI,IAAoB,CAAM,EAG1C,EAoBA,EAAA,UAAA,IAAA,SAAI,EAAuB,OAGzB,GAAI,GAAY,IAAa,KAC3B,GAAI,KAAK,OAGP,GAAc,CAAQ,MACjB,CACL,GAAI,YAAoB,GAAc,CAGpC,GAAI,EAAS,QAAU,EAAS,WAAW,IAAI,EAC7C,OAEF,EAAS,WAAW,IAAI,EAE1B,AAAC,MAAK,YAAc,GAAA,KAAK,eAAW,MAAA,IAAA,OAAA,EAAI,CAAA,GAAI,KAAK,CAAQ,EAG/D,EAOQ,EAAA,UAAA,WAAR,SAAmB,EAAoB,CAC7B,GAAA,GAAe,KAAI,WAC3B,MAAO,KAAe,GAAW,MAAM,QAAQ,CAAU,GAAK,EAAW,SAAS,CAAM,CAC1F,EASQ,EAAA,UAAA,WAAR,SAAmB,EAAoB,CAC7B,GAAA,GAAe,KAAI,WAC3B,KAAK,WAAa,MAAM,QAAQ,CAAU,EAAK,GAAW,KAAK,CAAM,EAAG,GAAc,EAAa,CAAC,EAAY,CAAM,EAAI,CAC5H,EAMQ,EAAA,UAAA,cAAR,SAAsB,EAAoB,CAChC,GAAA,GAAe,KAAI,WAC3B,AAAI,IAAe,EACjB,KAAK,WAAa,KACT,MAAM,QAAQ,CAAU,GACjC,GAAU,EAAY,CAAM,CAEhC,EAgBA,EAAA,UAAA,OAAA,SAAO,EAAsC,CACnC,GAAA,GAAgB,KAAI,YAC5B,GAAe,GAAU,EAAa,CAAQ,EAE1C,YAAoB,IACtB,EAAS,cAAc,IAAI,CAE/B,EAlLc,EAAA,MAAS,UAAA,CACrB,GAAM,GAAQ,GAAI,GAClB,SAAM,OAAS,GACR,CACT,EAAE,EA+KJ,GArLA,EAuLO,GAAM,IAAqB,GAAa,MAEzC,YAAyB,EAAU,CACvC,MACE,aAAiB,KAChB,GAAS,UAAY,IAAS,EAAW,EAAM,MAAM,GAAK,EAAW,EAAM,GAAG,GAAK,EAAW,EAAM,WAAW,CAEpH,CAEA,YAAuB,EAAwC,CAC7D,AAAI,EAAW,CAAS,EACtB,EAAS,EAET,EAAU,YAAW,CAEzB,CChNO,GAAM,IAAuB,CAClC,iBAAkB,KAClB,sBAAuB,KACvB,QAAS,OACT,sCAAuC,GACvC,yBAA0B,ICErB,GAAM,IAAmC,CAG9C,WAAA,SAAW,EAAqB,EAAgB,QAAE,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,EAAA,GAAA,UAAA,GACzC,GAAA,GAAY,GAAe,SAClC,MAAI,IAAQ,MAAR,EAAU,WACL,EAAS,WAAU,MAAnB,EAAQ,EAAA,CAAY,EAAS,CAAO,EAAA,EAAK,CAAI,CAAA,CAAA,EAE/C,WAAU,MAAA,OAAA,EAAA,CAAC,EAAS,CAAO,EAAA,EAAK,CAAI,CAAA,CAAA,CAC7C,EACA,aAAY,SAAC,EAAM,CACT,GAAA,GAAa,GAAe,SACpC,MAAQ,KAAQ,KAAA,OAAR,EAAU,eAAgB,cAAc,CAAM,CACxD,EACA,SAAU,QChBN,YAA+B,EAAQ,CAC3C,GAAgB,WAAW,UAAA,CACjB,GAAA,GAAqB,GAAM,iBACnC,GAAI,EAEF,EAAiB,CAAG,MAGpB,MAAM,EAEV,CAAC,CACH,CCtBM,aAAc,CAAK,CCMlB,GAAM,IAAyB,UAAA,CAAM,MAAA,IAAmB,IAAK,OAAW,MAAS,CAA5C,EAAsE,EAO5G,YAA4B,EAAU,CAC1C,MAAO,IAAmB,IAAK,OAAW,CAAK,CACjD,CAOM,YAA8B,EAAQ,CAC1C,MAAO,IAAmB,IAAK,EAAO,MAAS,CACjD,CAQM,YAA6B,EAAuB,EAAY,EAAU,CAC9E,MAAO,CACL,KAAI,EACJ,MAAK,EACL,MAAK,EAET,CCrCA,GAAI,IAAuD,KASrD,YAAuB,EAAc,CACzC,GAAI,GAAO,sCAAuC,CAChD,GAAM,GAAS,CAAC,GAKhB,GAJI,GACF,IAAU,CAAE,YAAa,GAAO,MAAO,IAAI,GAE7C,EAAE,EACE,EAAQ,CACJ,GAAA,GAAyB,GAAvB,EAAW,EAAA,YAAE,EAAK,EAAA,MAE1B,GADA,GAAU,KACN,EACF,KAAM,QAMV,GAAE,CAEN,CAMM,YAAuB,EAAQ,CACnC,AAAI,GAAO,uCAAyC,IAClD,IAAQ,YAAc,GACtB,GAAQ,MAAQ,EAEpB,CCrBA,GAAA,IAAA,SAAA,EAAA,CAAmC,GAAA,EAAA,CAAA,EA6BjC,WAAY,EAA6C,CAAzD,GAAA,GACE,EAAA,KAAA,IAAA,GAAO,KATC,SAAA,UAAqB,GAU7B,AAAI,EACF,GAAK,YAAc,EAGf,GAAe,CAAW,GAC5B,EAAY,IAAI,CAAI,GAGtB,EAAK,YAAc,IAEvB,CAzBO,SAAA,OAAP,SAAiB,EAAwB,EAA2B,EAAqB,CACvF,MAAO,IAAI,IAAe,EAAM,EAAO,CAAQ,CACjD,EAgCA,EAAA,UAAA,KAAA,SAAK,EAAS,CACZ,AAAI,KAAK,UACP,GAA0B,GAAiB,CAAK,EAAG,IAAI,EAEvD,KAAK,MAAM,CAAM,CAErB,EASA,EAAA,UAAA,MAAA,SAAM,EAAS,CACb,AAAI,KAAK,UACP,GAA0B,GAAkB,CAAG,EAAG,IAAI,EAEtD,MAAK,UAAY,GACjB,KAAK,OAAO,CAAG,EAEnB,EAQA,EAAA,UAAA,SAAA,UAAA,CACE,AAAI,KAAK,UACP,GAA0B,GAAuB,IAAI,EAErD,MAAK,UAAY,GACjB,KAAK,UAAS,EAElB,EAEA,EAAA,UAAA,YAAA,UAAA,CACE,AAAK,KAAK,QACR,MAAK,UAAY,GACjB,EAAA,UAAM,YAAW,KAAA,IAAA,EACjB,KAAK,YAAc,KAEvB,EAEU,EAAA,UAAA,MAAV,SAAgB,EAAQ,CACtB,KAAK,YAAY,KAAK,CAAK,CAC7B,EAEU,EAAA,UAAA,OAAV,SAAiB,EAAQ,CACvB,GAAI,CACF,KAAK,YAAY,MAAM,CAAG,UAE1B,KAAK,YAAW,EAEpB,EAEU,EAAA,UAAA,UAAV,UAAA,CACE,GAAI,CACF,KAAK,YAAY,SAAQ,UAEzB,KAAK,YAAW,EAEpB,EACF,CAAA,EApHmC,EAAY,EA2H/C,GAAM,IAAQ,SAAS,UAAU,KAEjC,YAAkD,EAAQ,EAAY,CACpE,MAAO,IAAM,KAAK,EAAI,CAAO,CAC/B,CAMA,GAAA,IAAA,UAAA,CACE,WAAoB,EAAqC,CAArC,KAAA,gBAAA,CAAwC,CAE5D,SAAA,UAAA,KAAA,SAAK,EAAQ,CACH,GAAA,GAAoB,KAAI,gBAChC,GAAI,EAAgB,KAClB,GAAI,CACF,EAAgB,KAAK,CAAK,QACnB,EAAP,CACA,GAAqB,CAAK,EAGhC,EAEA,EAAA,UAAA,MAAA,SAAM,EAAQ,CACJ,GAAA,GAAoB,KAAI,gBAChC,GAAI,EAAgB,MAClB,GAAI,CACF,EAAgB,MAAM,CAAG,QAClB,EAAP,CACA,GAAqB,CAAK,MAG5B,IAAqB,CAAG,CAE5B,EAEA,EAAA,UAAA,SAAA,UAAA,CACU,GAAA,GAAoB,KAAI,gBAChC,GAAI,EAAgB,SAClB,GAAI,CACF,EAAgB,SAAQ,QACjB,EAAP,CACA,GAAqB,CAAK,EAGhC,EACF,CAAA,EArCA,EAuCA,GAAA,SAAA,EAAA,CAAuC,GAAA,EAAA,CAAA,EACrC,WACE,EACA,EACA,EAA8B,CAHhC,GAAA,GAKE,EAAA,KAAA,IAAA,GAAO,KAEH,EACJ,GAAI,EAAW,CAAc,GAAK,CAAC,EAGjC,EAAkB,CAChB,KAAM,GAAc,KAAd,EAAkB,OACxB,MAAO,GAAK,KAAL,EAAS,OAChB,SAAU,GAAQ,KAAR,EAAY,YAEnB,CAEL,GAAI,GACJ,AAAI,GAAQ,GAAO,yBAIjB,GAAU,OAAO,OAAO,CAAc,EACtC,EAAQ,YAAc,UAAA,CAAM,MAAA,GAAK,YAAW,CAAhB,EAC5B,EAAkB,CAChB,KAAM,EAAe,MAAQ,GAAK,EAAe,KAAM,CAAO,EAC9D,MAAO,EAAe,OAAS,GAAK,EAAe,MAAO,CAAO,EACjE,SAAU,EAAe,UAAY,GAAK,EAAe,SAAU,CAAO,IAI5E,EAAkB,EAMtB,SAAK,YAAc,GAAI,IAAiB,CAAe,GACzD,CACF,MAAA,EAAA,EAzCuC,EAAU,EA2CjD,YAA8B,EAAU,CACtC,AAAI,GAAO,sCACT,GAAa,CAAK,EAIlB,GAAqB,CAAK,CAE9B,CAQA,YAA6B,EAAQ,CACnC,KAAM,EACR,CAOA,YAAmC,EAA2C,EAA2B,CAC/F,GAAA,GAA0B,GAAM,sBACxC,GAAyB,GAAgB,WAAW,UAAA,CAAM,MAAA,GAAsB,EAAc,CAAU,CAA9C,CAA+C,CAC3G,CAOO,GAAM,IAA6D,CACxE,OAAQ,GACR,KAAM,GACN,MAAO,GACP,SAAU,ICjRL,GAAM,IAA+B,UAAA,CAAM,MAAC,OAAO,SAAW,YAAc,OAAO,YAAe,cAAvD,EAAsE,ECyClH,YAAsB,EAAI,CAC9B,MAAO,EACT,CCiCM,aAAc,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACnB,MAAO,IAAc,CAAG,CAC1B,CAGM,YAA8B,EAA+B,CACjE,MAAI,GAAI,SAAW,EACV,GAGL,EAAI,SAAW,EACV,EAAI,GAGN,SAAe,EAAQ,CAC5B,MAAO,GAAI,OAAO,SAAC,EAAW,EAAuB,CAAK,MAAA,GAAG,CAAI,CAAP,EAAU,CAAY,CAClF,CACF,CC9EA,GAAA,GAAA,UAAA,CAkBE,WAAY,EAA6E,CACvF,AAAI,GACF,MAAK,WAAa,EAEtB,CA4BA,SAAA,UAAA,KAAA,SAAQ,EAAyB,CAC/B,GAAM,GAAa,GAAI,GACvB,SAAW,OAAS,KACpB,EAAW,SAAW,EACf,CACT,EA8IA,EAAA,UAAA,UAAA,SACE,EACA,EACA,EAA8B,CAHhC,GAAA,GAAA,KAKQ,EAAa,GAAa,CAAc,EAAI,EAAiB,GAAI,IAAe,EAAgB,EAAO,CAAQ,EAErH,UAAa,UAAA,CACL,GAAA,GAAuB,EAArB,EAAQ,EAAA,SAAE,EAAM,EAAA,OACxB,EAAW,IACT,EAGI,EAAS,KAAK,EAAY,CAAM,EAChC,EAIA,EAAK,WAAW,CAAU,EAG1B,EAAK,cAAc,CAAU,CAAC,CAEtC,CAAC,EAEM,CACT,EAGU,EAAA,UAAA,cAAV,SAAwB,EAAmB,CACzC,GAAI,CACF,MAAO,MAAK,WAAW,CAAI,QACpB,EAAP,CAIA,EAAK,MAAM,CAAG,EAElB,EA6DA,EAAA,UAAA,QAAA,SAAQ,EAA0B,EAAoC,CAAtE,GAAA,GAAA,KACE,SAAc,GAAe,CAAW,EAEjC,GAAI,GAAkB,SAAC,EAAS,EAAM,CAC3C,GAAM,GAAa,GAAI,IAAkB,CACvC,KAAM,SAAC,EAAK,CACV,GAAI,CACF,EAAK,CAAK,QACH,EAAP,CACA,EAAO,CAAG,EACV,EAAW,YAAW,EAE1B,EACA,MAAO,EACP,SAAU,EACX,EACD,EAAK,UAAU,CAAU,CAC3B,CAAC,CACH,EAGU,EAAA,UAAA,WAAV,SAAqB,EAA2B,OAC9C,MAAO,GAAA,KAAK,UAAM,MAAA,IAAA,OAAA,OAAA,EAAE,UAAU,CAAU,CAC1C,EAOA,EAAA,UAAC,IAAD,UAAA,CACE,MAAO,KACT,EA4FA,EAAA,UAAA,KAAA,UAAA,QAAK,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACH,MAAO,IAAc,CAAU,EAAE,IAAI,CACvC,EA6BA,EAAA,UAAA,UAAA,SAAU,EAAoC,CAA9C,GAAA,GAAA,KACE,SAAc,GAAe,CAAW,EAEjC,GAAI,GAAY,SAAC,EAAS,EAAM,CACrC,GAAI,GACJ,EAAK,UACH,SAAC,EAAI,CAAK,MAAC,GAAQ,CAAT,EACV,SAAC,EAAQ,CAAK,MAAA,GAAO,CAAG,CAAV,EACd,UAAA,CAAM,MAAA,GAAQ,CAAK,CAAb,CAAc,CAExB,CAAC,CACH,EA3aO,EAAA,OAAkC,SAAI,EAAwD,CACnG,MAAO,IAAI,GAAc,CAAS,CACpC,EA0aF,GA/cA,EAwdA,YAAwB,EAA+C,OACrE,MAAO,GAAA,GAAW,KAAX,EAAe,GAAO,WAAO,MAAA,IAAA,OAAA,EAAI,OAC1C,CAEA,YAAuB,EAAU,CAC/B,MAAO,IAAS,EAAW,EAAM,IAAI,GAAK,EAAW,EAAM,KAAK,GAAK,EAAW,EAAM,QAAQ,CAChG,CAEA,YAAyB,EAAU,CACjC,MAAQ,IAAS,YAAiB,KAAgB,GAAW,CAAK,GAAK,GAAe,CAAK,CAC7F,CC1eM,YAAkB,EAAW,CACjC,MAAO,GAAW,GAAM,KAAA,OAAN,EAAQ,IAAI,CAChC,CAMM,WACJ,EAAqF,CAErF,MAAO,UAAC,EAAqB,CAC3B,GAAI,GAAQ,CAAM,EAChB,MAAO,GAAO,KAAK,SAA+B,EAA2B,CAC3E,GAAI,CACF,MAAO,GAAK,EAAc,IAAI,QACvB,EAAP,CACA,KAAK,MAAM,CAAG,EAElB,CAAC,EAEH,KAAM,IAAI,WAAU,wCAAwC,CAC9D,CACF,CCjBM,WACJ,EACA,EACA,EACA,EACA,EAAuB,CAEvB,MAAO,IAAI,IAAmB,EAAa,EAAQ,EAAY,EAAS,CAAU,CACpF,CAMA,GAAA,IAAA,SAAA,EAAA,CAA2C,GAAA,EAAA,CAAA,EAiBzC,WACE,EACA,EACA,EACA,EACQ,EACA,EAAiC,CAN3C,GAAA,GAoBE,EAAA,KAAA,KAAM,CAAW,GAAC,KAfV,SAAA,WAAA,EACA,EAAA,kBAAA,EAeR,EAAK,MAAQ,EACT,SAAuC,EAAQ,CAC7C,GAAI,CACF,EAAO,CAAK,QACL,EAAP,CACA,EAAY,MAAM,CAAG,EAEzB,EACA,EAAA,UAAM,MACV,EAAK,OAAS,EACV,SAAuC,EAAQ,CAC7C,GAAI,CACF,EAAQ,CAAG,QACJ,EAAP,CAEA,EAAY,MAAM,CAAG,UAGrB,KAAK,YAAW,EAEpB,EACA,EAAA,UAAM,OACV,EAAK,UAAY,EACb,UAAA,CACE,GAAI,CACF,EAAU,QACH,EAAP,CAEA,EAAY,MAAM,CAAG,UAGrB,KAAK,YAAW,EAEpB,EACA,EAAA,UAAM,WACZ,CAEA,SAAA,UAAA,YAAA,UAAA,OACE,GAAI,CAAC,KAAK,mBAAqB,KAAK,kBAAiB,EAAI,CAC/C,GAAA,GAAW,KAAI,OACvB,EAAA,UAAM,YAAW,KAAA,IAAA,EAEjB,CAAC,GAAU,IAAA,KAAK,cAAU,MAAA,IAAA,QAAA,EAAA,KAAf,IAAI,GAEnB,EACF,CAAA,EAnF2C,EAAU,ECd9C,GAAM,IAAiD,CAG5D,SAAA,SAAS,EAAQ,CACf,GAAI,GAAU,sBACV,EAAkD,qBAC9C,EAAa,GAAsB,SAC3C,AAAI,GACF,GAAU,EAAS,sBACnB,EAAS,EAAS,sBAEpB,GAAM,GAAS,EAAQ,SAAC,EAAS,CAI/B,EAAS,OACT,EAAS,CAAS,CACpB,CAAC,EACD,MAAO,IAAI,IAAa,UAAA,CAAM,MAAA,IAAM,KAAA,OAAN,EAAS,CAAM,CAAf,CAAgB,CAChD,EACA,sBAAqB,UAAA,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACZ,GAAA,GAAa,GAAsB,SAC3C,MAAQ,KAAQ,KAAA,OAAR,EAAU,wBAAyB,uBAAsB,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAI,CAAA,CAAA,CAC3E,EACA,qBAAoB,UAAA,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACX,GAAA,GAAa,GAAsB,SAC3C,MAAQ,KAAQ,KAAA,OAAR,EAAU,uBAAwB,sBAAqB,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAI,CAAA,CAAA,CACzE,EACA,SAAU,QCrBL,GAAM,IAAuD,GAClE,SAAC,EAAM,CACL,MAAA,WAAoC,CAClC,EAAO,IAAI,EACX,KAAK,KAAO,0BACZ,KAAK,QAAU,qBACjB,CAJA,CAIC,ECXL,GAAA,GAAA,SAAA,EAAA,CAAgC,GAAA,EAAA,CAAA,EAwB9B,YAAA,CAAA,GAAA,GAEE,EAAA,KAAA,IAAA,GAAO,KAzBT,SAAA,OAAS,GAED,EAAA,iBAAyC,KAGjD,EAAA,UAA2B,CAAA,EAE3B,EAAA,UAAY,GAEZ,EAAA,SAAW,GAEX,EAAA,YAAmB,MAenB,CAGA,SAAA,UAAA,KAAA,SAAQ,EAAwB,CAC9B,GAAM,GAAU,GAAI,IAAiB,KAAM,IAAI,EAC/C,SAAQ,SAAW,EACZ,CACT,EAGU,EAAA,UAAA,eAAV,UAAA,CACE,GAAI,KAAK,OACP,KAAM,IAAI,GAEd,EAEA,EAAA,UAAA,KAAA,SAAK,EAAQ,CAAb,GAAA,GAAA,KACE,GAAa,UAAA,SAEX,GADA,EAAK,eAAc,EACf,CAAC,EAAK,UAAW,CACnB,AAAK,EAAK,kBACR,GAAK,iBAAmB,MAAM,KAAK,EAAK,SAAS,OAEnD,OAAuB,GAAA,GAAA,EAAK,gBAAgB,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAAzC,GAAM,GAAQ,EAAA,MACjB,EAAS,KAAK,CAAK,qGAGzB,CAAC,CACH,EAEA,EAAA,UAAA,MAAA,SAAM,EAAQ,CAAd,GAAA,GAAA,KACE,GAAa,UAAA,CAEX,GADA,EAAK,eAAc,EACf,CAAC,EAAK,UAAW,CACnB,EAAK,SAAW,EAAK,UAAY,GACjC,EAAK,YAAc,EAEnB,OADQ,GAAc,EAAI,UACnB,EAAU,QACf,EAAU,MAAK,EAAI,MAAM,CAAG,EAGlC,CAAC,CACH,EAEA,EAAA,UAAA,SAAA,UAAA,CAAA,GAAA,GAAA,KACE,GAAa,UAAA,CAEX,GADA,EAAK,eAAc,EACf,CAAC,EAAK,UAAW,CACnB,EAAK,UAAY,GAEjB,OADQ,GAAc,EAAI,UACnB,EAAU,QACf,EAAU,MAAK,EAAI,SAAQ,EAGjC,CAAC,CACH,EAEA,EAAA,UAAA,YAAA,UAAA,CACE,KAAK,UAAY,KAAK,OAAS,GAC/B,KAAK,UAAY,KAAK,iBAAmB,IAC3C,EAEA,OAAA,eAAI,EAAA,UAAA,WAAQ,KAAZ,UAAA,OACE,MAAO,IAAA,KAAK,aAAS,MAAA,IAAA,OAAA,OAAA,EAAE,QAAS,CAClC,kCAGU,EAAA,UAAA,cAAV,SAAwB,EAAyB,CAC/C,YAAK,eAAc,EACZ,EAAA,UAAM,cAAa,KAAA,KAAC,CAAU,CACvC,EAGU,EAAA,UAAA,WAAV,SAAqB,EAAyB,CAC5C,YAAK,eAAc,EACnB,KAAK,wBAAwB,CAAU,EAChC,KAAK,gBAAgB,CAAU,CACxC,EAGU,EAAA,UAAA,gBAAV,SAA0B,EAA2B,CAArD,GAAA,GAAA,KACQ,EAAqC,KAAnC,EAAQ,EAAA,SAAE,EAAS,EAAA,UAAE,EAAS,EAAA,UACtC,MAAI,IAAY,EACP,GAET,MAAK,iBAAmB,KACxB,EAAU,KAAK,CAAU,EAClB,GAAI,IAAa,UAAA,CACtB,EAAK,iBAAmB,KACxB,GAAU,EAAW,CAAU,CACjC,CAAC,EACH,EAGU,EAAA,UAAA,wBAAV,SAAkC,EAA2B,CACrD,GAAA,GAAuC,KAArC,EAAQ,EAAA,SAAE,EAAW,EAAA,YAAE,EAAS,EAAA,UACxC,AAAI,EACF,EAAW,MAAM,CAAW,EACnB,GACT,EAAW,SAAQ,CAEvB,EAQA,EAAA,UAAA,aAAA,UAAA,CACE,GAAM,GAAkB,GAAI,GAC5B,SAAW,OAAS,KACb,CACT,EAxHO,EAAA,OAAkC,SAAI,EAA0B,EAAqB,CAC1F,MAAO,IAAI,IAAoB,EAAa,CAAM,CACpD,EAuHF,GA7IgC,CAAU,EAkJ1C,GAAA,IAAA,SAAA,EAAA,CAAyC,GAAA,EAAA,CAAA,EACvC,WAES,EACP,EAAsB,CAHxB,GAAA,GAKE,EAAA,KAAA,IAAA,GAAO,KAHA,SAAA,YAAA,EAIP,EAAK,OAAS,GAChB,CAEA,SAAA,UAAA,KAAA,SAAK,EAAQ,SACX,AAAA,GAAA,GAAA,KAAK,eAAW,MAAA,IAAA,OAAA,OAAA,EAAE,QAAI,MAAA,IAAA,QAAA,EAAA,KAAA,EAAG,CAAK,CAChC,EAEA,EAAA,UAAA,MAAA,SAAM,EAAQ,SACZ,AAAA,GAAA,GAAA,KAAK,eAAW,MAAA,IAAA,OAAA,OAAA,EAAE,SAAK,MAAA,IAAA,QAAA,EAAA,KAAA,EAAG,CAAG,CAC/B,EAEA,EAAA,UAAA,SAAA,UAAA,SACE,AAAA,GAAA,GAAA,KAAK,eAAW,MAAA,IAAA,OAAA,OAAA,EAAE,YAAQ,MAAA,IAAA,QAAA,EAAA,KAAA,CAAA,CAC5B,EAGU,EAAA,UAAA,WAAV,SAAqB,EAAyB,SAC5C,MAAO,GAAA,GAAA,KAAK,UAAM,MAAA,IAAA,OAAA,OAAA,EAAE,UAAU,CAAU,KAAC,MAAA,IAAA,OAAA,EAAI,EAC/C,EACF,CAAA,EA1ByC,CAAO,EC5JzC,GAAM,IAA+C,CAC1D,IAAG,UAAA,CAGD,MAAQ,IAAsB,UAAY,MAAM,IAAG,CACrD,EACA,SAAU,QCwBZ,GAAA,IAAA,SAAA,EAAA,CAAsC,GAAA,EAAA,CAAA,EAUpC,WACU,EACA,EACA,EAA6D,CAF7D,AAAA,IAAA,QAAA,GAAA,KACA,IAAA,QAAA,GAAA,KACA,IAAA,QAAA,GAAA,IAHV,GAAA,GAKE,EAAA,KAAA,IAAA,GAAO,KAJC,SAAA,YAAA,EACA,EAAA,YAAA,EACA,EAAA,mBAAA,EAZF,EAAA,QAA0B,CAAA,EAC1B,EAAA,oBAAsB,GAc5B,EAAK,oBAAsB,IAAgB,IAC3C,EAAK,YAAc,KAAK,IAAI,EAAG,CAAW,EAC1C,EAAK,YAAc,KAAK,IAAI,EAAG,CAAW,GAC5C,CAEA,SAAA,UAAA,KAAA,SAAK,EAAQ,CACL,GAAA,GAA+E,KAA7E,EAAS,EAAA,UAAE,EAAO,EAAA,QAAE,EAAmB,EAAA,oBAAE,EAAkB,EAAA,mBAAE,EAAW,EAAA,YAChF,AAAK,GACH,GAAQ,KAAK,CAAK,EAClB,CAAC,GAAuB,EAAQ,KAAK,EAAmB,IAAG,EAAK,CAAW,GAE7E,KAAK,YAAW,EAChB,EAAA,UAAM,KAAI,KAAA,KAAC,CAAK,CAClB,EAGU,EAAA,UAAA,WAAV,SAAqB,EAAyB,CAC5C,KAAK,eAAc,EACnB,KAAK,YAAW,EAQhB,OANM,GAAe,KAAK,gBAAgB,CAAU,EAE9C,EAAmC,KAAjC,EAAmB,EAAA,oBAAE,EAAO,EAAA,QAG9B,EAAO,EAAQ,MAAK,EACjB,EAAI,EAAG,EAAI,EAAK,QAAU,CAAC,EAAW,OAAQ,GAAK,EAAsB,EAAI,EACpF,EAAW,KAAK,EAAK,EAAO,EAG9B,YAAK,wBAAwB,CAAU,EAEhC,CACT,EAEQ,EAAA,UAAA,YAAR,UAAA,CACQ,GAAA,GAAoE,KAAlE,EAAW,EAAA,YAAE,EAAkB,EAAA,mBAAE,EAAO,EAAA,QAAE,EAAmB,EAAA,oBAK/D,EAAsB,GAAsB,EAAI,GAAK,EAK3D,GAJA,EAAc,KAAY,EAAqB,EAAQ,QAAU,EAAQ,OAAO,EAAG,EAAQ,OAAS,CAAkB,EAIlH,CAAC,EAAqB,CAKxB,OAJM,GAAM,EAAmB,IAAG,EAC9B,EAAO,EAGF,EAAI,EAAG,EAAI,EAAQ,QAAW,EAAQ,IAAiB,EAAK,GAAK,EACxE,EAAO,EAET,GAAQ,EAAQ,OAAO,EAAG,EAAO,CAAC,EAEtC,EACF,CAAA,EAzEsC,CAAO,EClB7C,GAAA,IAAA,SAAA,EAAA,CAA+B,GAAA,EAAA,CAAA,EAC7B,WAAY,EAAsB,EAAmD,OACnF,GAAA,KAAA,IAAA,GAAO,IACT,CAWO,SAAA,UAAA,SAAP,SAAgB,EAAW,EAAiB,CAAjB,MAAA,KAAA,QAAA,GAAA,GAClB,IACT,EACF,CAAA,EAjB+B,EAAY,ECJpC,GAAM,IAAqC,CAGhD,YAAA,SAAY,EAAqB,EAAgB,QAAE,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,EAAA,GAAA,UAAA,GAC1C,GAAA,GAAY,GAAgB,SACnC,MAAI,IAAQ,MAAR,EAAU,YACL,EAAS,YAAW,MAApB,EAAQ,EAAA,CAAa,EAAS,CAAO,EAAA,EAAK,CAAI,CAAA,CAAA,EAEhD,YAAW,MAAA,OAAA,EAAA,CAAC,EAAS,CAAO,EAAA,EAAK,CAAI,CAAA,CAAA,CAC9C,EACA,cAAa,SAAC,EAAM,CACV,GAAA,GAAa,GAAgB,SACrC,MAAQ,KAAQ,KAAA,OAAR,EAAU,gBAAiB,eAAe,CAAM,CAC1D,EACA,SAAU,QCrBZ,GAAA,IAAA,SAAA,EAAA,CAAoC,GAAA,EAAA,CAAA,EAOlC,WAAsB,EAAqC,EAAmD,CAA9G,GAAA,GACE,EAAA,KAAA,KAAM,EAAW,CAAI,GAAC,KADF,SAAA,UAAA,EAAqC,EAAA,KAAA,EAFjD,EAAA,QAAmB,IAI7B,CAEO,SAAA,UAAA,SAAP,SAAgB,EAAW,EAAiB,CAC1C,GADyB,IAAA,QAAA,GAAA,GACrB,KAAK,OACP,MAAO,MAIT,KAAK,MAAQ,EAEb,GAAM,GAAK,KAAK,GACV,EAAY,KAAK,UAuBvB,MAAI,IAAM,MACR,MAAK,GAAK,KAAK,eAAe,EAAW,EAAI,CAAK,GAKpD,KAAK,QAAU,GAEf,KAAK,MAAQ,EAEb,KAAK,GAAK,KAAK,IAAM,KAAK,eAAe,EAAW,KAAK,GAAI,CAAK,EAE3D,IACT,EAEU,EAAA,UAAA,eAAV,SAAyB,EAA2B,EAAW,EAAiB,CAAjB,MAAA,KAAA,QAAA,GAAA,GACtD,GAAiB,YAAY,EAAU,MAAM,KAAK,EAAW,IAAI,EAAG,CAAK,CAClF,EAEU,EAAA,UAAA,eAAV,SAAyB,EAA4B,EAAS,EAAwB,CAEpF,GAF4D,IAAA,QAAA,GAAA,GAExD,GAAS,MAAQ,KAAK,QAAU,GAAS,KAAK,UAAY,GAC5D,MAAO,GAIT,GAAiB,cAAc,CAAE,CAEnC,EAMO,EAAA,UAAA,QAAP,SAAe,EAAU,EAAa,CACpC,GAAI,KAAK,OACP,MAAO,IAAI,OAAM,8BAA8B,EAGjD,KAAK,QAAU,GACf,GAAM,GAAQ,KAAK,SAAS,EAAO,CAAK,EACxC,GAAI,EACF,MAAO,GACF,AAAI,KAAK,UAAY,IAAS,KAAK,IAAM,MAc9C,MAAK,GAAK,KAAK,eAAe,KAAK,UAAW,KAAK,GAAI,IAAI,EAE/D,EAEU,EAAA,UAAA,SAAV,SAAmB,EAAU,EAAc,CACzC,GAAI,GAAmB,GACnB,EACJ,GAAI,CACF,KAAK,KAAK,CAAK,QACR,EAAP,CACA,EAAU,GAIV,EAAa,GAAQ,GAAI,OAAM,oCAAoC,EAErE,GAAI,EACF,YAAK,YAAW,EACT,CAEX,EAEA,EAAA,UAAA,YAAA,UAAA,CACE,GAAI,CAAC,KAAK,OAAQ,CACV,GAAA,GAAoB,KAAlB,EAAE,EAAA,GAAE,EAAS,EAAA,UACb,EAAY,EAAS,QAE7B,KAAK,KAAO,KAAK,MAAQ,KAAK,UAAY,KAC1C,KAAK,QAAU,GAEf,GAAU,EAAS,IAAI,EACnB,GAAM,MACR,MAAK,GAAK,KAAK,eAAe,EAAW,EAAI,IAAI,GAGnD,KAAK,MAAQ,KACb,EAAA,UAAM,YAAW,KAAA,IAAA,EAErB,EACF,CAAA,EA3IoC,EAAM,ECiB1C,GAAA,IAAA,UAAA,CAGE,WAAoB,EAAoC,EAAiC,CAAjC,AAAA,IAAA,QAAA,GAAoB,EAAU,KAAlE,KAAA,oBAAA,EAClB,KAAK,IAAM,CACb,CA6BO,SAAA,UAAA,SAAP,SAAmB,EAAqD,EAAmB,EAAS,CAA5B,MAAA,KAAA,QAAA,GAAA,GAC/D,GAAI,MAAK,oBAAuB,KAAM,CAAI,EAAE,SAAS,EAAO,CAAK,CAC1E,EAnCc,EAAA,IAAoB,GAAsB,IAoC1D,GArCA,ECpBA,GAAA,IAAA,SAAA,EAAA,CAAoC,GAAA,EAAA,CAAA,EAkBlC,WAAY,EAAgC,EAAiC,CAAjC,AAAA,IAAA,QAAA,GAAoB,GAAU,KAA1E,GAAA,GACE,EAAA,KAAA,KAAM,EAAiB,CAAG,GAAC,KAlBtB,SAAA,QAAmC,CAAA,EAOnC,EAAA,QAAmB,GAQnB,EAAA,WAAkB,QAIzB,CAEO,SAAA,UAAA,MAAP,SAAa,EAAwB,CAC3B,GAAA,GAAY,KAAI,QAExB,GAAI,KAAK,QAAS,CAChB,EAAQ,KAAK,CAAM,EACnB,OAGF,GAAI,GACJ,KAAK,QAAU,GAEf,EACE,IAAK,EAAQ,EAAO,QAAQ,EAAO,MAAO,EAAO,KAAK,EACpD,YAEM,EAAS,EAAQ,MAAK,GAIhC,GAFA,KAAK,QAAU,GAEX,EAAO,CACT,KAAQ,EAAS,EAAQ,MAAK,GAC5B,EAAO,YAAW,EAEpB,KAAM,GAEV,EACF,CAAA,EAhDoC,EAAS,EC8CtC,GAAM,IAAiB,GAAI,IAAe,EAAW,EAK/C,GAAQ,GClDrB,GAAA,IAAA,SAAA,EAAA,CAA6C,GAAA,EAAA,CAAA,EAC3C,WAAsB,EAA8C,EAAmD,CAAvH,GAAA,GACE,EAAA,KAAA,KAAM,EAAW,CAAI,GAAC,KADF,SAAA,UAAA,EAA8C,EAAA,KAAA,GAEpE,CAEU,SAAA,UAAA,eAAV,SAAyB,EAAoC,EAAU,EAAiB,CAEtF,MAFqE,KAAA,QAAA,GAAA,GAEjE,IAAU,MAAQ,EAAQ,EACrB,EAAA,UAAM,eAAc,KAAA,KAAC,EAAW,EAAI,CAAK,EAGlD,GAAU,QAAQ,KAAK,IAAI,EAIpB,EAAU,YAAe,GAAU,WAAa,GAAuB,sBAAsB,UAAA,CAAM,MAAA,GAAU,MAAM,MAAS,CAAzB,CAA0B,GACtI,EACU,EAAA,UAAA,eAAV,SAAyB,EAAoC,EAAU,EAAiB,CAItF,GAJqE,IAAA,QAAA,GAAA,GAIhE,GAAS,MAAQ,EAAQ,GAAO,GAAS,MAAQ,KAAK,MAAQ,EACjE,MAAO,GAAA,UAAM,eAAc,KAAA,KAAC,EAAW,EAAI,CAAK,EAKlD,AAAK,EAAU,QAAQ,KAAK,SAAC,EAAM,CAAK,MAAA,GAAO,KAAO,CAAd,CAAgB,GACtD,IAAuB,qBAAqB,CAAE,EAC9C,EAAU,WAAa,OAI3B,EACF,CAAA,EAlC6C,EAAW,ECFxD,GAAA,IAAA,SAAA,EAAA,CAA6C,GAAA,EAAA,CAAA,EAA7C,YAAA,+CAkCA,CAjCS,SAAA,UAAA,MAAP,SAAa,EAAyB,CACpC,KAAK,QAAU,GAUf,GAAM,GAAU,KAAK,WACrB,KAAK,WAAa,OAEV,GAAA,GAAY,KAAI,QACpB,EACJ,EAAS,GAAU,EAAQ,MAAK,EAEhC,EACE,IAAK,EAAQ,EAAO,QAAQ,EAAO,MAAO,EAAO,KAAK,EACpD,YAEM,GAAS,EAAQ,KAAO,EAAO,KAAO,GAAW,EAAQ,MAAK,GAIxE,GAFA,KAAK,QAAU,GAEX,EAAO,CACT,KAAQ,GAAS,EAAQ,KAAO,EAAO,KAAO,GAAW,EAAQ,MAAK,GACpE,EAAO,YAAW,EAEpB,KAAM,GAEV,EACF,CAAA,EAlC6C,EAAc,ECgCpD,GAAM,IAA0B,GAAI,IAAwB,EAAoB,EC8BhF,GAAM,GAAQ,GAAI,GAAkB,SAAC,EAAU,CAAK,MAAA,GAAW,SAAQ,CAAnB,CAAqB,EC9D1E,YAAsB,EAAU,CACpC,MAAO,IAAS,EAAW,EAAM,QAAQ,CAC3C,CCDA,YAAiB,EAAQ,CACvB,MAAO,GAAI,EAAI,OAAS,EAC1B,CAEM,YAA4B,EAAW,CAC3C,MAAO,GAAW,GAAK,CAAI,CAAC,EAAI,EAAK,IAAG,EAAK,MAC/C,CAEM,YAAuB,EAAW,CACtC,MAAO,IAAY,GAAK,CAAI,CAAC,EAAI,EAAK,IAAG,EAAK,MAChD,CAEM,YAAoB,EAAa,EAAoB,CACzD,MAAO,OAAO,IAAK,CAAI,GAAM,SAAW,EAAK,IAAG,EAAM,CACxD,CClBO,GAAM,IAAe,SAAI,EAAM,CAAwB,MAAA,IAAK,MAAO,GAAE,QAAW,UAAY,MAAO,IAAM,UAAlD,ECMxD,YAAoB,EAAU,CAClC,MAAO,GAAW,GAAK,KAAA,OAAL,EAAO,IAAI,CAC/B,CCHM,YAA8B,EAAU,CAC5C,MAAO,GAAW,EAAM,GAAkB,CAC5C,CCLM,YAA6B,EAAQ,CACzC,MAAO,QAAO,eAAiB,EAAW,GAAG,KAAA,OAAH,EAAM,OAAO,cAAc,CACvE,CCAM,YAA2C,EAAU,CAEzD,MAAO,IAAI,WACT,gBACE,KAAU,MAAQ,MAAO,IAAU,SAAW,oBAAsB,IAAI,EAAK,KAAG,0HACwC,CAE9H,CCXM,aAA2B,CAC/B,MAAI,OAAO,SAAW,YAAc,CAAC,OAAO,SACnC,aAGF,OAAO,QAChB,CAEO,GAAM,IAAW,GAAiB,ECJnC,YAAqB,EAAU,CACnC,MAAO,GAAW,GAAK,KAAA,OAAL,EAAQ,GAAgB,CAC5C,CCHM,YAAuD,EAAqC,mGAC1F,EAAS,EAAe,UAAS,2DAGX,MAAA,CAAA,EAAA,GAAM,EAAO,KAAI,CAAE,CAAA,eAArC,GAAkB,EAAA,KAAA,EAAhB,EAAK,EAAA,MAAE,EAAI,EAAA,KACf,iBAAA,CAAA,EAAA,CAAA,SACF,MAAA,CAAA,EAAA,EAAA,KAAA,CAAA,qBAEI,CAAM,CAAA,SAAZ,MAAA,CAAA,EAAA,EAAA,KAAA,CAAA,SAAA,SAAA,KAAA,mCAGF,SAAO,YAAW,6BAIhB,YAAkC,EAAQ,CAG9C,MAAO,GAAW,GAAG,KAAA,OAAH,EAAK,SAAS,CAClC,CCRM,WAAuB,EAAyB,CACpD,GAAI,YAAiB,GACnB,MAAO,GAET,GAAI,GAAS,KAAM,CACjB,GAAI,GAAoB,CAAK,EAC3B,MAAO,IAAsB,CAAK,EAEpC,GAAI,GAAY,CAAK,EACnB,MAAO,IAAc,CAAK,EAE5B,GAAI,GAAU,CAAK,EACjB,MAAO,IAAY,CAAK,EAE1B,GAAI,GAAgB,CAAK,EACvB,MAAO,IAAkB,CAAK,EAEhC,GAAI,GAAW,CAAK,EAClB,MAAO,IAAa,CAAK,EAE3B,GAAI,GAAqB,CAAK,EAC5B,MAAO,IAAuB,CAAK,EAIvC,KAAM,IAAiC,CAAK,CAC9C,CAMM,YAAmC,EAAQ,CAC/C,MAAO,IAAI,GAAW,SAAC,EAAyB,CAC9C,GAAM,GAAM,EAAI,IAAkB,EAClC,GAAI,EAAW,EAAI,SAAS,EAC1B,MAAO,GAAI,UAAU,CAAU,EAGjC,KAAM,IAAI,WAAU,gEAAgE,CACtF,CAAC,CACH,CASM,YAA2B,EAAmB,CAClD,MAAO,IAAI,GAAW,SAAC,EAAyB,CAU9C,OAAS,GAAI,EAAG,EAAI,EAAM,QAAU,CAAC,EAAW,OAAQ,IACtD,EAAW,KAAK,EAAM,EAAE,EAE1B,EAAW,SAAQ,CACrB,CAAC,CACH,CAEM,YAAyB,EAAuB,CACpD,MAAO,IAAI,GAAW,SAAC,EAAyB,CAC9C,EACG,KACC,SAAC,EAAK,CACJ,AAAK,EAAW,QACd,GAAW,KAAK,CAAK,EACrB,EAAW,SAAQ,EAEvB,EACA,SAAC,EAAQ,CAAK,MAAA,GAAW,MAAM,CAAG,CAApB,CAAqB,EAEpC,KAAK,KAAM,EAAoB,CACpC,CAAC,CACH,CAEM,YAA0B,EAAqB,CACnD,MAAO,IAAI,GAAW,SAAC,EAAyB,aAC9C,OAAoB,GAAA,GAAA,CAAQ,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAAzB,GAAM,GAAK,EAAA,MAEd,GADA,EAAW,KAAK,CAAK,EACjB,EAAW,OACb,yGAGJ,EAAW,SAAQ,CACrB,CAAC,CACH,CAEM,YAA+B,EAA+B,CAClE,MAAO,IAAI,GAAW,SAAC,EAAyB,CAC9C,GAAQ,EAAe,CAAU,EAAE,MAAM,SAAC,EAAG,CAAK,MAAA,GAAW,MAAM,CAAG,CAApB,CAAqB,CACzE,CAAC,CACH,CAEM,YAAoC,EAAqC,CAC7E,MAAO,IAAkB,GAAmC,CAAc,CAAC,CAC7E,CAEA,YAA0B,EAAiC,EAAyB,uIACxD,EAAA,GAAA,CAAa,gFAIrC,GAJe,EAAK,EAAA,MACpB,EAAW,KAAK,CAAK,EAGjB,EAAW,OACb,MAAA,CAAA,CAAA,6RAGJ,SAAW,SAAQ,WC/Gf,YACJ,EACA,EACA,EACA,EACA,EAAc,CADd,AAAA,IAAA,QAAA,GAAA,GACA,IAAA,QAAA,GAAA,IAEA,GAAM,GAAuB,EAAU,SAAS,UAAA,CAC9C,EAAI,EACJ,AAAI,EACF,EAAmB,IAAI,KAAK,SAAS,KAAM,CAAK,CAAC,EAEjD,KAAK,YAAW,CAEpB,EAAG,CAAK,EAIR,GAFA,EAAmB,IAAI,CAAoB,EAEvC,CAAC,EAKH,MAAO,EAEX,CCeM,YAAuB,EAA0B,EAAS,CAAT,MAAA,KAAA,QAAA,GAAA,GAC9C,EAAQ,SAAC,EAAQ,EAAU,CAChC,EAAO,UACL,EACE,EACA,SAAC,EAAK,CAAK,MAAA,IAAgB,EAAY,EAAW,UAAA,CAAM,MAAA,GAAW,KAAK,CAAK,CAArB,EAAwB,CAAK,CAA1E,EACX,UAAA,CAAM,MAAA,IAAgB,EAAY,EAAW,UAAA,CAAM,MAAA,GAAW,SAAQ,CAAnB,EAAuB,CAAK,CAAzE,EACN,SAAC,EAAG,CAAK,MAAA,IAAgB,EAAY,EAAW,UAAA,CAAM,MAAA,GAAW,MAAM,CAAG,CAApB,EAAuB,CAAK,CAAzE,CAA0E,CACpF,CAEL,CAAC,CACH,CCPM,YAAyB,EAA0B,EAAiB,CAAjB,MAAA,KAAA,QAAA,GAAA,GAChD,EAAQ,SAAC,EAAQ,EAAU,CAChC,EAAW,IAAI,EAAU,SAAS,UAAA,CAAM,MAAA,GAAO,UAAU,CAAU,CAA3B,EAA8B,CAAK,CAAC,CAC9E,CAAC,CACH,CC7DM,YAAgC,EAA6B,EAAwB,CACzF,MAAO,GAAU,CAAK,EAAE,KAAK,GAAY,CAAS,EAAG,GAAU,CAAS,CAAC,CAC3E,CCFM,YAA6B,EAAuB,EAAwB,CAChF,MAAO,GAAU,CAAK,EAAE,KAAK,GAAY,CAAS,EAAG,GAAU,CAAS,CAAC,CAC3E,CCJM,YAA2B,EAAqB,EAAwB,CAC5E,MAAO,IAAI,GAAc,SAAC,EAAU,CAElC,GAAI,GAAI,EAER,MAAO,GAAU,SAAS,UAAA,CACxB,AAAI,IAAM,EAAM,OAGd,EAAW,SAAQ,EAInB,GAAW,KAAK,EAAM,IAAI,EAIrB,EAAW,QACd,KAAK,SAAQ,EAGnB,CAAC,CACH,CAAC,CACH,CCfM,YAA8B,EAAoB,EAAwB,CAC9E,MAAO,IAAI,GAAc,SAAC,EAAU,CAClC,GAAI,GAKJ,UAAgB,EAAY,EAAW,UAAA,CAErC,EAAY,EAAc,IAAgB,EAE1C,GACE,EACA,EACA,UAAA,OACM,EACA,EACJ,GAAI,CAEF,AAAC,EAAkB,EAAS,KAAI,EAA7B,EAAK,EAAA,MAAE,EAAI,EAAA,WACP,EAAP,CAEA,EAAW,MAAM,CAAG,EACpB,OAGF,AAAI,EAKF,EAAW,SAAQ,EAGnB,EAAW,KAAK,CAAK,CAEzB,EACA,EACA,EAAI,CAER,CAAC,EAMM,UAAA,CAAM,MAAA,GAAW,GAAQ,KAAA,OAAR,EAAU,MAAM,GAAK,EAAS,OAAM,CAA/C,CACf,CAAC,CACH,CCvDM,YAAmC,EAAyB,EAAwB,CACxF,GAAI,CAAC,EACH,KAAM,IAAI,OAAM,yBAAyB,EAE3C,MAAO,IAAI,GAAc,SAAC,EAAU,CAClC,GAAgB,EAAY,EAAW,UAAA,CACrC,GAAM,GAAW,EAAM,OAAO,eAAc,EAC5C,GACE,EACA,EACA,UAAA,CACE,EAAS,KAAI,EAAG,KAAK,SAAC,EAAM,CAC1B,AAAI,EAAO,KAGT,EAAW,SAAQ,EAEnB,EAAW,KAAK,EAAO,KAAK,CAEhC,CAAC,CACH,EACA,EACA,EAAI,CAER,CAAC,CACH,CAAC,CACH,CCzBM,YAAwC,EAA8B,EAAwB,CAClG,MAAO,IAAsB,GAAmC,CAAK,EAAG,CAAS,CACnF,CCoBM,YAAuB,EAA2B,EAAwB,CAC9E,GAAI,GAAS,KAAM,CACjB,GAAI,GAAoB,CAAK,EAC3B,MAAO,IAAmB,EAAO,CAAS,EAE5C,GAAI,GAAY,CAAK,EACnB,MAAO,IAAc,EAAO,CAAS,EAEvC,GAAI,GAAU,CAAK,EACjB,MAAO,IAAgB,EAAO,CAAS,EAEzC,GAAI,GAAgB,CAAK,EACvB,MAAO,IAAsB,EAAO,CAAS,EAE/C,GAAI,GAAW,CAAK,EAClB,MAAO,IAAiB,EAAO,CAAS,EAE1C,GAAI,GAAqB,CAAK,EAC5B,MAAO,IAA2B,EAAO,CAAS,EAGtD,KAAM,IAAiC,CAAK,CAC9C,CCoDM,YAAkB,EAA2B,EAAyB,CAC1E,MAAO,GAAY,GAAU,EAAO,CAAS,EAAI,EAAU,CAAK,CAClE,CCxBM,YAAY,QAAI,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACpB,GAAM,GAAY,GAAa,CAAI,EACnC,MAAO,IAAK,EAAa,CAAS,CACpC,CCsCM,YAAqB,EAA0B,EAAyB,CAC5E,GAAM,GAAe,EAAW,CAAmB,EAAI,EAAsB,UAAA,CAAM,MAAA,EAAA,EAC7E,EAAO,SAAC,EAA6B,CAAK,MAAA,GAAW,MAAM,EAAY,CAAE,CAA/B,EAChD,MAAO,IAAI,GAAW,EAAY,SAAC,EAAU,CAAK,MAAA,GAAU,SAAS,EAAa,EAAG,CAAU,CAA7C,EAAiD,CAAI,CACzG,CCrHM,YAAsB,EAAU,CACpC,MAAO,aAAiB,OAAQ,CAAC,MAAM,CAAY,CACrD,CCsCM,WAAoB,EAAyC,EAAa,CAC9E,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAEhC,GAAI,GAAQ,EAGZ,EAAO,UACL,EAAyB,EAAY,SAAC,EAAQ,CAG5C,EAAW,KAAK,EAAQ,KAAK,EAAS,EAAO,GAAO,CAAC,CACvD,CAAC,CAAC,CAEN,CAAC,CACH,CC1DQ,GAAA,IAAY,MAAK,QAEzB,YAA2B,EAA6B,EAAW,CAC/D,MAAO,IAAQ,CAAI,EAAI,EAAE,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAI,CAAA,CAAA,EAAI,EAAG,CAAI,CAChD,CAMM,YAAiC,EAA2B,CAC9D,MAAO,GAAI,SAAA,EAAI,CAAI,MAAA,IAAY,EAAI,CAAI,CAApB,CAAqB,CAC5C,CCfQ,GAAA,IAAY,MAAK,QACjB,GAA0D,OAAM,eAArC,GAA+B,OAAM,UAAlB,GAAY,OAAM,KAQlE,YAA+D,EAAuB,CAC1F,GAAI,EAAK,SAAW,EAAG,CACrB,GAAM,GAAQ,EAAK,GACnB,GAAI,GAAQ,CAAK,EACf,MAAO,CAAE,KAAM,EAAO,KAAM,IAAI,EAElC,GAAI,GAAO,CAAK,EAAG,CACjB,GAAM,GAAO,GAAQ,CAAK,EAC1B,MAAO,CACL,KAAM,EAAK,IAAI,SAAC,EAAG,CAAK,MAAA,GAAM,EAAN,CAAU,EAClC,KAAI,IAKV,MAAO,CAAE,KAAM,EAAa,KAAM,IAAI,CACxC,CAEA,YAAgB,EAAQ,CACtB,MAAO,IAAO,MAAO,IAAQ,UAAY,GAAe,CAAG,IAAM,EACnE,CC7BM,YAAuB,EAAgB,EAAa,CACxD,MAAO,GAAK,OAAO,SAAC,EAAQ,EAAK,EAAC,CAAK,MAAE,GAAO,GAAO,EAAO,GAAK,CAA5B,EAAqC,CAAA,CAAS,CACvF,CCsMM,YAAuB,QAAoC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAC/D,GAAM,GAAY,GAAa,CAAI,EAC7B,EAAiB,GAAkB,CAAI,EAEvC,EAA8B,GAAqB,CAAI,EAA/C,EAAW,EAAA,KAAE,EAAI,EAAA,KAE/B,GAAI,EAAY,SAAW,EAIzB,MAAO,IAAK,CAAA,EAAI,CAAgB,EAGlC,GAAM,GAAS,GAAI,GACjB,GACE,EACA,EACA,EAEI,SAAC,EAAM,CAAK,MAAA,IAAa,EAAM,CAAM,CAAzB,EAEZ,EAAQ,CACb,EAGH,MAAO,GAAkB,EAAO,KAAK,GAAiB,CAAc,CAAC,EAAsB,CAC7F,CAEM,YACJ,EACA,EACA,EAAiD,CAAjD,MAAA,KAAA,QAAA,GAAA,IAEO,SAAC,EAA2B,CAGjC,GACE,EACA,UAAA,CAaE,OAZQ,GAAW,EAAW,OAExB,EAAS,GAAI,OAAM,CAAM,EAG3B,EAAS,EAIT,EAAuB,aAGlB,EAAC,CACR,GACE,EACA,UAAA,CACE,GAAM,GAAS,GAAK,EAAY,GAAI,CAAgB,EAChD,EAAgB,GACpB,EAAO,UACL,EACE,EACA,SAAC,EAAK,CAEJ,EAAO,GAAK,EACP,GAEH,GAAgB,GAChB,KAEG,GAGH,EAAW,KAAK,EAAe,EAAO,MAAK,CAAE,CAAC,CAElD,EACA,UAAA,CACE,AAAK,EAAE,GAGL,EAAW,SAAQ,CAEvB,CAAC,CACF,CAEL,EACA,CAAU,GAjCL,EAAI,EAAG,EAAI,EAAQ,MAAnB,CAAC,CAoCZ,EACA,CAAU,CAEd,CACF,CAMA,YAAuB,EAAsC,EAAqB,EAA0B,CAC1G,AAAI,EACF,GAAgB,EAAc,EAAW,CAAO,EAEhD,EAAO,CAEX,CC3RM,YACJ,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EAAgC,CAGhC,GAAM,GAAc,CAAA,EAEhB,EAAS,EAET,EAAQ,EAER,EAAa,GAKX,EAAgB,UAAA,CAIpB,AAAI,GAAc,CAAC,EAAO,QAAU,CAAC,GACnC,EAAW,SAAQ,CAEvB,EAGM,EAAY,SAAC,EAAQ,CAAK,MAAC,GAAS,EAAa,EAAW,CAAK,EAAI,EAAO,KAAK,CAAK,CAA5D,EAE1B,EAAa,SAAC,EAAQ,CAI1B,GAAU,EAAW,KAAK,CAAY,EAItC,IAKA,GAAI,GAAgB,GAGpB,EAAU,EAAQ,EAAO,GAAO,CAAC,EAAE,UACjC,EACE,EACA,SAAC,EAAU,CAGT,GAAY,MAAZ,EAAe,CAAU,EAEzB,AAAI,EAGF,EAAU,CAAiB,EAG3B,EAAW,KAAK,CAAU,CAE9B,EACA,UAAA,CAGE,EAAgB,EAClB,EAEA,OACA,UAAA,CAIE,GAAI,EAKF,GAAI,CAIF,IAKA,qBACE,GAAM,GAAgB,EAAO,MAAK,EAIlC,AAAI,EACF,GAAgB,EAAY,EAAmB,UAAA,CAAM,MAAA,GAAW,CAAa,CAAxB,CAAyB,EAE9E,EAAW,CAAa,GARrB,EAAO,QAAU,EAAS,OAYjC,EAAa,QACN,EAAP,CACA,EAAW,MAAM,CAAG,EAG1B,CAAC,CACF,CAEL,EAGA,SAAO,UACL,EAAyB,EAAY,EAAW,UAAA,CAE9C,EAAa,GACb,EAAa,CACf,CAAC,CAAC,EAKG,UAAA,CACL,GAAmB,MAAnB,EAAmB,CACrB,CACF,CClEM,YACJ,EACA,EACA,EAA6B,CAE7B,MAFA,KAAA,QAAA,GAAA,KAEI,EAAW,CAAc,EAEpB,GAAS,SAAC,EAAG,EAAC,CAAK,MAAA,GAAI,SAAC,EAAQ,EAAU,CAAK,MAAA,GAAe,EAAG,EAAG,EAAG,CAAE,CAA1B,CAA2B,EAAE,EAAU,EAAQ,EAAG,CAAC,CAAC,CAAC,CAAjF,EAAoF,CAAU,EAC/G,OAAO,IAAmB,UACnC,GAAa,GAGR,EAAQ,SAAC,EAAQ,EAAU,CAAK,MAAA,IAAe,EAAQ,EAAY,EAAS,CAAU,CAAtD,CAAuD,EAChG,CChCM,YAAmD,EAA6B,CAA7B,MAAA,KAAA,QAAA,GAAA,KAChD,GAAS,GAAU,CAAU,CACtC,CCNM,aAAmB,CACvB,MAAO,IAAS,CAAC,CACnB,CCmDM,aAAgB,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACrB,MAAO,IAAS,EAAG,GAAK,EAAM,GAAa,CAAI,CAAC,CAAC,CACnD,CC9DM,WAAgD,EAA0B,CAC9E,MAAO,IAAI,GAA+B,SAAC,EAAU,CACnD,EAAU,EAAiB,CAAE,EAAE,UAAU,CAAU,CACrD,CAAC,CACH,CChDA,GAAM,IAA0B,CAAC,cAAe,gBAAgB,EAC1D,GAAqB,CAAC,mBAAoB,qBAAqB,EAC/D,GAAgB,CAAC,KAAM,KAAK,EA8N5B,WACJ,EACA,EACA,EACA,EAAsC,CAMtC,GAJI,EAAW,CAAO,GACpB,GAAiB,EACjB,EAAU,QAER,EACF,MAAO,GAAa,EAAQ,EAAW,CAA+B,EAAE,KAAK,GAAiB,CAAc,CAAC,EAUzG,GAAA,GAAA,EAEJ,GAAc,CAAM,EAChB,GAAmB,IAAI,SAAC,EAAU,CAAK,MAAA,UAAC,EAAY,CAAK,MAAA,GAAO,GAAY,EAAW,EAAS,CAA+B,CAAtE,CAAlB,CAAyF,EAElI,GAAwB,CAAM,EAC5B,GAAwB,IAAI,GAAwB,EAAQ,CAAS,CAAC,EACtE,GAA0B,CAAM,EAChC,GAAc,IAAI,GAAwB,EAAQ,CAAS,CAAC,EAC5D,CAAA,EAAE,CAAA,EATD,EAAG,EAAA,GAAE,EAAM,EAAA,GAgBlB,GAAI,CAAC,GACC,GAAY,CAAM,EACpB,MAAO,IAAS,SAAC,EAAc,CAAK,MAAA,GAAU,EAAW,EAAW,CAA+B,CAA/D,CAAgE,EAClG,EAAU,CAAM,CAAC,EAOvB,GAAI,CAAC,EACH,KAAM,IAAI,WAAU,sBAAsB,EAG5C,MAAO,IAAI,GAAc,SAAC,EAAU,CAIlC,GAAM,GAAU,UAAA,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAAmB,MAAA,GAAW,KAAK,EAAI,EAAK,OAAS,EAAO,EAAK,EAAE,CAAhD,EAEpC,SAAI,CAAO,EAEJ,UAAA,CAAM,MAAA,GAAQ,CAAO,CAAf,CACf,CAAC,CACH,CASA,YAAiC,EAAa,EAAiB,CAC7D,MAAO,UAAC,EAAkB,CAAK,MAAA,UAAC,EAAY,CAAK,MAAA,GAAO,GAAY,EAAW,CAAO,CAArC,CAAlB,CACjC,CAOA,YAAiC,EAAW,CAC1C,MAAO,GAAW,EAAO,WAAW,GAAK,EAAW,EAAO,cAAc,CAC3E,CAOA,YAAmC,EAAW,CAC5C,MAAO,GAAW,EAAO,EAAE,GAAK,EAAW,EAAO,GAAG,CACvD,CAOA,YAAuB,EAAW,CAChC,MAAO,GAAW,EAAO,gBAAgB,GAAK,EAAW,EAAO,mBAAmB,CACrF,CC/LM,YACJ,EACA,EACA,EAAsC,CAEtC,MAAI,GACK,GAAoB,EAAY,CAAa,EAAE,KAAK,GAAiB,CAAc,CAAC,EAGtF,GAAI,GAAoB,SAAC,EAAU,CACxC,GAAM,GAAU,UAAA,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAAc,MAAA,GAAW,KAAK,EAAE,SAAW,EAAI,EAAE,GAAK,CAAC,CAAzC,EACzB,EAAW,EAAW,CAAO,EACnC,MAAO,GAAW,CAAa,EAAI,UAAA,CAAM,MAAA,GAAc,EAAS,CAAQ,CAA/B,EAAmC,MAC9E,CAAC,CACH,CCtBM,YACJ,EACA,EACA,EAAyC,CAFzC,AAAA,IAAA,QAAA,GAAA,GAEA,IAAA,QAAA,GAAA,IAIA,GAAI,GAAmB,GAEvB,MAAI,IAAuB,MAIzB,CAAI,GAAY,CAAmB,EACjC,EAAY,EAIZ,EAAmB,GAIhB,GAAI,GAAW,SAAC,EAAU,CAI/B,GAAI,GAAM,GAAY,CAAO,EAAI,CAAC,EAAU,EAAW,IAAG,EAAK,EAE/D,AAAI,EAAM,GAER,GAAM,GAIR,GAAI,GAAI,EAGR,MAAO,GAAU,SAAS,UAAA,CACxB,AAAK,EAAW,QAEd,GAAW,KAAK,GAAG,EAEnB,AAAI,GAAK,EAGP,KAAK,SAAS,OAAW,CAAgB,EAGzC,EAAW,SAAQ,EAGzB,EAAG,CAAG,CACR,CAAC,CACH,CChGM,YAAe,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACpB,GAAM,GAAY,GAAa,CAAI,EAC7B,EAAa,GAAU,EAAM,GAAQ,EACrC,EAAU,EAChB,MAAO,AAAC,GAAQ,OAGZ,EAAQ,SAAW,EAEnB,EAAU,EAAQ,EAAE,EAEpB,GAAS,CAAU,EAAE,GAAK,EAAS,CAAS,CAAC,EAL7C,CAMN,CCjEO,GAAM,IAAQ,GAAI,GAAkB,EAAI,ECpCvC,GAAA,IAAY,MAAK,QAMnB,YAA4B,EAAiB,CACjD,MAAO,GAAK,SAAW,GAAK,GAAQ,EAAK,EAAE,EAAI,EAAK,GAAM,CAC5D,CCoDM,WAAoB,EAAiD,EAAa,CACtF,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAEhC,GAAI,GAAQ,EAIZ,EAAO,UAIL,EAAyB,EAAY,SAAC,EAAK,CAAK,MAAA,GAAU,KAAK,EAAS,EAAO,GAAO,GAAK,EAAW,KAAK,CAAK,CAAhE,CAAiE,CAAC,CAEtH,CAAC,CACH,CCxBM,aAAa,QAAC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAClB,GAAM,GAAiB,GAAkB,CAAI,EAEvC,EAAU,GAAe,CAAI,EAEnC,MAAO,GAAQ,OACX,GAAI,GAAsB,SAAC,EAAU,CAGnC,GAAI,GAAuB,EAAQ,IAAI,UAAA,CAAM,MAAA,CAAA,CAAA,CAAE,EAK3C,EAAY,EAAQ,IAAI,UAAA,CAAM,MAAA,EAAA,CAAK,EAGvC,EAAW,IAAI,UAAA,CACb,EAAU,EAAY,IACxB,CAAC,EAKD,mBAAS,EAAW,CAClB,EAAU,EAAQ,EAAY,EAAE,UAC9B,EACE,EACA,SAAC,EAAK,CAKJ,GAJA,EAAQ,GAAa,KAAK,CAAK,EAI3B,EAAQ,MAAM,SAAC,EAAM,CAAK,MAAA,GAAO,MAAP,CAAa,EAAG,CAC5C,GAAM,GAAc,EAAQ,IAAI,SAAC,EAAM,CAAK,MAAA,GAAO,MAAK,CAAZ,CAAe,EAE3D,EAAW,KAAK,EAAiB,EAAc,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAM,CAAA,CAAA,EAAI,CAAM,EAI/D,EAAQ,KAAK,SAAC,EAAQ,EAAC,CAAK,MAAA,CAAC,EAAO,QAAU,EAAU,EAA5B,CAA8B,GAC5D,EAAW,SAAQ,EAGzB,EACA,UAAA,CAGE,EAAU,GAAe,GAIzB,CAAC,EAAQ,GAAa,QAAU,EAAW,SAAQ,CACrD,CAAC,CACF,GA9BI,EAAc,EAAG,CAAC,EAAW,QAAU,EAAc,EAAQ,OAAQ,MAArE,CAAW,EAmCpB,MAAO,WAAA,CACL,EAAU,EAAY,IACxB,CACF,CAAC,EACD,CACN,CC9DM,YAAmB,EAAoD,CAC3E,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAW,GACX,EAAsB,KACtB,EAA6C,KAC7C,EAAa,GAEX,EAAc,UAAA,CAGlB,GAFA,GAAkB,MAAlB,EAAoB,YAAW,EAC/B,EAAqB,KACjB,EAAU,CACZ,EAAW,GACX,GAAM,GAAQ,EACd,EAAY,KACZ,EAAW,KAAK,CAAK,EAEvB,GAAc,EAAW,SAAQ,CACnC,EAEM,EAAkB,UAAA,CACtB,EAAqB,KACrB,GAAc,EAAW,SAAQ,CACnC,EAEA,EAAO,UACL,EACE,EACA,SAAC,EAAK,CACJ,EAAW,GACX,EAAY,EACP,GACH,EAAU,EAAiB,CAAK,CAAC,EAAE,UAChC,EAAqB,EAAyB,EAAY,EAAa,CAAe,CAAE,CAG/F,EACA,UAAA,CACE,EAAa,GACZ,EAAC,GAAY,CAAC,GAAsB,EAAmB,SAAW,EAAW,SAAQ,CACxF,CAAC,CACF,CAEL,CAAC,CACH,CC3CM,YAAuB,EAAkB,EAAyC,CAAzC,MAAA,KAAA,QAAA,GAAA,IACtC,GAAM,UAAA,CAAM,MAAA,IAAM,EAAU,CAAS,CAAzB,CAA0B,CAC/C,CCEM,YAAyB,EAAoB,EAAsC,CAAtC,MAAA,KAAA,QAAA,GAAA,MAGjD,EAAmB,GAAgB,KAAhB,EAAoB,EAEhC,EAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAiB,CAAA,EACjB,EAAQ,EAEZ,EAAO,UACL,EACE,EACA,SAAC,EAAK,aACA,EAAuB,KAK3B,AAAI,IAAU,IAAsB,GAClC,EAAQ,KAAK,CAAA,CAAE,MAIjB,OAAqB,GAAA,GAAA,CAAO,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAAzB,GAAM,GAAM,EAAA,MACf,EAAO,KAAK,CAAK,EAMb,GAAc,EAAO,QACvB,GAAS,GAAM,KAAN,EAAU,CAAA,EACnB,EAAO,KAAK,CAAM,qGAItB,GAAI,MAIF,OAAqB,GAAA,GAAA,CAAM,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAAxB,GAAM,GAAM,EAAA,MACf,GAAU,EAAS,CAAM,EACzB,EAAW,KAAK,CAAM,oGAG5B,EACA,UAAA,aAGE,OAAqB,GAAA,GAAA,CAAO,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAAzB,GAAM,GAAM,EAAA,MACf,EAAW,KAAK,CAAM,oGAExB,EAAW,SAAQ,CACrB,EAEA,OACA,UAAA,CAEE,EAAU,IACZ,CAAC,CACF,CAEL,CAAC,CACH,CCbM,YACJ,EAAgD,CAEhD,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAgC,KAChC,EAAY,GACZ,EAEJ,EAAW,EAAO,UAChB,EAAyB,EAAY,OAAW,OAAW,SAAC,EAAG,CAC7D,EAAgB,EAAU,EAAS,EAAK,GAAW,CAAQ,EAAE,CAAM,CAAC,CAAC,EACrE,AAAI,EACF,GAAS,YAAW,EACpB,EAAW,KACX,EAAc,UAAU,CAAU,GAIlC,EAAY,EAEhB,CAAC,CAAC,EAGA,GAMF,GAAS,YAAW,EACpB,EAAW,KACX,EAAe,UAAU,CAAU,EAEvC,CAAC,CACH,CC/HM,YACJ,EACA,EACA,EACA,EACA,EAAqC,CAErC,MAAO,UAAC,EAAuB,EAA2B,CAIxD,GAAI,GAAW,EAIX,EAAa,EAEb,EAAQ,EAGZ,EAAO,UACL,EACE,EACA,SAAC,EAAK,CAEJ,GAAM,GAAI,IAEV,EAAQ,EAEJ,EAAY,EAAO,EAAO,CAAC,EAIzB,GAAW,GAAO,GAGxB,GAAc,EAAW,KAAK,CAAK,CACrC,EAGA,GACG,UAAA,CACC,GAAY,EAAW,KAAK,CAAK,EACjC,EAAW,SAAQ,CACrB,CAAE,CACL,CAEL,CACF,CCnCM,aAAuB,QAAO,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAClC,GAAM,GAAiB,GAAkB,CAAI,EAC7C,MAAO,GACH,GAAK,GAAa,MAAA,OAAA,EAAA,CAAA,EAAA,EAAK,CAAoC,CAAA,CAAA,EAAG,GAAiB,CAAc,CAAC,EAC9F,EAAQ,SAAC,EAAQ,EAAU,CACzB,GAAiB,EAAA,CAAE,CAAM,EAAA,EAAK,GAAe,CAAI,CAAC,CAAA,CAAA,EAAG,CAAU,CACjE,CAAC,CACP,CCUM,aAA2B,QAC/B,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAEA,MAAO,IAAa,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAY,CAAA,CAAA,CACtC,CC+BM,YACJ,EACA,EAA6G,CAE7G,MAAO,GAAW,CAAc,EAAI,GAAS,EAAS,EAAgB,CAAC,EAAI,GAAS,EAAS,CAAC,CAChG,CCpBM,YAA0B,EAAiB,EAAyC,CAAzC,MAAA,KAAA,QAAA,GAAA,IACxC,EAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAkC,KAClC,EAAsB,KACtB,EAA0B,KAExB,EAAO,UAAA,CACX,GAAI,EAAY,CAEd,EAAW,YAAW,EACtB,EAAa,KACb,GAAM,GAAQ,EACd,EAAY,KACZ,EAAW,KAAK,CAAK,EAEzB,EACA,YAAqB,CAInB,GAAM,GAAa,EAAY,EACzB,EAAM,EAAU,IAAG,EACzB,GAAI,EAAM,EAAY,CAEpB,EAAa,KAAK,SAAS,OAAW,EAAa,CAAG,EACtD,EAAW,IAAI,CAAU,EACzB,OAGF,EAAI,CACN,CAEA,EAAO,UACL,EACE,EACA,SAAC,EAAQ,CACP,EAAY,EACZ,EAAW,EAAU,IAAG,EAGnB,GACH,GAAa,EAAU,SAAS,EAAc,CAAO,EACrD,EAAW,IAAI,CAAU,EAE7B,EACA,UAAA,CAGE,EAAI,EACJ,EAAW,SAAQ,CACrB,EAEA,OACA,UAAA,CAEE,EAAY,EAAa,IAC3B,CAAC,CACF,CAEL,CAAC,CACH,CCpFM,YAA+B,EAAe,CAClD,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAW,GACf,EAAO,UACL,EACE,EACA,SAAC,EAAK,CACJ,EAAW,GACX,EAAW,KAAK,CAAK,CACvB,EACA,UAAA,CACE,AAAK,GACH,EAAW,KAAK,CAAa,EAE/B,EAAW,SAAQ,CACrB,CAAC,CACF,CAEL,CAAC,CACH,CCXM,YAAkB,EAAa,CACnC,MAAO,IAAS,EAEZ,UAAA,CAAM,MAAA,EAAA,EACN,EAAQ,SAAC,EAAQ,EAAU,CACzB,GAAI,GAAO,EACX,EAAO,UACL,EAAyB,EAAY,SAAC,EAAK,CAIzC,AAAI,EAAE,GAAQ,GACZ,GAAW,KAAK,CAAK,EAIjB,GAAS,GACX,EAAW,SAAQ,EAGzB,CAAC,CAAC,CAEN,CAAC,CACP,CC9BM,aAAwB,CAC5B,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,EAAO,UAAU,EAAyB,EAAY,EAAI,CAAC,CAC7D,CAAC,CACH,CCCM,YAAmB,EAAQ,CAC/B,MAAO,GAAI,UAAA,CAAM,MAAA,EAAA,CAAK,CACxB,CC2BM,YACJ,EACA,EAAmC,CAEnC,MAAI,GAEK,SAAC,EAAqB,CAC3B,MAAA,IAAO,EAAkB,KAAK,GAAK,CAAC,EAAG,GAAc,CAAE,EAAG,EAAO,KAAK,GAAU,CAAqB,CAAC,CAAC,CAAvG,EAGG,GAAS,SAAC,EAAO,EAAK,CAAK,MAAA,GAAsB,EAAO,CAAK,EAAE,KAAK,GAAK,CAAC,EAAG,GAAM,CAAK,CAAC,CAA9D,CAA+D,CACnG,CCxBM,YAAmB,EAAoB,EAAyC,CAAzC,AAAA,IAAA,QAAA,GAAA,IAC3C,GAAM,GAAW,GAAM,EAAK,CAAS,EACrC,MAAO,IAAU,UAAA,CAAM,MAAA,EAAA,CAAQ,CACjC,CC4EM,WACJ,EACA,EAA0D,CAA1D,MAAA,KAAA,QAAA,GAA+B,IAK/B,EAAa,GAAU,KAAV,EAAc,GAEpB,EAAQ,SAAC,EAAQ,EAAU,CAGhC,GAAI,GAEA,EAAQ,GAEZ,EAAO,UACL,EAAyB,EAAY,SAAC,EAAK,CAEzC,GAAM,GAAa,EAAY,CAAK,EAKpC,AAAI,IAAS,CAAC,EAAY,EAAa,CAAU,IAM/C,GAAQ,GACR,EAAc,EAGd,EAAW,KAAK,CAAK,EAEzB,CAAC,CAAC,CAEN,CAAC,CACH,CAEA,YAAwB,EAAQ,EAAM,CACpC,MAAO,KAAM,CACf,CCnHM,WAAwD,EAAQ,EAAuC,CAC3G,MAAO,GAAqB,SAAC,EAAM,EAAI,CAAK,MAAA,GAAU,EAAQ,EAAE,GAAM,EAAE,EAAI,EAAI,EAAE,KAAS,EAAE,EAAjD,CAAqD,CACnG,CCLM,aAAiB,QAAI,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACzB,MAAO,UAAC,EAAqB,CAAK,MAAA,IAAO,EAAQ,EAAE,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAM,CAAA,CAAA,CAAA,CAA3B,CACpC,CCHM,WAAsB,EAAoB,CAC9C,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAGhC,GAAI,CACF,EAAO,UAAU,CAAU,UAE3B,EAAW,IAAI,CAAQ,EAE3B,CAAC,CACH,CC9BM,YAAsB,EAAa,CACvC,MAAO,IAAS,EACZ,UAAA,CAAM,MAAA,EAAA,EACN,EAAQ,SAAC,EAAQ,EAAU,CAKzB,GAAI,GAAc,CAAA,EAClB,EAAO,UACL,EACE,EACA,SAAC,EAAK,CAEJ,EAAO,KAAK,CAAK,EAGjB,EAAQ,EAAO,QAAU,EAAO,MAAK,CACvC,EACA,UAAA,aAGE,OAAoB,GAAA,GAAA,CAAM,EAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,KAAA,EAAE,CAAvB,GAAM,GAAK,EAAA,MACd,EAAW,KAAK,CAAK,oGAEvB,EAAW,SAAQ,CACrB,EAEA,OACA,UAAA,CAEE,EAAS,IACX,CAAC,CACF,CAEL,CAAC,CACP,CC1DM,aAAe,QAAI,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACvB,GAAM,GAAY,GAAa,CAAI,EAC7B,EAAa,GAAU,EAAM,GAAQ,EAC3C,SAAO,GAAe,CAAI,EAEnB,EAAQ,SAAC,EAAQ,EAAU,CAChC,GAAS,CAAU,EAAE,GAAI,EAAA,CAAE,CAAM,EAAA,EAAM,CAA6B,CAAA,EAAG,CAAS,CAAC,EAAE,UAAU,CAAU,CACzG,CAAC,CACH,CCcM,aAAmB,QACvB,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAEA,MAAO,IAAK,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAY,CAAA,CAAA,CAC9B,CCmEM,YAAoB,EAAqC,OACzD,EAAQ,IACR,EAEJ,MAAI,IAAiB,MACnB,CAAI,MAAO,IAAkB,SACxB,GAA4B,EAAa,MAAzC,EAAK,IAAA,OAAG,IAAQ,EAAE,EAAU,EAAa,OAE5C,EAAQ,GAIL,GAAS,EACZ,UAAA,CAAM,MAAA,EAAA,EACN,EAAQ,SAAC,EAAQ,EAAU,CACzB,GAAI,GAAQ,EACR,EAEE,EAAc,UAAA,CAGlB,GAFA,GAAS,MAAT,EAAW,YAAW,EACtB,EAAY,KACR,GAAS,KAAM,CACjB,GAAM,GAAW,MAAO,IAAU,SAAW,GAAM,CAAK,EAAI,EAAU,EAAM,CAAK,CAAC,EAC5E,EAAqB,EAAyB,EAAY,UAAA,CAC9D,EAAmB,YAAW,EAC9B,EAAiB,CACnB,CAAC,EACD,EAAS,UAAU,CAAkB,MAErC,GAAiB,CAErB,EAEM,EAAoB,UAAA,CACxB,GAAI,GAAY,GAChB,EAAY,EAAO,UACjB,EAAyB,EAAY,OAAW,UAAA,CAC9C,AAAI,EAAE,EAAQ,EACZ,AAAI,EACF,EAAW,EAEX,EAAY,GAGd,EAAW,SAAQ,CAEvB,CAAC,CAAC,EAGA,GACF,EAAW,CAEf,EAEA,EAAiB,CACnB,CAAC,CACP,CC7HM,YAAoB,EAAyB,CACjD,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAW,GACX,EAAsB,KAC1B,EAAO,UACL,EAAyB,EAAY,SAAC,EAAK,CACzC,EAAW,GACX,EAAY,CACd,CAAC,CAAC,EAEJ,EAAS,UACP,EACE,EACA,UAAA,CACE,GAAI,EAAU,CACZ,EAAW,GACX,GAAM,GAAQ,EACd,EAAY,KACZ,EAAW,KAAK,CAAK,EAEzB,EACA,EAAI,CACL,CAEL,CAAC,CACH,CCgBM,YAAwB,EAA6D,EAAQ,CAMjG,MAAO,GAAQ,GAAc,EAAa,EAAW,UAAU,QAAU,EAAG,EAAI,CAAC,CACnF,CCiDM,YAAmB,EAA4B,CAA5B,AAAA,IAAA,QAAA,GAAA,CAAA,GACf,GAAA,GAAgH,EAAO,UAAvH,EAAS,IAAA,OAAG,UAAA,CAAM,MAAA,IAAI,EAAJ,EAAgB,EAAE,EAA4E,EAAO,aAAnF,EAAY,IAAA,OAAG,GAAI,EAAE,EAAuD,EAAO,gBAA9D,EAAe,IAAA,OAAG,GAAI,EAAE,EAA+B,EAAO,oBAAtC,EAAmB,IAAA,OAAG,GAAI,EAUnH,MAAO,UAAC,EAAa,CACnB,GAAI,GAAuC,KACvC,EAAuC,KACvC,EAAiC,KACjC,EAAW,EACX,EAAe,GACf,EAAa,GAEX,EAAc,UAAA,CAClB,GAAe,MAAf,EAAiB,YAAW,EAC5B,EAAkB,IACpB,EAGM,EAAQ,UAAA,CACZ,EAAW,EACX,EAAa,EAAU,KACvB,EAAe,EAAa,EAC9B,EACM,EAAsB,UAAA,CAG1B,GAAM,GAAO,EACb,EAAK,EACL,GAAI,MAAJ,EAAM,YAAW,CACnB,EAEA,MAAO,GAAc,SAAC,EAAQ,GAAU,CACtC,IACI,CAAC,GAAc,CAAC,GAClB,EAAW,EAOb,GAAM,IAAQ,EAAU,GAAO,KAAP,EAAW,EAAS,EAO5C,GAAW,IAAI,UAAA,CACb,IAKI,IAAa,GAAK,CAAC,GAAc,CAAC,GACpC,GAAkB,GAAY,EAAqB,CAAmB,EAE1E,CAAC,EAID,GAAK,UAAU,EAAU,EAEpB,GAMH,GAAa,GAAI,IAAe,CAC9B,KAAM,SAAC,GAAK,CAAK,MAAA,IAAK,KAAK,EAAK,CAAf,EACjB,MAAO,SAAC,GAAG,CACT,EAAa,GACb,EAAW,EACX,EAAkB,GAAY,EAAO,EAAc,EAAG,EACtD,GAAK,MAAM,EAAG,CAChB,EACA,SAAU,UAAA,CACR,EAAe,GACf,EAAW,EACX,EAAkB,GAAY,EAAO,CAAe,EACpD,GAAK,SAAQ,CACf,EACD,EACD,GAAK,CAAM,EAAE,UAAU,CAAU,EAErC,CAAC,EAAE,CAAa,CAClB,CACF,CAEA,YACE,EACA,EAA+C,QAC/C,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,EAAA,GAAA,UAAA,GAEA,MAAI,KAAO,GACT,GAAK,EAEE,MAGL,IAAO,GACF,KAGF,EAAE,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAI,CAAA,CAAA,EACd,KAAK,GAAK,CAAC,CAAC,EACZ,UAAU,UAAA,CAAM,MAAA,GAAK,CAAL,CAAO,CAC5B,CCzGM,WACJ,EACA,EACA,EAAyB,WAErB,EACA,EAAW,GACf,MAAI,IAAsB,MAAO,IAAuB,SACnD,GAA8E,EAAkB,WAAhG,EAAU,IAAA,OAAG,IAAQ,EAAE,EAAuD,EAAkB,WAAzE,EAAU,IAAA,OAAG,IAAQ,EAAE,EAAgC,EAAkB,SAAlD,EAAQ,IAAA,OAAG,GAAK,EAAE,EAAc,EAAkB,WAEnG,EAAa,GAAkB,KAAlB,EAAsB,IAE9B,GAAS,CACd,UAAW,UAAA,CAAM,MAAA,IAAI,IAAc,EAAY,EAAY,CAAS,CAAnD,EACjB,aAAc,GACd,gBAAiB,GACjB,oBAAqB,EACtB,CACH,CCvIM,YAAkB,EAAa,CACnC,MAAO,GAAO,SAAC,EAAG,EAAK,CAAK,MAAA,IAAS,CAAT,CAAc,CAC5C,CCWM,YAAuB,EAAyB,CACpD,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAS,GAEP,EAAiB,EACrB,EACA,UAAA,CACE,GAAc,MAAd,EAAgB,YAAW,EAC3B,EAAS,EACX,EACA,EAAI,EAGN,EAAU,CAAQ,EAAE,UAAU,CAAc,EAE5C,EAAO,UAAU,EAAyB,EAAY,SAAC,EAAK,CAAK,MAAA,IAAU,EAAW,KAAK,CAAK,CAA/B,CAAgC,CAAC,CACpG,CAAC,CACH,CCRM,YAAmB,QAAO,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GAC9B,GAAM,GAAY,GAAa,CAAM,EACrC,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAIhC,AAAC,GAAY,GAAO,EAAQ,EAAQ,CAAS,EAAI,GAAO,EAAQ,CAAM,GAAG,UAAU,CAAU,CAC/F,CAAC,CACH,CCmBM,WACJ,EACA,EAA6G,CAE7G,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAyD,KACzD,EAAQ,EAER,EAAa,GAIX,EAAgB,UAAA,CAAM,MAAA,IAAc,CAAC,GAAmB,EAAW,SAAQ,CAArD,EAE5B,EAAO,UACL,EACE,EACA,SAAC,EAAK,CAEJ,GAAe,MAAf,EAAiB,YAAW,EAC5B,GAAI,GAAa,EACX,EAAa,IAEnB,EAAU,EAAQ,EAAO,CAAU,CAAC,EAAE,UACnC,EAAkB,EACjB,EAIA,SAAC,EAAU,CAAK,MAAA,GAAW,KAAK,EAAiB,EAAe,EAAO,EAAY,EAAY,GAAY,EAAI,CAAU,CAAzG,EAChB,UAAA,CAIE,EAAkB,KAClB,EAAa,CACf,CAAC,CACD,CAEN,EACA,UAAA,CACE,EAAa,GACb,EAAa,CACf,CAAC,CACF,CAEL,CAAC,CACH,CCvFM,WAAuB,EAA8B,CACzD,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,EAAU,CAAQ,EAAE,UAAU,EAAyB,EAAY,UAAA,CAAM,MAAA,GAAW,SAAQ,CAAnB,EAAuB,EAAI,CAAC,EACrG,CAAC,EAAW,QAAU,EAAO,UAAU,CAAU,CACnD,CAAC,CACH,CCIM,YAAuB,EAAiD,EAAiB,CAAjB,MAAA,KAAA,QAAA,GAAA,IACrE,EAAQ,SAAC,EAAQ,EAAU,CAChC,GAAI,GAAQ,EACZ,EAAO,UACL,EAAyB,EAAY,SAAC,EAAK,CACzC,GAAM,GAAS,EAAU,EAAO,GAAO,EACvC,AAAC,IAAU,IAAc,EAAW,KAAK,CAAK,EAC9C,CAAC,GAAU,EAAW,SAAQ,CAChC,CAAC,CAAC,CAEN,CAAC,CACH,CCyCM,WACJ,EACA,EACA,EAA8B,CAK9B,GAAM,GACJ,EAAW,CAAc,GAAK,GAAS,EAElC,CAAE,KAAM,EAA2E,MAAK,EAAE,SAAQ,CAAA,EACnG,EAEN,MAAO,GACH,EAAQ,SAAC,EAAQ,EAAU,OACzB,AAAA,GAAA,EAAY,aAAS,MAAA,IAAA,QAAA,EAAA,KAArB,CAAW,EACX,GAAI,GAAU,GACd,EAAO,UACL,EACE,EACA,SAAC,EAAK,OACJ,AAAA,GAAA,EAAY,QAAI,MAAA,IAAA,QAAA,EAAA,KAAhB,EAAmB,CAAK,EACxB,EAAW,KAAK,CAAK,CACvB,EACA,UAAA,OACE,EAAU,GACV,GAAA,EAAY,YAAQ,MAAA,IAAA,QAAA,EAAA,KAApB,CAAW,EACX,EAAW,SAAQ,CACrB,EACA,SAAC,EAAG,OACF,EAAU,GACV,GAAA,EAAY,SAAK,MAAA,IAAA,QAAA,EAAA,KAAjB,EAAoB,CAAG,EACvB,EAAW,MAAM,CAAG,CACtB,EACA,UAAA,SACE,AAAI,GACF,IAAA,EAAY,eAAW,MAAA,IAAA,QAAA,EAAA,KAAvB,CAAW,GAEb,GAAA,EAAY,YAAQ,MAAA,IAAA,QAAA,EAAA,KAApB,CAAW,CACb,CAAC,CACF,CAEL,CAAC,EAID,EACN,CC9IO,GAAM,IAAwC,CACnD,QAAS,GACT,SAAU,IAiDN,YACJ,EACA,EAA8C,CAA9C,MAAA,KAAA,QAAA,GAAA,IAEO,EAAQ,SAAC,EAAQ,EAAU,CACxB,GAAA,GAAsB,EAAM,QAAnB,EAAa,EAAM,SAChC,EAAW,GACX,EAAsB,KACtB,EAAiC,KACjC,EAAa,GAEX,EAAgB,UAAA,CACpB,GAAS,MAAT,EAAW,YAAW,EACtB,EAAY,KACR,GACF,GAAI,EACJ,GAAc,EAAW,SAAQ,EAErC,EAEM,EAAoB,UAAA,CACxB,EAAY,KACZ,GAAc,EAAW,SAAQ,CACnC,EAEM,EAAgB,SAAC,EAAQ,CAC7B,MAAC,GAAY,EAAU,EAAiB,CAAK,CAAC,EAAE,UAAU,EAAyB,EAAY,EAAe,CAAiB,CAAC,CAAhI,EAEI,EAAO,UAAA,CACX,GAAI,EAAU,CAIZ,EAAW,GACX,GAAM,GAAQ,EACd,EAAY,KAEZ,EAAW,KAAK,CAAK,EACrB,CAAC,GAAc,EAAc,CAAK,EAEtC,EAEA,EAAO,UACL,EACE,EAMA,SAAC,EAAK,CACJ,EAAW,GACX,EAAY,EACZ,CAAE,IAAa,CAAC,EAAU,SAAY,GAAU,EAAI,EAAK,EAAc,CAAK,EAC9E,EACA,UAAA,CACE,EAAa,GACb,CAAE,IAAY,GAAY,GAAa,CAAC,EAAU,SAAW,EAAW,SAAQ,CAClF,CAAC,CACF,CAEL,CAAC,CACH,CCvEM,YACJ,EACA,EACA,EAA8B,CAD9B,AAAA,IAAA,QAAA,GAAA,IACA,IAAA,QAAA,GAAA,IAEA,GAAM,GAAY,GAAM,EAAU,CAAS,EAC3C,MAAO,IAAS,UAAA,CAAM,MAAA,EAAA,EAAW,CAAM,CACzC,CCJM,aAAwB,QAAO,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACnC,GAAM,GAAU,GAAkB,CAAM,EAExC,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAehC,OAdM,GAAM,EAAO,OACb,EAAc,GAAI,OAAM,CAAG,EAI7B,EAAW,EAAO,IAAI,UAAA,CAAM,MAAA,EAAA,CAAK,EAGjC,EAAQ,cAMH,EAAC,CACR,EAAU,EAAO,EAAE,EAAE,UACnB,EACE,EACA,SAAC,EAAK,CACJ,EAAY,GAAK,EACb,CAAC,GAAS,CAAC,EAAS,IAEtB,GAAS,GAAK,GAKb,GAAQ,EAAS,MAAM,EAAQ,IAAO,GAAW,MAEtD,EAGA,EAAI,CACL,GAnBI,EAAI,EAAG,EAAI,EAAK,MAAhB,CAAC,EAwBV,EAAO,UACL,EAAyB,EAAY,SAAC,EAAK,CACzC,GAAI,EAAO,CAET,GAAM,GAAM,EAAA,CAAI,CAAK,EAAA,EAAK,CAAW,CAAA,EACrC,EAAW,KAAK,EAAU,EAAO,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAM,CAAA,CAAA,EAAI,CAAM,EAEzD,CAAC,CAAC,CAEN,CAAC,CACH,CCxFM,aAAa,QAAO,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACxB,MAAO,GAAQ,SAAC,EAAQ,EAAU,CAChC,GAAS,MAAA,OAAA,EAAA,CAAC,CAA8B,EAAA,EAAM,CAAuC,CAAA,CAAA,EAAE,UAAU,CAAU,CAC7G,CAAC,CACH,CCCM,aAAiB,QAAkC,GAAA,CAAA,EAAA,EAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,GAAA,UAAA,GACvD,MAAO,IAAG,MAAA,OAAA,EAAA,CAAA,EAAA,EAAI,CAAW,CAAA,CAAA,CAC3B,CCYO,aAA4C,CACjD,GAAM,GAAY,GAAI,IAAwB,CAAC,EAC/C,SAAU,SAAU,mBAAoB,CAAE,KAAM,EAAK,CAAC,EACnD,UAAU,IAAM,EAAU,KAAK,QAAQ,CAAC,EAGpC,CACT,CCHO,WACL,EAAkB,EAAmB,SAChC,CACL,MAAO,OAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,CACtD,CAuBO,WACL,EAAkB,EAAmB,SAClC,CACH,GAAM,GAAK,GAAsB,EAAU,CAAI,EAC/C,GAAI,MAAO,IAAO,YAChB,KAAM,IAAI,gBACR,8BAA8B,kBAChC,EAGF,MAAO,EACT,CAsBO,YACL,EAAkB,EAAmB,SACtB,CACf,MAAO,GAAK,cAAiB,CAAQ,GAAK,MAC5C,CAOO,aAAqD,CAC1D,MAAO,UAAS,wBAAyB,cACrC,SAAS,eAAiB,MAEhC,CClEO,YACL,EACqB,CACrB,MAAO,GACL,EAAU,SAAS,KAAM,SAAS,EAClC,EAAU,SAAS,KAAM,UAAU,CACrC,EACG,KACC,GAAa,CAAC,EACd,EAAI,IAAM,CACR,GAAM,GAAS,GAAiB,EAChC,MAAO,OAAO,IAAW,YACrB,EAAG,SAAS,CAAM,EAClB,EACN,CAAC,EACD,EAAU,IAAO,GAAiB,CAAC,EACnC,EAAqB,CACvB,CACJ,CChBO,YACL,EACe,CACf,MAAO,CACL,EAAG,EAAG,WACN,EAAG,EAAG,SACR,CACF,CAWO,YACL,EAC2B,CAC3B,MAAO,GACL,EAAU,OAAQ,MAAM,EACxB,EAAU,OAAQ,QAAQ,CAC5B,EACG,KACC,GAAU,EAAG,EAAuB,EACpC,EAAI,IAAM,GAAiB,CAAE,CAAC,EAC9B,EAAU,GAAiB,CAAE,CAAC,CAChC,CACJ,CCxCO,YACL,EACe,CACf,MAAO,CACL,EAAG,EAAG,WACN,EAAG,EAAG,SACR,CACF,CAWO,YACL,EAC2B,CAC3B,MAAO,GACL,EAAU,EAAI,QAAQ,EACtB,EAAU,OAAQ,QAAQ,CAC5B,EACG,KACC,GAAU,EAAG,EAAuB,EACpC,EAAI,IAAM,GAAwB,CAAE,CAAC,EACrC,EAAU,GAAwB,CAAE,CAAC,CACvC,CACJ,CCpEA,GAAI,IAAW,UAAY,CACvB,GAAI,MAAO,MAAQ,YACf,MAAO,KASX,WAAkB,EAAK,EAAK,CACxB,GAAI,GAAS,GACb,SAAI,KAAK,SAAU,EAAO,EAAO,CAC7B,MAAI,GAAM,KAAO,EACb,GAAS,EACF,IAEJ,EACX,CAAC,EACM,CACX,CACA,MAAsB,WAAY,CAC9B,YAAmB,CACf,KAAK,YAAc,CAAC,CACxB,CACA,cAAO,eAAe,EAAQ,UAAW,OAAQ,CAI7C,IAAK,UAAY,CACb,MAAO,MAAK,YAAY,MAC5B,EACA,WAAY,GACZ,aAAc,EAClB,CAAC,EAKD,EAAQ,UAAU,IAAM,SAAU,EAAK,CACnC,GAAI,GAAQ,EAAS,KAAK,YAAa,CAAG,EACtC,EAAQ,KAAK,YAAY,GAC7B,MAAO,IAAS,EAAM,EAC1B,EAMA,EAAQ,UAAU,IAAM,SAAU,EAAK,EAAO,CAC1C,GAAI,GAAQ,EAAS,KAAK,YAAa,CAAG,EAC1C,AAAI,CAAC,EACD,KAAK,YAAY,GAAO,GAAK,EAG7B,KAAK,YAAY,KAAK,CAAC,EAAK,CAAK,CAAC,CAE1C,EAKA,EAAQ,UAAU,OAAS,SAAU,EAAK,CACtC,GAAI,GAAU,KAAK,YACf,EAAQ,EAAS,EAAS,CAAG,EACjC,AAAI,CAAC,GACD,EAAQ,OAAO,EAAO,CAAC,CAE/B,EAKA,EAAQ,UAAU,IAAM,SAAU,EAAK,CACnC,MAAO,CAAC,CAAC,CAAC,EAAS,KAAK,YAAa,CAAG,CAC5C,EAIA,EAAQ,UAAU,MAAQ,UAAY,CAClC,KAAK,YAAY,OAAO,CAAC,CAC7B,EAMA,EAAQ,UAAU,QAAU,SAAU,EAAU,EAAK,CACjD,AAAI,IAAQ,QAAU,GAAM,MAC5B,OAAS,GAAK,EAAG,EAAK,KAAK,YAAa,EAAK,EAAG,OAAQ,IAAM,CAC1D,GAAI,GAAQ,EAAG,GACf,EAAS,KAAK,EAAK,EAAM,GAAI,EAAM,EAAE,CACzC,CACJ,EACO,CACX,EAAE,CACN,EAAG,EAKC,GAAY,MAAO,SAAW,aAAe,MAAO,WAAa,aAAe,OAAO,WAAa,SAGpG,GAAY,UAAY,CACxB,MAAI,OAAO,SAAW,aAAe,OAAO,OAAS,KAC1C,OAEP,MAAO,OAAS,aAAe,KAAK,OAAS,KACtC,KAEP,MAAO,SAAW,aAAe,OAAO,OAAS,KAC1C,OAGJ,SAAS,aAAa,EAAE,CACnC,EAAG,EAQC,GAA2B,UAAY,CACvC,MAAI,OAAO,wBAA0B,WAI1B,sBAAsB,KAAK,EAAQ,EAEvC,SAAU,EAAU,CAAE,MAAO,YAAW,UAAY,CAAE,MAAO,GAAS,KAAK,IAAI,CAAC,CAAG,EAAG,IAAO,EAAE,CAAG,CAC7G,EAAG,EAGC,GAAkB,EAStB,YAAmB,EAAU,EAAO,CAChC,GAAI,GAAc,GAAO,EAAe,GAAO,EAAe,EAO9D,YAA0B,CACtB,AAAI,GACA,GAAc,GACd,EAAS,GAET,GACA,EAAM,CAEd,CAQA,YAA2B,CACvB,GAAwB,CAAc,CAC1C,CAMA,YAAiB,CACb,GAAI,GAAY,KAAK,IAAI,EACzB,GAAI,EAAa,CAEb,GAAI,EAAY,EAAe,GAC3B,OAMJ,EAAe,EACnB,KAEI,GAAc,GACd,EAAe,GACf,WAAW,EAAiB,CAAK,EAErC,EAAe,CACnB,CACA,MAAO,EACX,CAGA,GAAI,IAAgB,GAGhB,GAAiB,CAAC,MAAO,QAAS,SAAU,OAAQ,QAAS,SAAU,OAAQ,QAAQ,EAEvF,GAA4B,MAAO,mBAAqB,YAIxD,GAA0C,UAAY,CAMtD,YAAoC,CAMhC,KAAK,WAAa,GAMlB,KAAK,qBAAuB,GAM5B,KAAK,mBAAqB,KAM1B,KAAK,WAAa,CAAC,EACnB,KAAK,iBAAmB,KAAK,iBAAiB,KAAK,IAAI,EACvD,KAAK,QAAU,GAAS,KAAK,QAAQ,KAAK,IAAI,EAAG,EAAa,CAClE,CAOA,SAAyB,UAAU,YAAc,SAAU,EAAU,CACjE,AAAK,CAAC,KAAK,WAAW,QAAQ,CAAQ,GAClC,KAAK,WAAW,KAAK,CAAQ,EAG5B,KAAK,YACN,KAAK,SAAS,CAEtB,EAOA,EAAyB,UAAU,eAAiB,SAAU,EAAU,CACpE,GAAI,GAAY,KAAK,WACjB,EAAQ,EAAU,QAAQ,CAAQ,EAEtC,AAAI,CAAC,GACD,EAAU,OAAO,EAAO,CAAC,EAGzB,CAAC,EAAU,QAAU,KAAK,YAC1B,KAAK,YAAY,CAEzB,EAOA,EAAyB,UAAU,QAAU,UAAY,CACrD,GAAI,GAAkB,KAAK,iBAAiB,EAG5C,AAAI,GACA,KAAK,QAAQ,CAErB,EASA,EAAyB,UAAU,iBAAmB,UAAY,CAE9D,GAAI,GAAkB,KAAK,WAAW,OAAO,SAAU,EAAU,CAC7D,MAAO,GAAS,aAAa,EAAG,EAAS,UAAU,CACvD,CAAC,EAMD,SAAgB,QAAQ,SAAU,EAAU,CAAE,MAAO,GAAS,gBAAgB,CAAG,CAAC,EAC3E,EAAgB,OAAS,CACpC,EAOA,EAAyB,UAAU,SAAW,UAAY,CAGtD,AAAI,CAAC,IAAa,KAAK,YAMvB,UAAS,iBAAiB,gBAAiB,KAAK,gBAAgB,EAChE,OAAO,iBAAiB,SAAU,KAAK,OAAO,EAC9C,AAAI,GACA,MAAK,mBAAqB,GAAI,kBAAiB,KAAK,OAAO,EAC3D,KAAK,mBAAmB,QAAQ,SAAU,CACtC,WAAY,GACZ,UAAW,GACX,cAAe,GACf,QAAS,EACb,CAAC,GAGD,UAAS,iBAAiB,qBAAsB,KAAK,OAAO,EAC5D,KAAK,qBAAuB,IAEhC,KAAK,WAAa,GACtB,EAOA,EAAyB,UAAU,YAAc,UAAY,CAGzD,AAAI,CAAC,IAAa,CAAC,KAAK,YAGxB,UAAS,oBAAoB,gBAAiB,KAAK,gBAAgB,EACnE,OAAO,oBAAoB,SAAU,KAAK,OAAO,EAC7C,KAAK,oBACL,KAAK,mBAAmB,WAAW,EAEnC,KAAK,sBACL,SAAS,oBAAoB,qBAAsB,KAAK,OAAO,EAEnE,KAAK,mBAAqB,KAC1B,KAAK,qBAAuB,GAC5B,KAAK,WAAa,GACtB,EAQA,EAAyB,UAAU,iBAAmB,SAAU,EAAI,CAChE,GAAI,GAAK,EAAG,aAAc,EAAe,IAAO,OAAS,GAAK,EAE1D,EAAmB,GAAe,KAAK,SAAU,EAAK,CACtD,MAAO,CAAC,CAAC,CAAC,EAAa,QAAQ,CAAG,CACtC,CAAC,EACD,AAAI,GACA,KAAK,QAAQ,CAErB,EAMA,EAAyB,YAAc,UAAY,CAC/C,MAAK,MAAK,WACN,MAAK,UAAY,GAAI,IAElB,KAAK,SAChB,EAMA,EAAyB,UAAY,KAC9B,CACX,EAAE,EASE,GAAsB,SAAU,EAAQ,EAAO,CAC/C,OAAS,GAAK,EAAG,EAAK,OAAO,KAAK,CAAK,EAAG,EAAK,EAAG,OAAQ,IAAM,CAC5D,GAAI,GAAM,EAAG,GACb,OAAO,eAAe,EAAQ,EAAK,CAC/B,MAAO,EAAM,GACb,WAAY,GACZ,SAAU,GACV,aAAc,EAClB,CAAC,CACL,CACA,MAAO,EACX,EAQI,GAAe,SAAU,EAAQ,CAIjC,GAAI,GAAc,GAAU,EAAO,eAAiB,EAAO,cAAc,YAGzE,MAAO,IAAe,EAC1B,EAGI,GAAY,GAAe,EAAG,EAAG,EAAG,CAAC,EAOzC,YAAiB,EAAO,CACpB,MAAO,YAAW,CAAK,GAAK,CAChC,CAQA,YAAwB,EAAQ,CAE5B,OADI,GAAY,CAAC,EACR,EAAK,EAAG,EAAK,UAAU,OAAQ,IACpC,EAAU,EAAK,GAAK,UAAU,GAElC,MAAO,GAAU,OAAO,SAAU,EAAM,EAAU,CAC9C,GAAI,GAAQ,EAAO,UAAY,EAAW,UAC1C,MAAO,GAAO,GAAQ,CAAK,CAC/B,EAAG,CAAC,CACR,CAOA,YAAqB,EAAQ,CAGzB,OAFI,GAAY,CAAC,MAAO,QAAS,SAAU,MAAM,EAC7C,EAAW,CAAC,EACP,EAAK,EAAG,EAAc,EAAW,EAAK,EAAY,OAAQ,IAAM,CACrE,GAAI,GAAW,EAAY,GACvB,EAAQ,EAAO,WAAa,GAChC,EAAS,GAAY,GAAQ,CAAK,CACtC,CACA,MAAO,EACX,CAQA,YAA2B,EAAQ,CAC/B,GAAI,GAAO,EAAO,QAAQ,EAC1B,MAAO,IAAe,EAAG,EAAG,EAAK,MAAO,EAAK,MAAM,CACvD,CAOA,YAAmC,EAAQ,CAGvC,GAAI,GAAc,EAAO,YAAa,EAAe,EAAO,aAS5D,GAAI,CAAC,GAAe,CAAC,EACjB,MAAO,IAEX,GAAI,GAAS,GAAY,CAAM,EAAE,iBAAiB,CAAM,EACpD,EAAW,GAAY,CAAM,EAC7B,EAAW,EAAS,KAAO,EAAS,MACpC,EAAU,EAAS,IAAM,EAAS,OAKlC,EAAQ,GAAQ,EAAO,KAAK,EAAG,EAAS,GAAQ,EAAO,MAAM,EAqBjE,GAlBI,EAAO,YAAc,cAOjB,MAAK,MAAM,EAAQ,CAAQ,IAAM,GACjC,IAAS,GAAe,EAAQ,OAAQ,OAAO,EAAI,GAEnD,KAAK,MAAM,EAAS,CAAO,IAAM,GACjC,IAAU,GAAe,EAAQ,MAAO,QAAQ,EAAI,IAOxD,CAAC,GAAkB,CAAM,EAAG,CAK5B,GAAI,GAAgB,KAAK,MAAM,EAAQ,CAAQ,EAAI,EAC/C,EAAiB,KAAK,MAAM,EAAS,CAAO,EAAI,EAMpD,AAAI,KAAK,IAAI,CAAa,IAAM,GAC5B,IAAS,GAET,KAAK,IAAI,CAAc,IAAM,GAC7B,IAAU,EAElB,CACA,MAAO,IAAe,EAAS,KAAM,EAAS,IAAK,EAAO,CAAM,CACpE,CAOA,GAAI,IAAwB,UAAY,CAGpC,MAAI,OAAO,qBAAuB,YACvB,SAAU,EAAQ,CAAE,MAAO,aAAkB,IAAY,CAAM,EAAE,kBAAoB,EAKzF,SAAU,EAAQ,CAAE,MAAQ,aAAkB,IAAY,CAAM,EAAE,YACrE,MAAO,GAAO,SAAY,UAAa,CAC/C,EAAG,EAOH,YAA2B,EAAQ,CAC/B,MAAO,KAAW,GAAY,CAAM,EAAE,SAAS,eACnD,CAOA,YAAwB,EAAQ,CAC5B,MAAK,IAGD,GAAqB,CAAM,EACpB,GAAkB,CAAM,EAE5B,GAA0B,CAAM,EAL5B,EAMf,CAQA,YAA4B,EAAI,CAC5B,GAAI,GAAI,EAAG,EAAG,EAAI,EAAG,EAAG,EAAQ,EAAG,MAAO,EAAS,EAAG,OAElD,EAAS,MAAO,kBAAoB,YAAc,gBAAkB,OACpE,EAAO,OAAO,OAAO,EAAO,SAAS,EAEzC,UAAmB,EAAM,CACrB,EAAG,EAAG,EAAG,EAAG,MAAO,EAAO,OAAQ,EAClC,IAAK,EACL,MAAO,EAAI,EACX,OAAQ,EAAS,EACjB,KAAM,CACV,CAAC,EACM,CACX,CAWA,YAAwB,EAAG,EAAG,EAAO,EAAQ,CACzC,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,EAAO,OAAQ,CAAO,CACtD,CAMA,GAAI,IAAmC,UAAY,CAM/C,WAA2B,EAAQ,CAM/B,KAAK,eAAiB,EAMtB,KAAK,gBAAkB,EAMvB,KAAK,aAAe,GAAe,EAAG,EAAG,EAAG,CAAC,EAC7C,KAAK,OAAS,CAClB,CAOA,SAAkB,UAAU,SAAW,UAAY,CAC/C,GAAI,GAAO,GAAe,KAAK,MAAM,EACrC,YAAK,aAAe,EACZ,EAAK,QAAU,KAAK,gBACxB,EAAK,SAAW,KAAK,eAC7B,EAOA,EAAkB,UAAU,cAAgB,UAAY,CACpD,GAAI,GAAO,KAAK,aAChB,YAAK,eAAiB,EAAK,MAC3B,KAAK,gBAAkB,EAAK,OACrB,CACX,EACO,CACX,EAAE,EAEE,GAAqC,UAAY,CAOjD,WAA6B,EAAQ,EAAU,CAC3C,GAAI,GAAc,GAAmB,CAAQ,EAO7C,GAAmB,KAAM,CAAE,OAAQ,EAAQ,YAAa,CAAY,CAAC,CACzE,CACA,MAAO,EACX,EAAE,EAEE,GAAmC,UAAY,CAW/C,WAA2B,EAAU,EAAY,EAAa,CAc1D,GAPA,KAAK,oBAAsB,CAAC,EAM5B,KAAK,cAAgB,GAAI,IACrB,MAAO,IAAa,WACpB,KAAM,IAAI,WAAU,yDAAyD,EAEjF,KAAK,UAAY,EACjB,KAAK,YAAc,EACnB,KAAK,aAAe,CACxB,CAOA,SAAkB,UAAU,QAAU,SAAU,EAAQ,CACpD,GAAI,CAAC,UAAU,OACX,KAAM,IAAI,WAAU,0CAA0C,EAGlE,GAAI,QAAO,UAAY,aAAe,CAAE,mBAAmB,UAG3D,IAAI,CAAE,aAAkB,IAAY,CAAM,EAAE,SACxC,KAAM,IAAI,WAAU,uCAAuC,EAE/D,GAAI,GAAe,KAAK,cAExB,AAAI,EAAa,IAAI,CAAM,GAG3B,GAAa,IAAI,EAAQ,GAAI,IAAkB,CAAM,CAAC,EACtD,KAAK,YAAY,YAAY,IAAI,EAEjC,KAAK,YAAY,QAAQ,GAC7B,EAOA,EAAkB,UAAU,UAAY,SAAU,EAAQ,CACtD,GAAI,CAAC,UAAU,OACX,KAAM,IAAI,WAAU,0CAA0C,EAGlE,GAAI,QAAO,UAAY,aAAe,CAAE,mBAAmB,UAG3D,IAAI,CAAE,aAAkB,IAAY,CAAM,EAAE,SACxC,KAAM,IAAI,WAAU,uCAAuC,EAE/D,GAAI,GAAe,KAAK,cAExB,AAAI,CAAC,EAAa,IAAI,CAAM,GAG5B,GAAa,OAAO,CAAM,EACrB,EAAa,MACd,KAAK,YAAY,eAAe,IAAI,GAE5C,EAMA,EAAkB,UAAU,WAAa,UAAY,CACjD,KAAK,YAAY,EACjB,KAAK,cAAc,MAAM,EACzB,KAAK,YAAY,eAAe,IAAI,CACxC,EAOA,EAAkB,UAAU,aAAe,UAAY,CACnD,GAAI,GAAQ,KACZ,KAAK,YAAY,EACjB,KAAK,cAAc,QAAQ,SAAU,EAAa,CAC9C,AAAI,EAAY,SAAS,GACrB,EAAM,oBAAoB,KAAK,CAAW,CAElD,CAAC,CACL,EAOA,EAAkB,UAAU,gBAAkB,UAAY,CAEtD,GAAI,EAAC,KAAK,UAAU,EAGpB,IAAI,GAAM,KAAK,aAEX,EAAU,KAAK,oBAAoB,IAAI,SAAU,EAAa,CAC9D,MAAO,IAAI,IAAoB,EAAY,OAAQ,EAAY,cAAc,CAAC,CAClF,CAAC,EACD,KAAK,UAAU,KAAK,EAAK,EAAS,CAAG,EACrC,KAAK,YAAY,EACrB,EAMA,EAAkB,UAAU,YAAc,UAAY,CAClD,KAAK,oBAAoB,OAAO,CAAC,CACrC,EAMA,EAAkB,UAAU,UAAY,UAAY,CAChD,MAAO,MAAK,oBAAoB,OAAS,CAC7C,EACO,CACX,EAAE,EAKE,GAAY,MAAO,UAAY,YAAc,GAAI,SAAY,GAAI,IAKjE,GAAgC,UAAY,CAO5C,WAAwB,EAAU,CAC9B,GAAI,CAAE,gBAAgB,IAClB,KAAM,IAAI,WAAU,oCAAoC,EAE5D,GAAI,CAAC,UAAU,OACX,KAAM,IAAI,WAAU,0CAA0C,EAElE,GAAI,GAAa,GAAyB,YAAY,EAClD,EAAW,GAAI,IAAkB,EAAU,EAAY,IAAI,EAC/D,GAAU,IAAI,KAAM,CAAQ,CAChC,CACA,MAAO,EACX,EAAE,EAEF,CACI,UACA,YACA,YACJ,EAAE,QAAQ,SAAU,EAAQ,CACxB,GAAe,UAAU,GAAU,UAAY,CAC3C,GAAI,GACJ,MAAQ,GAAK,GAAU,IAAI,IAAI,GAAG,GAAQ,MAAM,EAAI,SAAS,CACjE,CACJ,CAAC,EAED,GAAI,IAAS,UAAY,CAErB,MAAI,OAAO,IAAS,gBAAmB,YAC5B,GAAS,eAEb,EACX,EAAG,EAEI,GAAQ,GCr2Bf,GAAM,IAAS,GAAI,GAYb,GAAY,EAAM,IAAM,EAC5B,GAAI,IAAe,GAAW,CAC5B,OAAW,KAAS,GAClB,GAAO,KAAK,CAAK,CACrB,CAAC,CACH,CAAC,EACE,KACC,EAAU,GAAY,EAAM,GAAO,EAAG,CAAQ,CAAC,EAC5C,KACC,EAAS,IAAM,EAAS,WAAW,CAAC,CACtC,CACF,EACA,EAAY,CAAC,CACf,EAaK,YACL,EACa,CACb,MAAO,CACL,MAAQ,EAAG,YACX,OAAQ,EAAG,YACb,CACF,CAuBO,YACL,EACyB,CACzB,MAAO,IACJ,KACC,EAAI,GAAY,EAAS,QAAQ,CAAE,CAAC,EACpC,EAAU,GAAY,GACnB,KACC,EAAO,CAAC,CAAE,YAAa,IAAW,CAAE,EACpC,EAAS,IAAM,EAAS,UAAU,CAAE,CAAC,EACrC,EAAI,IAAM,GAAe,CAAE,CAAC,CAC9B,CACF,EACA,EAAU,GAAe,CAAE,CAAC,CAC9B,CACJ,CC1GO,YACL,EACa,CACb,MAAO,CACL,MAAQ,EAAG,YACX,OAAQ,EAAG,YACb,CACF,CCSA,GAAM,IAAS,GAAI,GAUb,GAAY,EAAM,IAAM,EAC5B,GAAI,sBAAqB,GAAW,CAClC,OAAW,KAAS,GAClB,GAAO,KAAK,CAAK,CACrB,EAAG,CACD,UAAW,CACb,CAAC,CACH,CAAC,EACE,KACC,EAAU,GAAY,EAAM,GAAO,EAAG,CAAQ,CAAC,EAC5C,KACC,EAAS,IAAM,EAAS,WAAW,CAAC,CACtC,CACF,EACA,EAAY,CAAC,CACf,EAaK,YACL,EACqB,CACrB,MAAO,IACJ,KACC,EAAI,GAAY,EAAS,QAAQ,CAAE,CAAC,EACpC,EAAU,GAAY,GACnB,KACC,EAAO,CAAC,CAAE,YAAa,IAAW,CAAE,EACpC,EAAS,IAAM,EAAS,UAAU,CAAE,CAAC,EACrC,EAAI,CAAC,CAAE,oBAAqB,CAAc,CAC5C,CACF,CACF,CACJ,CAaO,YACL,EAAiB,EAAY,GACR,CACrB,MAAO,IAA0B,CAAE,EAChC,KACC,EAAI,CAAC,CAAE,OAAQ,CACb,GAAM,GAAU,GAAe,CAAE,EAC3B,EAAU,GAAsB,CAAE,EACxC,MAAO,IACL,EAAQ,OAAS,EAAQ,OAAS,CAEtC,CAAC,EACD,EAAqB,CACvB,CACJ,CCjFA,GAAM,IAA4C,CAChD,OAAQ,EAAW,yBAAyB,EAC5C,OAAQ,EAAW,yBAAyB,CAC9C,EAaO,YAAmB,EAAuB,CAC/C,MAAO,IAAQ,GAAM,OACvB,CAaO,YAAmB,EAAc,EAAsB,CAC5D,AAAI,GAAQ,GAAM,UAAY,GAC5B,GAAQ,GAAM,MAAM,CACxB,CAWO,YAAqB,EAAmC,CAC7D,GAAM,GAAK,GAAQ,GACnB,MAAO,GAAU,EAAI,QAAQ,EAC1B,KACC,EAAI,IAAM,EAAG,OAAO,EACpB,EAAU,EAAG,OAAO,CACtB,CACJ,CClCA,YACE,EAAiB,EACR,CACT,OAAQ,EAAG,iBAGJ,kBAEH,MAAI,GAAG,OAAS,QACP,SAAS,KAAK,CAAI,EAElB,OAGN,uBACA,qBACH,MAAO,WAIP,MAAO,GAAG,kBAEhB,CAWO,aAA+C,CACpD,MAAO,GAAyB,OAAQ,SAAS,EAC9C,KACC,EAAO,GAAM,CAAE,GAAG,SAAW,EAAG,QAAQ,EACxC,EAAI,GAAO,EACT,KAAM,GAAU,QAAQ,EAAI,SAAW,SACvC,KAAM,EAAG,IACT,OAAQ,CACN,EAAG,eAAe,EAClB,EAAG,gBAAgB,CACrB,CACF,EAAc,EACd,EAAO,CAAC,CAAE,OAAM,UAAW,CACzB,GAAI,IAAS,SAAU,CACrB,GAAM,GAAS,GAAiB,EAChC,GAAI,MAAO,IAAW,YACpB,MAAO,CAAC,GAAwB,EAAQ,CAAI,CAChD,CACA,MAAO,EACT,CAAC,EACD,GAAM,CACR,CACJ,CCpFO,aAA4B,CACjC,MAAO,IAAI,KAAI,SAAS,IAAI,CAC9B,CAOO,YAAqB,EAAgB,CAC1C,SAAS,KAAO,EAAI,IACtB,CASO,aAAuC,CAC5C,MAAO,IAAI,EACb,CCLA,YAAqB,EAAiB,EAA8B,CAGlE,GAAI,MAAO,IAAU,UAAY,MAAO,IAAU,SAChD,EAAG,WAAa,EAAM,SAAS,UAGtB,YAAiB,MAC1B,EAAG,YAAY,CAAK,UAGX,MAAM,QAAQ,CAAK,EAC5B,OAAW,KAAQ,GACjB,GAAY,EAAI,CAAI,CAE1B,CAyBO,WACL,EAAa,KAAmC,EAC7C,CACH,GAAM,GAAK,SAAS,cAAc,CAAG,EAGrC,GAAI,EACF,OAAW,KAAQ,QAAO,KAAK,CAAU,EACvC,AAAI,MAAO,GAAW,IAAU,aAIhC,CAAI,MAAO,GAAW,IAAU,UAC9B,EAAG,aAAa,EAAM,EAAW,EAAK,EAEtC,EAAG,aAAa,EAAM,EAAE,GAI9B,OAAW,KAAS,GAClB,GAAY,EAAI,CAAK,EAGvB,MAAO,EACT,CChFO,YAAkB,EAAe,EAAmB,CACzD,GAAI,GAAI,EACR,GAAI,EAAM,OAAS,EAAG,CACpB,KAAO,EAAM,KAAO,KAAO,EAAE,EAAI,GAAG,CACpC,MAAO,GAAG,EAAM,UAAU,EAAG,CAAC,MAChC,CACA,MAAO,EACT,CAkBO,YAAe,EAAuB,CAC3C,GAAI,EAAQ,IAAK,CACf,GAAM,GAAS,CAAG,IAAQ,KAAO,IAAO,IACxC,MAAO,GAAK,IAAQ,MAAY,KAAM,QAAQ,CAAM,IACtD,KACE,OAAO,GAAM,SAAS,CAE1B,CC5BO,aAAmC,CACxC,MAAO,UAAS,KAAK,UAAU,CAAC,CAClC,CAYO,YAAyB,EAAoB,CAClD,GAAM,GAAK,EAAE,IAAK,CAAE,KAAM,CAAK,CAAC,EAChC,EAAG,iBAAiB,QAAS,GAAM,EAAG,gBAAgB,CAAC,EACvD,EAAG,MAAM,CACX,CASO,aAAiD,CACtD,MAAO,GAA2B,OAAQ,YAAY,EACnD,KACC,EAAI,EAAe,EACnB,EAAU,GAAgB,CAAC,EAC3B,EAAO,GAAQ,EAAK,OAAS,CAAC,EAC9B,EAAY,CAAC,CACf,CACJ,CAOO,aAAwD,CAC7D,MAAO,IAAkB,EACtB,KACC,EAAI,GAAM,GAAmB,QAAQ,KAAM,CAAE,EAC7C,EAAO,GAAM,MAAO,IAAO,WAAW,CACxC,CACJ,CC1CO,YAAoB,EAAoC,CAC7D,GAAM,GAAQ,WAAW,CAAK,EAC9B,MAAO,IAA0B,GAC/B,EAAM,YAAY,IAAM,EAAK,EAAM,OAAO,CAAC,CAC5C,EACE,KACC,EAAU,EAAM,OAAO,CACzB,CACJ,CAOO,aAA2C,CAChD,GAAM,GAAQ,WAAW,OAAO,EAChC,MAAO,GACL,EAAU,OAAQ,aAAa,EAAE,KAAK,EAAI,IAAM,EAAI,CAAC,EACrD,EAAU,OAAQ,YAAY,EAAE,KAAK,EAAI,IAAM,EAAK,CAAC,CACvD,EACG,KACC,EAAU,EAAM,OAAO,CACzB,CACJ,CAcO,YACL,EAA6B,EACd,CACf,MAAO,GACJ,KACC,EAAU,GAAU,EAAS,EAAQ,EAAI,CAAK,CAChD,CACJ,CC7CO,YACL,EAAmB,EAAuB,CAAE,YAAa,aAAc,EACjD,CACtB,MAAO,IAAK,MAAM,GAAG,IAAO,CAAO,CAAC,EACjC,KACC,GAAW,IAAM,CAAK,EACtB,EAAU,GAAO,EAAI,SAAW,IAC5B,GAAW,IAAM,GAAI,OAAM,EAAI,UAAU,CAAC,EAC1C,EAAG,CAAG,CACV,CACF,CACJ,CAYO,YACL,EAAmB,EACJ,CACf,MAAO,IAAQ,EAAK,CAAO,EACxB,KACC,EAAU,GAAO,EAAI,KAAK,CAAC,EAC3B,EAAY,CAAC,CACf,CACJ,CAUO,YACL,EAAmB,EACG,CACtB,GAAM,GAAM,GAAI,WAChB,MAAO,IAAQ,EAAK,CAAO,EACxB,KACC,EAAU,GAAO,EAAI,KAAK,CAAC,EAC3B,EAAI,GAAO,EAAI,gBAAgB,EAAK,UAAU,CAAC,EAC/C,EAAY,CAAC,CACf,CACJ,CClDO,YAAqB,EAA+B,CACzD,GAAM,GAAS,EAAE,SAAU,CAAE,KAAI,CAAC,EAClC,MAAO,GAAM,IACX,UAAS,KAAK,YAAY,CAAM,EACzB,EACL,EAAU,EAAQ,MAAM,EACxB,EAAU,EAAQ,OAAO,EACtB,KACC,EAAU,IACR,GAAW,IAAM,GAAI,gBAAe,mBAAmB,GAAK,CAAC,CAC9D,CACH,CACJ,EACG,KACC,EAAI,IAAG,EAAY,EACnB,EAAS,IAAM,SAAS,KAAK,YAAY,CAAM,CAAC,EAChD,GAAK,CAAC,CACR,EACH,CACH,CCfO,aAA6C,CAClD,MAAO,CACL,EAAG,KAAK,IAAI,EAAG,OAAO,EACtB,EAAG,KAAK,IAAI,EAAG,OAAO,CACxB,CACF,CASO,aAA2D,CAChE,MAAO,GACL,EAAU,OAAQ,SAAU,CAAE,QAAS,EAAK,CAAC,EAC7C,EAAU,OAAQ,SAAU,CAAE,QAAS,EAAK,CAAC,CAC/C,EACG,KACC,EAAI,EAAiB,EACrB,EAAU,GAAkB,CAAC,CAC/B,CACJ,CC3BO,aAAyC,CAC9C,MAAO,CACL,MAAQ,WACR,OAAQ,WACV,CACF,CASO,aAAuD,CAC5D,MAAO,GAAU,OAAQ,SAAU,CAAE,QAAS,EAAK,CAAC,EACjD,KACC,EAAI,EAAe,EACnB,EAAU,GAAgB,CAAC,CAC7B,CACJ,CCXO,aAA+C,CACpD,MAAO,GAAc,CACnB,GAAoB,EACpB,GAAkB,CACpB,CAAC,EACE,KACC,EAAI,CAAC,CAAC,EAAQ,KAAW,EAAE,SAAQ,MAAK,EAAE,EAC1C,EAAY,CAAC,CACf,CACJ,CCVO,YACL,EAAiB,CAAE,YAAW,WACR,CACtB,GAAM,GAAQ,EACX,KACC,EAAwB,MAAM,CAChC,EAGI,EAAU,EAAc,CAAC,EAAO,CAAO,CAAC,EAC3C,KACC,EAAI,IAAM,GAAiB,CAAE,CAAC,CAChC,EAGF,MAAO,GAAc,CAAC,EAAS,EAAW,CAAO,CAAC,EAC/C,KACC,EAAI,CAAC,CAAC,CAAE,UAAU,CAAE,SAAQ,QAAQ,CAAE,IAAG,QAAU,EACjD,OAAQ,CACN,EAAG,EAAO,EAAI,EACd,EAAG,EAAO,EAAI,EAAI,CACpB,EACA,MACF,EAAE,CACJ,CACJ,CCIO,YACL,EAAgB,CAAE,OACH,CAGf,GAAM,GAAM,EAAwB,EAAQ,SAAS,EAClD,KACC,EAAI,CAAC,CAAE,UAAW,CAAS,CAC7B,EAGF,MAAO,GACJ,KACC,GAAS,IAAM,EAAK,CAAE,QAAS,GAAM,SAAU,EAAK,CAAC,EACrD,EAAI,GAAW,EAAO,YAAY,CAAO,CAAC,EAC1C,EAAU,IAAM,CAAG,EACnB,GAAM,CACR,CACJ,CCHA,GAAM,IAAS,EAAW,WAAW,EAC/B,GAAiB,KAAK,MAAM,GAAO,WAAY,EACrD,GAAO,KAAO,GAAG,GAAI,KAAI,GAAO,KAAM,GAAY,CAAC,IAW5C,aAAiC,CACtC,MAAO,GACT,CASO,YAAiB,EAAqB,CAC3C,MAAO,IAAO,SAAS,SAAS,CAAI,CACtC,CAUO,YACL,EAAkB,EACV,CACR,MAAO,OAAO,IAAU,YACpB,GAAO,aAAa,GAAK,QAAQ,IAAK,EAAM,SAAS,CAAC,EACtD,GAAO,aAAa,EAC1B,CC/BO,YACL,EAAS,EAAmB,SACP,CACrB,MAAO,GAAW,sBAAsB,KAAS,CAAI,CACvD,CAYO,YACL,EAAS,EAAmB,SACL,CACvB,MAAO,GAAY,sBAAsB,KAAS,CAAI,CACxD,CC/GA,OAAwB,SCajB,YAA0B,EAAyB,CACxD,MACE,GAAC,SAAM,MAAM,gBAAgB,SAAU,GACrC,EAAC,OAAI,MAAM,mCACT,EAAC,OAAI,MAAM,+BAA+B,CAC5C,EACA,EAAC,QAAK,MAAM,wBACV,EAAC,QAAK,wBAAuB,EAAI,CACnC,CACF,CAEJ,CCVO,YAA+B,EAAyB,CAC7D,MACE,GAAC,UACC,MAAM,uBACN,MAAO,GAAY,gBAAgB,EACnC,wBAAuB,IAAI,WAC5B,CAEL,CCYA,YACE,EAA2C,EAC9B,CACb,GAAM,GAAS,EAAO,EAChB,EAAS,EAAO,EAGhB,EAAU,OAAO,KAAK,EAAS,KAAK,EACvC,OAAO,GAAO,CAAC,EAAS,MAAM,EAAI,EAClC,OAAyB,CAAC,EAAM,IAAQ,CACvC,GAAG,EAAM,EAAC,WAAK,CAAI,EAAQ,GAC7B,EAAG,CAAC,CAAC,EACJ,MAAM,EAAG,EAAE,EAGR,EAAM,GAAI,KAAI,EAAS,QAAQ,EACrC,MAAI,IAAQ,kBAAkB,GAC5B,EAAI,aAAa,IAAI,IAAK,OAAO,QAAQ,EAAS,KAAK,EACpD,OAAO,CAAC,CAAC,CAAE,KAAW,CAAK,EAC3B,OAAO,CAAC,EAAW,CAAC,KAAW,GAAG,KAAa,IAAQ,KAAK,EAAG,EAAE,CACpE,EAIA,EAAC,KAAE,KAAM,GAAG,IAAO,MAAM,yBAAyB,SAAU,IAC1D,EAAC,WACC,MAAO,CAAC,4BAA6B,GAAG,EACpC,CAAC,qCAAqC,EACtC,CAAC,CACL,EAAE,KAAK,GAAG,EACV,gBAAe,EAAS,MAAM,QAAQ,CAAC,GAEtC,EAAS,GAAK,EAAC,OAAI,MAAM,iCAAiC,EAC3D,EAAC,MAAG,MAAM,2BAA2B,EAAS,KAAM,EACnD,EAAS,GAAK,EAAS,KAAK,OAAS,GACpC,EAAC,KAAE,MAAM,4BACN,GAAS,EAAS,KAAM,GAAG,CAC9B,EAED,EAAS,MAAQ,EAAS,KAAK,IAAI,GAClC,EAAC,QAAK,MAAM,UAAU,CAAI,CAC3B,EACA,EAAS,GAAK,EAAQ,OAAS,GAC9B,EAAC,KAAE,MAAM,2BACN,GAAY,4BAA4B,EAAE,KAAG,GAAG,CACnD,CAEJ,CACF,CAEJ,CAaO,YACL,EACa,CACb,GAAM,GAAY,EAAO,GAAG,MACtB,EAAO,CAAC,GAAG,CAAM,EAGjB,EAAS,EAAK,UAAU,GAAO,CAAC,EAAI,SAAS,SAAS,GAAG,CAAC,EAC1D,CAAC,GAAW,EAAK,OAAO,EAAQ,CAAC,EAGnC,EAAQ,EAAK,UAAU,GAAO,EAAI,MAAQ,CAAS,EACvD,AAAI,IAAU,IACZ,GAAQ,EAAK,QAGf,GAAM,GAAO,EAAK,MAAM,EAAG,CAAK,EAC1B,EAAO,EAAK,MAAM,CAAK,EAGvB,EAAW,CACf,GAAqB,EAAS,EAAc,CAAE,EAAC,GAAU,IAAU,EAAE,EACrE,GAAG,EAAK,IAAI,GAAW,GAAqB,EAAS,CAAW,CAAC,EACjE,GAAG,EAAK,OAAS,CACf,EAAC,WAAQ,MAAM,0BACb,EAAC,WAAQ,SAAU,IAChB,EAAK,OAAS,GAAK,EAAK,SAAW,EAChC,GAAY,wBAAwB,EACpC,GAAY,2BAA4B,EAAK,MAAM,CAEzD,EACC,GAAG,EAAK,IAAI,GAAW,GAAqB,EAAS,CAAW,CAAC,CACpE,CACF,EAAI,CAAC,CACP,EAGA,MACE,GAAC,MAAG,MAAM,0BACP,CACH,CAEJ,CC7HO,YAA2B,EAAiC,CACjE,MACE,GAAC,MAAG,MAAM,oBACP,OAAO,QAAQ,CAAK,EAAE,IAAI,CAAC,CAAC,EAAK,KAChC,EAAC,MAAG,MAAO,oCAAoC,KAC5C,MAAO,IAAU,SAAW,GAAM,CAAK,EAAI,CAC9C,CACD,CACH,CAEJ,CCAO,YACL,EACa,CACb,GAAM,GAAU,kCAAkC,IAClD,MACE,GAAC,OAAI,MAAO,EAAS,OAAM,IACzB,EAAC,UAAO,MAAM,gBAAgB,SAAU,GAAI,CAC9C,CAEJ,CCpBO,YAAqB,EAAiC,CAC3D,MACE,GAAC,OAAI,MAAM,0BACT,EAAC,OAAI,MAAM,qBACR,CACH,CACF,CAEJ,CCMA,YAAuB,EAA+B,CACpD,GAAM,GAAS,GAAc,EAGvB,EAAM,GAAI,KAAI,MAAM,EAAQ,WAAY,EAAO,IAAI,EACzD,MACE,GAAC,MAAG,MAAM,oBACR,EAAC,KAAE,KAAM,GAAG,IAAO,MAAM,oBACtB,EAAQ,KACX,CACF,CAEJ,CAcO,YACL,EAAqB,EACR,CACb,MACE,GAAC,OAAI,MAAM,cACT,EAAC,UACC,MAAM,sBACN,aAAY,GAAY,sBAAsB,GAE7C,EAAO,KACV,EACA,EAAC,MAAG,MAAM,oBACP,EAAS,IAAI,EAAa,CAC7B,CACF,CAEJ,CCfO,YACL,EAAiB,EACO,CACxB,GAAM,GAAU,EAAM,IAAM,EAAc,CACxC,GAAmB,CAAE,EACrB,GAA0B,CAAS,CACrC,CAAC,CAAC,EACC,KACC,EAAI,CAAC,CAAC,CAAE,IAAG,KAAK,KAAY,CAC1B,GAAM,CAAE,SAAU,GAAe,CAAE,EACnC,MAAQ,CACN,EAAG,EAAI,EAAO,EAAI,EAAQ,EAC1B,EAAG,EAAI,EAAO,CAChB,CACF,CAAC,CACH,EAGF,MAAO,IAAkB,CAAE,EACxB,KACC,EAAU,GAAU,EACjB,KACC,EAAI,GAAW,EAAE,SAAQ,QAAO,EAAE,EAClC,GAAK,CAAC,CAAC,GAAU,GAAQ,CAC3B,CACF,CACF,CACJ,CAUO,YACL,EAAiB,EACkB,CACnC,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,EAAM,UAAU,CAGd,KAAK,CAAE,UAAU,CACf,EAAG,MAAM,YAAY,iBAAkB,GAAG,EAAO,KAAK,EACtD,EAAG,MAAM,YAAY,iBAAkB,GAAG,EAAO,KAAK,CACxD,EAGA,UAAW,CACT,EAAG,MAAM,eAAe,gBAAgB,EACxC,EAAG,MAAM,eAAe,gBAAgB,CAC1C,CACF,CAAC,EAGD,GAAM,GAAQ,EAAM,KAAK,GAAS,CAAC,CAAC,EACpC,GAAuB,CAAE,EACtB,KACC,EAAU,CAAK,CACjB,EACG,UAAU,GAAW,CACpB,EAAG,gBAAgB,kBAAmB,CAAO,CAC/C,CAAC,EAGL,EACG,KACC,GAAa,IAAK,EAAuB,EACzC,EAAI,IAAM,EAAU,sBAAsB,CAAC,EAC3C,EAAI,CAAC,CAAE,OAAQ,CAAC,CAClB,EACG,UAAU,CAGT,KAAK,EAAQ,CACX,AAAI,EACF,EAAG,MAAM,YAAY,iBAAkB,GAAG,CAAC,KAAU,EAErD,EAAG,MAAM,eAAe,gBAAgB,CAC5C,EAGA,UAAW,CACT,EAAG,MAAM,eAAe,gBAAgB,CAC1C,CACF,CAAC,EAGL,GAAM,GAAQ,EAAW,uBAAwB,CAAE,EAC7C,EAAQ,EAAU,EAAO,YAAa,CAAE,KAAM,EAAK,CAAC,EAC1D,SACG,KACC,EAAU,CAAC,CAAE,YAAa,EAAS,EAAQ,CAAK,EAChD,EAAI,GAAM,EAAG,eAAe,CAAC,CAC/B,EACG,UAAU,IAAM,EAAG,KAAK,CAAC,EAGvB,GAAgB,EAAI,CAAS,EACjC,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CCnHA,YAA+B,EAAgC,CAC7D,GAAM,GAAkB,CAAC,EACzB,OAAW,KAAW,GAAY,eAAgB,CAAS,EAAG,CAC5D,GAAI,GAGA,EAAO,EAAQ,WACnB,GAAI,YAAgB,MAClB,KAAQ,EAAQ,YAAY,KAAK,EAAK,WAAY,GAAI,CACpD,GAAM,GAAS,EAAK,UAAU,EAAM,KAAK,EACzC,EAAO,EAAO,UAAU,EAAM,GAAG,MAAM,EACvC,EAAQ,KAAK,CAAM,CACrB,CACJ,CACA,MAAO,EACT,CAQA,YAAc,EAAqB,EAA2B,CAC5D,EAAO,OAAO,GAAG,MAAM,KAAK,EAAO,UAAU,CAAC,CAChD,CAoBO,YACL,EAAiB,EAAwB,CAAE,UACR,CAGnC,GAAM,GAAc,GAAI,KACxB,OAAW,KAAU,IAAsB,CAAS,EAAG,CACrD,GAAM,CAAC,CAAE,GAAM,EAAO,YAAa,MAAM,WAAW,EACpD,AAAI,GAAmB,gBAAgB,KAAO,CAAE,GAC9C,GAAY,IAAI,CAAC,EAAI,GAAiB,CAAC,CAAE,CAAC,EAC1C,EAAO,YAAY,EAAY,IAAI,CAAC,CAAE,CAAE,EAE5C,CAGA,MAAI,GAAY,OAAS,EAChB,EAGF,EAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAGlB,SACG,KACC,EAAU,EAAM,KAAK,GAAS,CAAC,CAAC,CAAC,CACnC,EACG,UAAU,GAAU,CACnB,EAAG,OAAS,CAAC,EAGb,OAAW,CAAC,EAAI,IAAe,GAAa,CAC1C,GAAM,GAAQ,EAAW,cAAe,CAAU,EAC5C,EAAQ,EAAW,gBAAgB,KAAO,CAAE,EAClD,AAAK,EAGH,GAAK,EAAO,CAAK,EAFjB,GAAK,EAAO,CAAK,CAGrB,CACF,CAAC,EAGE,EAAM,GAAG,CAAC,GAAG,CAAW,EAC5B,IAAI,CAAC,CAAC,CAAE,KACP,GAAgB,EAAY,CAAS,CACtC,CACH,EACG,KACC,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,GAAM,CACR,CACJ,CAAC,CACH,CTlFA,GAAI,IAAW,EAaf,YAA2B,EAA0C,CACnE,GAAI,EAAG,mBAAoB,CACzB,GAAM,GAAU,EAAG,mBACnB,GAAI,EAAQ,UAAY,KACtB,MAAO,GAGJ,GAAI,EAAQ,UAAY,KAAO,CAAC,EAAQ,SAAS,OACpD,MAAO,IAAkB,CAAO,CACpC,CAIF,CAgBO,YACL,EACuB,CACvB,MAAO,IAAiB,CAAE,EACvB,KACC,EAAI,CAAC,CAAE,WAEE,EACL,WAAY,AAFE,GAAsB,CAAE,EAElB,MAAQ,CAC9B,EACD,EACD,EAAwB,YAAY,CACtC,CACJ,CAeO,YACL,EAAiB,EAC8B,CAC/C,GAAM,CAAE,QAAS,GAAU,WAAW,SAAS,EAGzC,EAAW,EAAM,IAAM,CAC3B,GAAM,GAAQ,GAAI,GASlB,GARA,EAAM,UAAU,CAAC,CAAE,gBAAiB,CAClC,AAAI,GAAc,EAChB,EAAG,aAAa,WAAY,GAAG,EAE/B,EAAG,gBAAgB,UAAU,CACjC,CAAC,EAGG,WAAY,YAAY,EAAG,CAC7B,GAAM,GAAS,EAAG,QAAQ,KAAK,EAC/B,EAAO,GAAK,UAAU,EAAE,KACxB,EAAO,aACL,GAAsB,EAAO,EAAE,EAC/B,CACF,CACF,CAGA,GAAM,GAAY,EAAG,QAAQ,YAAY,EACzC,GAAI,YAAqB,aAAa,CACpC,GAAM,GAAO,GAAkB,CAAS,EAGxC,GAAI,MAAO,IAAS,aAClB,GAAU,UAAU,SAAS,UAAU,GACvC,GAAQ,uBAAuB,GAC9B,CACD,GAAM,GAAe,GAAoB,EAAM,EAAI,CAAO,EAG1D,MAAO,IAAe,CAAE,EACrB,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,EACpC,GACE,GAAiB,CAAS,EACvB,KACC,EAAU,EAAM,KAAK,GAAS,CAAC,CAAC,CAAC,EACjC,EAAI,CAAC,CAAE,QAAO,YAAa,GAAS,CAAM,EAC1C,EAAqB,EACrB,EAAU,GAAU,EAAS,EAAe,CAAK,CACnD,CACJ,CACF,CACJ,CACF,CAGA,MAAO,IAAe,CAAE,EACrB,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,EAGD,MAAO,IAAuB,CAAE,EAC7B,KACC,EAAO,GAAW,CAAO,EACzB,GAAK,CAAC,EACN,EAAU,IAAM,CAAQ,CAC1B,CACJ,4uJU7KA,GAAI,IAKA,GAAW,EAWf,aAA0C,CACxC,MAAO,OAAO,UAAY,aAAe,kBAAmB,SACxD,GAAY,qDAAqD,EACjE,EAAG,MAAS,CAClB,CAaO,YACL,EACgC,CAChC,SAAG,UAAU,OAAO,SAAS,EAC7B,QAAa,GAAa,EACvB,KACC,EAAI,IAAM,QAAQ,WAAW,CAC3B,YAAa,GACb,WACF,CAAC,CAAC,EACF,EAAI,IAAG,EAAY,EACnB,EAAY,CAAC,CACf,GAGF,GAAS,UAAU,IAAM,CACvB,EAAG,UAAU,IAAI,SAAS,EAC1B,GAAM,GAAK,aAAa,OAClB,EAAO,EAAE,MAAO,CAAE,MAAO,SAAU,CAAC,EAC1C,QAAQ,WAAW,OAAO,EAAI,EAAG,YAAa,AAAC,GAAgB,CAG7D,GAAM,GAAS,EAAK,aAAa,CAAE,KAAM,QAAS,CAAC,EACnD,EAAO,UAAY,EAGnB,EAAG,YAAY,CAAI,CACrB,CAAC,CACH,CAAC,EAGM,GACJ,KACC,EAAI,IAAO,EAAE,IAAK,CAAG,EAAE,CACzB,CACJ,CC1CO,YACL,EAAwB,CAAE,UAAS,UACd,CACrB,GAAI,GAAO,GACX,MAAO,GAGL,EACG,KACC,EAAI,GAAU,EAAO,QAAQ,qBAAqB,CAAE,EACpD,EAAO,GAAW,IAAO,CAAO,EAChC,EAAI,IAAO,EACT,OAAQ,OAAQ,OAAQ,EAC1B,EAAa,CACf,EAGF,EACG,KACC,EAAO,GAAU,GAAU,CAAC,CAAI,EAChC,EAAI,IAAM,EAAO,EAAG,IAAI,EACxB,EAAI,GAAW,EACb,OAAQ,EAAS,OAAS,OAC5B,EAAa,CACf,CACJ,CACF,CAaO,YACL,EAAwB,EACQ,CAChC,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,SAAM,UAAU,CAAC,CAAE,SAAQ,YAAa,CACtC,AAAI,IAAW,OACb,EAAG,aAAa,OAAQ,EAAE,EAE1B,EAAG,gBAAgB,MAAM,EACvB,GACF,EAAG,eAAe,CACtB,CAAC,EAGM,GAAa,EAAI,CAAO,EAC5B,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CC/FA,GAAM,IAAW,EAAE,OAAO,EAgBnB,YACL,EACkC,CAClC,SAAG,YAAY,EAAQ,EACvB,GAAS,YAAY,GAAY,CAAE,CAAC,EAG7B,EAAG,CAAE,IAAK,CAAG,CAAC,CACvB,CCUO,YACL,EACyB,CACzB,GAAM,GAAS,EAA8B,iBAAkB,CAAE,EAC3D,EAAU,EAAO,KAAK,GAAS,EAAM,OAAO,GAAK,EAAO,GAC9D,MAAO,GAAM,GAAG,EAAO,IAAI,GAAS,EAAU,EAAO,QAAQ,EAC1D,KACC,EAAI,IAAM,EAA6B,aAAa,EAAM,KAAK,CAAC,CAClE,CACF,CAAC,EACE,KACC,EAAU,EAA6B,aAAa,EAAQ,KAAK,CAAC,EAClE,EAAI,GAAW,EAAE,QAAO,EAAE,CAC5B,CACJ,CAcO,YACL,EACoC,CAGpC,GAAM,GAAO,GAAoB,MAAM,EACvC,EAAG,OAAO,CAAI,EAGd,GAAM,GAAO,GAAoB,MAAM,EACvC,EAAG,OAAO,CAAI,EAGd,GAAM,GAAY,EAAW,iBAAkB,CAAE,EACjD,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GACZ,EAAQ,EAAM,KAAK,GAAS,CAAC,CAAC,EACpC,SAAc,CAAC,EAAO,GAAiB,CAAE,CAAC,CAAC,EACxC,KACC,GAAU,EAAG,EAAuB,EACpC,EAAU,CAAK,CACjB,EACG,UAAU,CAGT,KAAK,CAAC,CAAE,UAAU,GAAO,CACvB,GAAM,GAAS,GAAiB,CAAM,EAChC,CAAE,SAAU,GAAe,CAAM,EAGvC,EAAG,MAAM,YAAY,mBAAoB,GAAG,EAAO,KAAK,EACxD,EAAG,MAAM,YAAY,uBAAwB,GAAG,KAAS,EAGzD,GAAM,GAAU,GAAwB,CAAS,EACjD,AACE,GAAO,EAAY,EAAQ,GAC3B,EAAO,EAAI,EAAQ,EAAQ,EAAI,EAAK,QAEpC,EAAU,SAAS,CACjB,KAAM,KAAK,IAAI,EAAG,EAAO,EAAI,EAAE,EAC/B,SAAU,QACZ,CAAC,CACL,EAGA,UAAW,CACT,EAAG,MAAM,eAAe,kBAAkB,EAC1C,EAAG,MAAM,eAAe,sBAAsB,CAChD,CACF,CAAC,EAGL,EAAc,CACZ,GAA0B,CAAS,EACnC,GAAiB,CAAS,CAC5B,CAAC,EACE,KACC,EAAU,CAAK,CACjB,EACG,UAAU,CAAC,CAAC,EAAQ,KAAU,CAC7B,GAAM,GAAU,GAAsB,CAAS,EAC/C,EAAK,OAAS,EAAO,EAAI,GACzB,EAAK,OAAS,EAAO,EAAI,EAAQ,MAAQ,EAAK,MAAQ,EACxD,CAAC,EAGL,EACE,EAAU,EAAM,OAAO,EAAE,KAAK,EAAI,IAAM,EAAE,CAAC,EAC3C,EAAU,EAAM,OAAO,EAAE,KAAK,EAAI,IAAM,CAAE,CAAC,CAC7C,EACG,KACC,EAAU,CAAK,CACjB,EACG,UAAU,GAAa,CACtB,GAAM,CAAE,SAAU,GAAe,CAAS,EAC1C,EAAU,SAAS,CACjB,KAAM,EAAQ,EACd,SAAU,QACZ,CAAC,CACH,CAAC,EAGD,GAAQ,mBAAmB,GAC7B,EAAM,KAAK,GAAK,CAAC,CAAC,EACf,UAAU,CAAC,CAAE,YAAa,CACzB,GAAM,GAAM,EAAO,UAAU,KAAK,EAClC,OAAW,KAAO,GAAY,aAAa,EACzC,OAAW,KAAS,GAClB,iBAAkB,CACpB,EAEE,GAAI,AADU,EAAW,aAAa,EAAM,KAAK,EACvC,UAAU,KAAK,IAAM,EAAK,CAClC,EAAM,MAAM,EACZ,KACF,CAIJ,GAAM,GAAO,SAAmB,QAAQ,GAAK,CAAC,EAC9C,SAAS,SAAU,CAAC,GAAG,GAAI,KAAI,CAAC,EAAK,GAAG,CAAI,CAAC,CAAC,CAAC,CACjD,CAAC,EAGE,GAAiB,CAAE,EACvB,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,EACE,KACC,GAAY,EAAc,CAC5B,CACJ,CCpIO,YACL,EAAiB,CAAE,UAAS,UACI,CAChC,MAAO,GAGL,GAAG,EAAY,2BAA4B,CAAE,EAC1C,IAAI,GAAS,GAAe,EAAO,CAAE,QAAO,CAAC,CAAC,EAGjD,GAAG,EAAY,cAAe,CAAE,EAC7B,IAAI,GAAS,GAAa,CAAK,CAAC,EAGnC,GAAG,EAAY,qBAAsB,CAAE,EACpC,IAAI,GAAS,GAAe,CAAK,CAAC,EAGrC,GAAG,EAAY,UAAW,CAAE,EACzB,IAAI,GAAS,GAAa,EAAO,CAAE,UAAS,QAAO,CAAC,CAAC,EAGxD,GAAG,EAAY,cAAe,CAAE,EAC7B,IAAI,GAAS,GAAiB,CAAK,CAAC,CACzC,CACF,CCjCO,YACL,EAAkB,CAAE,UACA,CACpB,MAAO,GACJ,KACC,EAAU,GAAW,EACnB,EAAG,EAAI,EACP,EAAG,EAAK,EAAE,KAAK,GAAM,GAAI,CAAC,CAC5B,EACG,KACC,EAAI,GAAW,EAAE,UAAS,QAAO,EAAE,CACrC,CACF,CACF,CACJ,CAaO,YACL,EAAiB,EACc,CAC/B,GAAM,GAAQ,EAAW,cAAe,CAAE,EAC1C,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,SAAM,UAAU,CAAC,CAAE,UAAS,YAAa,CACvC,EAAG,UAAU,OAAO,oBAAqB,CAAM,EAC/C,EAAM,YAAc,CACtB,CAAC,EAGM,GAAY,EAAI,CAAO,EAC3B,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CC9BA,YAAkB,CAAE,aAAgD,CAClE,GAAI,CAAC,GAAQ,iBAAiB,EAC5B,MAAO,GAAG,EAAK,EAGjB,GAAM,GAAa,EAChB,KACC,EAAI,CAAC,CAAE,OAAQ,CAAE,QAAU,CAAC,EAC5B,GAAY,EAAG,CAAC,EAChB,EAAI,CAAC,CAAC,EAAG,KAAO,CAAC,EAAI,EAAG,CAAC,CAAU,EACnC,EAAwB,CAAC,CAC3B,EAGI,EAAU,EAAc,CAAC,EAAW,CAAU,CAAC,EAClD,KACC,EAAO,CAAC,CAAC,CAAE,UAAU,CAAC,CAAE,MAAQ,KAAK,IAAI,EAAI,EAAO,CAAC,EAAI,GAAG,EAC5D,EAAI,CAAC,CAAC,CAAE,CAAC,MAAgB,CAAS,EAClC,EAAqB,CACvB,EAGI,EAAU,GAAY,QAAQ,EACpC,MAAO,GAAc,CAAC,EAAW,CAAO,CAAC,EACtC,KACC,EAAI,CAAC,CAAC,CAAE,UAAU,KAAY,EAAO,EAAI,KAAO,CAAC,CAAM,EACvD,EAAqB,EACrB,EAAU,GAAU,EAAS,EAAU,EAAG,EAAK,CAAC,EAChD,EAAU,EAAK,CACjB,CACJ,CAcO,YACL,EAAiB,EACG,CACpB,MAAO,GAAM,IAAM,EAAc,CAC/B,GAAiB,CAAE,EACnB,GAAS,CAAO,CAClB,CAAC,CAAC,EACC,KACC,EAAI,CAAC,CAAC,CAAE,UAAU,KAAa,EAC7B,SACA,QACF,EAAE,EACF,EAAqB,CAAC,EAAG,IACvB,EAAE,SAAW,EAAE,QACf,EAAE,SAAW,EAAE,MAChB,EACD,EAAY,CAAC,CACf,CACJ,CAaO,YACL,EAAiB,CAAE,UAAS,SACG,CAC/B,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GACZ,EAAQ,EAAM,KAAK,GAAS,CAAC,CAAC,EACpC,SACG,KACC,EAAwB,QAAQ,EAChC,GAAkB,CAAO,CAC3B,EACG,UAAU,CAAC,CAAC,CAAE,UAAU,CAAE,aAAc,CACvC,EAAG,UAAU,OAAO,oBAAqB,GAAU,CAAC,CAAM,EAC1D,EAAG,OAAS,CACd,CAAC,EAGL,EAAM,UAAU,CAAK,EAGd,EACJ,KACC,EAAU,CAAK,EACf,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CChHO,YACL,EAAiB,CAAE,YAAW,WACL,CACzB,MAAO,IAAgB,EAAI,CAAE,YAAW,SAAQ,CAAC,EAC9C,KACC,EAAI,CAAC,CAAE,OAAQ,CAAE,QAAU,CACzB,GAAM,CAAE,UAAW,GAAe,CAAE,EACpC,MAAO,CACL,OAAQ,GAAK,CACf,CACF,CAAC,EACD,EAAwB,QAAQ,CAClC,CACJ,CAaO,YACL,EAAiB,EACmB,CACpC,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,EAAM,UAAU,CAAC,CAAE,YAAa,CAC9B,EAAG,UAAU,OAAO,2BAA4B,CAAM,CACxD,CAAC,EAGD,GAAM,GAAU,GAAmB,YAAY,EAC/C,MAAI,OAAO,IAAY,YACd,EAGF,GAAiB,EAAS,CAAO,EACrC,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CCvDO,YACL,EAAiB,CAAE,YAAW,WACZ,CAGlB,GAAM,GAAU,EACb,KACC,EAAI,CAAC,CAAE,YAAa,CAAM,EAC1B,EAAqB,CACvB,EAGI,EAAU,EACb,KACC,EAAU,IAAM,GAAiB,CAAE,EAChC,KACC,EAAI,CAAC,CAAE,YAAc,EACnB,IAAQ,EAAG,UACX,OAAQ,EAAG,UAAY,CACzB,EAAE,EACF,EAAwB,QAAQ,CAClC,CACF,CACF,EAGF,MAAO,GAAc,CAAC,EAAS,EAAS,CAAS,CAAC,EAC/C,KACC,EAAI,CAAC,CAAC,EAAQ,CAAE,MAAK,UAAU,CAAE,OAAQ,CAAE,KAAK,KAAM,CAAE,cACtD,GAAS,KAAK,IAAI,EAAG,EACjB,KAAK,IAAI,EAAG,EAAS,EAAI,CAAM,EAC/B,KAAK,IAAI,EAAG,EAAS,EAAI,CAAM,CACnC,EACO,CACL,OAAQ,EAAM,EACd,SACA,OAAQ,EAAM,GAAU,CAC1B,EACD,EACD,EAAqB,CAAC,EAAG,IACvB,EAAE,SAAW,EAAE,QACf,EAAE,SAAW,EAAE,QACf,EAAE,SAAW,EAAE,MAChB,CACH,CACJ,CClDO,YACL,EACqB,CACrB,GAAM,GAAU,SAAkB,WAAW,GAAK,CAChD,MAAO,EAAO,UAAU,GAAS,WAC/B,EAAM,aAAa,qBAAqB,CAC1C,EAAE,OAAO,CACX,EAGA,MAAO,GAAG,GAAG,CAAM,EAChB,KACC,GAAS,GAAS,EAAU,EAAO,QAAQ,EACxC,KACC,EAAI,IAAM,CAAK,CACjB,CACF,EACA,EAAU,EAAO,KAAK,IAAI,EAAG,EAAQ,KAAK,EAAE,EAC5C,EAAI,GAAU,EACZ,MAAO,EAAO,QAAQ,CAAK,EAC3B,MAAO,CACL,OAAS,EAAM,aAAa,sBAAsB,EAClD,QAAS,EAAM,aAAa,uBAAuB,EACnD,OAAS,EAAM,aAAa,sBAAsB,CACpD,CACF,EAAa,EACb,EAAY,CAAC,CACf,CACJ,CASO,YACL,EACgC,CAChC,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,EAAM,UAAU,GAAW,CACzB,SAAS,KAAK,aAAa,0BAA2B,EAAE,EAGxD,OAAW,CAAC,EAAK,IAAU,QAAO,QAAQ,EAAQ,KAAK,EACrD,SAAS,KAAK,aAAa,iBAAiB,IAAO,CAAK,EAG1D,OAAS,GAAQ,EAAG,EAAQ,EAAO,OAAQ,IAAS,CAClD,GAAM,GAAQ,EAAO,GAAO,mBAC5B,AAAI,YAAiB,cACnB,GAAM,OAAS,EAAQ,QAAU,EACrC,CAGA,SAAS,YAAa,CAAO,CAC/B,CAAC,EAGD,EAAM,KAAK,GAAU,EAAc,CAAC,EACjC,UAAU,IAAM,CACf,SAAS,KAAK,gBAAgB,yBAAyB,CACzD,CAAC,EAGH,GAAM,GAAS,EAA8B,QAAS,CAAE,EACxD,MAAO,IAAa,CAAM,EACvB,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CC/HA,OAAwB,SAiCxB,YAAiB,EAAyB,CACxC,EAAG,aAAa,kBAAmB,EAAE,EACrC,GAAM,GAAO,EAAG,UAChB,SAAG,gBAAgB,iBAAiB,EAC7B,CACT,CAWO,YACL,CAAE,UACI,CACN,AAAI,WAAY,YAAY,GAC1B,GAAI,GAA8B,GAAc,CAC9C,GAAI,YAAY,iDAAkD,CAChE,KAAM,GACJ,EAAG,aAAa,qBAAqB,GACrC,GAAQ,EACN,EAAG,aAAa,uBAAuB,CACzC,CAAC,CAEL,CAAC,EACE,GAAG,UAAW,GAAM,EAAW,KAAK,CAAE,CAAC,CAC5C,CAAC,EACE,KACC,EAAI,GAAM,CAER,AADgB,EAAG,QACX,MAAM,CAChB,CAAC,EACD,EAAI,IAAM,GAAY,kBAAkB,CAAC,CAC3C,EACG,UAAU,CAAM,CAEzB,CCrCA,YAAoB,EAAwB,CAC1C,GAAI,EAAK,OAAS,EAChB,MAAO,CAAC,EAAE,EAGZ,GAAM,CAAC,EAAM,GAAQ,CAAC,GAAG,CAAI,EAC1B,KAAK,CAAC,EAAG,IAAM,EAAE,OAAS,EAAE,MAAM,EAClC,IAAI,GAAO,EAAI,QAAQ,SAAU,EAAE,CAAC,EAGnC,EAAQ,EACZ,GAAI,IAAS,EACX,EAAQ,EAAK,WAEb,MAAO,EAAK,WAAW,CAAK,IAAM,EAAK,WAAW,CAAK,GACrD,IAGJ,MAAO,GAAK,IAAI,GAAO,EAAI,QAAQ,EAAK,MAAM,EAAG,CAAK,EAAG,EAAE,CAAC,CAC9D,CAaO,YAAsB,EAAiC,CAC5D,GAAM,GAAS,SAAkB,YAAa,eAAgB,CAAI,EAClE,GAAI,EACF,MAAO,GAAG,CAAM,EACX,CACL,GAAM,GAAS,GAAc,EAC7B,MAAO,IAAW,GAAI,KAAI,cAAe,GAAQ,EAAO,IAAI,CAAC,EAC1D,KACC,EAAI,GAAW,GAAW,EAAY,MAAO,CAAO,EACjD,IAAI,GAAQ,EAAK,WAAY,CAChC,CAAC,EACD,GAAW,IAAM,CAAK,EACtB,GAAe,CAAC,CAAC,EACjB,EAAI,GAAW,SAAS,YAAa,EAAS,eAAgB,CAAI,CAAC,CACrE,CACJ,CACF,CCIO,YACL,CAAE,YAAW,YAAW,aAClB,CACN,GAAM,GAAS,GAAc,EAC7B,GAAI,SAAS,WAAa,QACxB,OAGF,AAAI,qBAAuB,UACzB,SAAQ,kBAAoB,SAG5B,EAAU,OAAQ,cAAc,EAC7B,UAAU,IAAM,CACf,QAAQ,kBAAoB,MAC9B,CAAC,GAIL,GAAM,GAAU,GAAoC,gBAAgB,EACpE,AAAI,MAAO,IAAY,aACrB,GAAQ,KAAO,EAAQ,MAGzB,GAAM,GAAQ,GAAa,EACxB,KACC,EAAI,GAAS,EAAM,IAAI,GAAQ,GAAG,GAAI,KAAI,EAAM,EAAO,IAAI,GAAG,CAAC,EAC/D,EAAU,GAAQ,EAAsB,SAAS,KAAM,OAAO,EAC3D,KACC,EAAO,GAAM,CAAC,EAAG,SAAW,CAAC,EAAG,OAAO,EACvC,EAAU,GAAM,CACd,GAAI,EAAG,iBAAkB,SAAS,CAChC,GAAM,GAAK,EAAG,OAAO,QAAQ,GAAG,EAChC,GAAI,GAAM,CAAC,EAAG,OAAQ,CACpB,GAAM,GAAM,GAAI,KAAI,EAAG,IAAI,EAO3B,GAJA,EAAI,OAAS,GACb,EAAI,KAAO,GAIT,EAAI,WAAa,SAAS,UAC1B,EAAK,SAAS,EAAI,SAAS,CAAC,EAE5B,SAAG,eAAe,EACX,EAAG,CACR,IAAK,GAAI,KAAI,EAAG,IAAI,CACtB,CAAC,CAEL,CACF,CACA,MAAO,GACT,CAAC,CACH,CACF,EACA,GAAoB,CACtB,EAGI,EAAO,EAAyB,OAAQ,UAAU,EACrD,KACC,EAAO,GAAM,EAAG,QAAU,IAAI,EAC9B,EAAI,GAAO,EACT,IAAK,GAAI,KAAI,SAAS,IAAI,EAC1B,OAAQ,EAAG,KACb,EAAE,EACF,GAAoB,CACtB,EAGF,EAAM,EAAO,CAAI,EACd,KACC,EAAqB,CAAC,EAAG,IAAM,EAAE,IAAI,OAAS,EAAE,IAAI,IAAI,EACxD,EAAI,CAAC,CAAE,SAAU,CAAG,CACtB,EACG,UAAU,CAAS,EAGxB,GAAM,GAAY,EACf,KACC,EAAwB,UAAU,EAClC,EAAU,GAAO,GAAQ,EAAI,IAAI,EAC9B,KACC,GAAW,IACT,IAAY,CAAG,EACR,GACR,CACH,CACF,EACA,GAAM,CACR,EAGF,EACG,KACC,GAAO,CAAS,CAClB,EACG,UAAU,CAAC,CAAE,SAAU,CACtB,QAAQ,UAAU,CAAC,EAAG,GAAI,GAAG,GAAK,CACpC,CAAC,EAGL,GAAM,GAAM,GAAI,WAChB,EACG,KACC,EAAU,GAAO,EAAI,KAAK,CAAC,EAC3B,EAAI,GAAO,EAAI,gBAAgB,EAAK,WAAW,CAAC,CAClD,EACG,UAAU,CAAS,EAGxB,EACG,KACC,GAAK,CAAC,CACR,EACG,UAAU,GAAe,CACxB,OAAW,KAAY,CAGrB,QACA,sBACA,oBACA,yBAGA,+BACA,gCACA,mCACA,+BACA,2BACA,2BACA,GAAG,GAAQ,wBAAwB,EAC/B,CAAC,0BAA0B,EAC3B,CAAC,CACP,EAAG,CACD,GAAM,GAAS,GAAmB,CAAQ,EACpC,EAAS,GAAmB,EAAU,CAAW,EACvD,AACE,MAAO,IAAW,aAClB,MAAO,IAAW,aAElB,EAAO,YAAY,CAAM,CAE7B,CACF,CAAC,EAGL,EACG,KACC,GAAK,CAAC,EACN,EAAI,IAAM,GAAoB,WAAW,CAAC,EAC1C,EAAU,GAAM,EAAY,SAAU,CAAE,CAAC,EACzC,GAAU,GAAM,CACd,GAAM,GAAS,EAAE,QAAQ,EACzB,GAAI,EAAG,IAAK,CACV,OAAW,KAAQ,GAAG,kBAAkB,EACtC,EAAO,aAAa,EAAM,EAAG,aAAa,CAAI,CAAE,EAClD,SAAG,YAAY,CAAM,EAGd,GAAI,GAAW,GAAY,CAChC,EAAO,OAAS,IAAM,EAAS,SAAS,CAC1C,CAAC,CAGH,KACE,UAAO,YAAc,EAAG,YACxB,EAAG,YAAY,CAAM,EACd,CAEX,CAAC,CACH,EACG,UAAU,EAGf,EAAM,EAAO,CAAI,EACd,KACC,GAAO,CAAS,CAClB,EACG,UAAU,CAAC,CAAE,MAAK,YAAa,CAC9B,AAAI,EAAI,MAAQ,CAAC,EACf,GAAgB,EAAI,IAAI,EAExB,OAAO,SAAS,EAAG,kBAAQ,IAAK,CAAC,CAErC,CAAC,EAGL,EACG,KACC,GAAU,CAAK,EACf,GAAa,GAAG,EAChB,EAAwB,QAAQ,CAClC,EACG,UAAU,CAAC,CAAE,YAAa,CACzB,QAAQ,aAAa,EAAQ,EAAE,CACjC,CAAC,EAGL,EAAM,EAAO,CAAI,EACd,KACC,GAAY,EAAG,CAAC,EAChB,EAAO,CAAC,CAAC,EAAG,KAAO,EAAE,IAAI,WAAa,EAAE,IAAI,QAAQ,EACpD,EAAI,CAAC,CAAC,CAAE,KAAW,CAAK,CAC1B,EACG,UAAU,CAAC,CAAE,YAAa,CACzB,OAAO,SAAS,EAAG,kBAAQ,IAAK,CAAC,CACnC,CAAC,CACP,CCzSA,OAAuB,SCAvB,OAAuB,SAsChB,YACL,EAA2B,EACD,CAC1B,GAAM,GAAY,GAAI,QAAO,EAAO,UAAW,KAAK,EAC9C,EAAY,CAAC,EAAY,EAAc,IACpC,GAAG,4BAA+B,WAI3C,MAAO,AAAC,IAAkB,CACxB,EAAQ,EACL,QAAQ,gBAAiB,GAAG,EAC5B,KAAK,EAGR,GAAM,GAAQ,GAAI,QAAO,MAAM,EAAO,cACpC,EACG,QAAQ,uBAAwB,MAAM,EACtC,QAAQ,EAAW,GAAG,KACtB,KAAK,EAGV,MAAO,IACL,GACI,eAAW,CAAK,EAChB,GAED,QAAQ,EAAO,CAAS,EACxB,QAAQ,8BAA+B,IAAI,CAClD,CACF,CC9BO,YAA0B,EAAuB,CACtD,MAAO,GACJ,MAAM,YAAY,EAChB,IAAI,CAAC,EAAO,IAAU,EAAQ,EAC3B,EAAM,QAAQ,+BAAgC,IAAI,EAClD,CACJ,EACC,KAAK,EAAE,EACT,QAAQ,kCAAmC,EAAE,EAC7C,KAAK,CACV,CCoCO,YACL,EAC+B,CAC/B,MAAO,GAAQ,OAAS,CAC1B,CASO,YACL,EAC+B,CAC/B,MAAO,GAAQ,OAAS,CAC1B,CASO,YACL,EACgC,CAChC,MAAO,GAAQ,OAAS,CAC1B,CCvEA,YAA0B,CAAE,SAAQ,QAAkC,CAGpE,AAAI,EAAO,KAAK,SAAW,GAAK,EAAO,KAAK,KAAO,MACjD,GAAO,KAAO,CACZ,GAAY,oBAAoB,CAClC,GAGE,EAAO,YAAc,aACvB,GAAO,UAAY,GAAY,yBAAyB,GAQ1D,GAAM,GAAyB,CAC7B,SANe,GAAY,wBAAwB,EAClD,MAAM,SAAS,EACf,OAAO,OAAO,EAKf,YAAa,GAAQ,gBAAgB,CACvC,EAGA,MAAO,CAAE,SAAQ,OAAM,SAAQ,CACjC,CAkBO,YACL,EAAa,EACC,CACd,GAAM,GAAS,GAAc,EACvB,EAAS,GAAI,QAAO,CAAG,EAGvB,EAAM,GAAI,GACV,EAAM,GAAY,EAAQ,CAAE,KAAI,CAAC,EACpC,KACC,EAAI,GAAW,CACb,GAAI,GAAsB,CAAO,EAC/B,OAAW,KAAU,GAAQ,KAAK,MAChC,OAAW,KAAY,GACrB,EAAS,SAAW,GAAG,GAAI,KAAI,EAAS,SAAU,EAAO,IAAI,IAEnE,MAAO,EACT,CAAC,EACD,GAAM,CACR,EAGF,UAAK,CAAK,EACP,KACC,EAAI,GAAS,EACX,KAAM,EACN,KAAM,GAAiB,CAAI,CAC7B,EAAwB,CAC1B,EACG,UAAU,EAAI,KAAK,KAAK,CAAG,CAAC,EAG1B,CAAE,MAAK,KAAI,CACpB,CCxEO,YACL,CAAE,aACI,CACN,GAAM,GAAS,GAAc,EACvB,EAAY,GAChB,GAAI,KAAI,mBAAoB,EAAO,IAAI,CACzC,EACG,KACC,GAAW,IAAM,CAAK,CACxB,EAGI,EAAW,EACd,KACC,EAAI,GAAY,CACd,GAAM,CAAC,CAAE,GAAW,EAAO,KAAK,MAAM,aAAa,EACnD,MAAO,GAAS,KAAK,CAAC,CAAE,UAAS,aAC/B,IAAY,GAAW,EAAQ,SAAS,CAAO,CAChD,GAAK,EAAS,EACjB,CAAC,CACH,EAGF,EAAc,CAAC,EAAW,CAAQ,CAAC,EAChC,KACC,EAAI,CAAC,CAAC,EAAU,KAAa,GAAI,KAAI,EAClC,OAAO,GAAW,IAAY,CAAO,EACrC,IAAI,GAAW,CACd,GAAG,GAAI,KAAI,MAAM,EAAQ,WAAY,EAAO,IAAI,IAChD,CACF,CAAC,CACH,CAAC,EACD,EAAU,GAAQ,EAAsB,SAAS,KAAM,OAAO,EAC3D,KACC,EAAO,GAAM,CAAC,EAAG,SAAW,CAAC,EAAG,OAAO,EACvC,EAAU,GAAM,CACd,GAAI,EAAG,iBAAkB,SAAS,CAChC,GAAM,GAAK,EAAG,OAAO,QAAQ,GAAG,EAChC,GAAI,GAAM,CAAC,EAAG,QAAU,EAAK,IAAI,EAAG,IAAI,EACtC,SAAG,eAAe,EACX,EAAG,EAAG,IAAI,CAErB,CACA,MAAO,EACT,CAAC,EACD,EAAU,GAAO,CACf,GAAM,CAAE,WAAY,EAAK,IAAI,CAAG,EAChC,MAAO,IAAa,GAAI,KAAI,CAAG,CAAC,EAC7B,KACC,EAAI,GAAW,CAEb,GAAM,GAAO,AADI,GAAY,EACP,KAAK,QAAQ,EAAO,KAAM,EAAE,EAClD,MAAO,GAAQ,SAAS,CAAI,EACxB,GAAI,KAAI,MAAM,KAAW,IAAQ,EAAO,IAAI,EAC5C,GAAI,KAAI,CAAG,CACjB,CAAC,CACH,CACJ,CAAC,CACH,CACF,CACF,EACG,UAAU,GAAO,GAAY,CAAG,CAAC,EAGtC,EAAc,CAAC,EAAW,CAAQ,CAAC,EAChC,UAAU,CAAC,CAAC,EAAU,KAAa,CAElC,AADc,EAAW,mBAAmB,EACtC,YAAY,GAAsB,EAAU,CAAO,CAAC,CAC5D,CAAC,EAGH,EAAU,KAAK,EAAU,IAAM,CAAQ,CAAC,EACrC,UAAU,GAAW,CA7I1B,MAgJM,GAAI,GAAW,SAAS,aAAc,cAAc,EACpD,GAAI,IAAa,KAAM,CACrB,GAAM,GAAS,MAAO,UAAP,cAAgB,UAAW,SAC1C,EAAW,CAAC,EAAQ,QAAQ,SAAS,CAAM,EAG3C,SAAS,aAAc,EAAU,cAAc,CACjD,CAGA,GAAI,EACF,OAAW,KAAW,IAAqB,UAAU,EACnD,EAAQ,OAAS,EACvB,CAAC,CACL,CCvEO,YACL,EAAsB,CAAE,OACC,CACzB,GAAM,GAAK,gCAAU,YAAa,GAG5B,CAAE,gBAAiB,GAAY,EACrC,AAAI,EAAa,IAAI,GAAG,GACtB,GAAU,SAAU,EAAI,EAG1B,GAAM,GAAS,EACZ,KACC,EAAO,EAAoB,EAC3B,GAAK,CAAC,EACN,EAAI,IAAM,EAAa,IAAI,GAAG,GAAK,EAAE,CACvC,EAGF,GAAY,QAAQ,EACjB,KACC,EAAO,GAAU,CAAC,CAAM,EACxB,GAAK,CAAC,CACR,EACG,UAAU,IAAM,CACf,GAAM,GAAM,GAAI,KAAI,SAAS,IAAI,EACjC,EAAI,aAAa,OAAO,GAAG,EAC3B,QAAQ,aAAa,CAAC,EAAG,GAAI,GAAG,GAAK,CACvC,CAAC,EAGL,EAAO,UAAU,GAAS,CACxB,AAAI,GACF,GAAG,MAAQ,EACX,EAAG,MAAM,EAEb,CAAC,EAGD,GAAM,GAAS,GAAkB,CAAE,EAC7B,EAAS,EACb,EAAU,EAAI,OAAO,EACrB,EAAU,EAAI,OAAO,EAAE,KAAK,GAAM,CAAC,CAAC,EACpC,CACF,EACG,KACC,EAAI,IAAM,EAAG,EAAG,KAAK,CAAC,EACtB,EAAU,EAAE,EACZ,EAAqB,CACvB,EAGF,MAAO,GAAc,CAAC,EAAQ,CAAM,CAAC,EAClC,KACC,EAAI,CAAC,CAAC,EAAO,KAAY,EAAE,QAAO,OAAM,EAAE,EAC1C,EAAY,CAAC,CACf,CACJ,CAUO,YACL,EAAsB,CAAE,MAAK,OACyB,CACtD,GAAM,GAAQ,GAAI,GACZ,EAAQ,EAAM,KAAK,GAAS,CAAC,CAAC,EAGpC,SACG,KACC,EAAwB,OAAO,EAC/B,EAAI,CAAC,CAAE,WAAiC,EACtC,KAAM,EACN,KAAM,CACR,EAAE,CACJ,EACG,UAAU,EAAI,KAAK,KAAK,CAAG,CAAC,EAGjC,EACG,KACC,EAAwB,OAAO,CACjC,EACG,UAAU,CAAC,CAAE,WAAY,CACxB,AAAI,EACF,IAAU,SAAU,CAAK,EACzB,EAAG,YAAc,IAEjB,EAAG,YAAc,GAAY,oBAAoB,CAErD,CAAC,EAGL,EAAU,EAAG,KAAO,OAAO,EACxB,KACC,EAAU,CAAK,CACjB,EACG,UAAU,IAAM,EAAG,MAAM,CAAC,EAGxB,GAAiB,EAAI,CAAE,MAAK,KAAI,CAAC,EACrC,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,EACpC,GAAM,CACR,CACJ,CCrHO,YACL,EAAiB,CAAE,OAAqB,CAAE,UACL,CACrC,GAAM,GAAQ,GAAI,GACZ,EAAY,GAAqB,EAAG,aAAc,EACrD,KACC,EAAO,OAAO,CAChB,EAGI,EAAO,EAAW,wBAAyB,CAAE,EAC7C,EAAO,EAAW,uBAAwB,CAAE,EAG5C,EAAS,EACZ,KACC,EAAO,EAAoB,EAC3B,GAAK,CAAC,CACR,EAGF,SACG,KACC,GAAe,CAAM,EACrB,GAAU,CAAM,CAClB,EACG,UAAU,CAAC,CAAC,CAAE,SAAS,CAAE,YAAa,CACrC,GAAI,EACF,OAAQ,EAAM,YAGP,GACH,EAAK,YAAc,GAAY,oBAAoB,EACnD,UAGG,GACH,EAAK,YAAc,GAAY,mBAAmB,EAClD,cAIA,EAAK,YAAc,GACjB,sBACA,GAAM,EAAM,MAAM,CACpB,MAGJ,GAAK,YAAc,GAAY,2BAA2B,CAE9D,CAAC,EAGL,EACG,KACC,EAAI,IAAM,EAAK,UAAY,EAAE,EAC7B,EAAU,CAAC,CAAE,WAAY,EACvB,EAAG,GAAG,EAAM,MAAM,EAAG,EAAE,CAAC,EACxB,EAAG,GAAG,EAAM,MAAM,EAAE,CAAC,EAClB,KACC,GAAY,CAAC,EACb,GAAQ,CAAS,EACjB,EAAU,CAAC,CAAC,KAAW,CAAK,CAC9B,CACJ,CAAC,CACH,EACG,UAAU,GAAU,EAAK,YACxB,GAAuB,CAAM,CAC/B,CAAC,EAUE,AAPS,EACb,KACC,EAAO,EAAqB,EAC5B,EAAI,CAAC,CAAE,UAAW,CAAI,CACxB,EAIC,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CC1FO,YACL,EAAkB,CAAE,UACK,CACzB,MAAO,GACJ,KACC,EAAI,CAAC,CAAE,WAAY,CACjB,GAAM,GAAM,GAAY,EACxB,SAAI,KAAO,GACX,EAAI,aAAa,OAAO,GAAG,EAC3B,EAAI,aAAa,IAAI,IAAK,CAAK,EACxB,CAAE,KAAI,CACf,CAAC,CACH,CACJ,CAUO,YACL,EAAuB,EACa,CACpC,GAAM,GAAQ,GAAI,GAClB,SAAM,UAAU,CAAC,CAAE,SAAU,CAC3B,EAAG,aAAa,sBAAuB,EAAG,IAAI,EAC9C,EAAG,KAAO,GAAG,GACf,CAAC,EAGD,EAAU,EAAI,OAAO,EAClB,UAAU,GAAM,EAAG,eAAe,CAAC,EAG/B,GAAiB,EAAI,CAAO,EAChC,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CCtCO,YACL,EAAiB,CAAE,OAAqB,CAAE,aACJ,CACtC,GAAM,GAAQ,GAAI,GAGZ,EAAS,GAAoB,cAAc,EAC3C,EAAS,EACb,EAAU,EAAO,SAAS,EAC1B,EAAU,EAAO,OAAO,CAC1B,EACG,KACC,GAAU,EAAc,EACxB,EAAI,IAAM,EAAM,KAAK,EACrB,EAAqB,CACvB,EAGF,SACG,KACC,GAAkB,CAAM,EACxB,EAAI,CAAC,CAAC,CAAE,eAAe,KAAW,CAChC,GAAM,GAAQ,EAAM,MAAM,UAAU,EACpC,GAAI,kBAAa,SAAU,EAAM,EAAM,OAAS,GAAI,CAClD,GAAM,GAAO,EAAY,EAAY,OAAS,GAC9C,AAAI,EAAK,WAAW,EAAM,EAAM,OAAS,EAAE,GACzC,GAAM,EAAM,OAAS,GAAK,EAC9B,KACE,GAAM,OAAS,EAEjB,MAAO,EACT,CAAC,CACH,EACG,UAAU,GAAS,EAAG,UAAY,EAChC,KAAK,EAAE,EACP,QAAQ,MAAO,QAAQ,CAC1B,EAGJ,EACG,KACC,EAAO,CAAC,CAAE,UAAW,IAAS,QAAQ,CACxC,EACG,UAAU,GAAO,CAChB,OAAQ,EAAI,UAGL,aACH,AACE,EAAG,UAAU,QACb,EAAM,iBAAmB,EAAM,MAAM,QAErC,GAAM,MAAQ,EAAG,WACnB,MAEN,CAAC,EAUE,AAPS,EACb,KACC,EAAO,EAAqB,EAC5B,EAAI,CAAC,CAAE,UAAW,CAAI,CACxB,EAIC,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,IAAO,EAAE,IAAK,CAAG,EAAE,CACzB,CACJ,CC9CO,YACL,EAAiB,CAAE,SAAQ,aACI,CAC/B,GAAM,GAAS,GAAc,EAC7B,GAAI,CACF,GAAM,GAAM,gCAAU,SAAU,EAAO,OACjC,EAAS,GAAkB,EAAK,CAAM,EAGtC,EAAS,GAAoB,eAAgB,CAAE,EAC/C,EAAS,GAAoB,gBAAiB,CAAE,EAGhD,CAAE,MAAK,OAAQ,EACrB,EACG,KACC,EAAO,EAAoB,EAC3B,GAAO,EAAI,KAAK,EAAO,EAAoB,CAAC,CAAC,EAC7C,GAAK,CAAC,CACR,EACG,UAAU,EAAI,KAAK,KAAK,CAAG,CAAC,EAGjC,EACG,KACC,EAAO,CAAC,CAAE,UAAW,IAAS,QAAQ,CACxC,EACG,UAAU,GAAO,CAChB,GAAM,GAAS,GAAiB,EAChC,OAAQ,EAAI,UAGL,QACH,GAAI,IAAW,EAAO,CACpB,GAAM,GAAU,GAAI,KACpB,OAAW,KAAU,GACnB,sBAAuB,CACzB,EAAG,CACD,GAAM,GAAU,EAAO,kBACvB,EAAQ,IAAI,EAAQ,WAClB,EAAQ,aAAa,eAAe,CACtC,CAAC,CACH,CAGA,GAAI,EAAQ,KAAM,CAChB,GAAM,CAAC,CAAC,IAAS,CAAC,GAAG,CAAO,EAAE,KAAK,CAAC,CAAC,CAAE,GAAI,CAAC,CAAE,KAAO,EAAI,CAAC,EAC1D,EAAK,MAAM,CACb,CAGA,EAAI,MAAM,CACZ,CACA,UAGG,aACA,MACH,GAAU,SAAU,EAAK,EACzB,EAAM,KAAK,EACX,UAGG,cACA,YACH,GAAI,MAAO,IAAW,YACpB,EAAM,MAAM,MACP,CACL,GAAM,GAAM,CAAC,EAAO,GAAG,EACrB,wDACA,CACF,CAAC,EACK,EAAI,KAAK,IAAI,EACjB,MAAK,IAAI,EAAG,EAAI,QAAQ,CAAM,CAAC,EAAI,EAAI,OACrC,GAAI,OAAS,UAAY,GAAK,IAE9B,EAAI,MAAM,EACd,EAAI,GAAG,MAAM,CACf,CAGA,EAAI,MAAM,EACV,cAIA,AAAI,IAAU,GAAiB,GAC7B,EAAM,MAAM,EAEpB,CAAC,EAGL,EACG,KACC,EAAO,CAAC,CAAE,UAAW,IAAS,QAAQ,CACxC,EACG,UAAU,GAAO,CAChB,OAAQ,EAAI,UAGL,QACA,QACA,IACH,EAAM,MAAM,EACZ,EAAM,OAAO,EAGb,EAAI,MAAM,EACV,MAEN,CAAC,EAGL,GAAM,GAAU,GAAiB,EAAO,CAAM,EACxC,EAAU,GAAkB,EAAQ,EAAQ,CAAE,QAAO,CAAC,EAC5D,MAAO,GAAM,EAAQ,CAAO,EACzB,KACC,GAGE,GAAG,GAAqB,eAAgB,CAAE,EACvC,IAAI,GAAS,GAAiB,EAAO,CAAE,QAAO,CAAC,CAAC,EAGnD,GAAG,GAAqB,iBAAkB,CAAE,EACzC,IAAI,GAAS,GAAmB,EAAO,EAAQ,CAAE,WAAU,CAAC,CAAC,CAClE,CACF,CAGJ,OAAS,EAAP,CACA,SAAG,OAAS,GACL,EACT,CACF,CCtKO,YACL,EAAiB,CAAE,SAAQ,aACa,CACxC,MAAO,GAAc,CACnB,EACA,EACG,KACC,EAAU,GAAY,CAAC,EACvB,EAAO,GAAO,CAAC,CAAC,EAAI,aAAa,IAAI,GAAG,CAAC,CAC3C,CACJ,CAAC,EACE,KACC,EAAI,CAAC,CAAC,EAAO,KAAS,GAAuB,EAAM,OAAQ,EAAI,EAC7D,EAAI,aAAa,IAAI,GAAG,CAC1B,CAAC,EACD,EAAI,GAAM,CA1FhB,MA2FQ,GAAM,GAAQ,GAAI,KAGZ,EAAK,SAAS,mBAAmB,EAAI,WAAW,SAAS,EAC/D,OAAS,GAAO,EAAG,SAAS,EAAG,EAAM,EAAO,EAAG,SAAS,EACtD,GAAI,KAAK,gBAAL,QAAoB,aAAc,CACpC,GAAM,GAAW,EAAK,YAChB,EAAW,EAAG,CAAQ,EAC5B,AAAI,EAAS,OAAS,EAAS,QAC7B,EAAM,IAAI,EAAmB,CAAQ,CACzC,CAIF,OAAW,CAAC,EAAM,IAAS,GAAO,CAChC,GAAM,CAAE,cAAe,EAAE,OAAQ,KAAM,CAAI,EAC3C,EAAK,YAAY,GAAG,MAAM,KAAK,CAAU,CAAC,CAC5C,CAGA,MAAO,CAAE,IAAK,EAAI,OAAM,CAC1B,CAAC,CACH,CACJ,CClBO,YACL,EAAiB,CAAE,YAAW,SACT,CACrB,GAAM,GAAS,EAAG,cACZ,EACJ,EAAO,UACP,EAAO,cAAe,UAGxB,MAAO,GAAc,CAAC,EAAO,CAAS,CAAC,EACpC,KACC,EAAI,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAE,OAAQ,CAAE,SACpC,GAAS,EACL,KAAK,IAAI,EAAQ,KAAK,IAAI,EAAG,EAAI,CAAM,CAAC,EACxC,EACG,CACL,SACA,OAAQ,GAAK,EAAS,CACxB,EACD,EACD,EAAqB,CAAC,EAAG,IACvB,EAAE,SAAW,EAAE,QACf,EAAE,SAAW,EAAE,MAChB,CACH,CACJ,CAuBO,YACL,EAAiB,EACe,CADf,QAAE,YAAF,EAAc,KAAd,EAAc,CAAZ,YAEnB,GAAM,GAAQ,EAAW,0BAA2B,CAAE,EAChD,CAAE,KAAM,GAAiB,CAAK,EACpC,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,SACG,KACC,GAAU,EAAG,EAAuB,EACpC,GAAe,CAAO,CACxB,EACG,UAAU,CAGT,KAAK,CAAC,CAAE,UAAU,CAAE,OAAQ,IAAW,CACrC,EAAM,MAAM,OAAS,GAAG,EAAS,EAAI,MACrC,EAAG,MAAM,IAAY,GAAG,KAC1B,EAGA,UAAW,CACT,EAAM,MAAM,OAAS,GACrB,EAAG,MAAM,IAAY,EACvB,CACF,CAAC,EAGE,GAAa,EAAI,CAAO,EAC5B,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CCxHO,YACL,EAAc,EACW,CACzB,GAAI,MAAO,IAAS,YAAa,CAC/B,GAAM,GAAM,gCAAgC,KAAQ,IACpD,MAAO,IAGL,GAAqB,GAAG,mBAAqB,EAC1C,KACC,GAAW,IAAM,CAAK,EACtB,EAAI,GAAY,EACd,QAAS,EAAQ,QACnB,EAAE,EACF,GAAe,CAAC,CAAC,CACnB,EAGF,GAAkB,CAAG,EAClB,KACC,GAAW,IAAM,CAAK,EACtB,EAAI,GAAS,EACX,MAAO,EAAK,iBACZ,MAAO,EAAK,WACd,EAAE,EACF,GAAe,CAAC,CAAC,CACnB,CACJ,EACG,KACC,EAAI,CAAC,CAAC,EAAS,KAAW,OAAK,GAAY,EAAO,CACpD,CAGJ,KAAO,CACL,GAAM,GAAM,gCAAgC,IAC5C,MAAO,IAAkB,CAAG,EACzB,KACC,EAAI,GAAS,EACX,aAAc,EAAK,YACrB,EAAE,EACF,GAAe,CAAC,CAAC,CACnB,CACJ,CACF,CCvDO,YACL,EAAc,EACW,CACzB,GAAM,GAAM,WAAW,qBAAwB,mBAAmB,CAAO,IACzE,MAAO,IAA2B,CAAG,EAClC,KACC,GAAW,IAAM,CAAK,EACtB,EAAI,CAAC,CAAE,aAAY,iBAAmB,EACpC,MAAO,EACP,MAAO,CACT,EAAE,EACF,GAAe,CAAC,CAAC,CACnB,CACJ,CCOO,YACL,EACyB,CACzB,GAAM,CAAC,GAAQ,EAAI,MAAM,mBAAmB,GAAK,CAAC,EAClD,OAAQ,EAAK,YAAY,OAGlB,SACH,GAAM,CAAC,CAAE,EAAM,GAAQ,EAAI,MAAM,qCAAqC,EACtE,MAAO,IAA2B,EAAM,CAAI,MAGzC,SACH,GAAM,CAAC,CAAE,EAAM,GAAQ,EAAI,MAAM,oCAAoC,EACrE,MAAO,IAA2B,EAAM,CAAI,UAI5C,MAAO,GAEb,CCxBA,GAAI,IAgBG,YACL,EACoB,CACpB,MAAO,SAAW,EAAM,IAAM,CAC5B,GAAM,GAAS,SAAsB,WAAY,cAAc,EAC/D,MAAI,GACK,EAAG,CAAM,EAET,GAAiB,EAAG,IAAI,EAC5B,KACC,EAAI,GAAS,SAAS,WAAY,EAAO,cAAc,CAAC,CAC1D,CACN,CAAC,EACE,KACC,GAAW,IAAM,CAAK,EACtB,EAAO,GAAS,OAAO,KAAK,CAAK,EAAE,OAAS,CAAC,EAC7C,EAAI,GAAU,EAAE,OAAM,EAAE,EACxB,EAAY,CAAC,CACf,EACJ,CASO,YACL,EAC+B,CAC/B,GAAM,GAAQ,EAAW,uBAAwB,CAAE,EACnD,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,SAAM,UAAU,CAAC,CAAE,WAAY,CAC7B,EAAM,YAAY,GAAkB,CAAK,CAAC,EAC1C,EAAM,UAAU,IAAI,+BAA+B,CACrD,CAAC,EAGM,GAAY,CAAE,EAClB,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CCvCO,YACL,EAAiB,CAAE,YAAW,WACZ,CAClB,MAAO,IAAiB,SAAS,IAAI,EAClC,KACC,EAAU,IAAM,GAAgB,EAAI,CAAE,UAAS,WAAU,CAAC,CAAC,EAC3D,EAAI,CAAC,CAAE,OAAQ,CAAE,QACR,EACL,OAAQ,GAAK,EACf,EACD,EACD,EAAwB,QAAQ,CAClC,CACJ,CAaO,YACL,EAAiB,EACY,CAC7B,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GAClB,SAAM,UAAU,CAGd,KAAK,CAAE,UAAU,CACf,EAAG,OAAS,CACd,EAGA,UAAW,CACT,EAAG,OAAS,EACd,CACF,CAAC,EAIC,IAAQ,wBAAwB,EAC5B,EAAG,CAAE,OAAQ,EAAM,CAAC,EACpB,GAAU,EAAI,CAAO,GAExB,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CCxBO,YACL,EAAiB,CAAE,YAAW,WACD,CAC7B,GAAM,GAAQ,GAAI,KAGZ,EAAU,EAA+B,cAAe,CAAE,EAChE,OAAW,KAAU,GAAS,CAC5B,GAAM,GAAK,mBAAmB,EAAO,KAAK,UAAU,CAAC,CAAC,EAChD,EAAS,GAAmB,QAAQ,KAAM,EAChD,AAAI,MAAO,IAAW,aACpB,EAAM,IAAI,EAAQ,CAAM,CAC5B,CAGA,GAAM,GAAU,EACb,KACC,EAAwB,QAAQ,EAChC,EAAI,CAAC,CAAE,YAAa,CAClB,GAAM,GAAO,GAAoB,MAAM,EACjC,EAAO,EAAW,wBAAyB,CAAI,EACrD,MAAO,GAAS,GACd,GAAK,UACL,EAAK,UAET,CAAC,EACD,GAAM,CACR,EAgFF,MAAO,AA7EY,IAAiB,SAAS,IAAI,EAC9C,KACC,EAAwB,QAAQ,EAGhC,EAAU,GAAQ,EAAM,IAAM,CAC5B,GAAI,GAA4B,CAAC,EACjC,MAAO,GAAG,CAAC,GAAG,CAAK,EAAE,OAAO,CAAC,EAAO,CAAC,EAAQ,KAAY,CACvD,KAAO,EAAK,QAEN,AADS,EAAM,IAAI,EAAK,EAAK,OAAS,EAAE,EACnC,SAAW,EAAO,SACzB,EAAK,IAAI,EAOb,GAAI,GAAS,EAAO,UACpB,KAAO,CAAC,GAAU,EAAO,eACvB,EAAS,EAAO,cAChB,EAAS,EAAO,UAIlB,MAAO,GAAM,IACX,CAAC,GAAG,EAAO,CAAC,GAAG,EAAM,CAAM,CAAC,EAAE,QAAQ,EACtC,CACF,CACF,EAAG,GAAI,IAAkC,CAAC,CAC5C,CAAC,EACE,KAGC,EAAI,GAAS,GAAI,KAAI,CAAC,GAAG,CAAK,EAAE,KAAK,CAAC,CAAC,CAAE,GAAI,CAAC,CAAE,KAAO,EAAI,CAAC,CAAC,CAAC,EAC9D,GAAkB,CAAO,EAGzB,EAAU,CAAC,CAAC,EAAO,KAAY,EAC5B,KACC,GAAK,CAAC,CAAC,EAAM,GAAO,CAAE,OAAQ,CAAE,KAAK,UAAW,CAC9C,GAAM,GAAO,EAAI,EAAK,QAAU,KAAK,MAAM,EAAK,MAAM,EAGtD,KAAO,EAAK,QAAQ,CAClB,GAAM,CAAC,CAAE,GAAU,EAAK,GACxB,GAAI,EAAS,EAAS,GAAK,EACzB,EAAO,CAAC,GAAG,EAAM,EAAK,MAAM,CAAE,MAE9B,MAEJ,CAGA,KAAO,EAAK,QAAQ,CAClB,GAAM,CAAC,CAAE,GAAU,EAAK,EAAK,OAAS,GACtC,GAAI,EAAS,GAAU,GAAK,CAAC,EAC3B,EAAO,CAAC,EAAK,IAAI,EAAI,GAAG,CAAI,MAE5B,MAEJ,CAGA,MAAO,CAAC,EAAM,CAAI,CACpB,EAAG,CAAC,CAAC,EAAG,CAAC,GAAG,CAAK,CAAC,CAAC,EACnB,EAAqB,CAAC,EAAG,IACvB,EAAE,KAAO,EAAE,IACX,EAAE,KAAO,EAAE,EACZ,CACH,CACF,CACF,CACF,CACF,EAIC,KACC,EAAI,CAAC,CAAC,EAAM,KAAW,EACrB,KAAM,EAAK,IAAI,CAAC,CAAC,KAAU,CAAI,EAC/B,KAAM,EAAK,IAAI,CAAC,CAAC,KAAU,CAAI,CACjC,EAAE,EAGF,EAAU,CAAE,KAAM,CAAC,EAAG,KAAM,CAAC,CAAE,CAAC,EAChC,GAAY,EAAG,CAAC,EAChB,EAAI,CAAC,CAAC,EAAG,KAGH,EAAE,KAAK,OAAS,EAAE,KAAK,OAClB,CACL,KAAM,EAAE,KAAK,MAAM,KAAK,IAAI,EAAG,EAAE,KAAK,OAAS,CAAC,EAAG,EAAE,KAAK,MAAM,EAChE,KAAM,CAAC,CACT,EAIO,CACL,KAAM,EAAE,KAAK,MAAM,EAAE,EACrB,KAAM,EAAE,KAAK,MAAM,EAAG,EAAE,KAAK,OAAS,EAAE,KAAK,MAAM,CACrD,CAEH,CACH,CACJ,CAYO,YACL,EAAiB,CAAE,YAAW,UAAS,WACC,CACxC,MAAO,GAAM,IAAM,CACjB,GAAM,GAAQ,GAAI,GACZ,EAAQ,EAAM,KAAK,GAAS,CAAC,CAAC,EACpC,SAAM,UAAU,CAAC,CAAE,OAAM,UAAW,CAGlC,OAAW,CAAC,IAAW,GACrB,EAAO,UAAU,OAAO,sBAAsB,EAC9C,EAAO,UAAU,OAAO,sBAAsB,EAIhD,OAAW,CAAC,EAAO,CAAC,KAAY,GAAK,QAAQ,EAC3C,EAAO,UAAU,IAAI,sBAAsB,EAC3C,EAAO,UAAU,OACf,uBACA,IAAU,EAAK,OAAS,CAC1B,CAEJ,CAAC,EAGG,GAAQ,qBAAqB,GAC/B,EACG,KACC,EAAU,CAAK,EACf,EAAwB,QAAQ,EAChC,GAAa,GAAG,EAChB,GAAK,CAAC,EACN,EAAU,EAAQ,KAAK,GAAK,CAAC,CAAC,CAAC,EAC/B,GAAO,CAAE,MAAO,GAAI,CAAC,EACrB,GAAe,CAAK,CACtB,EACG,UAAU,CAAC,CAAC,CAAE,CAAE,WAAY,CAC3B,GAAM,GAAM,GAAY,EAGlB,EAAS,EAAK,EAAK,OAAS,GAClC,GAAI,GAAU,EAAO,OAAQ,CAC3B,GAAM,CAAC,GAAU,EACX,CAAE,QAAS,GAAI,KAAI,EAAO,IAAI,EACpC,AAAI,EAAI,OAAS,GACf,GAAI,KAAO,EACX,QAAQ,aAAa,CAAC,EAAG,GAAI,GAAG,GAAK,EAIzC,KACE,GAAI,KAAO,GACX,QAAQ,aAAa,CAAC,EAAG,GAAI,GAAG,GAAK,CAEzC,CAAC,EAGA,GAAqB,EAAI,CAAE,YAAW,SAAQ,CAAC,EACnD,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CAAC,CACH,CC/OO,YACL,EAAkB,CAAE,YAAW,QAAO,WACf,CAGvB,GAAM,GAAa,EAChB,KACC,EAAI,CAAC,CAAE,OAAQ,CAAE,QAAU,CAAC,EAC5B,GAAY,EAAG,CAAC,EAChB,EAAI,CAAC,CAAC,EAAG,KAAO,EAAI,GAAK,EAAI,CAAC,EAC9B,EAAqB,CACvB,EAGI,EAAU,EACb,KACC,EAAI,CAAC,CAAE,YAAa,CAAM,CAC5B,EAGF,MAAO,GAAc,CAAC,EAAS,CAAU,CAAC,EACvC,KACC,EAAI,CAAC,CAAC,EAAQ,KAAe,CAAE,IAAU,EAAU,EACnD,EAAqB,EACrB,EAAU,EAAQ,KAAK,GAAK,CAAC,CAAC,CAAC,EAC/B,GAAQ,EAAI,EACZ,GAAO,CAAE,MAAO,GAAI,CAAC,EACrB,EAAI,GAAW,EAAE,QAAO,EAAE,CAC5B,CACJ,CAYO,YACL,EAAiB,CAAE,YAAW,UAAS,QAAO,WACZ,CAClC,GAAM,GAAQ,GAAI,GACZ,EAAQ,EAAM,KAAK,GAAS,CAAC,CAAC,EACpC,SAAM,UAAU,CAGd,KAAK,CAAE,UAAU,CACf,EAAG,OAAS,EACZ,AAAI,EACF,GAAG,aAAa,WAAY,IAAI,EAChC,EAAG,KAAK,GAER,EAAG,gBAAgB,UAAU,CAEjC,EAGA,UAAW,CACT,EAAG,MAAM,IAAM,GACf,EAAG,OAAS,GACZ,EAAG,gBAAgB,UAAU,CAC/B,CACF,CAAC,EAGD,EACG,KACC,EAAU,CAAK,EACf,EAAwB,QAAQ,CAClC,EACG,UAAU,CAAC,CAAE,YAAa,CACzB,EAAG,MAAM,IAAM,GAAG,EAAS,MAC7B,CAAC,EAGE,GAAe,EAAI,CAAE,YAAW,QAAO,SAAQ,CAAC,EACpD,KACC,EAAI,GAAS,EAAM,KAAK,CAAK,CAAC,EAC9B,EAAS,IAAM,EAAM,SAAS,CAAC,EAC/B,EAAI,GAAU,GAAE,IAAK,GAAO,EAAQ,CACtC,CACJ,CCpHO,YACL,CAAE,YAAW,WACP,CACN,EACG,KACC,EAAU,IAAM,EAEd,0DACF,CAAC,EACD,EAAI,GAAM,CACR,EAAG,cAAgB,GACnB,EAAG,QAAU,EACf,CAAC,EACD,GAAS,GAAM,EAAU,EAAI,QAAQ,EAClC,KACC,GAAU,IAAM,EAAG,UAAU,SAAS,0BAA0B,CAAC,EACjE,EAAI,IAAM,CAAE,CACd,CACF,EACA,GAAe,CAAO,CACxB,EACG,UAAU,CAAC,CAAC,EAAI,KAAY,CAC3B,EAAG,UAAU,OAAO,0BAA0B,EAC1C,GACF,GAAG,QAAU,GACjB,CAAC,CACP,CC/BA,aAAkC,CAChC,MAAO,qBAAqB,KAAK,UAAU,SAAS,CACtD,CAiBO,YACL,CAAE,aACI,CACN,EACG,KACC,EAAU,IAAM,EAAY,qBAAqB,CAAC,EAClD,EAAI,GAAM,EAAG,gBAAgB,mBAAmB,CAAC,EACjD,EAAO,EAAa,EACpB,GAAS,GAAM,EAAU,EAAI,YAAY,EACtC,KACC,EAAI,IAAM,CAAE,CACd,CACF,CACF,EACG,UAAU,GAAM,CACf,GAAM,GAAM,EAAG,UAGf,AAAI,IAAQ,EACV,EAAG,UAAY,EAGN,EAAM,EAAG,eAAiB,EAAG,cACtC,GAAG,UAAY,EAAM,EAEzB,CAAC,CACP,CCpCO,YACL,CAAE,YAAW,WACP,CACN,EAAc,CAAC,GAAY,QAAQ,EAAG,CAAO,CAAC,EAC3C,KACC,EAAI,CAAC,CAAC,EAAQ,KAAY,GAAU,CAAC,CAAM,EAC3C,EAAU,GAAU,EAAG,CAAM,EAC1B,KACC,GAAM,EAAS,IAAM,GAAG,CAC1B,CACF,EACA,GAAe,CAAS,CAC1B,EACG,UAAU,CAAC,CAAC,EAAQ,CAAE,OAAQ,CAAE,SAAU,CACzC,GAAI,EACF,SAAS,KAAK,aAAa,qBAAsB,EAAE,EACnD,SAAS,KAAK,MAAM,IAAM,IAAI,UACzB,CACL,GAAM,GAAQ,GAAK,SAAS,SAAS,KAAK,MAAM,IAAK,EAAE,EACvD,SAAS,KAAK,gBAAgB,oBAAoB,EAClD,SAAS,KAAK,MAAM,IAAM,GACtB,GACF,OAAO,SAAS,EAAG,CAAK,CAC5B,CACF,CAAC,CACP,CC7DA,AAAK,OAAO,SACV,QAAO,QAAU,SAAU,EAAa,CACtC,GAAM,GAA2B,CAAC,EAClC,OAAW,KAAO,QAAO,KAAK,CAAG,EAE/B,EAAK,KAAK,CAAC,EAAK,EAAI,EAAI,CAAC,EAG3B,MAAO,EACT,GAGF,AAAK,OAAO,QACV,QAAO,OAAS,SAAU,EAAa,CACrC,GAAM,GAAiB,CAAC,EACxB,OAAW,KAAO,QAAO,KAAK,CAAG,EAE/B,EAAK,KAAK,EAAI,EAAI,EAGpB,MAAO,EACT,GAKF,AAAI,MAAO,UAAY,aAGhB,SAAQ,UAAU,UACrB,SAAQ,UAAU,SAAW,SAC3B,EAA8B,EACxB,CACN,AAAI,MAAO,IAAM,SACf,MAAK,WAAa,EAAE,KACpB,KAAK,UAAY,EAAE,KAEnB,MAAK,WAAa,EAClB,KAAK,UAAY,EAErB,GAGG,QAAQ,UAAU,aACrB,SAAQ,UAAU,YAAc,YAC3B,EACG,CACN,GAAM,GAAS,KAAK,WACpB,GAAI,EAAQ,CACV,AAAI,EAAM,SAAW,GACnB,EAAO,YAAY,IAAI,EAGzB,OAAS,GAAI,EAAM,OAAS,EAAG,GAAK,EAAG,IAAK,CAC1C,GAAI,GAAO,EAAM,GACjB,AAAI,MAAO,IAAS,SAClB,EAAO,SAAS,eAAe,CAAI,EAC5B,EAAK,YACZ,EAAK,WAAW,YAAY,CAAI,EAGlC,AAAK,EAGH,EAAO,aAAa,KAAK,gBAAkB,CAAI,EAF/C,EAAO,aAAa,EAAM,IAAI,CAGlC,CACF,CACF,I9LHJ,SAAS,gBAAgB,UAAU,OAAO,OAAO,EACjD,SAAS,gBAAgB,UAAU,IAAI,IAAI,EAG3C,GAAM,IAAY,GAAc,EAC1B,GAAY,GAAc,EAC1B,GAAY,GAAoB,EAChC,GAAY,GAAc,EAG1B,GAAY,GAAc,EAC1B,GAAY,GAAW,oBAAoB,EAC3C,GAAY,GAAW,qBAAqB,EAC5C,GAAY,GAAW,EAGvB,GAAS,GAAc,EACvB,GAAS,SAAS,MAAM,UAAU,QAAQ,EAC5C,gCAAU,QAAS,GACnB,GAAI,KAAI,2BAA4B,GAAO,IAAI,CACjD,EACE,GAGE,GAAS,GAAI,GACnB,GAAiB,CAAE,SAAO,CAAC,EAG3B,AAAI,GAAQ,oBAAoB,GAC9B,GAAoB,CAAE,aAAW,aAAW,YAAU,CAAC,EAxHzD,OA2HA,AAAI,QAAO,UAAP,eAAgB,YAAa,QAC/B,GAAqB,CAAE,YAAU,CAAC,EAGpC,EAAM,GAAW,EAAO,EACrB,KACC,GAAM,GAAG,CACX,EACG,UAAU,IAAM,CACf,GAAU,SAAU,EAAK,EACzB,GAAU,SAAU,EAAK,CAC3B,CAAC,EAGL,GACG,KACC,EAAO,CAAC,CAAE,UAAW,IAAS,QAAQ,CACxC,EACG,UAAU,GAAO,CAChB,OAAQ,EAAI,UAGL,QACA,IACH,GAAM,GAAO,GAAmB,kBAAkB,EAClD,AAAI,MAAO,IAAS,aAClB,EAAK,MAAM,EACb,UAGG,QACA,IACH,GAAM,GAAO,GAAmB,kBAAkB,EAClD,AAAI,MAAO,IAAS,aAClB,EAAK,MAAM,EACb,MAEN,CAAC,EAGL,GAAmB,CAAE,aAAW,UAAQ,CAAC,EACzC,GAAe,CAAE,YAAU,CAAC,EAC5B,GAAgB,CAAE,aAAW,UAAQ,CAAC,EAGtC,GAAM,IAAU,GAAY,GAAoB,QAAQ,EAAG,CAAE,YAAU,CAAC,EAClE,GAAQ,GACX,KACC,EAAI,IAAM,GAAoB,MAAM,CAAC,EACrC,EAAU,GAAM,GAAU,EAAI,CAAE,aAAW,UAAQ,CAAC,CAAC,EACrD,EAAY,CAAC,CACf,EAGI,GAAW,EAGf,GAAG,GAAqB,QAAQ,EAC7B,IAAI,GAAM,GAAY,EAAI,CAAE,SAAO,CAAC,CAAC,EAGxC,GAAG,GAAqB,QAAQ,EAC7B,IAAI,GAAM,GAAY,EAAI,CAAE,aAAW,WAAS,QAAM,CAAC,CAAC,EAG3D,GAAG,GAAqB,SAAS,EAC9B,IAAI,GAAM,GAAa,CAAE,CAAC,EAG7B,GAAG,GAAqB,QAAQ,EAC7B,IAAI,GAAM,GAAY,EAAI,CAAE,UAAQ,YAAU,CAAC,CAAC,EAGnD,GAAG,GAAqB,QAAQ,EAC7B,IAAI,GAAM,GAAY,CAAE,CAAC,CAC9B,EAGM,GAAW,EAAM,IAAM,EAG3B,GAAG,GAAqB,SAAS,EAC9B,IAAI,GAAM,GAAa,EAAI,CAAE,WAAS,SAAO,CAAC,CAAC,EAGlD,GAAG,GAAqB,SAAS,EAC9B,IAAI,GAAM,GAAQ,kBAAkB,EACjC,GAAoB,EAAI,CAAE,UAAQ,YAAU,CAAC,EAC7C,CACJ,EAGF,GAAG,GAAqB,cAAc,EACnC,IAAI,GAAM,GAAiB,EAAI,CAAE,aAAW,UAAQ,CAAC,CAAC,EAGzD,GAAG,GAAqB,SAAS,EAC9B,IAAI,GAAM,EAAG,aAAa,cAAc,IAAM,aAC3C,GAAG,GAAS,IAAM,GAAa,EAAI,CAAE,aAAW,WAAS,QAAM,CAAC,CAAC,EACjE,GAAG,GAAS,IAAM,GAAa,EAAI,CAAE,aAAW,WAAS,QAAM,CAAC,CAAC,CACrE,EAGF,GAAG,GAAqB,MAAM,EAC3B,IAAI,GAAM,GAAU,EAAI,CAAE,aAAW,UAAQ,CAAC,CAAC,EAGlD,GAAG,GAAqB,KAAK,EAC1B,IAAI,GAAM,GAAqB,EAAI,CAAE,aAAW,WAAS,UAAQ,CAAC,CAAC,EAGtE,GAAG,GAAqB,KAAK,EAC1B,IAAI,GAAM,GAAe,EAAI,CAAE,aAAW,WAAS,SAAO,UAAQ,CAAC,CAAC,CACzE,CAAC,EAGK,GAAa,GAChB,KACC,EAAU,IAAM,EAAQ,EACxB,GAAU,EAAQ,EAClB,EAAY,CAAC,CACf,EAGF,GAAW,UAAU,EAMrB,OAAO,UAAa,GACpB,OAAO,UAAa,GACpB,OAAO,QAAa,GACpB,OAAO,UAAa,GACpB,OAAO,UAAa,GACpB,OAAO,QAAa,GACpB,OAAO,QAAa,GACpB,OAAO,OAAa,GACpB,OAAO,OAAa,GACpB,OAAO,WAAa", + "names": [] +} diff --git a/assets/javascripts/lunr/min/lunr.ar.min.js b/assets/javascripts/lunr/min/lunr.ar.min.js new file mode 100644 index 00000000..248ddc5d --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ar.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ar=function(){this.pipeline.reset(),this.pipeline.add(e.ar.trimmer,e.ar.stopWordFilter,e.ar.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ar.stemmer))},e.ar.wordCharacters="ء-ٛٱـ",e.ar.trimmer=e.trimmerSupport.generateTrimmer(e.ar.wordCharacters),e.Pipeline.registerFunction(e.ar.trimmer,"trimmer-ar"),e.ar.stemmer=function(){var e=this;return e.result=!1,e.preRemoved=!1,e.sufRemoved=!1,e.pre={pre1:"ف ك ب و س ل ن ا ي ت",pre2:"ال لل",pre3:"بال وال فال تال كال ولل",pre4:"فبال كبال وبال وكال"},e.suf={suf1:"ه ك ت ن ا ي",suf2:"نك نه ها وك يا اه ون ين تن تم نا وا ان كم كن ني نن ما هم هن تك ته ات يه",suf3:"تين كهم نيه نهم ونه وها يهم ونا ونك وني وهم تكم تنا تها تني تهم كما كها ناه نكم هنا تان يها",suf4:"كموه ناها ونني ونهم تكما تموه تكاه كماه ناكم ناهم نيها وننا"},e.patterns=JSON.parse('{"pt43":[{"pt":[{"c":"ا","l":1}]},{"pt":[{"c":"ا,ت,ن,ي","l":0}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"و","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ي","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ا","l":2},{"c":"ل","l":3,"m":3}]},{"pt":[{"c":"م","l":0}]}],"pt53":[{"pt":[{"c":"ت","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":3},{"c":"ل","l":3,"m":4},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":3}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ن","l":4}]},{"pt":[{"c":"ت","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"م","l":0},{"c":"و","l":3}]},{"pt":[{"c":"ا","l":1},{"c":"و","l":3}]},{"pt":[{"c":"و","l":1},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"ا","l":2},{"c":"ن","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":1},{"c":"ا","l":3}]},{"pt":[{"c":"ي,ت,ا,ن","l":0},{"c":"ت","l":1}],"mPt":[{"c":"ف","l":0,"m":2},{"c":"ع","l":1,"m":3},{"c":"ا","l":2},{"c":"ل","l":3,"m":4}]},{"pt":[{"c":"ت,ي,ا,ن","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":2},{"c":"ي","l":3}]},{"pt":[{"c":"ا,ي,ت,ن","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ء","l":4}]}],"pt63":[{"pt":[{"c":"ا","l":0},{"c":"ت","l":2},{"c":"ا","l":4}]},{"pt":[{"c":"ا,ت,ن,ي","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"و","l":3}]},{"pt":[{"c":"م","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ي","l":1},{"c":"ي","l":3},{"c":"ا","l":4},{"c":"ء","l":5}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ا","l":4}]}],"pt54":[{"pt":[{"c":"ت","l":0}]},{"pt":[{"c":"ا,ي,ت,ن","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"م","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":2}]}],"pt64":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":1}]}],"pt73":[{"pt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ا","l":5}]}],"pt75":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":5}]}]}'),e.execArray=["cleanWord","removeDiacritics","cleanAlef","removeStopWords","normalizeHamzaAndAlef","removeStartWaw","removePre432","removeEndTaa","wordCheck"],e.stem=function(){var r=0;for(e.result=!1,e.preRemoved=!1,e.sufRemoved=!1;r=0)return!0},e.normalizeHamzaAndAlef=function(){return e.word=e.word.replace("ؤ","ء"),e.word=e.word.replace("ئ","ء"),e.word=e.word.replace(/([\u0627])\1+/gi,"ا"),!1},e.removeEndTaa=function(){return!(e.word.length>2)||(e.word=e.word.replace(/[\u0627]$/,""),e.word=e.word.replace("ة",""),!1)},e.removeStartWaw=function(){return e.word.length>3&&"و"==e.word[0]&&"و"==e.word[1]&&(e.word=e.word.slice(1)),!1},e.removePre432=function(){var r=e.word;if(e.word.length>=7){var t=new RegExp("^("+e.pre.pre4.split(" ").join("|")+")");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=6){var c=new RegExp("^("+e.pre.pre3.split(" ").join("|")+")");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=5){var l=new RegExp("^("+e.pre.pre2.split(" ").join("|")+")");e.word=e.word.replace(l,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.patternCheck=function(r){for(var t=0;t3){var t=new RegExp("^("+e.pre.pre1.split(" ").join("|")+")");e.word=e.word.replace(t,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.removeSuf1=function(){var r=e.word;if(0==e.sufRemoved&&e.word.length>3){var t=new RegExp("("+e.suf.suf1.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.removeSuf432=function(){var r=e.word;if(e.word.length>=6){var t=new RegExp("("+e.suf.suf4.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=5){var c=new RegExp("("+e.suf.suf3.split(" ").join("|")+")$");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=4){var l=new RegExp("("+e.suf.suf2.split(" ").join("|")+")$");e.word=e.word.replace(l,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.wordCheck=function(){for(var r=(e.word,[e.removeSuf432,e.removeSuf1,e.removePre1]),t=0,c=!1;e.word.length>=7&&!e.result&&t=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.de.min.js b/assets/javascripts/lunr/min/lunr.de.min.js new file mode 100644 index 00000000..f3b5c108 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.de.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `German` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!v.eq_s(1,e)||(v.ket=v.cursor,!v.in_grouping(p,97,252)))&&(v.slice_from(r),v.cursor=n,!0)}function i(){for(var r,n,i,s,t=v.cursor;;)if(r=v.cursor,v.bra=r,v.eq_s(1,"ß"))v.ket=v.cursor,v.slice_from("ss");else{if(r>=v.limit)break;v.cursor=r+1}for(v.cursor=t;;)for(n=v.cursor;;){if(i=v.cursor,v.in_grouping(p,97,252)){if(s=v.cursor,v.bra=s,e("u","U",i))break;if(v.cursor=s,e("y","Y",i))break}if(i>=v.limit)return void(v.cursor=n);v.cursor=i+1}}function s(){for(;!v.in_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}for(;!v.out_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}return!1}function t(){m=v.limit,l=m;var e=v.cursor+3;0<=e&&e<=v.limit&&(d=e,s()||(m=v.cursor,m=v.limit)return;v.cursor++}}}function c(){return m<=v.cursor}function u(){return l<=v.cursor}function a(){var e,r,n,i,s=v.limit-v.cursor;if(v.ket=v.cursor,(e=v.find_among_b(w,7))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:v.slice_del(),v.ket=v.cursor,v.eq_s_b(1,"s")&&(v.bra=v.cursor,v.eq_s_b(3,"nis")&&v.slice_del());break;case 3:v.in_grouping_b(g,98,116)&&v.slice_del()}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(f,4))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:if(v.in_grouping_b(k,98,116)){var t=v.cursor-3;v.limit_backward<=t&&t<=v.limit&&(v.cursor=t,v.slice_del())}}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(_,8))&&(v.bra=v.cursor,u()))switch(e){case 1:v.slice_del(),v.ket=v.cursor,v.eq_s_b(2,"ig")&&(v.bra=v.cursor,r=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-r,u()&&v.slice_del()));break;case 2:n=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-n,v.slice_del());break;case 3:if(v.slice_del(),v.ket=v.cursor,i=v.limit-v.cursor,!v.eq_s_b(2,"er")&&(v.cursor=v.limit-i,!v.eq_s_b(2,"en")))break;v.bra=v.cursor,c()&&v.slice_del();break;case 4:v.slice_del(),v.ket=v.cursor,e=v.find_among_b(b,2),e&&(v.bra=v.cursor,u()&&1==e&&v.slice_del())}}var d,l,m,h=[new r("",-1,6),new r("U",0,2),new r("Y",0,1),new r("ä",0,3),new r("ö",0,4),new r("ü",0,5)],w=[new r("e",-1,2),new r("em",-1,1),new r("en",-1,2),new r("ern",-1,1),new r("er",-1,1),new r("s",-1,3),new r("es",5,2)],f=[new r("en",-1,1),new r("er",-1,1),new r("st",-1,2),new r("est",2,1)],b=[new r("ig",-1,1),new r("lich",-1,1)],_=[new r("end",-1,1),new r("ig",-1,2),new r("ung",-1,1),new r("lich",-1,3),new r("isch",-1,2),new r("ik",-1,2),new r("heit",-1,3),new r("keit",-1,4)],p=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],g=[117,30,5],k=[117,30,4],v=new n;this.setCurrent=function(e){v.setCurrent(e)},this.getCurrent=function(){return v.getCurrent()},this.stem=function(){var e=v.cursor;return i(),v.cursor=e,t(),v.limit_backward=e,v.cursor=v.limit,a(),v.cursor=v.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.du.min.js b/assets/javascripts/lunr/min/lunr.du.min.js new file mode 100644 index 00000000..49a0f3f0 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.du.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Dutch` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e,r,i,o=C.cursor;;){if(C.bra=C.cursor,e=C.find_among(b,11))switch(C.ket=C.cursor,e){case 1:C.slice_from("a");continue;case 2:C.slice_from("e");continue;case 3:C.slice_from("i");continue;case 4:C.slice_from("o");continue;case 5:C.slice_from("u");continue;case 6:if(C.cursor>=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(r=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=r);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=r;else if(n(r))break}else if(n(r))break}function n(e){return C.cursor=e,e>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,f=_,t()||(_=C.cursor,_<3&&(_=3),t()||(f=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var e;;)if(C.bra=C.cursor,e=C.find_among(p,3))switch(C.ket=C.cursor,e){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return f<=C.cursor}function a(){var e=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-e,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var e;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.slice_del(),w=!0,a())))}function m(){var e;u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.eq_s_b(3,"gem")||(C.cursor=C.limit-e,C.slice_del(),a())))}function d(){var e,r,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,e=C.find_among_b(h,5))switch(C.bra=C.cursor,e){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(z,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(r=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-r,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,e=C.find_among_b(k,6))switch(C.bra=C.cursor,e){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(j,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var f,_,w,b=[new r("",-1,6),new r("á",0,1),new r("ä",0,1),new r("é",0,2),new r("ë",0,2),new r("í",0,3),new r("ï",0,3),new r("ó",0,4),new r("ö",0,4),new r("ú",0,5),new r("ü",0,5)],p=[new r("",-1,3),new r("I",0,2),new r("Y",0,1)],g=[new r("dd",-1,-1),new r("kk",-1,-1),new r("tt",-1,-1)],h=[new r("ene",-1,2),new r("se",-1,3),new r("en",-1,2),new r("heden",2,1),new r("s",-1,3)],k=[new r("end",-1,1),new r("ig",-1,2),new r("ing",-1,1),new r("lijk",-1,3),new r("baar",-1,4),new r("bar",-1,5)],v=[new r("aa",-1,-1),new r("ee",-1,-1),new r("oo",-1,-1),new r("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(e){C.setCurrent(e)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var r=C.cursor;return e(),C.cursor=r,o(),C.limit_backward=r,C.cursor=C.limit,d(),C.cursor=C.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.es.min.js b/assets/javascripts/lunr/min/lunr.es.min.js new file mode 100644 index 00000000..2989d342 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.es.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Spanish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,s){"function"==typeof define&&define.amd?define(s):"object"==typeof exports?module.exports=s():s()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.es=function(){this.pipeline.reset(),this.pipeline.add(e.es.trimmer,e.es.stopWordFilter,e.es.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.es.stemmer))},e.es.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.es.trimmer=e.trimmerSupport.generateTrimmer(e.es.wordCharacters),e.Pipeline.registerFunction(e.es.trimmer,"trimmer-es"),e.es.stemmer=function(){var s=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(){if(A.out_grouping(x,97,252)){for(;!A.in_grouping(x,97,252);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}return!0}function n(){if(A.in_grouping(x,97,252)){var s=A.cursor;if(e()){if(A.cursor=s,!A.in_grouping(x,97,252))return!0;for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!0;A.cursor++}}return!1}return!0}function i(){var s,r=A.cursor;if(n()){if(A.cursor=r,!A.out_grouping(x,97,252))return;if(s=A.cursor,e()){if(A.cursor=s,!A.in_grouping(x,97,252)||A.cursor>=A.limit)return;A.cursor++}}g=A.cursor}function a(){for(;!A.in_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}return!0}function t(){var e=A.cursor;g=A.limit,p=g,v=g,i(),A.cursor=e,a()&&(p=A.cursor,a()&&(v=A.cursor))}function o(){for(var e;;){if(A.bra=A.cursor,e=A.find_among(k,6))switch(A.ket=A.cursor,e){case 1:A.slice_from("a");continue;case 2:A.slice_from("e");continue;case 3:A.slice_from("i");continue;case 4:A.slice_from("o");continue;case 5:A.slice_from("u");continue;case 6:if(A.cursor>=A.limit)break;A.cursor++;continue}break}}function u(){return g<=A.cursor}function w(){return p<=A.cursor}function c(){return v<=A.cursor}function m(){var e;if(A.ket=A.cursor,A.find_among_b(y,13)&&(A.bra=A.cursor,(e=A.find_among_b(q,11))&&u()))switch(e){case 1:A.bra=A.cursor,A.slice_from("iendo");break;case 2:A.bra=A.cursor,A.slice_from("ando");break;case 3:A.bra=A.cursor,A.slice_from("ar");break;case 4:A.bra=A.cursor,A.slice_from("er");break;case 5:A.bra=A.cursor,A.slice_from("ir");break;case 6:A.slice_del();break;case 7:A.eq_s_b(1,"u")&&A.slice_del()}}function l(e,s){if(!c())return!0;A.slice_del(),A.ket=A.cursor;var r=A.find_among_b(e,s);return r&&(A.bra=A.cursor,1==r&&c()&&A.slice_del()),!1}function d(e){return!c()||(A.slice_del(),A.ket=A.cursor,A.eq_s_b(2,e)&&(A.bra=A.cursor,c()&&A.slice_del()),!1)}function b(){var e;if(A.ket=A.cursor,e=A.find_among_b(S,46)){switch(A.bra=A.cursor,e){case 1:if(!c())return!1;A.slice_del();break;case 2:if(d("ic"))return!1;break;case 3:if(!c())return!1;A.slice_from("log");break;case 4:if(!c())return!1;A.slice_from("u");break;case 5:if(!c())return!1;A.slice_from("ente");break;case 6:if(!w())return!1;A.slice_del(),A.ket=A.cursor,e=A.find_among_b(C,4),e&&(A.bra=A.cursor,c()&&(A.slice_del(),1==e&&(A.ket=A.cursor,A.eq_s_b(2,"at")&&(A.bra=A.cursor,c()&&A.slice_del()))));break;case 7:if(l(P,3))return!1;break;case 8:if(l(F,3))return!1;break;case 9:if(d("at"))return!1}return!0}return!1}function f(){var e,s;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(W,12),A.limit_backward=s,e)){if(A.bra=A.cursor,1==e){if(!A.eq_s_b(1,"u"))return!1;A.slice_del()}return!0}return!1}function _(){var e,s,r,n;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(L,96),A.limit_backward=s,e))switch(A.bra=A.cursor,e){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"u")?(n=A.limit-A.cursor,A.eq_s_b(1,"g")?A.cursor=A.limit-n:A.cursor=A.limit-r):A.cursor=A.limit-r,A.bra=A.cursor;case 2:A.slice_del()}}function h(){var e,s;if(A.ket=A.cursor,e=A.find_among_b(z,8))switch(A.bra=A.cursor,e){case 1:u()&&A.slice_del();break;case 2:u()&&(A.slice_del(),A.ket=A.cursor,A.eq_s_b(1,"u")&&(A.bra=A.cursor,s=A.limit-A.cursor,A.eq_s_b(1,"g")&&(A.cursor=A.limit-s,u()&&A.slice_del())))}}var v,p,g,k=[new s("",-1,6),new s("á",0,1),new s("é",0,2),new s("í",0,3),new s("ó",0,4),new s("ú",0,5)],y=[new s("la",-1,-1),new s("sela",0,-1),new s("le",-1,-1),new s("me",-1,-1),new s("se",-1,-1),new s("lo",-1,-1),new s("selo",5,-1),new s("las",-1,-1),new s("selas",7,-1),new s("les",-1,-1),new s("los",-1,-1),new s("selos",10,-1),new s("nos",-1,-1)],q=[new s("ando",-1,6),new s("iendo",-1,6),new s("yendo",-1,7),new s("ándo",-1,2),new s("iéndo",-1,1),new s("ar",-1,6),new s("er",-1,6),new s("ir",-1,6),new s("ár",-1,3),new s("ér",-1,4),new s("ír",-1,5)],C=[new s("ic",-1,-1),new s("ad",-1,-1),new s("os",-1,-1),new s("iv",-1,1)],P=[new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,1)],F=[new s("ic",-1,1),new s("abil",-1,1),new s("iv",-1,1)],S=[new s("ica",-1,1),new s("ancia",-1,2),new s("encia",-1,5),new s("adora",-1,2),new s("osa",-1,1),new s("ista",-1,1),new s("iva",-1,9),new s("anza",-1,1),new s("logía",-1,3),new s("idad",-1,8),new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,2),new s("mente",-1,7),new s("amente",13,6),new s("ación",-1,2),new s("ución",-1,4),new s("ico",-1,1),new s("ismo",-1,1),new s("oso",-1,1),new s("amiento",-1,1),new s("imiento",-1,1),new s("ivo",-1,9),new s("ador",-1,2),new s("icas",-1,1),new s("ancias",-1,2),new s("encias",-1,5),new s("adoras",-1,2),new s("osas",-1,1),new s("istas",-1,1),new s("ivas",-1,9),new s("anzas",-1,1),new s("logías",-1,3),new s("idades",-1,8),new s("ables",-1,1),new s("ibles",-1,1),new s("aciones",-1,2),new s("uciones",-1,4),new s("adores",-1,2),new s("antes",-1,2),new s("icos",-1,1),new s("ismos",-1,1),new s("osos",-1,1),new s("amientos",-1,1),new s("imientos",-1,1),new s("ivos",-1,9)],W=[new s("ya",-1,1),new s("ye",-1,1),new s("yan",-1,1),new s("yen",-1,1),new s("yeron",-1,1),new s("yendo",-1,1),new s("yo",-1,1),new s("yas",-1,1),new s("yes",-1,1),new s("yais",-1,1),new s("yamos",-1,1),new s("yó",-1,1)],L=[new s("aba",-1,2),new s("ada",-1,2),new s("ida",-1,2),new s("ara",-1,2),new s("iera",-1,2),new s("ía",-1,2),new s("aría",5,2),new s("ería",5,2),new s("iría",5,2),new s("ad",-1,2),new s("ed",-1,2),new s("id",-1,2),new s("ase",-1,2),new s("iese",-1,2),new s("aste",-1,2),new s("iste",-1,2),new s("an",-1,2),new s("aban",16,2),new s("aran",16,2),new s("ieran",16,2),new s("ían",16,2),new s("arían",20,2),new s("erían",20,2),new s("irían",20,2),new s("en",-1,1),new s("asen",24,2),new s("iesen",24,2),new s("aron",-1,2),new s("ieron",-1,2),new s("arán",-1,2),new s("erán",-1,2),new s("irán",-1,2),new s("ado",-1,2),new s("ido",-1,2),new s("ando",-1,2),new s("iendo",-1,2),new s("ar",-1,2),new s("er",-1,2),new s("ir",-1,2),new s("as",-1,2),new s("abas",39,2),new s("adas",39,2),new s("idas",39,2),new s("aras",39,2),new s("ieras",39,2),new s("ías",39,2),new s("arías",45,2),new s("erías",45,2),new s("irías",45,2),new s("es",-1,1),new s("ases",49,2),new s("ieses",49,2),new s("abais",-1,2),new s("arais",-1,2),new s("ierais",-1,2),new s("íais",-1,2),new s("aríais",55,2),new s("eríais",55,2),new s("iríais",55,2),new s("aseis",-1,2),new s("ieseis",-1,2),new s("asteis",-1,2),new s("isteis",-1,2),new s("áis",-1,2),new s("éis",-1,1),new s("aréis",64,2),new s("eréis",64,2),new s("iréis",64,2),new s("ados",-1,2),new s("idos",-1,2),new s("amos",-1,2),new s("ábamos",70,2),new s("áramos",70,2),new s("iéramos",70,2),new s("íamos",70,2),new s("aríamos",74,2),new s("eríamos",74,2),new s("iríamos",74,2),new s("emos",-1,1),new s("aremos",78,2),new s("eremos",78,2),new s("iremos",78,2),new s("ásemos",78,2),new s("iésemos",78,2),new s("imos",-1,2),new s("arás",-1,2),new s("erás",-1,2),new s("irás",-1,2),new s("ís",-1,2),new s("ará",-1,2),new s("erá",-1,2),new s("irá",-1,2),new s("aré",-1,2),new s("eré",-1,2),new s("iré",-1,2),new s("ió",-1,2)],z=[new s("a",-1,1),new s("e",-1,2),new s("o",-1,1),new s("os",-1,1),new s("á",-1,1),new s("é",-1,2),new s("í",-1,1),new s("ó",-1,1)],x=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,4,10],A=new r;this.setCurrent=function(e){A.setCurrent(e)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return t(),A.limit_backward=e,A.cursor=A.limit,m(),A.cursor=A.limit,b()||(A.cursor=A.limit,f()||(A.cursor=A.limit,_())),A.cursor=A.limit,h(),A.cursor=A.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.es.stemmer,"stemmer-es"),e.es.stopWordFilter=e.generateStopWordFilter("a al algo algunas algunos ante antes como con contra cual cuando de del desde donde durante e el ella ellas ellos en entre era erais eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estamos estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos ha habida habidas habido habidos habiendo habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías han has hasta hay haya hayamos hayan hayas hayáis he hemos hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo la las le les lo los me mi mis mucho muchos muy más mí mía mías mío míos nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra otras otro otros para pero poco por porque que quien quienes qué se sea seamos sean seas seremos será serán serás seré seréis sería seríais seríamos serían serías seáis sido siendo sin sobre sois somos son soy su sus suya suyas suyo suyos sí también tanto te tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened tenemos tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías ti tiene tienen tienes todo todos tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú un una uno unos vosotras vosotros vuestra vuestras vuestro vuestros y ya yo él éramos".split(" ")),e.Pipeline.registerFunction(e.es.stopWordFilter,"stopWordFilter-es")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.fi.min.js b/assets/javascripts/lunr/min/lunr.fi.min.js new file mode 100644 index 00000000..29f5dfce --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.fi.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Finnish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(i,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(i.lunr)}(this,function(){return function(i){if(void 0===i)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===i.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");i.fi=function(){this.pipeline.reset(),this.pipeline.add(i.fi.trimmer,i.fi.stopWordFilter,i.fi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(i.fi.stemmer))},i.fi.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",i.fi.trimmer=i.trimmerSupport.generateTrimmer(i.fi.wordCharacters),i.Pipeline.registerFunction(i.fi.trimmer,"trimmer-fi"),i.fi.stemmer=function(){var e=i.stemmerSupport.Among,r=i.stemmerSupport.SnowballProgram,n=new function(){function i(){f=A.limit,d=f,n()||(f=A.cursor,n()||(d=A.cursor))}function n(){for(var i;;){if(i=A.cursor,A.in_grouping(W,97,246))break;if(A.cursor=i,i>=A.limit)return!0;A.cursor++}for(A.cursor=i;!A.out_grouping(W,97,246);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}function t(){return d<=A.cursor}function s(){var i,e;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(h,10)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.in_grouping_b(x,97,246))return;break;case 2:if(!t())return}A.slice_del()}else A.limit_backward=e}function o(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(v,9))switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"k")||(A.cursor=A.limit-r,A.slice_del());break;case 2:A.slice_del(),A.ket=A.cursor,A.eq_s_b(3,"kse")&&(A.bra=A.cursor,A.slice_from("ksi"));break;case 3:A.slice_del();break;case 4:A.find_among_b(p,6)&&A.slice_del();break;case 5:A.find_among_b(g,6)&&A.slice_del();break;case 6:A.find_among_b(j,2)&&A.slice_del()}else A.limit_backward=e}function l(){return A.find_among_b(q,7)}function a(){return A.eq_s_b(1,"i")&&A.in_grouping_b(L,97,246)}function u(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(C,30)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.eq_s_b(1,"a"))return;break;case 2:case 9:if(!A.eq_s_b(1,"e"))return;break;case 3:if(!A.eq_s_b(1,"i"))return;break;case 4:if(!A.eq_s_b(1,"o"))return;break;case 5:if(!A.eq_s_b(1,"ä"))return;break;case 6:if(!A.eq_s_b(1,"ö"))return;break;case 7:if(r=A.limit-A.cursor,!l()&&(A.cursor=A.limit-r,!A.eq_s_b(2,"ie"))){A.cursor=A.limit-r;break}if(A.cursor=A.limit-r,A.cursor<=A.limit_backward){A.cursor=A.limit-r;break}A.cursor--,A.bra=A.cursor;break;case 8:if(!A.in_grouping_b(W,97,246)||!A.out_grouping_b(W,97,246))return}A.slice_del(),k=!0}else A.limit_backward=e}function c(){var i,e,r;if(A.cursor>=d)if(e=A.limit_backward,A.limit_backward=d,A.ket=A.cursor,i=A.find_among_b(P,14)){if(A.bra=A.cursor,A.limit_backward=e,1==i){if(r=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-r}A.slice_del()}else A.limit_backward=e}function m(){var i;A.cursor>=f&&(i=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.find_among_b(F,2)?(A.bra=A.cursor,A.limit_backward=i,A.slice_del()):A.limit_backward=i)}function w(){var i,e,r,n,t,s;if(A.cursor>=f){if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.eq_s_b(1,"t")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.in_grouping_b(W,97,246)&&(A.cursor=A.limit-r,A.slice_del(),A.limit_backward=e,n=A.limit-A.cursor,A.cursor>=d&&(A.cursor=d,t=A.limit_backward,A.limit_backward=A.cursor,A.cursor=A.limit-n,A.ket=A.cursor,i=A.find_among_b(S,2))))){if(A.bra=A.cursor,A.limit_backward=t,1==i){if(s=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-s}return void A.slice_del()}A.limit_backward=e}}function _(){var i,e,r,n;if(A.cursor>=f){for(i=A.limit_backward,A.limit_backward=f,e=A.limit-A.cursor,l()&&(A.cursor=A.limit-e,A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.in_grouping_b(y,97,228)&&(A.bra=A.cursor,A.out_grouping_b(W,97,246)&&A.slice_del()),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"j")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.eq_s_b(1,"o")?A.slice_del():(A.cursor=A.limit-r,A.eq_s_b(1,"u")&&A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"o")&&(A.bra=A.cursor,A.eq_s_b(1,"j")&&A.slice_del()),A.cursor=A.limit-e,A.limit_backward=i;;){if(n=A.limit-A.cursor,A.out_grouping_b(W,97,246)){A.cursor=A.limit-n;break}if(A.cursor=A.limit-n,A.cursor<=A.limit_backward)return;A.cursor--}A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,b=A.slice_to(),A.eq_v_b(b)&&A.slice_del())}}var k,b,d,f,h=[new e("pa",-1,1),new e("sti",-1,2),new e("kaan",-1,1),new e("han",-1,1),new e("kin",-1,1),new e("hän",-1,1),new e("kään",-1,1),new e("ko",-1,1),new e("pä",-1,1),new e("kö",-1,1)],p=[new e("lla",-1,-1),new e("na",-1,-1),new e("ssa",-1,-1),new e("ta",-1,-1),new e("lta",3,-1),new e("sta",3,-1)],g=[new e("llä",-1,-1),new e("nä",-1,-1),new e("ssä",-1,-1),new e("tä",-1,-1),new e("ltä",3,-1),new e("stä",3,-1)],j=[new e("lle",-1,-1),new e("ine",-1,-1)],v=[new e("nsa",-1,3),new e("mme",-1,3),new e("nne",-1,3),new e("ni",-1,2),new e("si",-1,1),new e("an",-1,4),new e("en",-1,6),new e("än",-1,5),new e("nsä",-1,3)],q=[new e("aa",-1,-1),new e("ee",-1,-1),new e("ii",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1),new e("ää",-1,-1),new e("öö",-1,-1)],C=[new e("a",-1,8),new e("lla",0,-1),new e("na",0,-1),new e("ssa",0,-1),new e("ta",0,-1),new e("lta",4,-1),new e("sta",4,-1),new e("tta",4,9),new e("lle",-1,-1),new e("ine",-1,-1),new e("ksi",-1,-1),new e("n",-1,7),new e("han",11,1),new e("den",11,-1,a),new e("seen",11,-1,l),new e("hen",11,2),new e("tten",11,-1,a),new e("hin",11,3),new e("siin",11,-1,a),new e("hon",11,4),new e("hän",11,5),new e("hön",11,6),new e("ä",-1,8),new e("llä",22,-1),new e("nä",22,-1),new e("ssä",22,-1),new e("tä",22,-1),new e("ltä",26,-1),new e("stä",26,-1),new e("ttä",26,9)],P=[new e("eja",-1,-1),new e("mma",-1,1),new e("imma",1,-1),new e("mpa",-1,1),new e("impa",3,-1),new e("mmi",-1,1),new e("immi",5,-1),new e("mpi",-1,1),new e("impi",7,-1),new e("ejä",-1,-1),new e("mmä",-1,1),new e("immä",10,-1),new e("mpä",-1,1),new e("impä",12,-1)],F=[new e("i",-1,-1),new e("j",-1,-1)],S=[new e("mma",-1,1),new e("imma",0,-1)],y=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],W=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],x=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],A=new r;this.setCurrent=function(i){A.setCurrent(i)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return i(),k=!1,A.limit_backward=e,A.cursor=A.limit,s(),A.cursor=A.limit,o(),A.cursor=A.limit,u(),A.cursor=A.limit,c(),A.cursor=A.limit,k?(m(),A.cursor=A.limit):(A.cursor=A.limit,w(),A.cursor=A.limit),_(),!0}};return function(i){return"function"==typeof i.update?i.update(function(i){return n.setCurrent(i),n.stem(),n.getCurrent()}):(n.setCurrent(i),n.stem(),n.getCurrent())}}(),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.fr.min.js b/assets/javascripts/lunr/min/lunr.fr.min.js new file mode 100644 index 00000000..68cd0094 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.fr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `French` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.fr=function(){this.pipeline.reset(),this.pipeline.add(e.fr.trimmer,e.fr.stopWordFilter,e.fr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.fr.stemmer))},e.fr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.fr.trimmer=e.trimmerSupport.generateTrimmer(e.fr.wordCharacters),e.Pipeline.registerFunction(e.fr.trimmer,"trimmer-fr"),e.fr.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,s){return!(!W.eq_s(1,e)||(W.ket=W.cursor,!W.in_grouping(F,97,251)))&&(W.slice_from(r),W.cursor=s,!0)}function i(e,r,s){return!!W.eq_s(1,e)&&(W.ket=W.cursor,W.slice_from(r),W.cursor=s,!0)}function n(){for(var r,s;;){if(r=W.cursor,W.in_grouping(F,97,251)){if(W.bra=W.cursor,s=W.cursor,e("u","U",r))continue;if(W.cursor=s,e("i","I",r))continue;if(W.cursor=s,i("y","Y",r))continue}if(W.cursor=r,W.bra=r,!e("y","Y",r)){if(W.cursor=r,W.eq_s(1,"q")&&(W.bra=W.cursor,i("u","U",r)))continue;if(W.cursor=r,r>=W.limit)return;W.cursor++}}}function t(){for(;!W.in_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}for(;!W.out_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}return!1}function u(){var e=W.cursor;if(q=W.limit,g=q,p=q,W.in_grouping(F,97,251)&&W.in_grouping(F,97,251)&&W.cursor=W.limit){W.cursor=q;break}W.cursor++}while(!W.in_grouping(F,97,251))}q=W.cursor,W.cursor=e,t()||(g=W.cursor,t()||(p=W.cursor))}function o(){for(var e,r;;){if(r=W.cursor,W.bra=r,!(e=W.find_among(h,4)))break;switch(W.ket=W.cursor,e){case 1:W.slice_from("i");break;case 2:W.slice_from("u");break;case 3:W.slice_from("y");break;case 4:if(W.cursor>=W.limit)return;W.cursor++}}}function c(){return q<=W.cursor}function a(){return g<=W.cursor}function l(){return p<=W.cursor}function w(){var e,r;if(W.ket=W.cursor,e=W.find_among_b(C,43)){switch(W.bra=W.cursor,e){case 1:if(!l())return!1;W.slice_del();break;case 2:if(!l())return!1;W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")&&(W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU"));break;case 3:if(!l())return!1;W.slice_from("log");break;case 4:if(!l())return!1;W.slice_from("u");break;case 5:if(!l())return!1;W.slice_from("ent");break;case 6:if(!c())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(z,6))switch(W.bra=W.cursor,e){case 1:l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&W.slice_del()));break;case 2:l()?W.slice_del():a()&&W.slice_from("eux");break;case 3:l()&&W.slice_del();break;case 4:c()&&W.slice_from("i")}break;case 7:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(y,3))switch(W.bra=W.cursor,e){case 1:l()?W.slice_del():W.slice_from("abl");break;case 2:l()?W.slice_del():W.slice_from("iqU");break;case 3:l()&&W.slice_del()}break;case 8:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")))){W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU");break}break;case 9:W.slice_from("eau");break;case 10:if(!a())return!1;W.slice_from("al");break;case 11:if(l())W.slice_del();else{if(!a())return!1;W.slice_from("eux")}break;case 12:if(!a()||!W.out_grouping_b(F,97,251))return!1;W.slice_del();break;case 13:return c()&&W.slice_from("ant"),!1;case 14:return c()&&W.slice_from("ent"),!1;case 15:return r=W.limit-W.cursor,W.in_grouping_b(F,97,251)&&c()&&(W.cursor=W.limit-r,W.slice_del()),!1}return!0}return!1}function f(){var e,r;if(W.cursor=q){if(s=W.limit_backward,W.limit_backward=q,W.ket=W.cursor,e=W.find_among_b(P,7))switch(W.bra=W.cursor,e){case 1:if(l()){if(i=W.limit-W.cursor,!W.eq_s_b(1,"s")&&(W.cursor=W.limit-i,!W.eq_s_b(1,"t")))break;W.slice_del()}break;case 2:W.slice_from("i");break;case 3:W.slice_del();break;case 4:W.eq_s_b(2,"gu")&&W.slice_del()}W.limit_backward=s}}function b(){var e=W.limit-W.cursor;W.find_among_b(U,5)&&(W.cursor=W.limit-e,W.ket=W.cursor,W.cursor>W.limit_backward&&(W.cursor--,W.bra=W.cursor,W.slice_del()))}function d(){for(var e,r=1;W.out_grouping_b(F,97,251);)r--;if(r<=0){if(W.ket=W.cursor,e=W.limit-W.cursor,!W.eq_s_b(1,"é")&&(W.cursor=W.limit-e,!W.eq_s_b(1,"è")))return;W.bra=W.cursor,W.slice_from("e")}}function k(){if(!w()&&(W.cursor=W.limit,!f()&&(W.cursor=W.limit,!m())))return W.cursor=W.limit,void _();W.cursor=W.limit,W.ket=W.cursor,W.eq_s_b(1,"Y")?(W.bra=W.cursor,W.slice_from("i")):(W.cursor=W.limit,W.eq_s_b(1,"ç")&&(W.bra=W.cursor,W.slice_from("c")))}var p,g,q,v=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],h=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],z=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],y=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],C=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],x=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],I=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],P=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],U=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],F=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],S=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],W=new s;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){var e=W.cursor;return n(),W.cursor=e,u(),W.limit_backward=e,W.cursor=W.limit,k(),W.cursor=W.limit,b(),W.cursor=W.limit,d(),W.cursor=W.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.hi.min.js b/assets/javascripts/lunr/min/lunr.hi.min.js new file mode 100644 index 00000000..7dbc4140 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.hi.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Za-zA-Z0-90-9",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.hu.min.js b/assets/javascripts/lunr/min/lunr.hu.min.js new file mode 100644 index 00000000..ed9d909f --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.hu.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Hungarian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,n=L.cursor;if(d=L.limit,L.in_grouping(W,97,252))for(;;){if(e=L.cursor,L.out_grouping(W,97,252))return L.cursor=e,L.find_among(g,8)||(L.cursor=e,e=L.limit)return void(d=e);L.cursor++}if(L.cursor=n,L.out_grouping(W,97,252)){for(;!L.in_grouping(W,97,252);){if(L.cursor>=L.limit)return;L.cursor++}d=L.cursor}}function i(){return d<=L.cursor}function a(){var e;if(L.ket=L.cursor,(e=L.find_among_b(h,2))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e")}}function t(){var e=L.limit-L.cursor;return!!L.find_among_b(p,23)&&(L.cursor=L.limit-e,!0)}function s(){if(L.cursor>L.limit_backward){L.cursor--,L.ket=L.cursor;var e=L.cursor-1;L.limit_backward<=e&&e<=L.limit&&(L.cursor=e,L.bra=e,L.slice_del())}}function c(){var e;if(L.ket=L.cursor,(e=L.find_among_b(_,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function o(){L.ket=L.cursor,L.find_among_b(v,44)&&(L.bra=L.cursor,i()&&(L.slice_del(),a()))}function w(){var e;if(L.ket=L.cursor,(e=L.find_among_b(z,3))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("e");break;case 2:case 3:L.slice_from("a")}}function l(){var e;if(L.ket=L.cursor,(e=L.find_among_b(y,6))&&(L.bra=L.cursor,i()))switch(e){case 1:case 2:L.slice_del();break;case 3:L.slice_from("a");break;case 4:L.slice_from("e")}}function u(){var e;if(L.ket=L.cursor,(e=L.find_among_b(j,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function m(){var e;if(L.ket=L.cursor,(e=L.find_among_b(C,7))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:L.slice_del()}}function k(){var e;if(L.ket=L.cursor,(e=L.find_among_b(P,12))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 9:L.slice_del();break;case 2:case 5:case 8:L.slice_from("e");break;case 3:case 6:L.slice_from("a")}}function f(){var e;if(L.ket=L.cursor,(e=L.find_among_b(F,31))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:L.slice_del();break;case 2:case 5:case 10:case 14:case 19:L.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:L.slice_from("e")}}function b(){var e;if(L.ket=L.cursor,(e=L.find_among_b(S,42))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:L.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:L.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:L.slice_from("e")}}var d,g=[new n("cs",-1,-1),new n("dzs",-1,-1),new n("gy",-1,-1),new n("ly",-1,-1),new n("ny",-1,-1),new n("sz",-1,-1),new n("ty",-1,-1),new n("zs",-1,-1)],h=[new n("á",-1,1),new n("é",-1,2)],p=[new n("bb",-1,-1),new n("cc",-1,-1),new n("dd",-1,-1),new n("ff",-1,-1),new n("gg",-1,-1),new n("jj",-1,-1),new n("kk",-1,-1),new n("ll",-1,-1),new n("mm",-1,-1),new n("nn",-1,-1),new n("pp",-1,-1),new n("rr",-1,-1),new n("ccs",-1,-1),new n("ss",-1,-1),new n("zzs",-1,-1),new n("tt",-1,-1),new n("vv",-1,-1),new n("ggy",-1,-1),new n("lly",-1,-1),new n("nny",-1,-1),new n("tty",-1,-1),new n("ssz",-1,-1),new n("zz",-1,-1)],_=[new n("al",-1,1),new n("el",-1,2)],v=[new n("ba",-1,-1),new n("ra",-1,-1),new n("be",-1,-1),new n("re",-1,-1),new n("ig",-1,-1),new n("nak",-1,-1),new n("nek",-1,-1),new n("val",-1,-1),new n("vel",-1,-1),new n("ul",-1,-1),new n("nál",-1,-1),new n("nél",-1,-1),new n("ból",-1,-1),new n("ról",-1,-1),new n("tól",-1,-1),new n("bõl",-1,-1),new n("rõl",-1,-1),new n("tõl",-1,-1),new n("ül",-1,-1),new n("n",-1,-1),new n("an",19,-1),new n("ban",20,-1),new n("en",19,-1),new n("ben",22,-1),new n("képpen",22,-1),new n("on",19,-1),new n("ön",19,-1),new n("képp",-1,-1),new n("kor",-1,-1),new n("t",-1,-1),new n("at",29,-1),new n("et",29,-1),new n("ként",29,-1),new n("anként",32,-1),new n("enként",32,-1),new n("onként",32,-1),new n("ot",29,-1),new n("ért",29,-1),new n("öt",29,-1),new n("hez",-1,-1),new n("hoz",-1,-1),new n("höz",-1,-1),new n("vá",-1,-1),new n("vé",-1,-1)],z=[new n("án",-1,2),new n("én",-1,1),new n("ánként",-1,3)],y=[new n("stul",-1,2),new n("astul",0,1),new n("ástul",0,3),new n("stül",-1,2),new n("estül",3,1),new n("éstül",3,4)],j=[new n("á",-1,1),new n("é",-1,2)],C=[new n("k",-1,7),new n("ak",0,4),new n("ek",0,6),new n("ok",0,5),new n("ák",0,1),new n("ék",0,2),new n("ök",0,3)],P=[new n("éi",-1,7),new n("áéi",0,6),new n("ééi",0,5),new n("é",-1,9),new n("ké",3,4),new n("aké",4,1),new n("eké",4,1),new n("oké",4,1),new n("áké",4,3),new n("éké",4,2),new n("öké",4,1),new n("éé",3,8)],F=[new n("a",-1,18),new n("ja",0,17),new n("d",-1,16),new n("ad",2,13),new n("ed",2,13),new n("od",2,13),new n("ád",2,14),new n("éd",2,15),new n("öd",2,13),new n("e",-1,18),new n("je",9,17),new n("nk",-1,4),new n("unk",11,1),new n("ánk",11,2),new n("énk",11,3),new n("ünk",11,1),new n("uk",-1,8),new n("juk",16,7),new n("ájuk",17,5),new n("ük",-1,8),new n("jük",19,7),new n("éjük",20,6),new n("m",-1,12),new n("am",22,9),new n("em",22,9),new n("om",22,9),new n("ám",22,10),new n("ém",22,11),new n("o",-1,18),new n("á",-1,19),new n("é",-1,20)],S=[new n("id",-1,10),new n("aid",0,9),new n("jaid",1,6),new n("eid",0,9),new n("jeid",3,6),new n("áid",0,7),new n("éid",0,8),new n("i",-1,15),new n("ai",7,14),new n("jai",8,11),new n("ei",7,14),new n("jei",10,11),new n("ái",7,12),new n("éi",7,13),new n("itek",-1,24),new n("eitek",14,21),new n("jeitek",15,20),new n("éitek",14,23),new n("ik",-1,29),new n("aik",18,26),new n("jaik",19,25),new n("eik",18,26),new n("jeik",21,25),new n("áik",18,27),new n("éik",18,28),new n("ink",-1,20),new n("aink",25,17),new n("jaink",26,16),new n("eink",25,17),new n("jeink",28,16),new n("áink",25,18),new n("éink",25,19),new n("aitok",-1,21),new n("jaitok",32,20),new n("áitok",-1,22),new n("im",-1,5),new n("aim",35,4),new n("jaim",36,1),new n("eim",35,4),new n("jeim",38,1),new n("áim",35,2),new n("éim",35,3)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var n=L.cursor;return e(),L.limit_backward=n,L.cursor=L.limit,c(),L.cursor=L.limit,o(),L.cursor=L.limit,w(),L.cursor=L.limit,l(),L.cursor=L.limit,u(),L.cursor=L.limit,k(),L.cursor=L.limit,f(),L.cursor=L.limit,b(),L.cursor=L.limit,m(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.it.min.js b/assets/javascripts/lunr/min/lunr.it.min.js new file mode 100644 index 00000000..344b6a3c --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.it.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Italian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.it=function(){this.pipeline.reset(),this.pipeline.add(e.it.trimmer,e.it.stopWordFilter,e.it.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.it.stemmer))},e.it.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.it.trimmer=e.trimmerSupport.generateTrimmer(e.it.wordCharacters),e.Pipeline.registerFunction(e.it.trimmer,"trimmer-it"),e.it.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!x.eq_s(1,e)||(x.ket=x.cursor,!x.in_grouping(L,97,249)))&&(x.slice_from(r),x.cursor=n,!0)}function i(){for(var r,n,i,o,t=x.cursor;;){if(x.bra=x.cursor,r=x.find_among(h,7))switch(x.ket=x.cursor,r){case 1:x.slice_from("à");continue;case 2:x.slice_from("è");continue;case 3:x.slice_from("ì");continue;case 4:x.slice_from("ò");continue;case 5:x.slice_from("ù");continue;case 6:x.slice_from("qU");continue;case 7:if(x.cursor>=x.limit)break;x.cursor++;continue}break}for(x.cursor=t;;)for(n=x.cursor;;){if(i=x.cursor,x.in_grouping(L,97,249)){if(x.bra=x.cursor,o=x.cursor,e("u","U",i))break;if(x.cursor=o,e("i","I",i))break}if(x.cursor=i,x.cursor>=x.limit)return void(x.cursor=n);x.cursor++}}function o(e){if(x.cursor=e,!x.in_grouping(L,97,249))return!1;for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function t(){if(x.in_grouping(L,97,249)){var e=x.cursor;if(x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return o(e);x.cursor++}return!0}return o(e)}return!1}function s(){var e,r=x.cursor;if(!t()){if(x.cursor=r,!x.out_grouping(L,97,249))return;if(e=x.cursor,x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return x.cursor=e,void(x.in_grouping(L,97,249)&&x.cursor=x.limit)return;x.cursor++}k=x.cursor}function a(){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function u(){var e=x.cursor;k=x.limit,p=k,g=k,s(),x.cursor=e,a()&&(p=x.cursor,a()&&(g=x.cursor))}function c(){for(var e;;){if(x.bra=x.cursor,!(e=x.find_among(q,3)))break;switch(x.ket=x.cursor,e){case 1:x.slice_from("i");break;case 2:x.slice_from("u");break;case 3:if(x.cursor>=x.limit)return;x.cursor++}}}function w(){return k<=x.cursor}function l(){return p<=x.cursor}function m(){return g<=x.cursor}function f(){var e;if(x.ket=x.cursor,x.find_among_b(C,37)&&(x.bra=x.cursor,(e=x.find_among_b(z,5))&&w()))switch(e){case 1:x.slice_del();break;case 2:x.slice_from("e")}}function v(){var e;if(x.ket=x.cursor,!(e=x.find_among_b(S,51)))return!1;switch(x.bra=x.cursor,e){case 1:if(!m())return!1;x.slice_del();break;case 2:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del());break;case 3:if(!m())return!1;x.slice_from("log");break;case 4:if(!m())return!1;x.slice_from("u");break;case 5:if(!m())return!1;x.slice_from("ente");break;case 6:if(!w())return!1;x.slice_del();break;case 7:if(!l())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(P,4),e&&(x.bra=x.cursor,m()&&(x.slice_del(),1==e&&(x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&x.slice_del()))));break;case 8:if(!m())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(F,3),e&&(x.bra=x.cursor,1==e&&m()&&x.slice_del());break;case 9:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del())))}return!0}function b(){var e,r;x.cursor>=k&&(r=x.limit_backward,x.limit_backward=k,x.ket=x.cursor,e=x.find_among_b(W,87),e&&(x.bra=x.cursor,1==e&&x.slice_del()),x.limit_backward=r)}function d(){var e=x.limit-x.cursor;if(x.ket=x.cursor,x.in_grouping_b(y,97,242)&&(x.bra=x.cursor,w()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(1,"i")&&(x.bra=x.cursor,w()))))return void x.slice_del();x.cursor=x.limit-e}function _(){d(),x.ket=x.cursor,x.eq_s_b(1,"h")&&(x.bra=x.cursor,x.in_grouping_b(U,99,103)&&w()&&x.slice_del())}var g,p,k,h=[new r("",-1,7),new r("qu",0,6),new r("á",0,1),new r("é",0,2),new r("í",0,3),new r("ó",0,4),new r("ú",0,5)],q=[new r("",-1,3),new r("I",0,1),new r("U",0,2)],C=[new r("la",-1,-1),new r("cela",0,-1),new r("gliela",0,-1),new r("mela",0,-1),new r("tela",0,-1),new r("vela",0,-1),new r("le",-1,-1),new r("cele",6,-1),new r("gliele",6,-1),new r("mele",6,-1),new r("tele",6,-1),new r("vele",6,-1),new r("ne",-1,-1),new r("cene",12,-1),new r("gliene",12,-1),new r("mene",12,-1),new r("sene",12,-1),new r("tene",12,-1),new r("vene",12,-1),new r("ci",-1,-1),new r("li",-1,-1),new r("celi",20,-1),new r("glieli",20,-1),new r("meli",20,-1),new r("teli",20,-1),new r("veli",20,-1),new r("gli",20,-1),new r("mi",-1,-1),new r("si",-1,-1),new r("ti",-1,-1),new r("vi",-1,-1),new r("lo",-1,-1),new r("celo",31,-1),new r("glielo",31,-1),new r("melo",31,-1),new r("telo",31,-1),new r("velo",31,-1)],z=[new r("ando",-1,1),new r("endo",-1,1),new r("ar",-1,2),new r("er",-1,2),new r("ir",-1,2)],P=[new r("ic",-1,-1),new r("abil",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],F=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],S=[new r("ica",-1,1),new r("logia",-1,3),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,9),new r("anza",-1,1),new r("enza",-1,5),new r("ice",-1,1),new r("atrice",7,1),new r("iche",-1,1),new r("logie",-1,3),new r("abile",-1,1),new r("ibile",-1,1),new r("usione",-1,4),new r("azione",-1,2),new r("uzione",-1,4),new r("atore",-1,2),new r("ose",-1,1),new r("ante",-1,1),new r("mente",-1,1),new r("amente",19,7),new r("iste",-1,1),new r("ive",-1,9),new r("anze",-1,1),new r("enze",-1,5),new r("ici",-1,1),new r("atrici",25,1),new r("ichi",-1,1),new r("abili",-1,1),new r("ibili",-1,1),new r("ismi",-1,1),new r("usioni",-1,4),new r("azioni",-1,2),new r("uzioni",-1,4),new r("atori",-1,2),new r("osi",-1,1),new r("anti",-1,1),new r("amenti",-1,6),new r("imenti",-1,6),new r("isti",-1,1),new r("ivi",-1,9),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,6),new r("imento",-1,6),new r("ivo",-1,9),new r("ità",-1,8),new r("istà",-1,1),new r("istè",-1,1),new r("istì",-1,1)],W=[new r("isca",-1,1),new r("enda",-1,1),new r("ata",-1,1),new r("ita",-1,1),new r("uta",-1,1),new r("ava",-1,1),new r("eva",-1,1),new r("iva",-1,1),new r("erebbe",-1,1),new r("irebbe",-1,1),new r("isce",-1,1),new r("ende",-1,1),new r("are",-1,1),new r("ere",-1,1),new r("ire",-1,1),new r("asse",-1,1),new r("ate",-1,1),new r("avate",16,1),new r("evate",16,1),new r("ivate",16,1),new r("ete",-1,1),new r("erete",20,1),new r("irete",20,1),new r("ite",-1,1),new r("ereste",-1,1),new r("ireste",-1,1),new r("ute",-1,1),new r("erai",-1,1),new r("irai",-1,1),new r("isci",-1,1),new r("endi",-1,1),new r("erei",-1,1),new r("irei",-1,1),new r("assi",-1,1),new r("ati",-1,1),new r("iti",-1,1),new r("eresti",-1,1),new r("iresti",-1,1),new r("uti",-1,1),new r("avi",-1,1),new r("evi",-1,1),new r("ivi",-1,1),new r("isco",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("Yamo",-1,1),new r("iamo",-1,1),new r("avamo",-1,1),new r("evamo",-1,1),new r("ivamo",-1,1),new r("eremo",-1,1),new r("iremo",-1,1),new r("assimo",-1,1),new r("ammo",-1,1),new r("emmo",-1,1),new r("eremmo",54,1),new r("iremmo",54,1),new r("immo",-1,1),new r("ano",-1,1),new r("iscano",58,1),new r("avano",58,1),new r("evano",58,1),new r("ivano",58,1),new r("eranno",-1,1),new r("iranno",-1,1),new r("ono",-1,1),new r("iscono",65,1),new r("arono",65,1),new r("erono",65,1),new r("irono",65,1),new r("erebbero",-1,1),new r("irebbero",-1,1),new r("assero",-1,1),new r("essero",-1,1),new r("issero",-1,1),new r("ato",-1,1),new r("ito",-1,1),new r("uto",-1,1),new r("avo",-1,1),new r("evo",-1,1),new r("ivo",-1,1),new r("ar",-1,1),new r("ir",-1,1),new r("erà",-1,1),new r("irà",-1,1),new r("erò",-1,1),new r("irò",-1,1)],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2,1],y=[17,65,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2],U=[17],x=new n;this.setCurrent=function(e){x.setCurrent(e)},this.getCurrent=function(){return x.getCurrent()},this.stem=function(){var e=x.cursor;return i(),x.cursor=e,u(),x.limit_backward=e,x.cursor=x.limit,f(),x.cursor=x.limit,v()||(x.cursor=x.limit,b()),x.cursor=x.limit,_(),x.cursor=x.limit_backward,c(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.it.stemmer,"stemmer-it"),e.it.stopWordFilter=e.generateStopWordFilter("a abbia abbiamo abbiano abbiate ad agl agli ai al all alla alle allo anche avemmo avendo avesse avessero avessi avessimo aveste avesti avete aveva avevamo avevano avevate avevi avevo avrai avranno avrebbe avrebbero avrei avremmo avremo avreste avresti avrete avrà avrò avuta avute avuti avuto c che chi ci coi col come con contro cui da dagl dagli dai dal dall dalla dalle dallo degl degli dei del dell della delle dello di dov dove e ebbe ebbero ebbi ed era erano eravamo eravate eri ero essendo faccia facciamo facciano facciate faccio facemmo facendo facesse facessero facessi facessimo faceste facesti faceva facevamo facevano facevate facevi facevo fai fanno farai faranno farebbe farebbero farei faremmo faremo fareste faresti farete farà farò fece fecero feci fosse fossero fossi fossimo foste fosti fu fui fummo furono gli ha hai hanno ho i il in io l la le lei li lo loro lui ma mi mia mie miei mio ne negl negli nei nel nell nella nelle nello noi non nostra nostre nostri nostro o per perché più quale quanta quante quanti quanto quella quelle quelli quello questa queste questi questo sarai saranno sarebbe sarebbero sarei saremmo saremo sareste saresti sarete sarà sarò se sei si sia siamo siano siate siete sono sta stai stando stanno starai staranno starebbe starebbero starei staremmo staremo stareste staresti starete starà starò stava stavamo stavano stavate stavi stavo stemmo stesse stessero stessi stessimo steste stesti stette stettero stetti stia stiamo stiano stiate sto su sua sue sugl sugli sui sul sull sulla sulle sullo suo suoi ti tra tu tua tue tuo tuoi tutti tutto un una uno vi voi vostra vostre vostri vostro è".split(" ")),e.Pipeline.registerFunction(e.it.stopWordFilter,"stopWordFilter-it")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ja.min.js b/assets/javascripts/lunr/min/lunr.ja.min.js new file mode 100644 index 00000000..5f254ebe --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ja.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(e=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=e);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=e;else if(n(e))break}else if(n(e))break}function n(r){return C.cursor=r,r>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,d=_,t()||(_=C.cursor,_<3&&(_=3),t()||(d=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var r;;)if(C.bra=C.cursor,r=C.find_among(p,3))switch(C.ket=C.cursor,r){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return d<=C.cursor}function a(){var r=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-r,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var r;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.slice_del(),w=!0,a())))}function m(){var r;u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.eq_s_b(3,"gem")||(C.cursor=C.limit-r,C.slice_del(),a())))}function f(){var r,e,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,r=C.find_among_b(h,5))switch(C.bra=C.cursor,r){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(j,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(e=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-e,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,r=C.find_among_b(k,6))switch(C.bra=C.cursor,r){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(z,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var d,_,w,b=[new e("",-1,6),new e("á",0,1),new e("ä",0,1),new e("é",0,2),new e("ë",0,2),new e("í",0,3),new e("ï",0,3),new e("ó",0,4),new e("ö",0,4),new e("ú",0,5),new e("ü",0,5)],p=[new e("",-1,3),new e("I",0,2),new e("Y",0,1)],g=[new e("dd",-1,-1),new e("kk",-1,-1),new e("tt",-1,-1)],h=[new e("ene",-1,2),new e("se",-1,3),new e("en",-1,2),new e("heden",2,1),new e("s",-1,3)],k=[new e("end",-1,1),new e("ig",-1,2),new e("ing",-1,1),new e("lijk",-1,3),new e("baar",-1,4),new e("bar",-1,5)],v=[new e("aa",-1,-1),new e("ee",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(r){C.setCurrent(r)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var e=C.cursor;return r(),C.cursor=e,o(),C.limit_backward=e,C.cursor=C.limit,f(),C.cursor=C.limit_backward,s(),!0}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.no.min.js b/assets/javascripts/lunr/min/lunr.no.min.js new file mode 100644 index 00000000..92bc7e4e --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.no.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Norwegian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.pt.min.js b/assets/javascripts/lunr/min/lunr.pt.min.js new file mode 100644 index 00000000..6c16996d --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.pt.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Portuguese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(k,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("a~");continue;case 2:z.slice_from("o~");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function n(){if(z.out_grouping(y,97,250)){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!0;z.cursor++}return!1}return!0}function i(){if(z.in_grouping(y,97,250))for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return g=z.cursor,!0}function o(){var e,r,s=z.cursor;if(z.in_grouping(y,97,250))if(e=z.cursor,n()){if(z.cursor=e,i())return}else g=z.cursor;if(z.cursor=s,z.out_grouping(y,97,250)){if(r=z.cursor,n()){if(z.cursor=r,!z.in_grouping(y,97,250)||z.cursor>=z.limit)return;z.cursor++}g=z.cursor}}function t(){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return!0}function a(){var e=z.cursor;g=z.limit,b=g,h=g,o(),z.cursor=e,t()&&(b=z.cursor,t()&&(h=z.cursor))}function u(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(q,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("ã");continue;case 2:z.slice_from("õ");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function w(){return g<=z.cursor}function m(){return b<=z.cursor}function c(){return h<=z.cursor}function l(){var e;if(z.ket=z.cursor,!(e=z.find_among_b(F,45)))return!1;switch(z.bra=z.cursor,e){case 1:if(!c())return!1;z.slice_del();break;case 2:if(!c())return!1;z.slice_from("log");break;case 3:if(!c())return!1;z.slice_from("u");break;case 4:if(!c())return!1;z.slice_from("ente");break;case 5:if(!m())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(j,4),e&&(z.bra=z.cursor,c()&&(z.slice_del(),1==e&&(z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del()))));break;case 6:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(C,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 7:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(P,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 8:if(!c())return!1;z.slice_del(),z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del());break;case 9:if(!w()||!z.eq_s_b(1,"e"))return!1;z.slice_from("ir")}return!0}function f(){var e,r;if(z.cursor>=g){if(r=z.limit_backward,z.limit_backward=g,z.ket=z.cursor,e=z.find_among_b(S,120))return z.bra=z.cursor,1==e&&z.slice_del(),z.limit_backward=r,!0;z.limit_backward=r}return!1}function d(){var e;z.ket=z.cursor,(e=z.find_among_b(W,7))&&(z.bra=z.cursor,1==e&&w()&&z.slice_del())}function v(e,r){if(z.eq_s_b(1,e)){z.bra=z.cursor;var s=z.limit-z.cursor;if(z.eq_s_b(1,r))return z.cursor=z.limit-s,w()&&z.slice_del(),!1}return!0}function p(){var e;if(z.ket=z.cursor,e=z.find_among_b(L,4))switch(z.bra=z.cursor,e){case 1:w()&&(z.slice_del(),z.ket=z.cursor,z.limit-z.cursor,v("u","g")&&v("i","c"));break;case 2:z.slice_from("c")}}function _(){if(!l()&&(z.cursor=z.limit,!f()))return z.cursor=z.limit,void d();z.cursor=z.limit,z.ket=z.cursor,z.eq_s_b(1,"i")&&(z.bra=z.cursor,z.eq_s_b(1,"c")&&(z.cursor=z.limit,w()&&z.slice_del()))}var h,b,g,k=[new r("",-1,3),new r("ã",0,1),new r("õ",0,2)],q=[new r("",-1,3),new r("a~",0,1),new r("o~",0,2)],j=[new r("ic",-1,-1),new r("ad",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],C=[new r("ante",-1,1),new r("avel",-1,1),new r("ível",-1,1)],P=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],F=[new r("ica",-1,1),new r("ância",-1,1),new r("ência",-1,4),new r("ira",-1,9),new r("adora",-1,1),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,8),new r("eza",-1,1),new r("logía",-1,2),new r("idade",-1,7),new r("ante",-1,1),new r("mente",-1,6),new r("amente",12,5),new r("ável",-1,1),new r("ível",-1,1),new r("ución",-1,3),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,1),new r("imento",-1,1),new r("ivo",-1,8),new r("aça~o",-1,1),new r("ador",-1,1),new r("icas",-1,1),new r("ências",-1,4),new r("iras",-1,9),new r("adoras",-1,1),new r("osas",-1,1),new r("istas",-1,1),new r("ivas",-1,8),new r("ezas",-1,1),new r("logías",-1,2),new r("idades",-1,7),new r("uciones",-1,3),new r("adores",-1,1),new r("antes",-1,1),new r("aço~es",-1,1),new r("icos",-1,1),new r("ismos",-1,1),new r("osos",-1,1),new r("amentos",-1,1),new r("imentos",-1,1),new r("ivos",-1,8)],S=[new r("ada",-1,1),new r("ida",-1,1),new r("ia",-1,1),new r("aria",2,1),new r("eria",2,1),new r("iria",2,1),new r("ara",-1,1),new r("era",-1,1),new r("ira",-1,1),new r("ava",-1,1),new r("asse",-1,1),new r("esse",-1,1),new r("isse",-1,1),new r("aste",-1,1),new r("este",-1,1),new r("iste",-1,1),new r("ei",-1,1),new r("arei",16,1),new r("erei",16,1),new r("irei",16,1),new r("am",-1,1),new r("iam",20,1),new r("ariam",21,1),new r("eriam",21,1),new r("iriam",21,1),new r("aram",20,1),new r("eram",20,1),new r("iram",20,1),new r("avam",20,1),new r("em",-1,1),new r("arem",29,1),new r("erem",29,1),new r("irem",29,1),new r("assem",29,1),new r("essem",29,1),new r("issem",29,1),new r("ado",-1,1),new r("ido",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("indo",-1,1),new r("ara~o",-1,1),new r("era~o",-1,1),new r("ira~o",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("ir",-1,1),new r("as",-1,1),new r("adas",47,1),new r("idas",47,1),new r("ias",47,1),new r("arias",50,1),new r("erias",50,1),new r("irias",50,1),new r("aras",47,1),new r("eras",47,1),new r("iras",47,1),new r("avas",47,1),new r("es",-1,1),new r("ardes",58,1),new r("erdes",58,1),new r("irdes",58,1),new r("ares",58,1),new r("eres",58,1),new r("ires",58,1),new r("asses",58,1),new r("esses",58,1),new r("isses",58,1),new r("astes",58,1),new r("estes",58,1),new r("istes",58,1),new r("is",-1,1),new r("ais",71,1),new r("eis",71,1),new r("areis",73,1),new r("ereis",73,1),new r("ireis",73,1),new r("áreis",73,1),new r("éreis",73,1),new r("íreis",73,1),new r("ásseis",73,1),new r("ésseis",73,1),new r("ísseis",73,1),new r("áveis",73,1),new r("íeis",73,1),new r("aríeis",84,1),new r("eríeis",84,1),new r("iríeis",84,1),new r("ados",-1,1),new r("idos",-1,1),new r("amos",-1,1),new r("áramos",90,1),new r("éramos",90,1),new r("íramos",90,1),new r("ávamos",90,1),new r("íamos",90,1),new r("aríamos",95,1),new r("eríamos",95,1),new r("iríamos",95,1),new r("emos",-1,1),new r("aremos",99,1),new r("eremos",99,1),new r("iremos",99,1),new r("ássemos",99,1),new r("êssemos",99,1),new r("íssemos",99,1),new r("imos",-1,1),new r("armos",-1,1),new r("ermos",-1,1),new r("irmos",-1,1),new r("ámos",-1,1),new r("arás",-1,1),new r("erás",-1,1),new r("irás",-1,1),new r("eu",-1,1),new r("iu",-1,1),new r("ou",-1,1),new r("ará",-1,1),new r("erá",-1,1),new r("irá",-1,1)],W=[new r("a",-1,1),new r("i",-1,1),new r("o",-1,1),new r("os",-1,1),new r("á",-1,1),new r("í",-1,1),new r("ó",-1,1)],L=[new r("e",-1,1),new r("ç",-1,2),new r("é",-1,1),new r("ê",-1,1)],y=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],z=new s;this.setCurrent=function(e){z.setCurrent(e)},this.getCurrent=function(){return z.getCurrent()},this.stem=function(){var r=z.cursor;return e(),z.cursor=r,a(),z.limit_backward=r,z.cursor=z.limit,_(),z.cursor=z.limit,p(),z.cursor=z.limit_backward,u(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ro.min.js b/assets/javascripts/lunr/min/lunr.ro.min.js new file mode 100644 index 00000000..72771401 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ro.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Romanian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=function(){var i=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(e,i){L.eq_s(1,e)&&(L.ket=L.cursor,L.in_grouping(W,97,259)&&L.slice_from(i))}function n(){for(var i,r;;){if(i=L.cursor,L.in_grouping(W,97,259)&&(r=L.cursor,L.bra=r,e("u","U"),L.cursor=r,e("i","I")),L.cursor=i,L.cursor>=L.limit)break;L.cursor++}}function t(){if(L.out_grouping(W,97,259)){for(;!L.in_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}return!0}function a(){if(L.in_grouping(W,97,259))for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}function o(){var e,i,r=L.cursor;if(L.in_grouping(W,97,259)){if(e=L.cursor,!t())return void(h=L.cursor);if(L.cursor=e,!a())return void(h=L.cursor)}L.cursor=r,L.out_grouping(W,97,259)&&(i=L.cursor,t()&&(L.cursor=i,L.in_grouping(W,97,259)&&L.cursor=L.limit)return!1;L.cursor++}for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!1;L.cursor++}return!0}function c(){var e=L.cursor;h=L.limit,k=h,g=h,o(),L.cursor=e,u()&&(k=L.cursor,u()&&(g=L.cursor))}function s(){for(var e;;){if(L.bra=L.cursor,e=L.find_among(z,3))switch(L.ket=L.cursor,e){case 1:L.slice_from("i");continue;case 2:L.slice_from("u");continue;case 3:if(L.cursor>=L.limit)break;L.cursor++;continue}break}}function w(){return h<=L.cursor}function m(){return k<=L.cursor}function l(){return g<=L.cursor}function f(){var e,i;if(L.ket=L.cursor,(e=L.find_among_b(C,16))&&(L.bra=L.cursor,m()))switch(e){case 1:L.slice_del();break;case 2:L.slice_from("a");break;case 3:L.slice_from("e");break;case 4:L.slice_from("i");break;case 5:i=L.limit-L.cursor,L.eq_s_b(2,"ab")||(L.cursor=L.limit-i,L.slice_from("i"));break;case 6:L.slice_from("at");break;case 7:L.slice_from("aţi")}}function p(){var e,i=L.limit-L.cursor;if(L.ket=L.cursor,(e=L.find_among_b(P,46))&&(L.bra=L.cursor,m())){switch(e){case 1:L.slice_from("abil");break;case 2:L.slice_from("ibil");break;case 3:L.slice_from("iv");break;case 4:L.slice_from("ic");break;case 5:L.slice_from("at");break;case 6:L.slice_from("it")}return _=!0,L.cursor=L.limit-i,!0}return!1}function d(){var e,i;for(_=!1;;)if(i=L.limit-L.cursor,!p()){L.cursor=L.limit-i;break}if(L.ket=L.cursor,(e=L.find_among_b(F,62))&&(L.bra=L.cursor,l())){switch(e){case 1:L.slice_del();break;case 2:L.eq_s_b(1,"ţ")&&(L.bra=L.cursor,L.slice_from("t"));break;case 3:L.slice_from("ist")}_=!0}}function b(){var e,i,r;if(L.cursor>=h){if(i=L.limit_backward,L.limit_backward=h,L.ket=L.cursor,e=L.find_among_b(q,94))switch(L.bra=L.cursor,e){case 1:if(r=L.limit-L.cursor,!L.out_grouping_b(W,97,259)&&(L.cursor=L.limit-r,!L.eq_s_b(1,"u")))break;case 2:L.slice_del()}L.limit_backward=i}}function v(){var e;L.ket=L.cursor,(e=L.find_among_b(S,5))&&(L.bra=L.cursor,w()&&1==e&&L.slice_del())}var _,g,k,h,z=[new i("",-1,3),new i("I",0,1),new i("U",0,2)],C=[new i("ea",-1,3),new i("aţia",-1,7),new i("aua",-1,2),new i("iua",-1,4),new i("aţie",-1,7),new i("ele",-1,3),new i("ile",-1,5),new i("iile",6,4),new i("iei",-1,4),new i("atei",-1,6),new i("ii",-1,4),new i("ului",-1,1),new i("ul",-1,1),new i("elor",-1,3),new i("ilor",-1,4),new i("iilor",14,4)],P=[new i("icala",-1,4),new i("iciva",-1,4),new i("ativa",-1,5),new i("itiva",-1,6),new i("icale",-1,4),new i("aţiune",-1,5),new i("iţiune",-1,6),new i("atoare",-1,5),new i("itoare",-1,6),new i("ătoare",-1,5),new i("icitate",-1,4),new i("abilitate",-1,1),new i("ibilitate",-1,2),new i("ivitate",-1,3),new i("icive",-1,4),new i("ative",-1,5),new i("itive",-1,6),new i("icali",-1,4),new i("atori",-1,5),new i("icatori",18,4),new i("itori",-1,6),new i("ători",-1,5),new i("icitati",-1,4),new i("abilitati",-1,1),new i("ivitati",-1,3),new i("icivi",-1,4),new i("ativi",-1,5),new i("itivi",-1,6),new i("icităi",-1,4),new i("abilităi",-1,1),new i("ivităi",-1,3),new i("icităţi",-1,4),new i("abilităţi",-1,1),new i("ivităţi",-1,3),new i("ical",-1,4),new i("ator",-1,5),new i("icator",35,4),new i("itor",-1,6),new i("ător",-1,5),new i("iciv",-1,4),new i("ativ",-1,5),new i("itiv",-1,6),new i("icală",-1,4),new i("icivă",-1,4),new i("ativă",-1,5),new i("itivă",-1,6)],F=[new i("ica",-1,1),new i("abila",-1,1),new i("ibila",-1,1),new i("oasa",-1,1),new i("ata",-1,1),new i("ita",-1,1),new i("anta",-1,1),new i("ista",-1,3),new i("uta",-1,1),new i("iva",-1,1),new i("ic",-1,1),new i("ice",-1,1),new i("abile",-1,1),new i("ibile",-1,1),new i("isme",-1,3),new i("iune",-1,2),new i("oase",-1,1),new i("ate",-1,1),new i("itate",17,1),new i("ite",-1,1),new i("ante",-1,1),new i("iste",-1,3),new i("ute",-1,1),new i("ive",-1,1),new i("ici",-1,1),new i("abili",-1,1),new i("ibili",-1,1),new i("iuni",-1,2),new i("atori",-1,1),new i("osi",-1,1),new i("ati",-1,1),new i("itati",30,1),new i("iti",-1,1),new i("anti",-1,1),new i("isti",-1,3),new i("uti",-1,1),new i("işti",-1,3),new i("ivi",-1,1),new i("ităi",-1,1),new i("oşi",-1,1),new i("ităţi",-1,1),new i("abil",-1,1),new i("ibil",-1,1),new i("ism",-1,3),new i("ator",-1,1),new i("os",-1,1),new i("at",-1,1),new i("it",-1,1),new i("ant",-1,1),new i("ist",-1,3),new i("ut",-1,1),new i("iv",-1,1),new i("ică",-1,1),new i("abilă",-1,1),new i("ibilă",-1,1),new i("oasă",-1,1),new i("ată",-1,1),new i("ită",-1,1),new i("antă",-1,1),new i("istă",-1,3),new i("ută",-1,1),new i("ivă",-1,1)],q=[new i("ea",-1,1),new i("ia",-1,1),new i("esc",-1,1),new i("ăsc",-1,1),new i("ind",-1,1),new i("ând",-1,1),new i("are",-1,1),new i("ere",-1,1),new i("ire",-1,1),new i("âre",-1,1),new i("se",-1,2),new i("ase",10,1),new i("sese",10,2),new i("ise",10,1),new i("use",10,1),new i("âse",10,1),new i("eşte",-1,1),new i("ăşte",-1,1),new i("eze",-1,1),new i("ai",-1,1),new i("eai",19,1),new i("iai",19,1),new i("sei",-1,2),new i("eşti",-1,1),new i("ăşti",-1,1),new i("ui",-1,1),new i("ezi",-1,1),new i("âi",-1,1),new i("aşi",-1,1),new i("seşi",-1,2),new i("aseşi",29,1),new i("seseşi",29,2),new i("iseşi",29,1),new i("useşi",29,1),new i("âseşi",29,1),new i("işi",-1,1),new i("uşi",-1,1),new i("âşi",-1,1),new i("aţi",-1,2),new i("eaţi",38,1),new i("iaţi",38,1),new i("eţi",-1,2),new i("iţi",-1,2),new i("âţi",-1,2),new i("arăţi",-1,1),new i("serăţi",-1,2),new i("aserăţi",45,1),new i("seserăţi",45,2),new i("iserăţi",45,1),new i("userăţi",45,1),new i("âserăţi",45,1),new i("irăţi",-1,1),new i("urăţi",-1,1),new i("ârăţi",-1,1),new i("am",-1,1),new i("eam",54,1),new i("iam",54,1),new i("em",-1,2),new i("asem",57,1),new i("sesem",57,2),new i("isem",57,1),new i("usem",57,1),new i("âsem",57,1),new i("im",-1,2),new i("âm",-1,2),new i("ăm",-1,2),new i("arăm",65,1),new i("serăm",65,2),new i("aserăm",67,1),new i("seserăm",67,2),new i("iserăm",67,1),new i("userăm",67,1),new i("âserăm",67,1),new i("irăm",65,1),new i("urăm",65,1),new i("ârăm",65,1),new i("au",-1,1),new i("eau",76,1),new i("iau",76,1),new i("indu",-1,1),new i("ându",-1,1),new i("ez",-1,1),new i("ească",-1,1),new i("ară",-1,1),new i("seră",-1,2),new i("aseră",84,1),new i("seseră",84,2),new i("iseră",84,1),new i("useră",84,1),new i("âseră",84,1),new i("iră",-1,1),new i("ură",-1,1),new i("âră",-1,1),new i("ează",-1,1)],S=[new i("a",-1,1),new i("e",-1,1),new i("ie",1,1),new i("i",-1,1),new i("ă",-1,1)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var e=L.cursor;return n(),L.cursor=e,c(),L.limit_backward=e,L.cursor=L.limit,f(),L.cursor=L.limit,d(),L.cursor=L.limit,_||(L.cursor=L.limit,b(),L.cursor=L.limit),v(),L.cursor=L.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ru.min.js b/assets/javascripts/lunr/min/lunr.ru.min.js new file mode 100644 index 00000000..186cc485 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ru.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Russian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,t=new function(){function e(){for(;!W.in_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function t(){for(;!W.out_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function w(){b=W.limit,_=b,e()&&(b=W.cursor,t()&&e()&&t()&&(_=W.cursor))}function i(){return _<=W.cursor}function u(e,n){var r,t;if(W.ket=W.cursor,r=W.find_among_b(e,n)){switch(W.bra=W.cursor,r){case 1:if(t=W.limit-W.cursor,!W.eq_s_b(1,"а")&&(W.cursor=W.limit-t,!W.eq_s_b(1,"я")))return!1;case 2:W.slice_del()}return!0}return!1}function o(){return u(h,9)}function s(e,n){var r;return W.ket=W.cursor,!!(r=W.find_among_b(e,n))&&(W.bra=W.cursor,1==r&&W.slice_del(),!0)}function c(){return s(g,26)}function m(){return!!c()&&(u(C,8),!0)}function f(){return s(k,2)}function l(){return u(P,46)}function a(){s(v,36)}function p(){var e;W.ket=W.cursor,(e=W.find_among_b(F,2))&&(W.bra=W.cursor,i()&&1==e&&W.slice_del())}function d(){var e;if(W.ket=W.cursor,e=W.find_among_b(q,4))switch(W.bra=W.cursor,e){case 1:if(W.slice_del(),W.ket=W.cursor,!W.eq_s_b(1,"н"))break;W.bra=W.cursor;case 2:if(!W.eq_s_b(1,"н"))break;case 3:W.slice_del()}}var _,b,h=[new n("в",-1,1),new n("ив",0,2),new n("ыв",0,2),new n("вши",-1,1),new n("ивши",3,2),new n("ывши",3,2),new n("вшись",-1,1),new n("ившись",6,2),new n("ывшись",6,2)],g=[new n("ее",-1,1),new n("ие",-1,1),new n("ое",-1,1),new n("ые",-1,1),new n("ими",-1,1),new n("ыми",-1,1),new n("ей",-1,1),new n("ий",-1,1),new n("ой",-1,1),new n("ый",-1,1),new n("ем",-1,1),new n("им",-1,1),new n("ом",-1,1),new n("ым",-1,1),new n("его",-1,1),new n("ого",-1,1),new n("ему",-1,1),new n("ому",-1,1),new n("их",-1,1),new n("ых",-1,1),new n("ею",-1,1),new n("ою",-1,1),new n("ую",-1,1),new n("юю",-1,1),new n("ая",-1,1),new n("яя",-1,1)],C=[new n("ем",-1,1),new n("нн",-1,1),new n("вш",-1,1),new n("ивш",2,2),new n("ывш",2,2),new n("щ",-1,1),new n("ющ",5,1),new n("ующ",6,2)],k=[new n("сь",-1,1),new n("ся",-1,1)],P=[new n("ла",-1,1),new n("ила",0,2),new n("ыла",0,2),new n("на",-1,1),new n("ена",3,2),new n("ете",-1,1),new n("ите",-1,2),new n("йте",-1,1),new n("ейте",7,2),new n("уйте",7,2),new n("ли",-1,1),new n("или",10,2),new n("ыли",10,2),new n("й",-1,1),new n("ей",13,2),new n("уй",13,2),new n("л",-1,1),new n("ил",16,2),new n("ыл",16,2),new n("ем",-1,1),new n("им",-1,2),new n("ым",-1,2),new n("н",-1,1),new n("ен",22,2),new n("ло",-1,1),new n("ило",24,2),new n("ыло",24,2),new n("но",-1,1),new n("ено",27,2),new n("нно",27,1),new n("ет",-1,1),new n("ует",30,2),new n("ит",-1,2),new n("ыт",-1,2),new n("ют",-1,1),new n("уют",34,2),new n("ят",-1,2),new n("ны",-1,1),new n("ены",37,2),new n("ть",-1,1),new n("ить",39,2),new n("ыть",39,2),new n("ешь",-1,1),new n("ишь",-1,2),new n("ю",-1,2),new n("ую",44,2)],v=[new n("а",-1,1),new n("ев",-1,1),new n("ов",-1,1),new n("е",-1,1),new n("ие",3,1),new n("ье",3,1),new n("и",-1,1),new n("еи",6,1),new n("ии",6,1),new n("ами",6,1),new n("ями",6,1),new n("иями",10,1),new n("й",-1,1),new n("ей",12,1),new n("ией",13,1),new n("ий",12,1),new n("ой",12,1),new n("ам",-1,1),new n("ем",-1,1),new n("ием",18,1),new n("ом",-1,1),new n("ям",-1,1),new n("иям",21,1),new n("о",-1,1),new n("у",-1,1),new n("ах",-1,1),new n("ях",-1,1),new n("иях",26,1),new n("ы",-1,1),new n("ь",-1,1),new n("ю",-1,1),new n("ию",30,1),new n("ью",30,1),new n("я",-1,1),new n("ия",33,1),new n("ья",33,1)],F=[new n("ост",-1,1),new n("ость",-1,1)],q=[new n("ейше",-1,1),new n("н",-1,2),new n("ейш",-1,1),new n("ь",-1,3)],S=[33,65,8,232],W=new r;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){return w(),W.cursor=W.limit,!(W.cursor=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursors||e>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor>1),f=0,l=o0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.sv.min.js b/assets/javascripts/lunr/min/lunr.sv.min.js new file mode 100644 index 00000000..3e5eb640 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.sv.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Swedish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.th.min.js b/assets/javascripts/lunr/min/lunr.th.min.js new file mode 100644 index 00000000..dee3aac6 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.th.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.tr.min.js b/assets/javascripts/lunr/min/lunr.tr.min.js new file mode 100644 index 00000000..563f6ec1 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.tr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Turkish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(r,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");r.tr=function(){this.pipeline.reset(),this.pipeline.add(r.tr.trimmer,r.tr.stopWordFilter,r.tr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.tr.stemmer))},r.tr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.tr.trimmer=r.trimmerSupport.generateTrimmer(r.tr.wordCharacters),r.Pipeline.registerFunction(r.tr.trimmer,"trimmer-tr"),r.tr.stemmer=function(){var i=r.stemmerSupport.Among,e=r.stemmerSupport.SnowballProgram,n=new function(){function r(r,i,e){for(;;){var n=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(r,i,e)){Dr.cursor=Dr.limit-n;break}if(Dr.cursor=Dr.limit-n,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function n(){var i,e;i=Dr.limit-Dr.cursor,r(Wr,97,305);for(var n=0;nDr.limit_backward&&(Dr.cursor--,e=Dr.limit-Dr.cursor,i()))?(Dr.cursor=Dr.limit-e,!0):(Dr.cursor=Dr.limit-n,r()?(Dr.cursor=Dr.limit-n,!1):(Dr.cursor=Dr.limit-n,!(Dr.cursor<=Dr.limit_backward)&&(Dr.cursor--,!!i()&&(Dr.cursor=Dr.limit-n,!0))))}function u(r){return t(r,function(){return Dr.in_grouping_b(Wr,97,305)})}function o(){return u(function(){return Dr.eq_s_b(1,"n")})}function s(){return u(function(){return Dr.eq_s_b(1,"s")})}function c(){return u(function(){return Dr.eq_s_b(1,"y")})}function l(){return t(function(){return Dr.in_grouping_b(Lr,105,305)},function(){return Dr.out_grouping_b(Wr,97,305)})}function a(){return Dr.find_among_b(ur,10)&&l()}function m(){return n()&&Dr.in_grouping_b(Lr,105,305)&&s()}function d(){return Dr.find_among_b(or,2)}function f(){return n()&&Dr.in_grouping_b(Lr,105,305)&&c()}function b(){return n()&&Dr.find_among_b(sr,4)}function w(){return n()&&Dr.find_among_b(cr,4)&&o()}function _(){return n()&&Dr.find_among_b(lr,2)&&c()}function k(){return n()&&Dr.find_among_b(ar,2)}function p(){return n()&&Dr.find_among_b(mr,4)}function g(){return n()&&Dr.find_among_b(dr,2)}function y(){return n()&&Dr.find_among_b(fr,4)}function z(){return n()&&Dr.find_among_b(br,2)}function v(){return n()&&Dr.find_among_b(wr,2)&&c()}function h(){return Dr.eq_s_b(2,"ki")}function q(){return n()&&Dr.find_among_b(_r,2)&&o()}function C(){return n()&&Dr.find_among_b(kr,4)&&c()}function P(){return n()&&Dr.find_among_b(pr,4)}function F(){return n()&&Dr.find_among_b(gr,4)&&c()}function S(){return Dr.find_among_b(yr,4)}function W(){return n()&&Dr.find_among_b(zr,2)}function L(){return n()&&Dr.find_among_b(vr,4)}function x(){return n()&&Dr.find_among_b(hr,8)}function A(){return Dr.find_among_b(qr,2)}function E(){return n()&&Dr.find_among_b(Cr,32)&&c()}function j(){return Dr.find_among_b(Pr,8)&&c()}function T(){return n()&&Dr.find_among_b(Fr,4)&&c()}function Z(){return Dr.eq_s_b(3,"ken")&&c()}function B(){var r=Dr.limit-Dr.cursor;return!(T()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,Z()))))}function D(){if(A()){var r=Dr.limit-Dr.cursor;if(S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T())return!1}return!0}function G(){if(W()){Dr.bra=Dr.cursor,Dr.slice_del();var r=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,x()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,T()||(Dr.cursor=Dr.limit-r)))),nr=!1,!1}return!0}function H(){if(!L())return!0;var r=Dr.limit-Dr.cursor;return!E()&&(Dr.cursor=Dr.limit-r,!j())}function I(){var r,i=Dr.limit-Dr.cursor;return!(S()||(Dr.cursor=Dr.limit-i,F()||(Dr.cursor=Dr.limit-i,P()||(Dr.cursor=Dr.limit-i,C()))))||(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,T()||(Dr.cursor=Dr.limit-r),!1)}function J(){var r,i=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,nr=!0,B()&&(Dr.cursor=Dr.limit-i,D()&&(Dr.cursor=Dr.limit-i,G()&&(Dr.cursor=Dr.limit-i,H()&&(Dr.cursor=Dr.limit-i,I()))))){if(Dr.cursor=Dr.limit-i,!x())return;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T()||(Dr.cursor=Dr.limit-r)}Dr.bra=Dr.cursor,Dr.slice_del()}function K(){var r,i,e,n;if(Dr.ket=Dr.cursor,h()){if(r=Dr.limit-Dr.cursor,p())return Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,a()&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))),!0;if(Dr.cursor=Dr.limit-r,w()){if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,e=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-e,!m()&&(Dr.cursor=Dr.limit-e,!K())))return!0;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}return!0}if(Dr.cursor=Dr.limit-r,g()){if(n=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-n,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-n,!K())return!1;return!0}}return!1}function M(r){if(Dr.ket=Dr.cursor,!g()&&(Dr.cursor=Dr.limit-r,!k()))return!1;var i=Dr.limit-Dr.cursor;if(d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-i,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-i,!K())return!1;return!0}function N(r){if(Dr.ket=Dr.cursor,!z()&&(Dr.cursor=Dr.limit-r,!b()))return!1;var i=Dr.limit-Dr.cursor;return!(!m()&&(Dr.cursor=Dr.limit-i,!d()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)}function O(){var r,i=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,!(!w()&&(Dr.cursor=Dr.limit-i,!v()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,!(!W()||(Dr.bra=Dr.cursor,Dr.slice_del(),!K()))||(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!(a()||(Dr.cursor=Dr.limit-r,m()||(Dr.cursor=Dr.limit-r,K())))||(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)))}function Q(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,!p()&&(Dr.cursor=Dr.limit-e,!f()&&(Dr.cursor=Dr.limit-e,!_())))return!1;if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,a())Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()||(Dr.cursor=Dr.limit-i);else if(Dr.cursor=Dr.limit-r,!W())return!0;return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,K(),!0}function R(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,W())return Dr.bra=Dr.cursor,Dr.slice_del(),void K();if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,q())if(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-r,!m())){if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!W())return;if(Dr.bra=Dr.cursor,Dr.slice_del(),!K())return}Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}else if(Dr.cursor=Dr.limit-e,!M(e)&&(Dr.cursor=Dr.limit-e,!N(e))){if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,y())return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,i=Dr.limit-Dr.cursor,void(a()?(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())):(Dr.cursor=Dr.limit-i,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,K())));if(Dr.cursor=Dr.limit-e,!O()){if(Dr.cursor=Dr.limit-e,d())return Dr.bra=Dr.cursor,void Dr.slice_del();Dr.cursor=Dr.limit-e,K()||(Dr.cursor=Dr.limit-e,Q()||(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,(a()||(Dr.cursor=Dr.limit-e,m()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))))}}}function U(){var r;if(Dr.ket=Dr.cursor,r=Dr.find_among_b(Sr,4))switch(Dr.bra=Dr.cursor,r){case 1:Dr.slice_from("p");break;case 2:Dr.slice_from("ç");break;case 3:Dr.slice_from("t");break;case 4:Dr.slice_from("k")}}function V(){for(;;){var r=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(Wr,97,305)){Dr.cursor=Dr.limit-r;break}if(Dr.cursor=Dr.limit-r,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function X(r,i,e){if(Dr.cursor=Dr.limit-r,V()){var n=Dr.limit-Dr.cursor;if(!Dr.eq_s_b(1,i)&&(Dr.cursor=Dr.limit-n,!Dr.eq_s_b(1,e)))return!0;Dr.cursor=Dr.limit-r;var t=Dr.cursor;return Dr.insert(Dr.cursor,Dr.cursor,e),Dr.cursor=t,!1}return!0}function Y(){var r=Dr.limit-Dr.cursor;(Dr.eq_s_b(1,"d")||(Dr.cursor=Dr.limit-r,Dr.eq_s_b(1,"g")))&&X(r,"a","ı")&&X(r,"e","i")&&X(r,"o","u")&&X(r,"ö","ü")}function $(){for(var r,i=Dr.cursor,e=2;;){for(r=Dr.cursor;!Dr.in_grouping(Wr,97,305);){if(Dr.cursor>=Dr.limit)return Dr.cursor=r,!(e>0)&&(Dr.cursor=i,!0);Dr.cursor++}e--}}function rr(r,i,e){for(;!Dr.eq_s(i,e);){if(Dr.cursor>=Dr.limit)return!0;Dr.cursor++}return(tr=i)!=Dr.limit||(Dr.cursor=r,!1)}function ir(){var r=Dr.cursor;return!rr(r,2,"ad")||(Dr.cursor=r,!rr(r,5,"soyad"))}function er(){var r=Dr.cursor;return!ir()&&(Dr.limit_backward=r,Dr.cursor=Dr.limit,Y(),Dr.cursor=Dr.limit,U(),!0)}var nr,tr,ur=[new i("m",-1,-1),new i("n",-1,-1),new i("miz",-1,-1),new i("niz",-1,-1),new i("muz",-1,-1),new i("nuz",-1,-1),new i("müz",-1,-1),new i("nüz",-1,-1),new i("mız",-1,-1),new i("nız",-1,-1)],or=[new i("leri",-1,-1),new i("ları",-1,-1)],sr=[new i("ni",-1,-1),new i("nu",-1,-1),new i("nü",-1,-1),new i("nı",-1,-1)],cr=[new i("in",-1,-1),new i("un",-1,-1),new i("ün",-1,-1),new i("ın",-1,-1)],lr=[new i("a",-1,-1),new i("e",-1,-1)],ar=[new i("na",-1,-1),new i("ne",-1,-1)],mr=[new i("da",-1,-1),new i("ta",-1,-1),new i("de",-1,-1),new i("te",-1,-1)],dr=[new i("nda",-1,-1),new i("nde",-1,-1)],fr=[new i("dan",-1,-1),new i("tan",-1,-1),new i("den",-1,-1),new i("ten",-1,-1)],br=[new i("ndan",-1,-1),new i("nden",-1,-1)],wr=[new i("la",-1,-1),new i("le",-1,-1)],_r=[new i("ca",-1,-1),new i("ce",-1,-1)],kr=[new i("im",-1,-1),new i("um",-1,-1),new i("üm",-1,-1),new i("ım",-1,-1)],pr=[new i("sin",-1,-1),new i("sun",-1,-1),new i("sün",-1,-1),new i("sın",-1,-1)],gr=[new i("iz",-1,-1),new i("uz",-1,-1),new i("üz",-1,-1),new i("ız",-1,-1)],yr=[new i("siniz",-1,-1),new i("sunuz",-1,-1),new i("sünüz",-1,-1),new i("sınız",-1,-1)],zr=[new i("lar",-1,-1),new i("ler",-1,-1)],vr=[new i("niz",-1,-1),new i("nuz",-1,-1),new i("nüz",-1,-1),new i("nız",-1,-1)],hr=[new i("dir",-1,-1),new i("tir",-1,-1),new i("dur",-1,-1),new i("tur",-1,-1),new i("dür",-1,-1),new i("tür",-1,-1),new i("dır",-1,-1),new i("tır",-1,-1)],qr=[new i("casına",-1,-1),new i("cesine",-1,-1)],Cr=[new i("di",-1,-1),new i("ti",-1,-1),new i("dik",-1,-1),new i("tik",-1,-1),new i("duk",-1,-1),new i("tuk",-1,-1),new i("dük",-1,-1),new i("tük",-1,-1),new i("dık",-1,-1),new i("tık",-1,-1),new i("dim",-1,-1),new i("tim",-1,-1),new i("dum",-1,-1),new i("tum",-1,-1),new i("düm",-1,-1),new i("tüm",-1,-1),new i("dım",-1,-1),new i("tım",-1,-1),new i("din",-1,-1),new i("tin",-1,-1),new i("dun",-1,-1),new i("tun",-1,-1),new i("dün",-1,-1),new i("tün",-1,-1),new i("dın",-1,-1),new i("tın",-1,-1),new i("du",-1,-1),new i("tu",-1,-1),new i("dü",-1,-1),new i("tü",-1,-1),new i("dı",-1,-1),new i("tı",-1,-1)],Pr=[new i("sa",-1,-1),new i("se",-1,-1),new i("sak",-1,-1),new i("sek",-1,-1),new i("sam",-1,-1),new i("sem",-1,-1),new i("san",-1,-1),new i("sen",-1,-1)],Fr=[new i("miş",-1,-1),new i("muş",-1,-1),new i("müş",-1,-1),new i("mış",-1,-1)],Sr=[new i("b",-1,1),new i("c",-1,2),new i("d",-1,3),new i("ğ",-1,4)],Wr=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,8,0,0,0,0,0,0,1],Lr=[1,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1],xr=[1,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],Ar=[17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130],Er=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],jr=[17],Tr=[65],Zr=[65],Br=[["a",xr,97,305],["e",Ar,101,252],["ı",Er,97,305],["i",jr,101,105],["o",Tr,111,117],["ö",Zr,246,252],["u",Tr,111,117]],Dr=new e;this.setCurrent=function(r){Dr.setCurrent(r)},this.getCurrent=function(){return Dr.getCurrent()},this.stem=function(){return!!($()&&(Dr.limit_backward=Dr.cursor,Dr.cursor=Dr.limit,J(),Dr.cursor=Dr.limit,nr&&(R(),Dr.cursor=Dr.limit_backward,er())))}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.tr.stemmer,"stemmer-tr"),r.tr.stopWordFilter=r.generateStopWordFilter("acaba altmış altı ama ancak arada aslında ayrıca bana bazı belki ben benden beni benim beri beş bile bin bir biri birkaç birkez birçok birşey birşeyi biz bizden bize bizi bizim bu buna bunda bundan bunlar bunları bunların bunu bunun burada böyle böylece da daha dahi de defa değil diye diğer doksan dokuz dolayı dolayısıyla dört edecek eden ederek edilecek ediliyor edilmesi ediyor elli en etmesi etti ettiği ettiğini eğer gibi göre halen hangi hatta hem henüz hep hepsi her herhangi herkesin hiç hiçbir iki ile ilgili ise itibaren itibariyle için işte kadar karşın katrilyon kendi kendilerine kendini kendisi kendisine kendisini kez ki kim kimden kime kimi kimse kırk milyar milyon mu mü mı nasıl ne neden nedenle nerde nerede nereye niye niçin o olan olarak oldu olduklarını olduğu olduğunu olmadı olmadığı olmak olması olmayan olmaz olsa olsun olup olur olursa oluyor on ona ondan onlar onlardan onları onların onu onun otuz oysa pek rağmen sadece sanki sekiz seksen sen senden seni senin siz sizden sizi sizin tarafından trilyon tüm var vardı ve veya ya yani yapacak yapmak yaptı yaptıkları yaptığı yaptığını yapılan yapılması yapıyor yedi yerine yetmiş yine yirmi yoksa yüz zaten çok çünkü öyle üzere üç şey şeyden şeyi şeyler şu şuna şunda şundan şunları şunu şöyle".split(" ")),r.Pipeline.registerFunction(r.tr.stopWordFilter,"stopWordFilter-tr")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.vi.min.js b/assets/javascripts/lunr/min/lunr.vi.min.js new file mode 100644 index 00000000..22aed28c --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.vi.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.zh.min.js b/assets/javascripts/lunr/min/lunr.zh.min.js new file mode 100644 index 00000000..7727bbe2 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.zh.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("nodejieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 以 于 上 他 而 后 之 来 及 了 因 下 可 到 由 这 与 也 此 但 并 个 其 已 无 小 我 们 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 从 到 得 打 凡 儿 尔 该 各 给 跟 和 何 还 即 几 既 看 据 距 靠 啦 了 另 么 每 们 嘛 拿 哪 那 您 凭 且 却 让 仍 啥 如 若 使 谁 虽 随 同 所 她 哇 嗡 往 哪 些 向 沿 哟 用 于 咱 则 怎 曾 至 致 着 诸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/tinyseg.js b/assets/javascripts/lunr/tinyseg.js new file mode 100644 index 00000000..167fa6dd --- /dev/null +++ b/assets/javascripts/lunr/tinyseg.js @@ -0,0 +1,206 @@ +/** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ +;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like environments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + factory()(root.lunr); + } +}(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + + return function(lunr) { + // TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript + // (c) 2008 Taku Kudo + // TinySegmenter is freely distributable under the terms of a new BSD licence. + // For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt + + function TinySegmenter() { + var patterns = { + "[一二三四五六七八九十百千万億兆]":"M", + "[一-龠々〆ヵヶ]":"H", + "[ぁ-ん]":"I", + "[ァ-ヴーア-ン゙ー]":"K", + "[a-zA-Za-zA-Z]":"A", + "[0-90-9]":"N" + } + this.chartype_ = []; + for (var i in patterns) { + var regexp = new RegExp(i); + this.chartype_.push([regexp, patterns[i]]); + } + + this.BIAS__ = -332 + this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378}; + this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920}; + this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266}; + this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352}; + this.BP2__ = {"BO":60,"OO":-1762}; + this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965}; + this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146}; + this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699}; + this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973}; + this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682}; + this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669}; + this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990}; + this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832}; + this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649}; + this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393}; + this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841}; + this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68}; + this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591}; + this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685}; + this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156}; + this.TW1__ = {"につい":-4681,"東京都":2026}; + this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216}; + this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287}; + this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865}; + this.UC1__ = {"A":484,"K":93,"M":645,"O":-505}; + this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646}; + this.UC3__ = {"A":-1370,"I":2311}; + this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646}; + this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831}; + this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387}; + this.UP1__ = {"O":-214}; + this.UP2__ = {"B":69,"O":935}; + this.UP3__ = {"B":189}; + this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422}; + this.UQ2__ = {"BH":216,"BI":113,"OK":1759}; + this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212}; + this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135}; + this.UW2__ = {",":-829,"、":-829,"〇":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568}; + this.UW3__ = {",":4889,"1":-800,"−":-1723,"、":4889,"々":-2311,"〇":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"1":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278}; + this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"〇":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637}; + this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"1":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343}; + this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"1":-270,"E1":306,"ル":-673,"ン":-496}; + + return this; + } + TinySegmenter.prototype.ctype_ = function(str) { + for (var i in this.chartype_) { + if (str.match(this.chartype_[i][0])) { + return this.chartype_[i][1]; + } + } + return "O"; + } + + TinySegmenter.prototype.ts_ = function(v) { + if (v) { return v; } + return 0; + } + + TinySegmenter.prototype.segment = function(input) { + if (input == null || input == undefined || input == "") { + return []; + } + var result = []; + var seg = ["B3","B2","B1"]; + var ctype = ["O","O","O"]; + var o = input.split(""); + for (i = 0; i < o.length; ++i) { + seg.push(o[i]); + ctype.push(this.ctype_(o[i])) + } + seg.push("E1"); + seg.push("E2"); + seg.push("E3"); + ctype.push("O"); + ctype.push("O"); + ctype.push("O"); + var word = seg[3]; + var p1 = "U"; + var p2 = "U"; + var p3 = "U"; + for (var i = 4; i < seg.length - 3; ++i) { + var score = this.BIAS__; + var w1 = seg[i-3]; + var w2 = seg[i-2]; + var w3 = seg[i-1]; + var w4 = seg[i]; + var w5 = seg[i+1]; + var w6 = seg[i+2]; + var c1 = ctype[i-3]; + var c2 = ctype[i-2]; + var c3 = ctype[i-1]; + var c4 = ctype[i]; + var c5 = ctype[i+1]; + var c6 = ctype[i+2]; + score += this.ts_(this.UP1__[p1]); + score += this.ts_(this.UP2__[p2]); + score += this.ts_(this.UP3__[p3]); + score += this.ts_(this.BP1__[p1 + p2]); + score += this.ts_(this.BP2__[p2 + p3]); + score += this.ts_(this.UW1__[w1]); + score += this.ts_(this.UW2__[w2]); + score += this.ts_(this.UW3__[w3]); + score += this.ts_(this.UW4__[w4]); + score += this.ts_(this.UW5__[w5]); + score += this.ts_(this.UW6__[w6]); + score += this.ts_(this.BW1__[w2 + w3]); + score += this.ts_(this.BW2__[w3 + w4]); + score += this.ts_(this.BW3__[w4 + w5]); + score += this.ts_(this.TW1__[w1 + w2 + w3]); + score += this.ts_(this.TW2__[w2 + w3 + w4]); + score += this.ts_(this.TW3__[w3 + w4 + w5]); + score += this.ts_(this.TW4__[w4 + w5 + w6]); + score += this.ts_(this.UC1__[c1]); + score += this.ts_(this.UC2__[c2]); + score += this.ts_(this.UC3__[c3]); + score += this.ts_(this.UC4__[c4]); + score += this.ts_(this.UC5__[c5]); + score += this.ts_(this.UC6__[c6]); + score += this.ts_(this.BC1__[c2 + c3]); + score += this.ts_(this.BC2__[c3 + c4]); + score += this.ts_(this.BC3__[c4 + c5]); + score += this.ts_(this.TC1__[c1 + c2 + c3]); + score += this.ts_(this.TC2__[c2 + c3 + c4]); + score += this.ts_(this.TC3__[c3 + c4 + c5]); + score += this.ts_(this.TC4__[c4 + c5 + c6]); + // score += this.ts_(this.TC5__[c4 + c5 + c6]); + score += this.ts_(this.UQ1__[p1 + c1]); + score += this.ts_(this.UQ2__[p2 + c2]); + score += this.ts_(this.UQ3__[p3 + c3]); + score += this.ts_(this.BQ1__[p2 + c2 + c3]); + score += this.ts_(this.BQ2__[p2 + c3 + c4]); + score += this.ts_(this.BQ3__[p3 + c2 + c3]); + score += this.ts_(this.BQ4__[p3 + c3 + c4]); + score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]); + score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]); + score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]); + score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]); + var p = "O"; + if (score > 0) { + result.push(word); + word = ""; + p = "B"; + } + p1 = p2; + p2 = p3; + p3 = p; + word += seg[i]; + } + result.push(word); + + return result; + } + + lunr.TinySegmenter = TinySegmenter; + }; + +})); \ No newline at end of file diff --git a/assets/javascripts/lunr/wordcut.js b/assets/javascripts/lunr/wordcut.js new file mode 100644 index 00000000..146f4b44 --- /dev/null +++ b/assets/javascripts/lunr/wordcut.js @@ -0,0 +1,6708 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.lunr || (g.lunr = {})).wordcut = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1; + }) + this.addWords(words, false) + } + if(finalize){ + this.finalizeDict(); + } + }, + + dictSeek: function (l, r, ch, strOffset, pos) { + var ans = null; + while (l <= r) { + var m = Math.floor((l + r) / 2), + dict_item = this.dict[m], + len = dict_item.length; + if (len <= strOffset) { + l = m + 1; + } else { + var ch_ = dict_item[strOffset]; + if (ch_ < ch) { + l = m + 1; + } else if (ch_ > ch) { + r = m - 1; + } else { + ans = m; + if (pos == LEFT) { + r = m - 1; + } else { + l = m + 1; + } + } + } + } + return ans; + }, + + isFinal: function (acceptor) { + return this.dict[acceptor.l].length == acceptor.strOffset; + }, + + createAcceptor: function () { + return { + l: 0, + r: this.dict.length - 1, + strOffset: 0, + isFinal: false, + dict: this, + transit: function (ch) { + return this.dict.transit(this, ch); + }, + isError: false, + tag: "DICT", + w: 1, + type: "DICT" + }; + }, + + transit: function (acceptor, ch) { + var l = this.dictSeek(acceptor.l, + acceptor.r, + ch, + acceptor.strOffset, + LEFT); + if (l !== null) { + var r = this.dictSeek(l, + acceptor.r, + ch, + acceptor.strOffset, + RIGHT); + acceptor.l = l; + acceptor.r = r; + acceptor.strOffset++; + acceptor.isFinal = this.isFinal(acceptor); + } else { + acceptor.isError = true; + } + return acceptor; + }, + + sortuniq: function(a){ + return a.sort().filter(function(item, pos, arr){ + return !pos || item != arr[pos - 1]; + }) + }, + + flatten: function(a){ + //[[1,2],[3]] -> [1,2,3] + return [].concat.apply([], a); + } +}; +module.exports = WordcutDict; + +}).call(this,"/dist/tmp") +},{"glob":16,"path":22}],3:[function(require,module,exports){ +var WordRule = { + createAcceptor: function(tag) { + if (tag["WORD_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + var lch = ch.toLowerCase(); + if (lch >= "a" && lch <= "z") { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "WORD_RULE", + type: "WORD_RULE", + w: 1}; + } +}; + +var NumberRule = { + createAcceptor: function(tag) { + if (tag["NUMBER_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (ch >= "0" && ch <= "9") { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "NUMBER_RULE", + type: "NUMBER_RULE", + w: 1}; + } +}; + +var SpaceRule = { + tag: "SPACE_RULE", + createAcceptor: function(tag) { + + if (tag["SPACE_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (ch == " " || ch == "\t" || ch == "\r" || ch == "\n" || + ch == "\u00A0" || ch=="\u2003"//nbsp and emsp + ) { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: SpaceRule.tag, + w: 1, + type: "SPACE_RULE"}; + } +} + +var SingleSymbolRule = { + tag: "SINSYM", + createAcceptor: function(tag) { + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (this.strOffset == 0 && ch.match(/^[\@\(\)\/\,\-\."`]$/)) { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "SINSYM", + w: 1, + type: "SINSYM"}; + } +} + + +var LatinRules = [WordRule, SpaceRule, SingleSymbolRule, NumberRule]; + +module.exports = LatinRules; + +},{}],4:[function(require,module,exports){ +var _ = require("underscore") + , WordcutCore = require("./wordcut_core"); +var PathInfoBuilder = { + + /* + buildByPartAcceptors: function(path, acceptors, i) { + var + var genInfos = partAcceptors.reduce(function(genInfos, acceptor) { + + }, []); + + return genInfos; + } + */ + + buildByAcceptors: function(path, finalAcceptors, i) { + var self = this; + var infos = finalAcceptors.map(function(acceptor) { + var p = i - acceptor.strOffset + 1 + , _info = path[p]; + + var info = {p: p, + mw: _info.mw + (acceptor.mw === undefined ? 0 : acceptor.mw), + w: acceptor.w + _info.w, + unk: (acceptor.unk ? acceptor.unk : 0) + _info.unk, + type: acceptor.type}; + + if (acceptor.type == "PART") { + for(var j = p + 1; j <= i; j++) { + path[j].merge = p; + } + info.merge = p; + } + + return info; + }); + return infos.filter(function(info) { return info; }); + }, + + fallback: function(path, leftBoundary, text, i) { + var _info = path[leftBoundary]; + if (text[i].match(/[\u0E48-\u0E4E]/)) { + if (leftBoundary != 0) + leftBoundary = path[leftBoundary].p; + return {p: leftBoundary, + mw: 0, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; +/* } else if(leftBoundary > 0 && path[leftBoundary].type !== "UNK") { + leftBoundary = path[leftBoundary].p; + return {p: leftBoundary, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; */ + } else { + return {p: leftBoundary, + mw: _info.mw, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; + } + }, + + build: function(path, finalAcceptors, i, leftBoundary, text) { + var basicPathInfos = this.buildByAcceptors(path, finalAcceptors, i); + if (basicPathInfos.length > 0) { + return basicPathInfos; + } else { + return [this.fallback(path, leftBoundary, text, i)]; + } + } +}; + +module.exports = function() { + return _.clone(PathInfoBuilder); +} + +},{"./wordcut_core":8,"underscore":25}],5:[function(require,module,exports){ +var _ = require("underscore"); + + +var PathSelector = { + selectPath: function(paths) { + var path = paths.reduce(function(selectedPath, path) { + if (selectedPath == null) { + return path; + } else { + if (path.unk < selectedPath.unk) + return path; + if (path.unk == selectedPath.unk) { + if (path.mw < selectedPath.mw) + return path + if (path.mw == selectedPath.mw) { + if (path.w < selectedPath.w) + return path; + } + } + return selectedPath; + } + }, null); + return path; + }, + + createPath: function() { + return [{p:null, w:0, unk:0, type: "INIT", mw:0}]; + } +}; + +module.exports = function() { + return _.clone(PathSelector); +}; + +},{"underscore":25}],6:[function(require,module,exports){ +function isMatch(pat, offset, ch) { + if (pat.length <= offset) + return false; + var _ch = pat[offset]; + return _ch == ch || + (_ch.match(/[กข]/) && ch.match(/[ก-ฮ]/)) || + (_ch.match(/[มบ]/) && ch.match(/[ก-ฮ]/)) || + (_ch.match(/\u0E49/) && ch.match(/[\u0E48-\u0E4B]/)); +} + +var Rule0 = { + pat: "เหก็ม", + createAcceptor: function(tag) { + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (isMatch(Rule0.pat, this.strOffset,ch)) { + this.isFinal = (this.strOffset + 1 == Rule0.pat.length); + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "THAI_RULE", + type: "THAI_RULE", + w: 1}; + } +}; + +var PartRule = { + createAcceptor: function(tag) { + return {strOffset: 0, + patterns: [ + "แก", "เก", "ก้", "กก์", "กา", "กี", "กิ", "กืก" + ], + isFinal: false, + transit: function(ch) { + var offset = this.strOffset; + this.patterns = this.patterns.filter(function(pat) { + return isMatch(pat, offset, ch); + }); + + if (this.patterns.length > 0) { + var len = 1 + offset; + this.isFinal = this.patterns.some(function(pat) { + return pat.length == len; + }); + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "PART", + type: "PART", + unk: 1, + w: 1}; + } +}; + +var ThaiRules = [Rule0, PartRule]; + +module.exports = ThaiRules; + +},{}],7:[function(require,module,exports){ +var sys = require("sys") + , WordcutDict = require("./dict") + , WordcutCore = require("./wordcut_core") + , PathInfoBuilder = require("./path_info_builder") + , PathSelector = require("./path_selector") + , Acceptors = require("./acceptors") + , latinRules = require("./latin_rules") + , thaiRules = require("./thai_rules") + , _ = require("underscore"); + + +var Wordcut = Object.create(WordcutCore); +Wordcut.defaultPathInfoBuilder = PathInfoBuilder; +Wordcut.defaultPathSelector = PathSelector; +Wordcut.defaultAcceptors = Acceptors; +Wordcut.defaultLatinRules = latinRules; +Wordcut.defaultThaiRules = thaiRules; +Wordcut.defaultDict = WordcutDict; + + +Wordcut.initNoDict = function(dict_path) { + var self = this; + self.pathInfoBuilder = new self.defaultPathInfoBuilder; + self.pathSelector = new self.defaultPathSelector; + self.acceptors = new self.defaultAcceptors; + self.defaultLatinRules.forEach(function(rule) { + self.acceptors.creators.push(rule); + }); + self.defaultThaiRules.forEach(function(rule) { + self.acceptors.creators.push(rule); + }); +}; + +Wordcut.init = function(dict_path, withDefault, additionalWords) { + withDefault = withDefault || false; + this.initNoDict(); + var dict = _.clone(this.defaultDict); + dict.init(dict_path, withDefault, additionalWords); + this.acceptors.creators.push(dict); +}; + +module.exports = Wordcut; + +},{"./acceptors":1,"./dict":2,"./latin_rules":3,"./path_info_builder":4,"./path_selector":5,"./thai_rules":6,"./wordcut_core":8,"sys":28,"underscore":25}],8:[function(require,module,exports){ +var WordcutCore = { + + buildPath: function(text) { + var self = this + , path = self.pathSelector.createPath() + , leftBoundary = 0; + self.acceptors.reset(); + for (var i = 0; i < text.length; i++) { + var ch = text[i]; + self.acceptors.transit(ch); + + var possiblePathInfos = self + .pathInfoBuilder + .build(path, + self.acceptors.getFinalAcceptors(), + i, + leftBoundary, + text); + var selectedPath = self.pathSelector.selectPath(possiblePathInfos) + + path.push(selectedPath); + if (selectedPath.type !== "UNK") { + leftBoundary = i; + } + } + return path; + }, + + pathToRanges: function(path) { + var e = path.length - 1 + , ranges = []; + + while (e > 0) { + var info = path[e] + , s = info.p; + + if (info.merge !== undefined && ranges.length > 0) { + var r = ranges[ranges.length - 1]; + r.s = info.merge; + s = r.s; + } else { + ranges.push({s:s, e:e}); + } + e = s; + } + return ranges.reverse(); + }, + + rangesToText: function(text, ranges, delimiter) { + return ranges.map(function(r) { + return text.substring(r.s, r.e); + }).join(delimiter); + }, + + cut: function(text, delimiter) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + return this + .rangesToText(text, ranges, + (delimiter === undefined ? "|" : delimiter)); + }, + + cutIntoRanges: function(text, noText) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + + if (!noText) { + ranges.forEach(function(r) { + r.text = text.substring(r.s, r.e); + }); + } + return ranges; + }, + + cutIntoArray: function(text) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + + return ranges.map(function(r) { + return text.substring(r.s, r.e) + }); + } +}; + +module.exports = WordcutCore; + +},{}],9:[function(require,module,exports){ +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// 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 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. + +// when used in node, this will actually load the util module we depend on +// versus loading the builtin util module as happens otherwise +// this is a bug in node module loading as far as I am concerned +var util = require('util/'); + +var pSlice = Array.prototype.slice; +var hasOwn = Object.prototype.hasOwnProperty; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } + else { + // non v8 browsers so we can have a stacktrace + var err = new Error(); + if (err.stack) { + var out = err.stack; + + // try to strip useless frames + var fn_name = stackStartFunction.name; + var idx = out.indexOf('\n' + fn_name); + if (idx >= 0) { + // once we have located the function frame + // we need to strip out everything before it (and its line) + var next_line = out.indexOf('\n', idx + 1); + out = out.substring(next_line + 1); + } + + this.stack = out; + } + } +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && !isFinite(value)) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + // if one is a primitive, the other must be same + if (util.isPrimitive(a) || util.isPrimitive(b)) { + return a === b; + } + var aIsArgs = isArguments(a), + bIsArgs = isArguments(b); + if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) + return false; + if (aIsArgs) { + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + var ka = objectKeys(a), + kb = objectKeys(b), + key, i; + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; + +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + if (hasOwn.call(obj, key)) keys.push(key); + } + return keys; +}; + +},{"util/":28}],10:[function(require,module,exports){ +'use strict'; +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; + } + + if (begs.length) { + result = [ left, right ]; + } + } + + return result; +} + +},{}],11:[function(require,module,exports){ +var concatMap = require('concat-map'); +var balanced = require('balanced-match'); + +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; + +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); +} + +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} + +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} + + +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); + } + + parts.push.apply(parts, p); + + return parts; +} + +function expandTop(str) { + if (!str) + return []; + + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, + // but a{},b}c will be expanded to [a}c,abc]. + // One could argue that this is a bug in Bash, but since the goal of + // this module is to match Bash's rules, we escape a leading {} + if (str.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); + } + + return expand(escapeBraces(str), true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} + +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} + +function expand(str, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,.*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.abs(numeric(n[2])) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, false) }); + } + + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + + return expansions; +} + + +},{"balanced-match":10,"concat-map":13}],12:[function(require,module,exports){ + +},{}],13:[function(require,module,exports){ +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +},{}],14:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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 EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],15:[function(require,module,exports){ +(function (process){ +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored + +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) +} + +var path = require("path") +var minimatch = require("minimatch") +var isAbsolute = require("path-is-absolute") +var Minimatch = minimatch.Minimatch + +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} + +function alphasort (a, b) { + return a.localeCompare(b) +} + +function setupIgnores (self, options) { + self.ignore = options.ignore || [] + + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] + + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) + } +} + +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern) + } + + return { + matcher: new Minimatch(pattern), + gmatcher: gmatcher + } +} + +function setopts (self, pattern, options) { + if (!options) + options = {} + + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern + } + + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) + + setupIgnores(self, options) + + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = options.cwd + self.changedCwd = path.resolve(options.cwd) !== cwd + } + + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") + + self.nomount = !!options.nomount + + // disable comments and negation unless the user explicitly + // passes in false as the option. + options.nonegate = options.nonegate === false ? false : true + options.nocomment = options.nocomment === false ? false : true + deprecationWarning(options) + + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} + +// TODO(isaacs): remove entirely in v6 +// exported to reset in tests +exports.deprecationWarned +function deprecationWarning(options) { + if (!options.nonegate || !options.nocomment) { + if (process.noDeprecation !== true && !exports.deprecationWarned) { + var msg = 'glob WARNING: comments and negation will be disabled in v6' + if (process.throwDeprecation) + throw new Error(msg) + else if (process.traceDeprecation) + console.trace(msg) + else + console.error(msg) + + exports.deprecationWarned = true + } + } +} + +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) + + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) + } + } + + if (!nou) + all = Object.keys(all) + + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) + + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + return !(/\/$/.test(e)) + }) + } + } + + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) + + self.found = all +} + +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' + + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) + + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] + } + } + + return m +} + +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) + } + return abs +} + + +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) +} + +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) +} + +}).call(this,require('_process')) +},{"_process":24,"minimatch":20,"path":22,"path-is-absolute":23}],16:[function(require,module,exports){ +(function (process){ +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. + +module.exports = glob + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var inherits = require('inherits') +var EE = require('events').EventEmitter +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var globSync = require('./sync.js') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = require('inflight') +var util = require('util') +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored + +var once = require('once') + +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} + + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) + } + + return new Glob(pattern, options, cb) +} + +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync + +// old api surface +glob.glob = glob + +glob.hasMagic = function (pattern, options_) { + var options = util._extend({}, options_) + options.noprocess = true + + var g = new Glob(pattern, options) + var set = g.minimatch.set + if (set.length > 1) + return true + + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true + } + + return false +} + +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) + } + + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) + + setopts(this, pattern, options) + this._didRealPath = false + + // process each pattern in the minimatch set + var n = this.minimatch.set.length + + // The matches are stored as {: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) + + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) + } + + var self = this + var n = this.minimatch.set.length + this._processing = 0 + this.matches = new Array(n) + + this._emitQueue = [] + this._processQueue = [] + this.paused = false + + if (this.noprocess) + return this + + if (n === 0) + return done() + + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) + } + + function done () { + --self._processing + if (self._processing <= 0) + self._finish() + } +} + +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return + + if (this.realpath && !this._didRealpath) + return this._realpath() + + common.finish(this) + this.emit('end', this.found) +} + +Glob.prototype._realpath = function () { + if (this._didRealpath) + return + + this._didRealpath = true + + var n = this.matches.length + if (n === 0) + return this._finish() + + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) + + function next () { + if (--n === 0) + self._finish() + } +} + +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() + + var found = Object.keys(matchset) + var self = this + var n = found.length + + if (n === 0) + return cb() + + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + fs.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here + + if (--n === 0) { + self.matches[index] = set + cb() + } + }) + }) +} + +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} + +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} + +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} + +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) + } + } + } +} + +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') + + if (this.aborted) + return + + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return + } + + //console.error('PROCESS %d', this._processing, pattern) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} + +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return cb() + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + this._process([e].concat(remain), index, inGlobStar, cb) + } + cb() +} + +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return + + if (this.matches[index][e]) + return + + if (isIgnored(this, e)) + return + + if (this.paused) { + this._emitQueue.push([index, e]) + return + } + + var abs = this._makeAbs(e) + + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } + + if (this.mark) + e = this._mark(e) + + this.matches[index][e] = true + + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) + + this.emit('match', e) +} + +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return + + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) + + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) + + if (lstatcb) + fs.lstat(abs, lstatcb) + + function lstatcb_ (er, lstat) { + if (er) + return cb() + + var isSym = lstat.isSymbolicLink() + self.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) + } +} + +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return + + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return + + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() + + if (Array.isArray(c)) + return cb(null, c) + } + + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} + +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) + } +} + +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return + + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + return cb(null, entries) +} + +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return + + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() + } + if (!this.silent) + console.error('glob error', er) + break + } + + return cb() +} + +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + + +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) + + var isSym = this.symlinks[abs] + var len = entries.length + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) + } + + cb() +} + +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { + + //console.error('ps2', prefix, exists) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} + +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return cb() + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) + + if (needDir && c === 'FILE') + return cb() + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } + } + + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) + + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) + } + } +} + +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er) { + this.statCache[abs] = false + return cb() + } + + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat + + if (abs.slice(-1) === '/' && !stat.isDirectory()) + return cb(null, false, stat) + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return cb() + + return cb(null, c, stat) +} + +}).call(this,require('_process')) +},{"./common.js":15,"./sync.js":17,"_process":24,"assert":9,"events":14,"fs":12,"inflight":18,"inherits":19,"minimatch":20,"once":21,"path":22,"path-is-absolute":23,"util":28}],17:[function(require,module,exports){ +(function (process){ +module.exports = globSync +globSync.GlobSync = GlobSync + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var Glob = require('./glob.js').Glob +var util = require('util') +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored + +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + return new GlobSync(pattern, options).found +} + +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') + + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) + + setopts(this, pattern, options) + + if (this.noprocess) + return this + + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() +} + +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = fs.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } + } + }) + } + common.finish(this) +} + + +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip processing + if (childrenIgnored(this, read)) + return + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} + + +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this.matches[index][e] = true + } + // This was the last one, and no stats were needed + return + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) + } +} + + +GlobSync.prototype._emitMatch = function (index, e) { + var abs = this._makeAbs(e) + if (this.mark) + e = this._mark(e) + + if (this.matches[index][e]) + return + + if (this.nodir) { + var c = this.cache[this._makeAbs(e)] + if (c === 'DIR' || Array.isArray(c)) + return + } + + this.matches[index][e] = true + if (this.stat) + this._stat(e) +} + + +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) + + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + // lstat failed, doesn't exist + return null + } + + var isSym = lstat.isSymbolicLink() + this.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) + + return entries +} + +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries + + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null + + if (Array.isArray(c)) + return c + } + + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null + } +} + +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + + // mark and cache dir-ness + return entries +} + +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break + } +} + +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { + + var entries = this._readdir(abs, inGlobStar) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) + + var len = entries.length + var isSym = this.symlinks[abs] + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) + } +} + +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this.matches[index][prefix] = true +} + +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return false + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c + + if (needDir && c === 'FILE') + return false + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + return false + } + + if (lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } + } else { + stat = lstat + } + } + + this.statCache[abs] = stat + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return false + + return c +} + +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) +} + +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +}).call(this,require('_process')) +},{"./common.js":15,"./glob.js":16,"_process":24,"assert":9,"fs":12,"minimatch":20,"path":22,"path-is-absolute":23,"util":28}],18:[function(require,module,exports){ +(function (process){ +var wrappy = require('wrappy') +var reqs = Object.create(null) +var once = require('once') + +module.exports = wrappy(inflight) + +function inflight (key, cb) { + if (reqs[key]) { + reqs[key].push(cb) + return null + } else { + reqs[key] = [cb] + return makeres(key) + } +} + +function makeres (key) { + return once(function RES () { + var cbs = reqs[key] + var len = cbs.length + var args = slice(arguments) + + // XXX It's somewhat ambiguous whether a new callback added in this + // pass should be queued for later execution if something in the + // list of callbacks throws, or if it should just be discarded. + // However, it's such an edge case that it hardly matters, and either + // choice is likely as surprising as the other. + // As it happens, we do go ahead and schedule it for later execution. + try { + for (var i = 0; i < len; i++) { + cbs[i].apply(null, args) + } + } finally { + if (cbs.length > len) { + // added more in the interim. + // de-zalgo, just in case, but don't call again. + cbs.splice(0, len) + process.nextTick(function () { + RES.apply(null, args) + }) + } else { + delete reqs[key] + } + } + }) +} + +function slice (args) { + var length = args.length + var array = [] + + for (var i = 0; i < length; i++) array[i] = args[i] + return array +} + +}).call(this,require('_process')) +},{"_process":24,"once":21,"wrappy":29}],19:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],20:[function(require,module,exports){ +module.exports = minimatch +minimatch.Minimatch = Minimatch + +var path = { sep: '/' } +try { + path = require('path') +} catch (er) {} + +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = require('brace-expansion') + +var plTypes = { + '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, + '?': { open: '(?:', close: ')?' }, + '+': { open: '(?:', close: ')+' }, + '*': { open: '(?:', close: ')*' }, + '@': { open: '(?:', close: ')' } +} + +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' + +// * => any number of characters +var star = qmark + '*?' + +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') + +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} + +// normalizes slashes. +var slashSplit = /\/+/ + +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} + +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} + +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch + + var orig = minimatch + + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } + + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } + + return m +} + +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} + +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } + + // "" only matches "" + if (pattern.trim() === '') return p === '' + + return new Minimatch(pattern, options).match(p) +} + +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } + + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + pattern = pattern.trim() + + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } + + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false + + // make the set of regexps etc. + this.make() +} + +Minimatch.prototype.debug = function () {} + +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return + + var pattern = this.pattern + var options = this.options + + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + + // step 1: figure out negation, etc. + this.parseNegate() + + // step 2: expand braces + var set = this.globSet = this.braceExpand() + + if (options.debug) this.debug = console.error + + this.debug(this.pattern, set) + + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) + + this.debug(this.pattern, set) + + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) +} + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') + } + + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:) + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + try { + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') + } + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} + +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) + + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false + } + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} + +},{"brace-expansion":11,"path":22}],21:[function(require,module,exports){ +var wrappy = require('wrappy') +module.exports = wrappy(once) +module.exports.strict = wrappy(onceStrict) + +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true + }) + + Object.defineProperty(Function.prototype, 'onceStrict', { + value: function () { + return onceStrict(this) + }, + configurable: true + }) +}) + +function once (fn) { + var f = function () { + if (f.called) return f.value + f.called = true + return f.value = fn.apply(this, arguments) + } + f.called = false + return f +} + +function onceStrict (fn) { + var f = function () { + if (f.called) + throw new Error(f.onceError) + f.called = true + return f.value = fn.apply(this, arguments) + } + var name = fn.name || 'Function wrapped with `once`' + f.onceError = name + " shouldn't be called more than once" + f.called = false + return f +} + +},{"wrappy":29}],22:[function(require,module,exports){ +(function (process){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; + +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; + +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; + + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; + +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; + + +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + +exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; + +function filter (xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} + +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' + ? function (str, start, len) { return str.substr(start, len) } + : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); + } +; + +}).call(this,require('_process')) +},{"_process":24}],23:[function(require,module,exports){ +(function (process){ +'use strict'; + +function posix(path) { + return path.charAt(0) === '/'; +} + +function win32(path) { + // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path); + var device = result[1] || ''; + var isUnc = Boolean(device && device.charAt(1) !== ':'); + + // UNC paths are always absolute + return Boolean(result[2] || isUnc); +} + +module.exports = process.platform === 'win32' ? win32 : posix; +module.exports.posix = posix; +module.exports.win32 = win32; + +}).call(this,require('_process')) +},{"_process":24}],24:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],25:[function(require,module,exports){ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `exports` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var + push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind, + nativeCreate = Object.create; + + // Naked function reference for surrogate-prototype-swapping. + var Ctor = function(){}; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.8.3'; + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + var optimizeCb = function(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + case 2: return function(value, other) { + return func.call(context, value, other); + }; + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + }; + + // A mostly-internal function to generate callbacks that can be applied + // to each element in a collection, returning the desired result — either + // identity, an arbitrary callback, a property matcher, or a property accessor. + var cb = function(value, context, argCount) { + if (value == null) return _.identity; + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matcher(value); + return _.property(value); + }; + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc, undefinedOnly) { + return function(obj) { + var length = arguments.length; + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; + + var property = function(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Helper for collection methods to determine whether a collection + // should be iterated as an array or as an object + // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var getLength = property('length'); + var isArrayLike = function(collection) { + var length = getLength(collection); + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var keys = _.keys(obj); + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); + } + } + return obj; + }; + + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Create a reducing function iterating left or right. + function createReduce(dir) { + // Optimized iterator function as using arguments.length + // in the main function will deoptimize the, see #1991. + function iterator(obj, iteratee, memo, keys, index, length) { + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + } + + return function(obj, iteratee, memo, context) { + iteratee = optimizeCb(iteratee, context, 4); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + // Determine the initial value if none is provided. + if (arguments.length < 3) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + return iterator(obj, iteratee, memo, keys, index, length); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + _.reduce = _.foldl = _.inject = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + _.reduceRight = _.foldr = createReduce(-1); + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, predicate, context) { + var key; + if (isArrayLike(obj)) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; + }; + + // Return all the elements that pass a truth test. + // Aliased as `select`. + _.filter = _.select = function(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, predicate, context) { + return _.filter(obj, _.negate(cb(predicate)), context); + }; + + // Determine whether all of the elements match a truth test. + // Aliased as `all`. + _.every = _.all = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + }; + + // Determine if at least one element in the object matches a truth test. + // Aliased as `any`. + _.some = _.any = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + }; + + // Determine if the array or object contains a given item (using `===`). + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return _.indexOf(obj, item, fromIndex) >= 0; + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, _.property(key)); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs) { + return _.filter(obj, _.matcher(attrs)); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.find(obj, _.matcher(attrs)); + }; + + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Shuffle a collection, using the modern version of the + // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + _.shuffle = function(obj) { + var set = isArrayLike(obj) ? obj : _.values(obj); + var length = set.length; + var shuffled = Array(length); + for (var index = 0, rand; index < length; index++) { + rand = _.random(0, index); + if (rand !== index) shuffled[index] = shuffled[rand]; + shuffled[rand] = set[index]; + } + return shuffled; + }; + + // Sample **n** random values from a collection. + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `map`. + _.sample = function(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + return obj[_.random(obj.length - 1)]; + } + return _.shuffle(obj).slice(0, Math.max(0, n)); + }; + + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value: value, + index: index, + criteria: iteratee(value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(behavior) { + return function(obj, iteratee, context) { + var result = {}; + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `groupBy`, but for + // when you know that your index values will be unique. + _.indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; + }); + + // Safely create a real, live array from anything iterable. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (isArrayLike(obj)) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(obj, predicate, context) { + predicate = cb(predicate, context); + var pass = [], fail = []; + _.each(obj, function(value, key, obj) { + (predicate(value, key, obj) ? pass : fail).push(value); + }); + return [pass, fail]; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + _.initial = function(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0; + for (var i = startIndex || 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + }; + + // Flatten out an array, either recursively (by default), or just one level. + _.flatten = function(array, shallow) { + return flatten(array, shallow, false); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); + } + } + return result; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(flatten(arguments, true, true)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + for (var j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = flatten(arguments, true, true, 1); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + return _.unzip(arguments); + }; + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + var length = array && _.max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); + } + return result; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // Generator function to create the findIndex and findLastIndex functions + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a predicate test + _.findIndex = createPredicateIndexFinder(1); + _.findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + + // Generator function to create the indexOf and lastIndexOf functions + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), _.isNaN); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); + _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + step = step || 1; + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var args = slice.call(arguments, 2); + var bound = function() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); + }; + return bound; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. _ acts + // as a placeholder, allowing any combination of arguments to be pre-filled. + _.partial = function(func) { + var boundArgs = slice.call(arguments, 1); + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }; + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + _.bindAll = function(obj) { + var i, length = arguments.length, key; + if (length <= 1) throw new Error('bindAll must be passed function names'); + for (i = 1; i < length; i++) { + key = arguments[i]; + obj[key] = _.bind(obj[key], obj); + } + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ + return func.apply(null, args); + }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = _.partial(_.delay, _, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + if (!options) options = {}; + var later = function() { + previous = options.leading === false ? 0 : _.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function() { + var now = _.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + + var later = function() { + var last = _.now() - timestamp; + + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + if (!timeout) context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = _.now(); + var callNow = immediate && !timeout; + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return _.partial(wrapper, func); + }; + + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + }; + + // Returns a function that will only be executed on and after the Nth call. + _.after = function(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + + // Object Functions + // ---------------- + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = function(obj) { + if (!_.isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[keys[i]]; + } + return values; + }; + + // Returns the results of applying the iteratee to each element of the object + // In contrast to _.map it returns an object + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}, + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [keys[i], obj[keys[i]]]; + } + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + result[obj[keys[i]]] = keys[i]; + } + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(object, oiteratee, context) { + var result = {}, obj = object, iteratee, keys; + if (obj == null) return result; + if (_.isFunction(oiteratee)) { + keys = _.allKeys(obj); + iteratee = optimizeCb(oiteratee, context); + } else { + keys = flatten(arguments, false, false, 1); + iteratee = function(value, key, obj) { return key in obj; }; + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj, iteratee, context) { + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + } else { + var keys = _.map(flatten(arguments, false, false, 1), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; + } + return _.pick(obj, iteratee, context); + }; + + // Fill in a given object with default properties. + _.defaults = createAssigner(_.allKeys, true); + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + _.create = function(prototype, props) { + var result = baseCreate(prototype); + if (props) _.extendOwn(result, props); + return result; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + switch (className) { + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) === '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return _.has(obj, 'callee'); + }; + } + + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), and in Safari 8 (#1929). + if (typeof /./ != 'function' && typeof Int8Array != 'object') { + _.isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj !== +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iteratees. + _.identity = function(value) { + return value; + }; + + // Predicate-generating functions. Often useful outside of Underscore. + _.constant = function(value) { + return function() { + return value; + }; + }; + + _.noop = function(){}; + + _.property = property; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { + return obj[key]; + }; + }; + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); + return function(obj) { + return _.isMatch(obj, attrs); + }; + }; + + // Run a function **n** times. + _.times = function(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // A (possibly faster) way to get the current timestamp as an integer. + _.now = Date.now || function() { + return new Date().getTime(); + }; + + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + var unescapeMap = _.invert(escapeMap); + + // Functions for escaping and unescaping strings to/from HTML interpolation. + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); + + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } + return _.isFunction(value) ? value.call(object) : value; + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escaper, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offest. + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + try { + var render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function. Start chaining a wrapped Underscore object. + _.chain = function(obj) { + var instance = _(obj); + instance._chain = true; + return instance; + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result(this, func.apply(_, args)); + }; + }); + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return result(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + _.each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result(this, method.apply(this._wrapped, arguments)); + }; + }); + + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; + + // AMD registration happens at the end for compatibility with AMD loaders + // that may not enforce next-turn semantics on modules. Even though general + // practice for AMD registration is to be anonymous, underscore registers + // as a named module because, like jQuery, it is a base library that is + // popular enough to be bundled in a third party lib, but not be part of + // an AMD load request. Those cases could generate an error when an + // anonymous define() is called outside of a loader request. + if (typeof define === 'function' && define.amd) { + define('underscore', [], function() { + return _; + }); + } +}.call(this)); + +},{}],26:[function(require,module,exports){ +arguments[4][19][0].apply(exports,arguments) +},{"dup":19}],27:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],28:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":27,"_process":24,"inherits":26}],29:[function(require,module,exports){ +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) + + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') + + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) + + return wrapper + + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret + } +} + +},{}]},{},[7])(7) +}); \ No newline at end of file diff --git a/assets/javascripts/workers/search.85cb4492.min.js b/assets/javascripts/workers/search.85cb4492.min.js new file mode 100644 index 00000000..0933cd2b --- /dev/null +++ b/assets/javascripts/workers/search.85cb4492.min.js @@ -0,0 +1,48 @@ +(()=>{var ge=Object.create;var W=Object.defineProperty,ye=Object.defineProperties,me=Object.getOwnPropertyDescriptor,ve=Object.getOwnPropertyDescriptors,xe=Object.getOwnPropertyNames,G=Object.getOwnPropertySymbols,Se=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty,Qe=Object.prototype.propertyIsEnumerable;var J=(t,e,r)=>e in t?W(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,M=(t,e)=>{for(var r in e||(e={}))X.call(e,r)&&J(t,r,e[r]);if(G)for(var r of G(e))Qe.call(e,r)&&J(t,r,e[r]);return t},Z=(t,e)=>ye(t,ve(e));var K=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var be=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of xe(e))!X.call(t,i)&&i!==r&&W(t,i,{get:()=>e[i],enumerable:!(n=me(e,i))||n.enumerable});return t};var H=(t,e,r)=>(r=t!=null?ge(Se(t)):{},be(e||!t||!t.__esModule?W(r,"default",{value:t,enumerable:!0}):r,t));var z=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var re=K((ee,te)=>{/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";/*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + */t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,c],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;/*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + */t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(r+=n[c+1]*i[h+1],c+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),y=s.str.charAt(1),g;y in s.node.edges?g=s.node.edges[y]:(g=new t.TokenSet,s.node.edges[y]=g),s.str.length==1&&(g.final=!0),i.push({node:g,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};/*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + */t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ee=="object"?te.exports=r():e.lunr=r()}(this,function(){return t})})()});var q=K((Re,ne)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var Le=/["'&<>]/;ne.exports=we;function we(t){var e=""+t,r=Le.exec(e);if(!r)return e;var n,i="",s=0,o=0;for(s=r.index;s=0;r--){let n=t[r];typeof n!="object"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?e.insertBefore(this.previousSibling,n):e.replaceChild(n,this)}}}));var ie=H(q());function se(t){let e=new Map,r=new Set;for(let n of t){let[i,s]=n.location.split("#"),o=n.location,a=n.title,u=n.tags,c=(0,ie.default)(n.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(s){let h=e.get(i);r.has(h)?e.set(o,{location:o,title:a,text:c,parent:h}):(h.title=n.title,h.text=c,r.add(h))}else e.set(o,M({location:o,title:a,text:c},u&&{tags:u}))}return e}var oe=H(q());function ae(t,e){let r=new RegExp(t.separator,"img"),n=(i,s,o)=>`${s}${o}`;return i=>{i=i.replace(/[\s*+\-:~^]+/g," ").trim();let s=new RegExp(`(^|${t.separator})(${i.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return o=>(e?(0,oe.default)(o):o).replace(s,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function ue(t){let e=new lunr.Query(["title","text"]);return new lunr.QueryParser(t,e).parse(),e.clauses}function ce(t,e){var i;let r=new Set(t),n={};for(let s=0;s!n.has(i)))]}var U=class{constructor({config:e,docs:r,options:n}){this.options=n,this.documents=se(r),this.highlight=ae(e,!1),lunr.tokenizer.separator=new RegExp(e.separator),this.index=lunr(function(){e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang));let i=Ee(["trimmer","stopWordFilter","stemmer"],n.pipeline);for(let s of e.lang.map(o=>o==="en"?lunr:lunr[o]))for(let o of i)this.pipeline.remove(s[o]),this.searchPipeline.remove(s[o]);this.ref("location"),this.field("title",{boost:1e3}),this.field("text"),this.field("tags",{boost:1e6,extractor:s=>{let{tags:o=[]}=s;return o.flatMap(a=>lunr.tokenizer(a))}});for(let s of r)this.add(s,{boost:s.boost})})}search(e){if(e)try{let r=this.highlight(e),n=ue(e).filter(o=>o.presence!==lunr.Query.presence.PROHIBITED),i=this.index.search(`${e}*`).reduce((o,{ref:a,score:u,matchData:c})=>{let h=this.documents.get(a);if(typeof h!="undefined"){let{location:y,title:g,text:b,tags:m,parent:Q}=h,p=ce(n,Object.keys(c.metadata)),d=+!Q+ +Object.values(p).every(w=>w);o.push(Z(M({location:y,title:r(g),text:r(b)},m&&{tags:m.map(r)}),{score:u*(1+d),terms:p}))}return o},[]).sort((o,a)=>a.score-o.score).reduce((o,a)=>{let u=this.documents.get(a.location);if(typeof u!="undefined"){let c="parent"in u?u.parent.location:u.location;o.set(c,[...o.get(c)||[],a])}return o},new Map),s;if(this.options.suggestions){let o=this.index.query(a=>{for(let u of n)a.term(u.term,{fields:["title"],presence:lunr.Query.presence.REQUIRED,wildcard:lunr.Query.wildcard.TRAILING})});s=o.length?Object.keys(o[0].matchData.metadata):[]}return M({items:[...i.values()]},typeof s!="undefined"&&{suggestions:s})}catch(r){console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`)}return{items:[]}}};var Y;function ke(t){return z(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=document.querySelector("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang){switch(n){case"ja":r.push(`${e}/tinyseg.js`);break;case"hi":case"th":r.push(`${e}/wordcut.js`);break}n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`)}t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Te(t){return z(this,null,function*(){switch(t.type){case 0:return yield ke(t.data.config),Y=new U(t.data),{type:1};case 2:return{type:3,data:Y?Y.search(t.data):{items:[]}};default:throw new TypeError("Invalid message type")}})}self.lunr=le.default;addEventListener("message",t=>z(void 0,null,function*(){postMessage(yield Te(t.data))}));})(); +//# sourceMappingURL=search.85cb4492.min.js.map + diff --git a/assets/javascripts/workers/search.85cb4492.min.js.map b/assets/javascripts/workers/search.85cb4492.min.js.map new file mode 100644 index 00000000..120ad249 --- /dev/null +++ b/assets/javascripts/workers/search.85cb4492.min.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["node_modules/lunr/lunr.js", "node_modules/escape-html/index.js", "src/assets/javascripts/integrations/search/worker/main/index.ts", "src/assets/javascripts/polyfills/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/_/index.ts", "src/assets/javascripts/integrations/search/_/index.ts"], + "sourceRoot": "../../../..", + "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n * this.field('title')\n * this.field('body')\n * this.ref('id')\n *\n * documents.forEach(function (doc) {\n * this.add(doc)\n * }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n var builder = new lunr.Builder\n\n builder.pipeline.add(\n lunr.trimmer,\n lunr.stopWordFilter,\n lunr.stemmer\n )\n\n builder.searchPipeline.add(\n lunr.stemmer\n )\n\n config.call(builder, builder)\n return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n /* eslint-disable no-console */\n return function (message) {\n if (global.console && console.warn) {\n console.warn(message)\n }\n }\n /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n if (obj === void 0 || obj === null) {\n return \"\"\n } else {\n return obj.toString()\n }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n var clone = Object.create(null),\n keys = Object.keys(obj)\n\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i],\n val = obj[key]\n\n if (Array.isArray(val)) {\n clone[key] = val.slice()\n continue\n }\n\n if (typeof val === 'string' ||\n typeof val === 'number' ||\n typeof val === 'boolean') {\n clone[key] = val\n continue\n }\n\n throw new TypeError(\"clone is not deep and does not support nested objects\")\n }\n\n return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n this.docRef = docRef\n this.fieldName = fieldName\n this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n var n = s.indexOf(lunr.FieldRef.joiner)\n\n if (n === -1) {\n throw \"malformed field ref string\"\n }\n\n var fieldRef = s.slice(0, n),\n docRef = s.slice(n + 1)\n\n return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n if (this._stringValue == undefined) {\n this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n }\n\n return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n this.elements = Object.create(null)\n\n if (elements) {\n this.length = elements.length\n\n for (var i = 0; i < this.length; i++) {\n this.elements[elements[i]] = true\n }\n } else {\n this.length = 0\n }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n intersect: function (other) {\n return other\n },\n\n union: function () {\n return this\n },\n\n contains: function () {\n return true\n }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n intersect: function () {\n return this\n },\n\n union: function (other) {\n return other\n },\n\n contains: function () {\n return false\n }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n var a, b, elements, intersection = []\n\n if (other === lunr.Set.complete) {\n return this\n }\n\n if (other === lunr.Set.empty) {\n return other\n }\n\n if (this.length < other.length) {\n a = this\n b = other\n } else {\n a = other\n b = this\n }\n\n elements = Object.keys(a.elements)\n\n for (var i = 0; i < elements.length; i++) {\n var element = elements[i]\n if (element in b.elements) {\n intersection.push(element)\n }\n }\n\n return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n if (other === lunr.Set.complete) {\n return lunr.Set.complete\n }\n\n if (other === lunr.Set.empty) {\n return this\n }\n\n return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n var documentsWithTerm = 0\n\n for (var fieldName in posting) {\n if (fieldName == '_index') continue // Ignore the term index, its not a field\n documentsWithTerm += Object.keys(posting[fieldName]).length\n }\n\n var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n this.str = str || \"\"\n this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n * return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n this.str = fn(this.str, this.metadata)\n return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n fn = fn || function (s) { return s }\n return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n if (obj == null || obj == undefined) {\n return []\n }\n\n if (Array.isArray(obj)) {\n return obj.map(function (t) {\n return new lunr.Token(\n lunr.utils.asString(t).toLowerCase(),\n lunr.utils.clone(metadata)\n )\n })\n }\n\n var str = obj.toString().toLowerCase(),\n len = str.length,\n tokens = []\n\n for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n var char = str.charAt(sliceEnd),\n sliceLength = sliceEnd - sliceStart\n\n if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n if (sliceLength > 0) {\n var tokenMetadata = lunr.utils.clone(metadata) || {}\n tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n tokenMetadata[\"index\"] = tokens.length\n\n tokens.push(\n new lunr.Token (\n str.slice(sliceStart, sliceEnd),\n tokenMetadata\n )\n )\n }\n\n sliceStart = sliceEnd + 1\n }\n\n }\n\n return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n if (label in this.registeredFunctions) {\n lunr.utils.warn('Overwriting existing registered function: ' + label)\n }\n\n fn.label = label\n lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n if (!isRegistered) {\n lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n var pipeline = new lunr.Pipeline\n\n serialised.forEach(function (fnName) {\n var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n if (fn) {\n pipeline.add(fn)\n } else {\n throw new Error('Cannot load unregistered function: ' + fnName)\n }\n })\n\n return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n var fns = Array.prototype.slice.call(arguments)\n\n fns.forEach(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n this._stack.push(fn)\n }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n pos = pos + 1\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n var pos = this._stack.indexOf(fn)\n if (pos == -1) {\n return\n }\n\n this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n var stackLength = this._stack.length\n\n for (var i = 0; i < stackLength; i++) {\n var fn = this._stack[i]\n var memo = []\n\n for (var j = 0; j < tokens.length; j++) {\n var result = fn(tokens[j], j, tokens)\n\n if (result === null || result === void 0 || result === '') continue\n\n if (Array.isArray(result)) {\n for (var k = 0; k < result.length; k++) {\n memo.push(result[k])\n }\n } else {\n memo.push(result)\n }\n }\n\n tokens = memo\n }\n\n return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n var token = new lunr.Token (str, metadata)\n\n return this.run([token]).map(function (t) {\n return t.toString()\n })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n return this._stack.map(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n return fn.label\n })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n this._magnitude = 0\n this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n // For an empty vector the tuple can be inserted at the beginning\n if (this.elements.length == 0) {\n return 0\n }\n\n var start = 0,\n end = this.elements.length / 2,\n sliceLength = end - start,\n pivotPoint = Math.floor(sliceLength / 2),\n pivotIndex = this.elements[pivotPoint * 2]\n\n while (sliceLength > 1) {\n if (pivotIndex < index) {\n start = pivotPoint\n }\n\n if (pivotIndex > index) {\n end = pivotPoint\n }\n\n if (pivotIndex == index) {\n break\n }\n\n sliceLength = end - start\n pivotPoint = start + Math.floor(sliceLength / 2)\n pivotIndex = this.elements[pivotPoint * 2]\n }\n\n if (pivotIndex == index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex > index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex < index) {\n return (pivotPoint + 1) * 2\n }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n this.upsert(insertIdx, val, function () {\n throw \"duplicate index\"\n })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n this._magnitude = 0\n var position = this.positionForIndex(insertIdx)\n\n if (this.elements[position] == insertIdx) {\n this.elements[position + 1] = fn(this.elements[position + 1], val)\n } else {\n this.elements.splice(position, 0, insertIdx, val)\n }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n if (this._magnitude) return this._magnitude\n\n var sumOfSquares = 0,\n elementsLength = this.elements.length\n\n for (var i = 1; i < elementsLength; i += 2) {\n var val = this.elements[i]\n sumOfSquares += val * val\n }\n\n return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n var dotProduct = 0,\n a = this.elements, b = otherVector.elements,\n aLen = a.length, bLen = b.length,\n aVal = 0, bVal = 0,\n i = 0, j = 0\n\n while (i < aLen && j < bLen) {\n aVal = a[i], bVal = b[j]\n if (aVal < bVal) {\n i += 2\n } else if (aVal > bVal) {\n j += 2\n } else if (aVal == bVal) {\n dotProduct += a[i + 1] * b[j + 1]\n i += 2\n j += 2\n }\n }\n\n return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n var output = new Array (this.elements.length / 2)\n\n for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n output[j] = this.elements[i]\n }\n\n return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n var step2list = {\n \"ational\" : \"ate\",\n \"tional\" : \"tion\",\n \"enci\" : \"ence\",\n \"anci\" : \"ance\",\n \"izer\" : \"ize\",\n \"bli\" : \"ble\",\n \"alli\" : \"al\",\n \"entli\" : \"ent\",\n \"eli\" : \"e\",\n \"ousli\" : \"ous\",\n \"ization\" : \"ize\",\n \"ation\" : \"ate\",\n \"ator\" : \"ate\",\n \"alism\" : \"al\",\n \"iveness\" : \"ive\",\n \"fulness\" : \"ful\",\n \"ousness\" : \"ous\",\n \"aliti\" : \"al\",\n \"iviti\" : \"ive\",\n \"biliti\" : \"ble\",\n \"logi\" : \"log\"\n },\n\n step3list = {\n \"icate\" : \"ic\",\n \"ative\" : \"\",\n \"alize\" : \"al\",\n \"iciti\" : \"ic\",\n \"ical\" : \"ic\",\n \"ful\" : \"\",\n \"ness\" : \"\"\n },\n\n c = \"[^aeiou]\", // consonant\n v = \"[aeiouy]\", // vowel\n C = c + \"[^aeiouy]*\", // consonant sequence\n V = v + \"[aeiou]*\", // vowel sequence\n\n mgr0 = \"^(\" + C + \")?\" + V + C, // [C]VC... is m>0\n meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\", // [C]VC[V] is m=1\n mgr1 = \"^(\" + C + \")?\" + V + C + V + C, // [C]VCVC... is m>1\n s_v = \"^(\" + C + \")?\" + v; // vowel in stem\n\n var re_mgr0 = new RegExp(mgr0);\n var re_mgr1 = new RegExp(mgr1);\n var re_meq1 = new RegExp(meq1);\n var re_s_v = new RegExp(s_v);\n\n var re_1a = /^(.+?)(ss|i)es$/;\n var re2_1a = /^(.+?)([^s])s$/;\n var re_1b = /^(.+?)eed$/;\n var re2_1b = /^(.+?)(ed|ing)$/;\n var re_1b_2 = /.$/;\n var re2_1b_2 = /(at|bl|iz)$/;\n var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var re_1c = /^(.+?[^aeiou])y$/;\n var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n var re_5 = /^(.+?)e$/;\n var re_5_1 = /ll$/;\n var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var porterStemmer = function porterStemmer(w) {\n var stem,\n suffix,\n firstch,\n re,\n re2,\n re3,\n re4;\n\n if (w.length < 3) { return w; }\n\n firstch = w.substr(0,1);\n if (firstch == \"y\") {\n w = firstch.toUpperCase() + w.substr(1);\n }\n\n // Step 1a\n re = re_1a\n re2 = re2_1a;\n\n if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n // Step 1b\n re = re_1b;\n re2 = re2_1b;\n if (re.test(w)) {\n var fp = re.exec(w);\n re = re_mgr0;\n if (re.test(fp[1])) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1];\n re2 = re_s_v;\n if (re2.test(stem)) {\n w = stem;\n re2 = re2_1b_2;\n re3 = re3_1b_2;\n re4 = re4_1b_2;\n if (re2.test(w)) { w = w + \"e\"; }\n else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n else if (re4.test(w)) { w = w + \"e\"; }\n }\n }\n\n // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n re = re_1c;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n w = stem + \"i\";\n }\n\n // Step 2\n re = re_2;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step2list[suffix];\n }\n }\n\n // Step 3\n re = re_3;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step3list[suffix];\n }\n }\n\n // Step 4\n re = re_4;\n re2 = re2_4;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n if (re.test(stem)) {\n w = stem;\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1] + fp[2];\n re2 = re_mgr1;\n if (re2.test(stem)) {\n w = stem;\n }\n }\n\n // Step 5\n re = re_5;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n re2 = re_meq1;\n re3 = re3_5;\n if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n w = stem;\n }\n }\n\n re = re_5_1;\n re2 = re_mgr1;\n if (re.test(w) && re2.test(w)) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n\n // and turn initial Y back to y\n\n if (firstch == \"y\") {\n w = firstch.toLowerCase() + w.substr(1);\n }\n\n return w;\n };\n\n return function (token) {\n return token.update(porterStemmer);\n }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n var words = stopWords.reduce(function (memo, stopWord) {\n memo[stopWord] = stopWord\n return memo\n }, {})\n\n return function (token) {\n if (token && words[token.toString()] !== token.toString()) return token\n }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n 'a',\n 'able',\n 'about',\n 'across',\n 'after',\n 'all',\n 'almost',\n 'also',\n 'am',\n 'among',\n 'an',\n 'and',\n 'any',\n 'are',\n 'as',\n 'at',\n 'be',\n 'because',\n 'been',\n 'but',\n 'by',\n 'can',\n 'cannot',\n 'could',\n 'dear',\n 'did',\n 'do',\n 'does',\n 'either',\n 'else',\n 'ever',\n 'every',\n 'for',\n 'from',\n 'get',\n 'got',\n 'had',\n 'has',\n 'have',\n 'he',\n 'her',\n 'hers',\n 'him',\n 'his',\n 'how',\n 'however',\n 'i',\n 'if',\n 'in',\n 'into',\n 'is',\n 'it',\n 'its',\n 'just',\n 'least',\n 'let',\n 'like',\n 'likely',\n 'may',\n 'me',\n 'might',\n 'most',\n 'must',\n 'my',\n 'neither',\n 'no',\n 'nor',\n 'not',\n 'of',\n 'off',\n 'often',\n 'on',\n 'only',\n 'or',\n 'other',\n 'our',\n 'own',\n 'rather',\n 'said',\n 'say',\n 'says',\n 'she',\n 'should',\n 'since',\n 'so',\n 'some',\n 'than',\n 'that',\n 'the',\n 'their',\n 'them',\n 'then',\n 'there',\n 'these',\n 'they',\n 'this',\n 'tis',\n 'to',\n 'too',\n 'twas',\n 'us',\n 'wants',\n 'was',\n 'we',\n 'were',\n 'what',\n 'when',\n 'where',\n 'which',\n 'while',\n 'who',\n 'whom',\n 'why',\n 'will',\n 'with',\n 'would',\n 'yet',\n 'you',\n 'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n return token.update(function (s) {\n return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n this.final = false\n this.edges = {}\n this.id = lunr.TokenSet._nextId\n lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n var builder = new lunr.TokenSet.Builder\n\n for (var i = 0, len = arr.length; i < len; i++) {\n builder.insert(arr[i])\n }\n\n builder.finish()\n return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n if ('editDistance' in clause) {\n return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n } else {\n return lunr.TokenSet.fromString(clause.term)\n }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n var root = new lunr.TokenSet\n\n var stack = [{\n node: root,\n editsRemaining: editDistance,\n str: str\n }]\n\n while (stack.length) {\n var frame = stack.pop()\n\n // no edit\n if (frame.str.length > 0) {\n var char = frame.str.charAt(0),\n noEditNode\n\n if (char in frame.node.edges) {\n noEditNode = frame.node.edges[char]\n } else {\n noEditNode = new lunr.TokenSet\n frame.node.edges[char] = noEditNode\n }\n\n if (frame.str.length == 1) {\n noEditNode.final = true\n }\n\n stack.push({\n node: noEditNode,\n editsRemaining: frame.editsRemaining,\n str: frame.str.slice(1)\n })\n }\n\n if (frame.editsRemaining == 0) {\n continue\n }\n\n // insertion\n if (\"*\" in frame.node.edges) {\n var insertionNode = frame.node.edges[\"*\"]\n } else {\n var insertionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = insertionNode\n }\n\n if (frame.str.length == 0) {\n insertionNode.final = true\n }\n\n stack.push({\n node: insertionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str\n })\n\n // deletion\n // can only do a deletion if we have enough edits remaining\n // and if there are characters left to delete in the string\n if (frame.str.length > 1) {\n stack.push({\n node: frame.node,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // deletion\n // just removing the last character from the str\n if (frame.str.length == 1) {\n frame.node.final = true\n }\n\n // substitution\n // can only do a substitution if we have enough edits remaining\n // and if there are characters left to substitute\n if (frame.str.length >= 1) {\n if (\"*\" in frame.node.edges) {\n var substitutionNode = frame.node.edges[\"*\"]\n } else {\n var substitutionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = substitutionNode\n }\n\n if (frame.str.length == 1) {\n substitutionNode.final = true\n }\n\n stack.push({\n node: substitutionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // transposition\n // can only do a transposition if there are edits remaining\n // and there are enough characters to transpose\n if (frame.str.length > 1) {\n var charA = frame.str.charAt(0),\n charB = frame.str.charAt(1),\n transposeNode\n\n if (charB in frame.node.edges) {\n transposeNode = frame.node.edges[charB]\n } else {\n transposeNode = new lunr.TokenSet\n frame.node.edges[charB] = transposeNode\n }\n\n if (frame.str.length == 1) {\n transposeNode.final = true\n }\n\n stack.push({\n node: transposeNode,\n editsRemaining: frame.editsRemaining - 1,\n str: charA + frame.str.slice(2)\n })\n }\n }\n\n return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n var node = new lunr.TokenSet,\n root = node\n\n /*\n * Iterates through all characters within the passed string\n * appending a node for each character.\n *\n * When a wildcard character is found then a self\n * referencing edge is introduced to continually match\n * any number of any characters.\n */\n for (var i = 0, len = str.length; i < len; i++) {\n var char = str[i],\n final = (i == len - 1)\n\n if (char == \"*\") {\n node.edges[char] = node\n node.final = final\n\n } else {\n var next = new lunr.TokenSet\n next.final = final\n\n node.edges[char] = next\n node = next\n }\n }\n\n return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n var words = []\n\n var stack = [{\n prefix: \"\",\n node: this\n }]\n\n while (stack.length) {\n var frame = stack.pop(),\n edges = Object.keys(frame.node.edges),\n len = edges.length\n\n if (frame.node.final) {\n /* In Safari, at this point the prefix is sometimes corrupted, see:\n * https://github.com/olivernn/lunr.js/issues/279 Calling any\n * String.prototype method forces Safari to \"cast\" this string to what\n * it's supposed to be, fixing the bug. */\n frame.prefix.charAt(0)\n words.push(frame.prefix)\n }\n\n for (var i = 0; i < len; i++) {\n var edge = edges[i]\n\n stack.push({\n prefix: frame.prefix.concat(edge),\n node: frame.node.edges[edge]\n })\n }\n }\n\n return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n // NOTE: Using Object.keys here as this.edges is very likely\n // to enter 'hash-mode' with many keys being added\n //\n // avoiding a for-in loop here as it leads to the function\n // being de-optimised (at least in V8). From some simple\n // benchmarks the performance is comparable, but allowing\n // V8 to optimize may mean easy performance wins in the future.\n\n if (this._str) {\n return this._str\n }\n\n var str = this.final ? '1' : '0',\n labels = Object.keys(this.edges).sort(),\n len = labels.length\n\n for (var i = 0; i < len; i++) {\n var label = labels[i],\n node = this.edges[label]\n\n str = str + label + node.id\n }\n\n return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n var output = new lunr.TokenSet,\n frame = undefined\n\n var stack = [{\n qNode: b,\n output: output,\n node: this\n }]\n\n while (stack.length) {\n frame = stack.pop()\n\n // NOTE: As with the #toString method, we are using\n // Object.keys and a for loop instead of a for-in loop\n // as both of these objects enter 'hash' mode, causing\n // the function to be de-optimised in V8\n var qEdges = Object.keys(frame.qNode.edges),\n qLen = qEdges.length,\n nEdges = Object.keys(frame.node.edges),\n nLen = nEdges.length\n\n for (var q = 0; q < qLen; q++) {\n var qEdge = qEdges[q]\n\n for (var n = 0; n < nLen; n++) {\n var nEdge = nEdges[n]\n\n if (nEdge == qEdge || qEdge == '*') {\n var node = frame.node.edges[nEdge],\n qNode = frame.qNode.edges[qEdge],\n final = node.final && qNode.final,\n next = undefined\n\n if (nEdge in frame.output.edges) {\n // an edge already exists for this character\n // no need to create a new node, just set the finality\n // bit unless this node is already final\n next = frame.output.edges[nEdge]\n next.final = next.final || final\n\n } else {\n // no edge exists yet, must create one\n // set the finality bit and insert it\n // into the output\n next = new lunr.TokenSet\n next.final = final\n frame.output.edges[nEdge] = next\n }\n\n stack.push({\n qNode: qNode,\n output: next,\n node: node\n })\n }\n }\n }\n }\n\n return output\n}\nlunr.TokenSet.Builder = function () {\n this.previousWord = \"\"\n this.root = new lunr.TokenSet\n this.uncheckedNodes = []\n this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n var node,\n commonPrefix = 0\n\n if (word < this.previousWord) {\n throw new Error (\"Out of order word insertion\")\n }\n\n for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n if (word[i] != this.previousWord[i]) break\n commonPrefix++\n }\n\n this.minimize(commonPrefix)\n\n if (this.uncheckedNodes.length == 0) {\n node = this.root\n } else {\n node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n }\n\n for (var i = commonPrefix; i < word.length; i++) {\n var nextNode = new lunr.TokenSet,\n char = word[i]\n\n node.edges[char] = nextNode\n\n this.uncheckedNodes.push({\n parent: node,\n char: char,\n child: nextNode\n })\n\n node = nextNode\n }\n\n node.final = true\n this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n var node = this.uncheckedNodes[i],\n childKey = node.child.toString()\n\n if (childKey in this.minimizedNodes) {\n node.parent.edges[node.char] = this.minimizedNodes[childKey]\n } else {\n // Cache the key for this node since\n // we know it can't change anymore\n node.child._str = childKey\n\n this.minimizedNodes[childKey] = node.child\n }\n\n this.uncheckedNodes.pop()\n }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n this.invertedIndex = attrs.invertedIndex\n this.fieldVectors = attrs.fieldVectors\n this.tokenSet = attrs.tokenSet\n this.fields = attrs.fields\n this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example Simple single term query\n * hello\n * @example Multiple term query\n * hello world\n * @example term scoped to a field\n * title:hello\n * @example term with a boost of 10\n * hello^10\n * @example term with an edit distance of 2\n * hello~2\n * @example terms with presence modifiers\n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first. For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n return this.query(function (query) {\n var parser = new lunr.QueryParser(queryString, query)\n parser.parse()\n })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n // for each query clause\n // * process terms\n // * expand terms from token set\n // * find matching documents and metadata\n // * get document vectors\n // * score documents\n\n var query = new lunr.Query(this.fields),\n matchingFields = Object.create(null),\n queryVectors = Object.create(null),\n termFieldCache = Object.create(null),\n requiredMatches = Object.create(null),\n prohibitedMatches = Object.create(null)\n\n /*\n * To support field level boosts a query vector is created per\n * field. An empty vector is eagerly created to support negated\n * queries.\n */\n for (var i = 0; i < this.fields.length; i++) {\n queryVectors[this.fields[i]] = new lunr.Vector\n }\n\n fn.call(query, query)\n\n for (var i = 0; i < query.clauses.length; i++) {\n /*\n * Unless the pipeline has been disabled for this term, which is\n * the case for terms with wildcards, we need to pass the clause\n * term through the search pipeline. A pipeline returns an array\n * of processed terms. Pipeline functions may expand the passed\n * term, which means we may end up performing multiple index lookups\n * for a single query term.\n */\n var clause = query.clauses[i],\n terms = null,\n clauseMatches = lunr.Set.empty\n\n if (clause.usePipeline) {\n terms = this.pipeline.runString(clause.term, {\n fields: clause.fields\n })\n } else {\n terms = [clause.term]\n }\n\n for (var m = 0; m < terms.length; m++) {\n var term = terms[m]\n\n /*\n * Each term returned from the pipeline needs to use the same query\n * clause object, e.g. the same boost and or edit distance. The\n * simplest way to do this is to re-use the clause object but mutate\n * its term property.\n */\n clause.term = term\n\n /*\n * From the term in the clause we create a token set which will then\n * be used to intersect the indexes token set to get a list of terms\n * to lookup in the inverted index\n */\n var termTokenSet = lunr.TokenSet.fromClause(clause),\n expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n /*\n * If a term marked as required does not exist in the tokenSet it is\n * impossible for the search to return any matches. We set all the field\n * scoped required matches set to empty and stop examining any further\n * clauses.\n */\n if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = lunr.Set.empty\n }\n\n break\n }\n\n for (var j = 0; j < expandedTerms.length; j++) {\n /*\n * For each term get the posting and termIndex, this is required for\n * building the query vector.\n */\n var expandedTerm = expandedTerms[j],\n posting = this.invertedIndex[expandedTerm],\n termIndex = posting._index\n\n for (var k = 0; k < clause.fields.length; k++) {\n /*\n * For each field that this query term is scoped by (by default\n * all fields are in scope) we need to get all the document refs\n * that have this term in that field.\n *\n * The posting is the entry in the invertedIndex for the matching\n * term from above.\n */\n var field = clause.fields[k],\n fieldPosting = posting[field],\n matchingDocumentRefs = Object.keys(fieldPosting),\n termField = expandedTerm + \"/\" + field,\n matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n /*\n * if the presence of this term is required ensure that the matching\n * documents are added to the set of required matches for this clause.\n *\n */\n if (clause.presence == lunr.Query.presence.REQUIRED) {\n clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n if (requiredMatches[field] === undefined) {\n requiredMatches[field] = lunr.Set.complete\n }\n }\n\n /*\n * if the presence of this term is prohibited ensure that the matching\n * documents are added to the set of prohibited matches for this field,\n * creating that set if it does not yet exist.\n */\n if (clause.presence == lunr.Query.presence.PROHIBITED) {\n if (prohibitedMatches[field] === undefined) {\n prohibitedMatches[field] = lunr.Set.empty\n }\n\n prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n /*\n * Prohibited matches should not be part of the query vector used for\n * similarity scoring and no metadata should be extracted so we continue\n * to the next field\n */\n continue\n }\n\n /*\n * The query field vector is populated using the termIndex found for\n * the term and a unit value with the appropriate boost applied.\n * Using upsert because there could already be an entry in the vector\n * for the term we are working with. In that case we just add the scores\n * together.\n */\n queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n /**\n * If we've already seen this term, field combo then we've already collected\n * the matching documents and metadata, no need to go through all that again\n */\n if (termFieldCache[termField]) {\n continue\n }\n\n for (var l = 0; l < matchingDocumentRefs.length; l++) {\n /*\n * All metadata for this term/field/document triple\n * are then extracted and collected into an instance\n * of lunr.MatchData ready to be returned in the query\n * results\n */\n var matchingDocumentRef = matchingDocumentRefs[l],\n matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n metadata = fieldPosting[matchingDocumentRef],\n fieldMatch\n\n if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n } else {\n fieldMatch.add(expandedTerm, field, metadata)\n }\n\n }\n\n termFieldCache[termField] = true\n }\n }\n }\n\n /**\n * If the presence was required we need to update the requiredMatches field sets.\n * We do this after all fields for the term have collected their matches because\n * the clause terms presence is required in _any_ of the fields not _all_ of the\n * fields.\n */\n if (clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n }\n }\n }\n\n /**\n * Need to combine the field scoped required and prohibited\n * matching documents into a global set of required and prohibited\n * matches\n */\n var allRequiredMatches = lunr.Set.complete,\n allProhibitedMatches = lunr.Set.empty\n\n for (var i = 0; i < this.fields.length; i++) {\n var field = this.fields[i]\n\n if (requiredMatches[field]) {\n allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n }\n\n if (prohibitedMatches[field]) {\n allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n }\n }\n\n var matchingFieldRefs = Object.keys(matchingFields),\n results = [],\n matches = Object.create(null)\n\n /*\n * If the query is negated (contains only prohibited terms)\n * we need to get _all_ fieldRefs currently existing in the\n * index. This is only done when we know that the query is\n * entirely prohibited terms to avoid any cost of getting all\n * fieldRefs unnecessarily.\n *\n * Additionally, blank MatchData must be created to correctly\n * populate the results.\n */\n if (query.isNegated()) {\n matchingFieldRefs = Object.keys(this.fieldVectors)\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n var matchingFieldRef = matchingFieldRefs[i]\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n matchingFields[matchingFieldRef] = new lunr.MatchData\n }\n }\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n /*\n * Currently we have document fields that match the query, but we\n * need to return documents. The matchData and scores are combined\n * from multiple fields belonging to the same document.\n *\n * Scores are calculated by field, using the query vectors created\n * above, and combined into a final document score using addition.\n */\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n docRef = fieldRef.docRef\n\n if (!allRequiredMatches.contains(docRef)) {\n continue\n }\n\n if (allProhibitedMatches.contains(docRef)) {\n continue\n }\n\n var fieldVector = this.fieldVectors[fieldRef],\n score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n docMatch\n\n if ((docMatch = matches[docRef]) !== undefined) {\n docMatch.score += score\n docMatch.matchData.combine(matchingFields[fieldRef])\n } else {\n var match = {\n ref: docRef,\n score: score,\n matchData: matchingFields[fieldRef]\n }\n matches[docRef] = match\n results.push(match)\n }\n }\n\n /*\n * Sort the results objects by score, highest first.\n */\n return results.sort(function (a, b) {\n return b.score - a.score\n })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n var invertedIndex = Object.keys(this.invertedIndex)\n .sort()\n .map(function (term) {\n return [term, this.invertedIndex[term]]\n }, this)\n\n var fieldVectors = Object.keys(this.fieldVectors)\n .map(function (ref) {\n return [ref, this.fieldVectors[ref].toJSON()]\n }, this)\n\n return {\n version: lunr.version,\n fields: this.fields,\n fieldVectors: fieldVectors,\n invertedIndex: invertedIndex,\n pipeline: this.pipeline.toJSON()\n }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n var attrs = {},\n fieldVectors = {},\n serializedVectors = serializedIndex.fieldVectors,\n invertedIndex = Object.create(null),\n serializedInvertedIndex = serializedIndex.invertedIndex,\n tokenSetBuilder = new lunr.TokenSet.Builder,\n pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n if (serializedIndex.version != lunr.version) {\n lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n }\n\n for (var i = 0; i < serializedVectors.length; i++) {\n var tuple = serializedVectors[i],\n ref = tuple[0],\n elements = tuple[1]\n\n fieldVectors[ref] = new lunr.Vector(elements)\n }\n\n for (var i = 0; i < serializedInvertedIndex.length; i++) {\n var tuple = serializedInvertedIndex[i],\n term = tuple[0],\n posting = tuple[1]\n\n tokenSetBuilder.insert(term)\n invertedIndex[term] = posting\n }\n\n tokenSetBuilder.finish()\n\n attrs.fields = serializedIndex.fields\n\n attrs.fieldVectors = fieldVectors\n attrs.invertedIndex = invertedIndex\n attrs.tokenSet = tokenSetBuilder.root\n attrs.pipeline = pipeline\n\n return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n this._ref = \"id\"\n this._fields = Object.create(null)\n this._documents = Object.create(null)\n this.invertedIndex = Object.create(null)\n this.fieldTermFrequencies = {}\n this.fieldLengths = {}\n this.tokenizer = lunr.tokenizer\n this.pipeline = new lunr.Pipeline\n this.searchPipeline = new lunr.Pipeline\n this.documentCount = 0\n this._b = 0.75\n this._k1 = 1.2\n this.termIndex = 0\n this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example Extracting a nested field\n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n if (/\\//.test(fieldName)) {\n throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n }\n\n this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n if (number < 0) {\n this._b = 0\n } else if (number > 1) {\n this._b = 1\n } else {\n this._b = number\n }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n var docRef = doc[this._ref],\n fields = Object.keys(this._fields)\n\n this._documents[docRef] = attributes || {}\n this.documentCount += 1\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i],\n extractor = this._fields[fieldName].extractor,\n field = extractor ? extractor(doc) : doc[fieldName],\n tokens = this.tokenizer(field, {\n fields: [fieldName]\n }),\n terms = this.pipeline.run(tokens),\n fieldRef = new lunr.FieldRef (docRef, fieldName),\n fieldTerms = Object.create(null)\n\n this.fieldTermFrequencies[fieldRef] = fieldTerms\n this.fieldLengths[fieldRef] = 0\n\n // store the length of this field for this document\n this.fieldLengths[fieldRef] += terms.length\n\n // calculate term frequencies for this field\n for (var j = 0; j < terms.length; j++) {\n var term = terms[j]\n\n if (fieldTerms[term] == undefined) {\n fieldTerms[term] = 0\n }\n\n fieldTerms[term] += 1\n\n // add to inverted index\n // create an initial posting if one doesn't exist\n if (this.invertedIndex[term] == undefined) {\n var posting = Object.create(null)\n posting[\"_index\"] = this.termIndex\n this.termIndex += 1\n\n for (var k = 0; k < fields.length; k++) {\n posting[fields[k]] = Object.create(null)\n }\n\n this.invertedIndex[term] = posting\n }\n\n // add an entry for this term/fieldName/docRef to the invertedIndex\n if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n }\n\n // store all whitelisted metadata about this token in the\n // inverted index\n for (var l = 0; l < this.metadataWhitelist.length; l++) {\n var metadataKey = this.metadataWhitelist[l],\n metadata = term.metadata[metadataKey]\n\n if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n }\n\n this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n }\n }\n\n }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n var fieldRefs = Object.keys(this.fieldLengths),\n numberOfFields = fieldRefs.length,\n accumulator = {},\n documentsWithField = {}\n\n for (var i = 0; i < numberOfFields; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n field = fieldRef.fieldName\n\n documentsWithField[field] || (documentsWithField[field] = 0)\n documentsWithField[field] += 1\n\n accumulator[field] || (accumulator[field] = 0)\n accumulator[field] += this.fieldLengths[fieldRef]\n }\n\n var fields = Object.keys(this._fields)\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i]\n accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n }\n\n this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n var fieldVectors = {},\n fieldRefs = Object.keys(this.fieldTermFrequencies),\n fieldRefsLength = fieldRefs.length,\n termIdfCache = Object.create(null)\n\n for (var i = 0; i < fieldRefsLength; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n fieldName = fieldRef.fieldName,\n fieldLength = this.fieldLengths[fieldRef],\n fieldVector = new lunr.Vector,\n termFrequencies = this.fieldTermFrequencies[fieldRef],\n terms = Object.keys(termFrequencies),\n termsLength = terms.length\n\n\n var fieldBoost = this._fields[fieldName].boost || 1,\n docBoost = this._documents[fieldRef.docRef].boost || 1\n\n for (var j = 0; j < termsLength; j++) {\n var term = terms[j],\n tf = termFrequencies[term],\n termIndex = this.invertedIndex[term]._index,\n idf, score, scoreWithPrecision\n\n if (termIdfCache[term] === undefined) {\n idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n termIdfCache[term] = idf\n } else {\n idf = termIdfCache[term]\n }\n\n score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n score *= fieldBoost\n score *= docBoost\n scoreWithPrecision = Math.round(score * 1000) / 1000\n // Converts 1.23456789 to 1.234.\n // Reducing the precision so that the vectors take up less\n // space when serialised. Doing it now so that they behave\n // the same before and after serialisation. Also, this is\n // the fastest approach to reducing a number's precision in\n // JavaScript.\n\n fieldVector.insert(termIndex, scoreWithPrecision)\n }\n\n fieldVectors[fieldRef] = fieldVector\n }\n\n this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n this.tokenSet = lunr.TokenSet.fromArray(\n Object.keys(this.invertedIndex).sort()\n )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n this.calculateAverageFieldLengths()\n this.createFieldVectors()\n this.createTokenSet()\n\n return new lunr.Index({\n invertedIndex: this.invertedIndex,\n fieldVectors: this.fieldVectors,\n tokenSet: this.tokenSet,\n fields: Object.keys(this._fields),\n pipeline: this.searchPipeline\n })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n var args = Array.prototype.slice.call(arguments, 1)\n args.unshift(this)\n fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n var clonedMetadata = Object.create(null),\n metadataKeys = Object.keys(metadata || {})\n\n // Cloning the metadata to prevent the original\n // being mutated during match data combination.\n // Metadata is kept in an array within the inverted\n // index so cloning the data can be done with\n // Array#slice\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n clonedMetadata[key] = metadata[key].slice()\n }\n\n this.metadata = Object.create(null)\n\n if (term !== undefined) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = clonedMetadata\n }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n var terms = Object.keys(otherMatchData.metadata)\n\n for (var i = 0; i < terms.length; i++) {\n var term = terms[i],\n fields = Object.keys(otherMatchData.metadata[term])\n\n if (this.metadata[term] == undefined) {\n this.metadata[term] = Object.create(null)\n }\n\n for (var j = 0; j < fields.length; j++) {\n var field = fields[j],\n keys = Object.keys(otherMatchData.metadata[term][field])\n\n if (this.metadata[term][field] == undefined) {\n this.metadata[term][field] = Object.create(null)\n }\n\n for (var k = 0; k < keys.length; k++) {\n var key = keys[k]\n\n if (this.metadata[term][field][key] == undefined) {\n this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n } else {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n }\n\n }\n }\n }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n if (!(term in this.metadata)) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = metadata\n return\n }\n\n if (!(field in this.metadata[term])) {\n this.metadata[term][field] = metadata\n return\n }\n\n var metadataKeys = Object.keys(metadata)\n\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n\n if (key in this.metadata[term][field]) {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n } else {\n this.metadata[term][field][key] = metadata[key]\n }\n }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n this.clauses = []\n this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with trailing wildcard\n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example query term with leading and trailing wildcard\n * query.term('foo', {\n * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with required presence\n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n /**\n * Term's presence in a document is optional, this is the default value.\n */\n OPTIONAL: 1,\n\n /**\n * Term's presence in a document is required, documents that do not contain\n * this term will not be returned.\n */\n REQUIRED: 2,\n\n /**\n * Term's presence in a document is prohibited, documents that do contain\n * this term will not be returned.\n */\n PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n if (!('fields' in clause)) {\n clause.fields = this.allFields\n }\n\n if (!('boost' in clause)) {\n clause.boost = 1\n }\n\n if (!('usePipeline' in clause)) {\n clause.usePipeline = true\n }\n\n if (!('wildcard' in clause)) {\n clause.wildcard = lunr.Query.wildcard.NONE\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n clause.term = \"*\" + clause.term\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n clause.term = \"\" + clause.term + \"*\"\n }\n\n if (!('presence' in clause)) {\n clause.presence = lunr.Query.presence.OPTIONAL\n }\n\n this.clauses.push(clause)\n\n return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n for (var i = 0; i < this.clauses.length; i++) {\n if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example adding a single term to a query\n * query.term(\"foo\")\n * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard\n * query.term(\"foo\", {\n * fields: [\"title\"],\n * boost: 10,\n * wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example using lunr.tokenizer to convert a string to tokens before using them as terms\n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n if (Array.isArray(term)) {\n term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n return this\n }\n\n var clause = options || {}\n clause.term = term.toString()\n\n this.clause(clause)\n\n return this\n}\nlunr.QueryParseError = function (message, start, end) {\n this.name = \"QueryParseError\"\n this.message = message\n this.start = start\n this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n this.lexemes = []\n this.str = str\n this.length = str.length\n this.pos = 0\n this.start = 0\n this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n var state = lunr.QueryLexer.lexText\n\n while (state) {\n state = state(this)\n }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n var subSlices = [],\n sliceStart = this.start,\n sliceEnd = this.pos\n\n for (var i = 0; i < this.escapeCharPositions.length; i++) {\n sliceEnd = this.escapeCharPositions[i]\n subSlices.push(this.str.slice(sliceStart, sliceEnd))\n sliceStart = sliceEnd + 1\n }\n\n subSlices.push(this.str.slice(sliceStart, this.pos))\n this.escapeCharPositions.length = 0\n\n return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n this.lexemes.push({\n type: type,\n str: this.sliceString(),\n start: this.start,\n end: this.pos\n })\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n this.escapeCharPositions.push(this.pos - 1)\n this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n if (this.pos >= this.length) {\n return lunr.QueryLexer.EOS\n }\n\n var char = this.str.charAt(this.pos)\n this.pos += 1\n return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n if (this.start == this.pos) {\n this.pos += 1\n }\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n var char, charCode\n\n do {\n char = this.next()\n charCode = char.charCodeAt(0)\n } while (charCode > 47 && charCode < 58)\n\n if (char != lunr.QueryLexer.EOS) {\n this.backup()\n }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.FIELD)\n lexer.ignore()\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n if (lexer.width() > 1) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.TERM)\n }\n\n lexer.ignore()\n\n if (lexer.more()) {\n return lunr.QueryLexer.lexText\n }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.BOOST)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n while (true) {\n var char = lexer.next()\n\n if (char == lunr.QueryLexer.EOS) {\n return lunr.QueryLexer.lexEOS\n }\n\n // Escape character is '\\'\n if (char.charCodeAt(0) == 92) {\n lexer.escapeCharacter()\n continue\n }\n\n if (char == \":\") {\n return lunr.QueryLexer.lexField\n }\n\n if (char == \"~\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexEditDistance\n }\n\n if (char == \"^\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexBoost\n }\n\n // \"+\" indicates term presence is required\n // checking for length to ensure that only\n // leading \"+\" are considered\n if (char == \"+\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n // \"-\" indicates term presence is prohibited\n // checking for length to ensure that only\n // leading \"-\" are considered\n if (char == \"-\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n if (char.match(lunr.QueryLexer.termSeparator)) {\n return lunr.QueryLexer.lexTerm\n }\n }\n}\n\nlunr.QueryParser = function (str, query) {\n this.lexer = new lunr.QueryLexer (str)\n this.query = query\n this.currentClause = {}\n this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n this.lexer.run()\n this.lexemes = this.lexer.lexemes\n\n var state = lunr.QueryParser.parseClause\n\n while (state) {\n state = state(this)\n }\n\n return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n var lexeme = this.peekLexeme()\n this.lexemeIdx += 1\n return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n var completedClause = this.currentClause\n this.query.clause(completedClause)\n this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n var lexeme = parser.peekLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.type) {\n case lunr.QueryLexer.PRESENCE:\n return lunr.QueryParser.parsePresence\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n if (lexeme.str.length >= 1) {\n errorMessage += \" with value '\" + lexeme.str + \"'\"\n }\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.str) {\n case \"-\":\n parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n break\n case \"+\":\n parser.currentClause.presence = lunr.Query.presence.REQUIRED\n break\n default:\n var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term or field, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.fields = [lexeme.str]\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n parser.currentClause.term = lexeme.str.toLowerCase()\n\n if (lexeme.str.indexOf(\"*\") != -1) {\n parser.currentClause.usePipeline = false\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var editDistance = parseInt(lexeme.str, 10)\n\n if (isNaN(editDistance)) {\n var errorMessage = \"edit distance must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.editDistance = editDistance\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var boost = parseInt(lexeme.str, 10)\n\n if (isNaN(boost)) {\n var errorMessage = \"boost must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.boost = boost\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\n /**\n * export the module via AMD, CommonJS or as a browser global\n * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n */\n ;(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define(factory)\n } else if (typeof exports === 'object') {\n /**\n * Node. Does not work with strict CommonJS, but\n * only CommonJS-like enviroments that support module.exports,\n * like Node.\n */\n module.exports = factory()\n } else {\n // Browser globals (root is window)\n root.lunr = factory()\n }\n }(this, function () {\n /**\n * Just return a value to define the module export.\n * This example returns an object, but the module\n * can return a function as the exported value.\n */\n return lunr\n }))\n})();\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport \"~/polyfills\"\n\nimport { Search, SearchIndexConfig } from \"../../_\"\nimport {\n SearchMessage,\n SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for usage with `iframe-worker` polyfill\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous polyfilled implementation. The\n * cool thing is that awaiting a non-Promise is a noop, so extending the type\n * definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n */\ndeclare global {\n function importScripts(...urls: string[]): Promise | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages, which are defined through the search index configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search index configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n config: SearchIndexConfig\n): Promise {\n let base = \"../lunr\"\n\n /* Detect `iframe-worker` and fix base URL */\n if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n const worker = document.querySelector(\"script[src]\")!\n const [path] = worker.src.split(\"/worker\")\n\n /* Prefix base with path */\n base = base.replace(\"..\", path)\n }\n\n /* Add scripts for languages */\n const scripts = []\n for (const lang of config.lang) {\n switch (lang) {\n\n /* Add segmenter for Japanese */\n case \"ja\":\n scripts.push(`${base}/tinyseg.js`)\n break\n\n /* Add segmenter for Hindi and Thai */\n case \"hi\":\n case \"th\":\n scripts.push(`${base}/wordcut.js`)\n break\n }\n\n /* Add language support */\n if (lang !== \"en\")\n scripts.push(`${base}/min/lunr.${lang}.min.js`)\n }\n\n /* Add multi-language support */\n if (config.lang.length > 1)\n scripts.push(`${base}/min/lunr.multi.min.js`)\n\n /* Load scripts synchronously */\n if (scripts.length)\n await importScripts(\n `${base}/min/lunr.stemmer.support.min.js`,\n ...scripts\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n message: SearchMessage\n): Promise {\n switch (message.type) {\n\n /* Search setup message */\n case SearchMessageType.SETUP:\n await setupSearchLanguages(message.data.config)\n index = new Search(message.data)\n return {\n type: SearchMessageType.READY\n }\n\n /* Search query message */\n case SearchMessageType.QUERY:\n return {\n type: SearchMessageType.RESULT,\n data: index ? index.search(message.data) : { items: [] }\n }\n\n /* All other messages */\n default:\n throw new TypeError(\"Invalid message type\")\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* @ts-expect-error - expose Lunr.js in global scope, or stemmers won't work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Polyfills\n * ------------------------------------------------------------------------- */\n\n/* Polyfill `Object.entries` */\nif (!Object.entries)\n Object.entries = function (obj: object) {\n const data: [string, string][] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push([key, obj[key]])\n\n /* Return entries */\n return data\n }\n\n/* Polyfill `Object.values` */\nif (!Object.values)\n Object.values = function (obj: object) {\n const data: string[] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push(obj[key])\n\n /* Return values */\n return data\n }\n\n/* ------------------------------------------------------------------------- */\n\n/* Polyfills for `Element` */\nif (typeof Element !== \"undefined\") {\n\n /* Polyfill `Element.scrollTo` */\n if (!Element.prototype.scrollTo)\n Element.prototype.scrollTo = function (\n x?: ScrollToOptions | number, y?: number\n ): void {\n if (typeof x === \"object\") {\n this.scrollLeft = x.left!\n this.scrollTop = x.top!\n } else {\n this.scrollLeft = x!\n this.scrollTop = y!\n }\n }\n\n /* Polyfill `Element.replaceWith` */\n if (!Element.prototype.replaceWith)\n Element.prototype.replaceWith = function (\n ...nodes: Array\n ): void {\n const parent = this.parentNode\n if (parent) {\n if (nodes.length === 0)\n parent.removeChild(this)\n\n /* Replace children and create text nodes */\n for (let i = nodes.length - 1; i >= 0; i--) {\n let node = nodes[i]\n if (typeof node !== \"object\")\n node = document.createTextNode(node)\n else if (node.parentNode)\n node.parentNode.removeChild(node)\n\n /* Replace child or insert before previous sibling */\n if (!i)\n parent.replaceChild(node, this)\n else\n parent.insertBefore(this.previousSibling!, node)\n }\n }\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n parent?: SearchIndexDocument /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchIndexDocument[]\n): SearchDocumentMap {\n const documents = new Map()\n const parents = new Set()\n for (const doc of docs) {\n const [path, hash] = doc.location.split(\"#\")\n\n /* Extract location, title and tags */\n const location = doc.location\n const title = doc.title\n const tags = doc.tags\n\n /* Escape and cleanup text */\n const text = escapeHTML(doc.text)\n .replace(/\\s+(?=[,.:;!?])/g, \"\")\n .replace(/\\s+/g, \" \")\n\n /* Handle section */\n if (hash) {\n const parent = documents.get(path)!\n\n /* Ignore first section, override article */\n if (!parents.has(parent)) {\n parent.title = doc.title\n parent.text = text\n\n /* Remember that we processed the article */\n parents.add(parent)\n\n /* Add subsequent section */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n parent\n })\n }\n\n /* Add article */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n ...tags && { tags }\n })\n }\n }\n return documents\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n * @param escape - Whether to escape HTML\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n config: SearchIndexConfig, escape: boolean\n): SearchHighlightFactoryFn {\n const separator = new RegExp(config.separator, \"img\")\n const highlight = (_: unknown, data: string, term: string) => {\n return `${data}${term}`\n }\n\n /* Return factory function */\n return (query: string) => {\n query = query\n .replace(/[\\s*+\\-:~^]+/g, \" \")\n .trim()\n\n /* Create search term match expression */\n const match = new RegExp(`(^|${config.separator})(${\n query\n .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n .replace(separator, \"|\")\n })`, \"img\")\n\n /* Highlight string value */\n return value => (\n escape\n ? escapeHTML(value)\n : value\n )\n .replace(match, highlight)\n .replace(/<\\/mark>(\\s+)]*>/img, \"$1\")\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n presence: lunr.Query.presence /* Clause presence */\n term: string /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n value: string\n): SearchQueryClause[] {\n const query = new (lunr as any).Query([\"title\", \"text\"])\n const parser = new (lunr as any).QueryParser(value, query)\n\n /* Parse and return query clauses */\n parser.parse()\n return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n const clauses = new Set(query)\n\n /* Match query clauses against terms */\n const result: SearchQueryTerms = {}\n for (let t = 0; t < terms.length; t++)\n for (const clause of clauses)\n if (terms[t].startsWith(clause.term)) {\n result[clause.term] = true\n clauses.delete(clause)\n }\n\n /* Annotate unmatched non-stopword query clauses */\n for (const clause of clauses)\n if (lunr.stopWordFilter?.(clause.term as any))\n result[clause.term] = false\n\n /* Return query terms */\n return result\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n SearchDocument,\n SearchDocumentMap,\n setupSearchDocumentMap\n} from \"../document\"\nimport {\n SearchHighlightFactoryFn,\n setupSearchHighlighter\n} from \"../highlighter\"\nimport { SearchOptions } from \"../options\"\nimport {\n SearchQueryTerms,\n getSearchQueryTerms,\n parseSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index configuration\n */\nexport interface SearchIndexConfig {\n lang: string[] /* Search languages */\n separator: string /* Search separator */\n}\n\n/**\n * Search index document\n */\nexport interface SearchIndexDocument {\n location: string /* Document location */\n title: string /* Document title */\n text: string /* Document text */\n tags?: string[] /* Document tags */\n boost?: number /* Document boost */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n *\n * This interfaces describes the format of the `search_index.json` file which\n * is automatically built by the MkDocs search plugin.\n */\nexport interface SearchIndex {\n config: SearchIndexConfig /* Search index configuration */\n docs: SearchIndexDocument[] /* Search index documents */\n options: SearchOptions /* Search options */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search metadata\n */\nexport interface SearchMetadata {\n score: number /* Score (relevance) */\n terms: SearchQueryTerms /* Search query terms */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result document\n */\nexport type SearchResultDocument = SearchDocument & SearchMetadata\n\n/**\n * Search result item\n */\nexport type SearchResultItem = SearchResultDocument[]\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport interface SearchResult {\n items: SearchResultItem[] /* Search result items */\n suggestions?: string[] /* Search suggestions */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n const [x, y] = [new Set(a), new Set(b)]\n return [\n ...new Set([...x].filter(value => !y.has(value)))\n ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n /**\n * Search document mapping\n *\n * A mapping of URLs (including hash fragments) to the actual articles and\n * sections of the documentation. The search document mapping must be created\n * regardless of whether the index was prebuilt or not, as Lunr.js itself\n * only stores the actual index.\n */\n protected documents: SearchDocumentMap\n\n /**\n * Search highlight factory function\n */\n protected highlight: SearchHighlightFactoryFn\n\n /**\n * The underlying Lunr.js search index\n */\n protected index: lunr.Index\n\n /**\n * Search options\n */\n protected options: SearchOptions\n\n /**\n * Create the search integration\n *\n * @param data - Search index\n */\n public constructor({ config, docs, options }: SearchIndex) {\n this.options = options\n\n /* Set up document map and highlighter factory */\n this.documents = setupSearchDocumentMap(docs)\n this.highlight = setupSearchHighlighter(config, false)\n\n /* Set separator for tokenizer */\n lunr.tokenizer.separator = new RegExp(config.separator)\n\n /* Create search index */\n this.index = lunr(function () {\n\n /* Set up multi-language support */\n if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n this.use((lunr as any)[config.lang[0]])\n } else if (config.lang.length > 1) {\n this.use((lunr as any).multiLanguage(...config.lang))\n }\n\n /* Compute functions to be removed from the pipeline */\n const fns = difference([\n \"trimmer\", \"stopWordFilter\", \"stemmer\"\n ], options.pipeline)\n\n /* Remove functions from the pipeline for registered languages */\n for (const lang of config.lang.map(language => (\n language === \"en\" ? lunr : (lunr as any)[language]\n ))) {\n for (const fn of fns) {\n this.pipeline.remove(lang[fn])\n this.searchPipeline.remove(lang[fn])\n }\n }\n\n /* Set up reference */\n this.ref(\"location\")\n\n /* Set up fields */\n this.field(\"title\", { boost: 1e3 })\n this.field(\"text\")\n this.field(\"tags\", { boost: 1e6, extractor: doc => {\n const { tags = [] } = doc as SearchDocument\n return tags.flatMap(tag => lunr.tokenizer(tag))\n } })\n\n /* Index documents */\n for (const doc of docs)\n this.add(doc, { boost: doc.boost })\n })\n }\n\n /**\n * Search for matching documents\n *\n * The search index which MkDocs provides is divided up into articles, which\n * contain the whole content of the individual pages, and sections, which only\n * contain the contents of the subsections obtained by breaking the individual\n * pages up at `h1` ... `h6`. As there may be many sections on different pages\n * with identical titles (for example within this very project, e.g. \"Usage\"\n * or \"Installation\"), they need to be put into the context of the containing\n * page. For this reason, section results are grouped within their respective\n * articles which are the top-level results that are returned.\n *\n * @param query - Query value\n *\n * @returns Search results\n */\n public search(query: string): SearchResult {\n if (query) {\n try {\n const highlight = this.highlight(query)\n\n /* Parse query to extract clauses for analysis */\n const clauses = parseSearchQuery(query)\n .filter(clause => (\n clause.presence !== lunr.Query.presence.PROHIBITED\n ))\n\n /* Perform search and post-process results */\n const groups = this.index.search(`${query}*`)\n\n /* Apply post-query boosts based on title and search query terms */\n .reduce((item, { ref, score, matchData }) => {\n const document = this.documents.get(ref)\n if (typeof document !== \"undefined\") {\n const { location, title, text, tags, parent } = document\n\n /* Compute and analyze search query terms */\n const terms = getSearchQueryTerms(\n clauses,\n Object.keys(matchData.metadata)\n )\n\n /* Highlight title and text and apply post-query boosts */\n const boost = +!parent + +Object.values(terms).every(t => t)\n item.push({\n location,\n title: highlight(title),\n text: highlight(text),\n ...tags && { tags: tags.map(highlight) },\n score: score * (1 + boost),\n terms\n })\n }\n return item\n }, [])\n\n /* Sort search results again after applying boosts */\n .sort((a, b) => b.score - a.score)\n\n /* Group search results by page */\n .reduce((items, result) => {\n const document = this.documents.get(result.location)\n if (typeof document !== \"undefined\") {\n const ref = \"parent\" in document\n ? document.parent!.location\n : document.location\n items.set(ref, [...items.get(ref) || [], result])\n }\n return items\n }, new Map())\n\n /* Generate search suggestions, if desired */\n let suggestions: string[] | undefined\n if (this.options.suggestions) {\n const titles = this.index.query(builder => {\n for (const clause of clauses)\n builder.term(clause.term, {\n fields: [\"title\"],\n presence: lunr.Query.presence.REQUIRED,\n wildcard: lunr.Query.wildcard.TRAILING\n })\n })\n\n /* Retrieve suggestions for best match */\n suggestions = titles.length\n ? Object.keys(titles[0].matchData.metadata)\n : []\n }\n\n /* Return items and suggestions */\n return {\n items: [...groups.values()],\n ...typeof suggestions !== \"undefined\" && { suggestions }\n }\n\n /* Log errors to console (for now) */\n } catch {\n console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n }\n }\n\n /* Return nothing in case of error or empty query */\n return { items: [] }\n }\n}\n"], + "mappings": "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", + "names": [] +} diff --git a/assets/stylesheets/main.4a0965b7.min.css b/assets/stylesheets/main.4a0965b7.min.css new file mode 100644 index 00000000..b93e5d4a --- /dev/null +++ b/assets/stylesheets/main.4a0965b7.min.css @@ -0,0 +1 @@ +@charset "UTF-8";html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none;box-sizing:border-box}*,:after,:before{box-sizing:inherit}@media (prefers-reduced-motion){*,:after,:before{transition:none!important}}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{border:0;box-sizing:initial;display:block;height:.05rem;overflow:visible;padding:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{background:transparent;border:0;font-family:inherit;font-size:inherit;margin:0;padding:0}input{border:0;outline:none}:root,[data-md-color-scheme=default]{--md-default-fg-color:rgba(0,0,0,.87);--md-default-fg-color--light:rgba(0,0,0,.54);--md-default-fg-color--lighter:rgba(0,0,0,.32);--md-default-fg-color--lightest:rgba(0,0,0,.07);--md-default-bg-color:#fff;--md-default-bg-color--light:hsla(0,0%,100%,.7);--md-default-bg-color--lighter:hsla(0,0%,100%,.3);--md-default-bg-color--lightest:hsla(0,0%,100%,.12);--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(82,108,254,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7);--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:rgba(255,255,0,.5);--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(255,255,0,.5);--md-typeset-del-color:rgba(245,80,61,.15);--md-typeset-ins-color:rgba(11,213,112,.15);--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-table-color:rgba(0,0,0,.12);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-fg-color:#fff;--md-footer-fg-color--light:hsla(0,0%,100%,.7);--md-footer-fg-color--lighter:hsla(0,0%,100%,.3);--md-footer-bg-color:rgba(0,0,0,.87);--md-footer-bg-color--dark:rgba(0,0,0,.32);--md-shadow-z1:0 0.2rem 0.5rem rgba(0,0,0,.05),0 0 0.05rem rgba(0,0,0,.1);--md-shadow-z2:0 0.2rem 0.5rem rgba(0,0,0,.1),0 0 0.05rem rgba(0,0,0,.25);--md-shadow-z3:0 0.2rem 0.5rem rgba(0,0,0,.2),0 0 0.05rem rgba(0,0,0,.35)}.md-icon svg{fill:currentcolor;display:block;height:1.2rem;width:1.2rem}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--md-text-font-family:var(--md-text-font,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;--md-code-font-family:var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,monospace}body,input{font-feature-settings:"kern","liga";font-family:var(--md-text-font-family)}body,code,input,kbd,pre{color:var(--md-typeset-color)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family)}:root{--md-typeset-table-sort-icon:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--asc:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--desc:url('data:image/svg+xml;charset=utf-8,')}.md-typeset{-webkit-print-color-adjust:exact;color-adjust:exact;font-size:.8rem;line-height:1.6}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{margin-bottom:1em;margin-top:1em}.md-typeset h1{color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3;margin:0 0 1.25em}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{font-size:1.5625em;line-height:1.4;margin:1.6em 0 .64em}.md-typeset h3{font-size:1.25em;font-weight:400;letter-spacing:-.01em;line-height:1.5;margin:1.6em 0 .8em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{font-weight:700;letter-spacing:-.01em;margin:1em 0}.md-typeset h5,.md-typeset h6{color:var(--md-default-fg-color--light);font-size:.8em;font-weight:700;letter-spacing:-.01em;margin:1.25em 0}.md-typeset h5{text-transform:uppercase}.md-typeset hr{border-bottom:.05rem solid var(--md-default-fg-color--lightest);display:flow-root;margin:1.5em 0}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset a:focus code,.md-typeset a:hover code{background-color:var(--md-accent-fg-color--transparent)}.md-typeset a code{color:currentcolor;transition:background-color 125ms}.md-typeset a.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone;font-size:.85em;padding:0 .2941176471em;word-break:break-word}.md-typeset code:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-typeset pre{display:flow-root;line-height:1.4;position:relative}.md-typeset pre>code{-webkit-box-decoration-break:slice;box-decoration-break:slice;box-shadow:none;display:block;margin:0;outline-color:var(--md-accent-fg-color);overflow:auto;padding:.7720588235em 1.1764705882em;scrollbar-color:var(--md-default-fg-color--lighter) transparent;scrollbar-width:thin;touch-action:auto;word-break:normal}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-typeset pre>code::-webkit-scrollbar{height:.2rem;width:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-typeset kbd{background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset;color:var(--md-default-fg-color);display:inline-block;font-size:.75em;padding:0 .6666666667em;vertical-align:text-top;word-break:break-word}.md-typeset mark{background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:inherit;word-break:break-word}.md-typeset abbr{border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help;text-decoration:none}@media (hover:none){.md-typeset abbr{position:relative}.md-typeset abbr[title]:-webkit-any(:focus,:hover):after{background-color:var(--md-default-fg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z3);color:var(--md-default-bg-color);content:attr(title);display:inline-block;font-size:.7rem;margin-top:2em;max-width:80%;min-width:-webkit-max-content;min-width:max-content;padding:.2rem .3rem;position:absolute;width:auto}.md-typeset abbr[title]:-moz-any(:focus,:hover):after{background-color:var(--md-default-fg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z3);color:var(--md-default-bg-color);content:attr(title);display:inline-block;font-size:.7rem;margin-top:2em;max-width:80%;min-width:-moz-max-content;min-width:max-content;padding:.2rem .3rem;position:absolute;width:auto}[dir=ltr] .md-typeset abbr[title]:-webkit-any(:focus,:hover):after{left:0}[dir=ltr] .md-typeset abbr[title]:-moz-any(:focus,:hover):after{left:0}[dir=ltr] .md-typeset abbr[title]:is(:focus,:hover):after{left:0}[dir=rtl] .md-typeset abbr[title]:-webkit-any(:focus,:hover):after{right:0}[dir=rtl] .md-typeset abbr[title]:-moz-any(:focus,:hover):after{right:0}[dir=rtl] .md-typeset abbr[title]:is(:focus,:hover):after{right:0}.md-typeset abbr[title]:is(:focus,:hover):after{background-color:var(--md-default-fg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z3);color:var(--md-default-bg-color);content:attr(title);display:inline-block;font-size:.7rem;margin-top:2em;max-width:80%;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;padding:.2rem .3rem;position:absolute;width:auto}}.md-typeset small{opacity:.75}[dir=ltr] .md-typeset sub,[dir=ltr] .md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em}[dir=ltr] .md-typeset blockquote{padding-left:.6rem}[dir=rtl] .md-typeset blockquote{padding-right:.6rem}[dir=ltr] .md-typeset blockquote{border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{border-right:.2rem solid var(--md-default-fg-color--lighter)}.md-typeset blockquote{color:var(--md-default-fg-color--light);margin-left:0;margin-right:0}.md-typeset ul{list-style-type:disc}[dir=ltr] .md-typeset ol,[dir=ltr] .md-typeset ul{margin-left:.625em}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em}.md-typeset ol,.md-typeset ul{padding:0}.md-typeset ol:not([hidden]),.md-typeset ul:not([hidden]){display:flow-root}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}[dir=ltr] .md-typeset ol li,[dir=ltr] .md-typeset ul li{margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li :-webkit-any(ul,ol),.md-typeset ul li :-webkit-any(ul,ol){margin-bottom:.5em;margin-top:.5em}.md-typeset ol li :-moz-any(ul,ol),.md-typeset ul li :-moz-any(ul,ol){margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset ol li :-webkit-any(ul,ol),[dir=ltr] .md-typeset ul li :-webkit-any(ul,ol){margin-left:.625em}[dir=ltr] .md-typeset ol li :-moz-any(ul,ol),[dir=ltr] .md-typeset ul li :-moz-any(ul,ol){margin-left:.625em}[dir=ltr] .md-typeset ol li :is(ul,ol),[dir=ltr] .md-typeset ul li :is(ul,ol){margin-left:.625em}[dir=rtl] .md-typeset ol li :-webkit-any(ul,ol),[dir=rtl] .md-typeset ul li :-webkit-any(ul,ol){margin-right:.625em}[dir=rtl] .md-typeset ol li :-moz-any(ul,ol),[dir=rtl] .md-typeset ul li :-moz-any(ul,ol){margin-right:.625em}[dir=rtl] .md-typeset ol li :is(ul,ol),[dir=rtl] .md-typeset ul li :is(ul,ol){margin-right:.625em}.md-typeset ol li :is(ul,ol),.md-typeset ul li :is(ul,ol){margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset dd{margin-left:1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em}.md-typeset dd{margin-bottom:1.5em;margin-top:1em}.md-typeset img,.md-typeset svg{height:auto;max-width:100%}.md-typeset img[align=left],.md-typeset svg[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right],.md-typeset svg[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child,.md-typeset svg[align]:only-child{margin-top:0}.md-typeset img[src$="#gh-dark-mode-only"],.md-typeset img[src$="#only-dark"]{display:none}.md-typeset figure{display:flow-root;margin:1em auto;max-width:100%;text-align:center;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.md-typeset figure img{display:block}.md-typeset figcaption{font-style:italic;margin:1em auto;max-width:24rem}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){background-color:var(--md-default-bg-color);border:.05rem solid var(--md-typeset-table-color);border-radius:.1rem;display:inline-block;font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) :-webkit-any(th,td)>:first-child{margin-top:0}.md-typeset table:not([class]) :-moz-any(th,td)>:first-child{margin-top:0}.md-typeset table:not([class]) :is(th,td)>:first-child{margin-top:0}.md-typeset table:not([class]) :-webkit-any(th,td)>:last-child{margin-bottom:0}.md-typeset table:not([class]) :-moz-any(th,td)>:last-child{margin-bottom:0}.md-typeset table:not([class]) :is(th,td)>:last-child{margin-bottom:0}.md-typeset table:not([class]) :-webkit-any(th,td):not([align]){text-align:left}.md-typeset table:not([class]) :-moz-any(th,td):not([align]){text-align:left}.md-typeset table:not([class]) :is(th,td):not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) :-webkit-any(th,td):not([align]){text-align:right}[dir=rtl] .md-typeset table:not([class]) :-moz-any(th,td):not([align]){text-align:right}[dir=rtl] .md-typeset table:not([class]) :is(th,td):not([align]){text-align:right}.md-typeset table:not([class]) th{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) th a{color:inherit}.md-typeset table:not([class]) td{border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) tbody tr{transition:background-color 125ms}.md-typeset table:not([class]) tbody tr:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}[dir=ltr] .md-typeset table th[role=columnheader]:after{margin-left:.5em}[dir=rtl] .md-typeset table th[role=columnheader]:after{margin-right:.5em}.md-typeset table th[role=columnheader]:after{content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-typeset-table-sort-icon);mask-image:var(--md-typeset-table-sort-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset table th[role=columnheader]:hover:after{background-color:var(--md-default-fg-color--lighter)}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--asc);mask-image:var(--md-typeset-table-sort-icon--asc)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--desc);mask-image:var(--md-typeset-table-sort-icon--desc)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;margin:0;overflow:hidden;width:100%}@media screen and (max-width:44.9375em){.md-content__inner>pre{margin:1em -.8rem}.md-content__inner>pre code{border-radius:0}}.md-banner{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color);overflow:auto}@media print{.md-banner{display:none}}.md-banner--warning{background:var(--md-typeset-mark-color);color:var(--md-default-fg-color)}.md-banner__inner{font-size:.7rem;margin:.6rem auto;padding:0 .8rem}html{font-size:125%;height:100%;overflow-x:hidden}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;font-size:.5rem;min-height:100%;position:relative;width:100%}@media print{body{display:block}}@media screen and (max-width:59.9375em){body[data-md-scrolllock]{position:fixed}}.md-grid{margin-left:auto;margin-right:auto;max-width:61rem}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.md-toggle{display:none}.md-option{height:0;opacity:0;position:absolute;width:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-color:var(--md-accent-fg-color);outline-style:auto}.md-skip{background-color:var(--md-default-fg-color);border-radius:.1rem;color:var(--md-default-bg-color);font-size:.64rem;margin:.5rem;opacity:0;outline-color:var(--md-accent-fg-color);padding:.3rem .5rem;position:fixed;transform:translateY(.4rem);z-index:-1}.md-skip:focus{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms;z-index:10}@page{margin:25mm}:root{--md-clipboard-icon:url('data:image/svg+xml;charset=utf-8,')}.md-clipboard{border-radius:.1rem;color:var(--md-default-fg-color--lightest);cursor:pointer;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;position:absolute;right:.5em;top:.5em;transition:color .25s;width:1.5em;z-index:1}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-clipboard:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-clipboard:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-clipboard:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:-webkit-any(:focus,:hover) code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-clipboard--inline:-moz-any(:focus,:hover) code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-clipboard--inline:is(:focus,:hover) code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-content{flex-grow:1;min-width:0}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){[dir=ltr] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=ltr] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner,[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}}.md-content__inner:before{content:"";display:block;height:.4rem}.md-content__inner>:last-child{margin-bottom:0}[dir=ltr] .md-content__button{margin-left:.4rem}[dir=rtl] .md-content__button{margin-right:.4rem}.md-content__button{float:right;margin:.4rem 0;padding:0}@media print{.md-content__button{display:none}}[dir=rtl] .md-content__button{float:left}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}[dir=ltr] .md-dialog{right:.8rem}[dir=rtl] .md-dialog{left:.8rem}.md-dialog{background-color:var(--md-default-fg-color);border-radius:.1rem;bottom:.8rem;box-shadow:var(--md-shadow-z3);min-width:11.1rem;opacity:0;padding:.4rem .6rem;pointer-events:none;position:fixed;transform:translateY(100%);transition:transform 0ms .4s,opacity .4s;z-index:4}@media print{.md-dialog{display:none}}.md-dialog--active{opacity:1;pointer-events:auto;transform:translateY(0);transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-footer{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color)}@media print{.md-footer{display:none}}.md-footer__inner{justify-content:space-between;overflow:auto;padding:.2rem}.md-footer__inner:not([hidden]){display:flex}.md-footer__link{display:flex;flex-grow:0.01;outline-color:var(--md-accent-fg-color);overflow:hidden;padding-bottom:.4rem;padding-top:1.4rem;transition:opacity .25s}.md-footer__link:-webkit-any(:focus,:hover){opacity:.7}.md-footer__link:-moz-any(:focus,:hover){opacity:.7}.md-footer__link:is(:focus,:hover){opacity:.7}[dir=rtl] .md-footer__link svg{transform:scaleX(-1)}@media screen and (max-width:44.9375em){.md-footer__link--prev .md-footer__title{display:none}}[dir=ltr] .md-footer__link--next{margin-left:auto}[dir=rtl] .md-footer__link--next{margin-right:auto}.md-footer__link--next{text-align:right}[dir=rtl] .md-footer__link--next{text-align:left}.md-footer__title{flex-grow:1;font-size:.9rem;line-height:2.4rem;max-width:calc(100% - 2.4rem);padding:0 1rem;position:relative;white-space:nowrap}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{font-size:.64rem;left:0;margin-top:-1rem;opacity:.7;padding:0 1rem;position:absolute;right:0}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:-webkit-any(:focus,:hover){color:var(--md-footer-fg-color)}html .md-footer-meta.md-typeset a:-moz-any(:focus,:hover){color:var(--md-footer-fg-color)}html .md-footer-meta.md-typeset a:is(:focus,:hover){color:var(--md-footer-fg-color)}.md-copyright{color:var(--md-footer-fg-color--lighter);font-size:.64rem;margin:auto .6rem;padding:.4rem 0;width:100%}@media screen and (min-width:45em){.md-copyright{width:auto}}.md-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-social{margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-social{padding:.6rem 0}}.md-social__link{display:inline-block;height:1.6rem;text-align:center;width:1.6rem}.md-social__link:before{line-height:1.9}.md-social__link svg{fill:currentcolor;max-height:.8rem;vertical-align:-25%}.md-typeset .md-button{border:.1rem solid;border-radius:.1rem;color:var(--md-primary-fg-color);cursor:pointer;display:inline-block;font-weight:700;padding:.625em 2em;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color)}.md-typeset .md-button:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-typeset .md-button:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-typeset .md-button:is(:focus,:hover){background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[dir=ltr] .md-typeset .md-input{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .md-input,[dir=rtl] .md-typeset .md-input{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .md-input{border-top-left-radius:.1rem}.md-typeset .md-input{border-bottom:.1rem solid var(--md-default-fg-color--lighter);box-shadow:var(--md-shadow-z1);font-size:.8rem;height:1.8rem;padding:0 .6rem;transition:border .25s,box-shadow .25s}.md-typeset .md-input:-webkit-any(:focus,:hover){border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input:-moz-any(:focus,:hover){border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input:is(:focus,:hover){border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input--stretch{width:100%}.md-header{background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem transparent,0 .2rem .4rem transparent;color:var(--md-primary-bg-color);display:block;left:0;position:-webkit-sticky;position:sticky;right:0;top:0;z-index:4}@media print{.md-header{display:none}}.md-header[hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header--shadow{box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header__inner{align-items:center;display:flex;padding:0 .2rem}.md-header__button{color:currentcolor;cursor:pointer;margin:.2rem;outline-color:var(--md-accent-fg-color);padding:.4rem;position:relative;transition:opacity .25s;vertical-align:middle;z-index:1}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.1875em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo :-webkit-any(img,svg){fill:currentcolor;display:block;height:1.2rem;width:auto}.md-header__button.md-logo :-moz-any(img,svg){fill:currentcolor;display:block;height:1.2rem;width:auto}.md-header__button.md-logo :is(img,svg){fill:currentcolor;display:block;height:1.2rem;width:auto}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{display:flex;max-width:100%;position:absolute;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;white-space:nowrap}.md-header__topic+.md-header__topic{opacity:0;pointer-events:none;transform:translateX(1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__topic:first-child{font-weight:700}[dir=ltr] .md-header__title{margin-right:.4rem}[dir=rtl] .md-header__title{margin-left:.4rem}[dir=ltr] .md-header__title{margin-left:1rem}[dir=rtl] .md-header__title{margin-right:1rem}.md-header__title{flex-grow:1;font-size:.9rem;height:2.4rem;line-height:2.4rem}.md-header__title--active .md-header__topic{opacity:0;pointer-events:none;transform:translateX(-1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__title--active .md-header__topic{transform:translateX(1.25rem)}.md-header__title--active .md-header__topic+.md-header__topic{opacity:1;pointer-events:auto;transform:translateX(0);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;z-index:0}.md-header__title>.md-header__ellipsis{height:100%;position:relative;width:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;transition:max-width 0ms .25s,opacity .25s .25s;white-space:nowrap}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__source{display:none}@media screen and (min-width:60em){[dir=ltr] .md-header__source{margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem}.md-header__source{display:block;max-width:11.7rem;width:11.7rem}}@media screen and (min-width:76.25em){[dir=ltr] .md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}:root{--md-nav-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-nav-icon--next:url('data:image/svg+xml;charset=utf-8,');--md-toc-icon:url('data:image/svg+xml;charset=utf-8,')}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;font-weight:700;overflow:hidden;padding:0 .6rem;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{height:100%;width:auto}.md-nav__title .md-nav__button.md-logo :-webkit-any(img,svg){fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__title .md-nav__button.md-logo :-moz-any(img,svg){fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__title .md-nav__button.md-logo :is(img,svg){fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__list{list-style:none;margin:0;padding:0}.md-nav__item{padding:0 .6rem}[dir=ltr] .md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-left:0}.md-nav__link{align-items:center;cursor:pointer;display:flex;justify-content:space-between;margin-top:.625em;overflow:hidden;scroll-snap-align:start;text-overflow:ellipsis;transition:color 125ms}.md-nav__link--passed{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active{color:var(--md-typeset-a-color)}.md-nav__item .md-nav__link--index [href]{width:100%}.md-nav__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{background-color:currentcolor;display:block;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);width:100%}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link>*{cursor:pointer;display:flex}.md-nav__icon{flex-shrink:0}.md-nav__source{display:none}@media screen and (max-width:76.1875em){.md-nav--primary,.md-nav--primary .md-nav{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;height:100%;left:0;position:absolute;right:0;top:0;z-index:1}.md-nav--primary :-webkit-any(.md-nav__title,.md-nav__item){font-size:.8rem;line-height:1.5}.md-nav--primary :-moz-any(.md-nav__title,.md-nav__item){font-size:.8rem;line-height:1.5}.md-nav--primary :is(.md-nav__title,.md-nav__item){font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);cursor:pointer;height:5.6rem;line-height:2.4rem;padding:3rem .8rem .2rem;position:relative;white-space:nowrap}[dir=ltr] .md-nav--primary .md-nav__title .md-nav__icon{left:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem}.md-nav--primary .md-nav__title .md-nav__icon{display:block;height:1.2rem;margin:.2rem;position:absolute;top:.4rem;width:1.2rem}.md-nav--primary .md-nav__title .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}.md-nav--primary .md-nav__title~.md-nav__list{background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;overflow-y:auto;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);font-weight:700}.md-nav--primary .md-nav__title .md-logo{display:block;left:.2rem;margin:.2rem;padding:.4rem;position:absolute;right:.2rem;top:.2rem}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{border-top:.05rem solid var(--md-default-fg-color--lightest);padding:0}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{margin-top:0;padding:.6rem .8rem}[dir=ltr] .md-nav--primary .md-nav__link .md-nav__icon{margin-right:-.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{margin-left:-.2rem}.md-nav--primary .md-nav__link .md-nav__icon{font-size:1.2rem;height:1.2rem;width:1.2rem}.md-nav--primary .md-nav__link .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav{background-color:initial;position:static}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;opacity:0;transform:translateX(100%);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{opacity:1;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.9375em){.md-nav--primary .md-nav__link[for=__toc]{display:flex}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{background-color:var(--md-primary-fg-color--dark);color:var(--md-primary-bg-color);display:block;padding:0 .2rem}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-nav--integrated .md-nav__link[for=__toc]{display:flex}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}}@media screen and (min-width:76.25em){.md-nav{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon,.md-nav__toggle~.md-nav{display:none}.md-nav__toggle:-webkit-any(:checked,:indeterminate)~.md-nav{display:block}.md-nav__toggle:-moz-any(:checked,:indeterminate)~.md-nav{display:block}.md-nav__toggle:is(:checked,:indeterminate)~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{font-weight:700;pointer-events:none}.md-nav__item--section>.md-nav__link--index [href]{pointer-events:auto}.md-nav__item--section>.md-nav__link .md-nav__icon{display:none}.md-nav__item--section>.md-nav{display:block}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{border-radius:100%;float:right;height:.9rem;transition:background-color .25s,transform .25s;width:.9rem}[dir=rtl] .md-nav__icon{float:left;transform:rotate(180deg)}.md-nav__icon:hover{background-color:var(--md-accent-fg-color--transparent)}.md-nav__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:-.1rem;width:100%}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon,.md-nav__item--nested .md-nav__toggle:indeterminate~.md-nav__link .md-nav__icon{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__list>.md-nav__item--nested,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block;padding:0}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{font-weight:700;margin-top:0;padding:0 .6rem;pointer-events:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link--index [href]{pointer-events:auto}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link .md-nav__icon{display:none}.md-nav--lifted .md-nav[data-md-level="1"]{display:block}[dir=ltr] .md-nav--lifted .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-right:.6rem}[dir=rtl] .md-nav--lifted .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:.6rem}.md-nav--integrated>.md-nav__list>.md-nav__item--active:not(.md-nav__item--nested){padding:0 .6rem}.md-nav--integrated>.md-nav__list>.md-nav__item--active:not(.md-nav__item--nested)>.md-nav__link{padding:0}[dir=ltr] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-left:.05rem solid var(--md-primary-fg-color)}[dir=rtl] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-right:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{display:block;margin-bottom:1.25em}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__title{display:none}}:root{--md-search-result-icon:url('data:image/svg+xml;charset=utf-8,')}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__overlay{left:-2.2rem}[dir=rtl] .md-search__overlay{right:-2.2rem}.md-search__overlay{background-color:var(--md-default-bg-color);border-radius:1rem;height:2rem;overflow:hidden;pointer-events:none;position:absolute;top:-1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;width:2rem}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){[dir=ltr] .md-search__overlay{left:0}[dir=rtl] .md-search__overlay{right:0}.md-search__overlay{background-color:rgba(0,0,0,.54);cursor:pointer;height:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0}[data-md-toggle=search]:checked~.md-header .md-search__overlay{height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@media screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__inner{left:0}[dir=rtl] .md-search__inner{right:0}.md-search__inner{height:0;opacity:0;overflow:hidden;position:fixed;top:0;transform:translateX(5%);transition:width 0ms .3s,height 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;width:0;z-index:2}[dir=rtl] .md-search__inner{transform:translateX(-5%)}[data-md-toggle=search]:checked~.md-header .md-search__inner{height:100%;opacity:1;transform:translateX(0);transition:width 0ms 0ms,height 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;width:100%}}@media screen and (min-width:60em){.md-search__inner{float:right;padding:.1rem 0;position:relative;transition:width .25s cubic-bezier(.1,.7,.1,1);width:11.7rem}[dir=rtl] .md-search__inner{float:left}}@media screen and (min-width:60em) and (max-width:76.1875em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem transparent;height:2.4rem;position:relative;transition:color .25s,background-color .25s;z-index:2}@media screen and (min-width:60em){.md-search__form{background-color:rgba(0,0,0,.26);border-radius:.1rem;height:1.8rem}.md-search__form:hover{background-color:hsla(0,0%,100%,.12)}}[data-md-toggle=search]:checked~.md-header .md-search__form{background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0;box-shadow:0 0 .6rem rgba(0,0,0,.07);color:var(--md-default-fg-color)}[dir=ltr] .md-search__input{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__input{padding-left:2.2rem;padding-right:3.6rem}.md-search__input{background:transparent;font-size:.9rem;height:100%;position:relative;text-overflow:ellipsis;width:100%;z-index:2}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s;transition:color .25s}.md-search__input::placeholder{transition:color .25s}.md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}@media screen and (max-width:59.9375em){.md-search__input{font-size:.9rem;height:2.4rem;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__input{padding-left:2.2rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input{color:inherit;font-size:.8rem}.md-search__input::-ms-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}[data-md-toggle=search]:checked~.md-header .md-search__input{text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:var(--md-default-fg-color--light)}}.md-search__icon{cursor:pointer;display:inline-block;height:1.2rem;transition:color .25s,opacity .25s;width:1.2rem}.md-search__icon:hover{opacity:.7}[dir=ltr] .md-search__icon[for=__search]{left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem}.md-search__icon[for=__search]{position:absolute;top:.3rem;z-index:2}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__icon[for=__search]{left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem}.md-search__icon[for=__search]{top:.6rem}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}[dir=ltr] .md-search__options{right:.5rem}[dir=rtl] .md-search__options{left:.5rem}.md-search__options{pointer-events:none;position:absolute;top:.3rem;z-index:2}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__options{right:.8rem}[dir=rtl] .md-search__options{left:.8rem}.md-search__options{top:.6rem}}[dir=ltr] .md-search__options>*{margin-left:.2rem}[dir=rtl] .md-search__options>*{margin-right:.2rem}.md-search__options>*{color:var(--md-default-fg-color--light);opacity:0;transform:scale(.75);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-search__options>:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>*{opacity:1;pointer-events:auto;transform:scale(1)}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>:hover{opacity:.7}[dir=ltr] .md-search__suggest{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__suggest{padding-left:2.2rem;padding-right:3.6rem}.md-search__suggest{align-items:center;color:var(--md-default-fg-color--lighter);display:flex;font-size:.9rem;height:100%;opacity:0;position:absolute;top:0;transition:opacity 50ms;white-space:nowrap;width:100%}@media screen and (min-width:60em){[dir=ltr] .md-search__suggest{padding-left:2.2rem}[dir=rtl] .md-search__suggest{padding-right:2.2rem}.md-search__suggest{font-size:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__suggest{opacity:1;transition:opacity .3s .1s}[dir=ltr] .md-search__output{border-bottom-left-radius:.1rem}[dir=ltr] .md-search__output,[dir=rtl] .md-search__output{border-bottom-right-radius:.1rem}[dir=rtl] .md-search__output{border-bottom-left-radius:.1rem}.md-search__output{overflow:hidden;position:absolute;width:100%;z-index:1}@media screen and (max-width:59.9375em){.md-search__output{bottom:0;top:2.4rem}}@media screen and (min-width:60em){.md-search__output{opacity:0;top:1.9rem;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:var(--md-shadow-z3);opacity:1}}.md-search__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);height:100%;overflow-y:auto;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1),(max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-color:var(--md-default-fg-color--lighter) transparent;scrollbar-width:thin}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-search__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;padding:0 .8rem;scroll-snap-align:start}@media screen and (min-width:60em){[dir=ltr] .md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem}}.md-search-result__list{list-style:none;margin:0;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-search-result__item{box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;scroll-snap-align:start;transition:background-color .25s}.md-search-result__link:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:is(:focus,:hover){background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more summary{color:var(--md-typeset-a-color);cursor:pointer;display:block;font-size:.64rem;outline:none;padding:.75em .8rem;scroll-snap-align:start;transition:color .25s,background-color .25s}@media screen and (min-width:60em){[dir=ltr] .md-search-result__more summary{padding-left:2.2rem}[dir=rtl] .md-search-result__more summary{padding-right:2.2rem}}.md-search-result__more summary:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more summary:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more summary:is(:focus,:hover){background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more summary::marker{display:none}.md-search-result__more summary::-webkit-details-marker{display:none}.md-search-result__more summary~*>*{opacity:.65}.md-search-result__article{overflow:hidden;padding:0 .8rem;position:relative}@media screen and (min-width:60em){[dir=ltr] .md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem}}.md-search-result__article--document .md-search-result__title{font-size:.8rem;font-weight:400;line-height:1.4;margin:.55rem 0}[dir=ltr] .md-search-result__icon{left:0}[dir=rtl] .md-search-result__icon{right:0}.md-search-result__icon{color:var(--md-default-fg-color--light);height:1.2rem;margin:.5rem;position:absolute;width:1.2rem}@media screen and (max-width:59.9375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result__title{font-size:.64rem;font-weight:700;line-height:1.6;margin:.5em 0}.md-search-result__teaser{-webkit-box-orient:vertical;-webkit-line-clamp:2;color:var(--md-default-fg-color--light);display:-webkit-box;font-size:.64rem;line-height:1.6;margin:.5em 0;max-height:2rem;overflow:hidden;text-overflow:ellipsis}@media screen and (max-width:44.9375em){.md-search-result__teaser{-webkit-line-clamp:3;max-height:3rem}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search-result__teaser{-webkit-line-clamp:3;max-height:3rem}}.md-search-result__teaser mark{background-color:initial;text-decoration:underline}.md-search-result__terms{font-size:.64rem;font-style:italic;margin:.5em 0}.md-search-result mark{background-color:initial;color:var(--md-accent-fg-color)}.md-select{position:relative;z-index:1}.md-select__inner{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:50%;margin-top:.2rem;max-height:0;opacity:0;position:absolute;top:calc(100% - .2rem);transform:translate3d(-50%,.3rem,0);transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:-webkit-any(:focus-within,:hover) .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);-webkit-transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select:-moz-any(:focus-within,:hover) .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);-moz-transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select:is(:focus-within,:hover) .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select__inner:after{border-bottom:.2rem solid transparent;border-bottom-color:var(--md-default-bg-color);border-left:.2rem solid transparent;border-right:.2rem solid transparent;border-top:0;content:"";height:0;left:50%;margin-left:-.2rem;margin-top:-.2rem;position:absolute;top:0;width:0}.md-select__list{border-radius:.1rem;font-size:.8rem;list-style-type:none;margin:0;max-height:inherit;overflow:auto;padding:0}.md-select__item{line-height:1.8rem}[dir=ltr] .md-select__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-select__link{padding-left:1.2rem;padding-right:.6rem}.md-select__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:background-color .25s,color .25s;width:100%}.md-select__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-select__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-select__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-select__link:focus{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{align-self:flex-start;flex-shrink:0;padding:1.2rem 0;position:-webkit-sticky;position:sticky;top:2.4rem;width:12.1rem}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.1875em){[dir=ltr] .md-sidebar--primary{left:-12.1rem}[dir=rtl] .md-sidebar--primary{right:-12.1rem}.md-sidebar--primary{background-color:var(--md-default-bg-color);display:block;height:100%;position:fixed;top:0;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;width:12.1rem;z-index:5}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:var(--md-shadow-z3);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{bottom:0;left:0;margin:0;overflow:hidden;position:absolute;right:0;-ms-scroll-snap-type:none;scroll-snap-type:none;top:0}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;margin:0 .2rem;overflow-y:auto;scrollbar-color:var(--md-default-fg-color--lighter) transparent;scrollbar-width:thin}.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-sidebar__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:76.1875em){.md-overlay{background-color:rgba(0,0,0,.54);height:0;opacity:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0;z-index:5}[data-md-toggle=drawer]:checked~.md-overlay{height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@-webkit-keyframes facts{0%{height:0}to{height:.65rem}}@keyframes facts{0%{height:0}to{height:.65rem}}@-webkit-keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}@keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}:root{--md-source-forks-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-repositories-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-stars-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-source{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.65rem;line-height:1.2;outline-color:var(--md-accent-fg-color);transition:opacity .25s;white-space:nowrap}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;height:2.4rem;vertical-align:middle;width:2rem}[dir=ltr] .md-source__icon svg{margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem}.md-source__icon svg{margin-top:.6rem}[dir=ltr] .md-source__icon+.md-source__repository{margin-left:-2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem}[dir=ltr] .md-source__icon+.md-source__repository{padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{padding-right:2rem}[dir=ltr] .md-source__repository{margin-left:.6rem}[dir=rtl] .md-source__repository{margin-right:.6rem}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{font-size:.55rem;list-style-type:none;margin:.1rem 0 0;opacity:.75;overflow:hidden;padding:0}.md-source__repository--active .md-source__facts{-webkit-animation:facts .25s ease-in;animation:facts .25s ease-in}.md-source__fact{display:inline-block}.md-source__repository--active .md-source__fact{-webkit-animation:fact .4s ease-out;animation:fact .4s ease-out}[dir=ltr] .md-source__fact:before{margin-right:.1rem}[dir=rtl] .md-source__fact:before{margin-left:.1rem}.md-source__fact:before{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-top;width:.6rem}[dir=ltr] .md-source__fact:nth-child(1n+2):before{margin-left:.4rem}[dir=rtl] .md-source__fact:nth-child(1n+2):before{margin-right:.4rem}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-tabs{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);display:block;line-height:1.3;overflow:auto;width:100%;z-index:3}@media print{.md-tabs{display:none}}@media screen and (max-width:76.1875em){.md-tabs{display:none}}.md-tabs[hidden]{pointer-events:none}[dir=ltr] .md-tabs__list{margin-left:.2rem}[dir=rtl] .md-tabs__list{margin-right:.2rem}.md-tabs__list{contain:content;list-style:none;margin:0;padding:0;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-left:.6rem;padding-right:.6rem}.md-tabs__link{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.7rem;margin-top:.8rem;opacity:.7;outline-color:var(--md-accent-fg-color);outline-offset:.2rem;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link--active,.md-tabs__link:-webkit-any(:focus,:hover){color:inherit;opacity:1}.md-tabs__link--active,.md-tabs__link:-moz-any(:focus,:hover){color:inherit;opacity:1}.md-tabs__link--active,.md-tabs__link:is(:focus,:hover){color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[hidden] .md-tabs__link{opacity:0;transform:translateY(50%);transition:transform 0ms .1s,opacity .1s}.md-tags{margin-bottom:.75em}[dir=ltr] .md-tag{margin-right:.5em}[dir=rtl] .md-tag{margin-left:.5em}.md-tag{background:var(--md-default-fg-color--lightest);border-radius:.4rem;display:inline-block;font-size:.64rem;font-weight:700;line-height:1.6;margin-bottom:.5em;padding:.3125em .9375em}.md-tag[href]{-webkit-tap-highlight-color:transparent;color:inherit;outline:none;transition:color 125ms,background-color 125ms}.md-tag[href]:focus,.md-tag[href]:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[id]>.md-tag{vertical-align:text-top}@-webkit-keyframes pulse{0%{box-shadow:0 0 0 0 var(--md-default-fg-color--lightest);transform:scale(.95)}75%{box-shadow:0 0 0 .625em transparent;transform:scale(1)}to{box-shadow:0 0 0 0 transparent;transform:scale(.95)}}@keyframes pulse{0%{box-shadow:0 0 0 0 var(--md-default-fg-color--lightest);transform:scale(.95)}75%{box-shadow:0 0 0 .625em transparent;transform:scale(1)}to{box-shadow:0 0 0 0 transparent;transform:scale(.95)}}:root{--md-tooltip-width:20rem}.md-tooltip{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:clamp(var(--md-tooltip-0,0rem) + .8rem,var(--md-tooltip-x),100vw + var(--md-tooltip-0,0rem) + .8rem - var(--md-tooltip-width) - 2 * .8rem);max-height:0;max-width:calc(100vw - 1.6rem);opacity:0;position:absolute;top:var(--md-tooltip-y);transform:translateY(-.4rem);transition:transform 0ms .25s,opacity .25s,max-height 0ms .25s,z-index .25s;width:var(--md-tooltip-width);z-index:0}:focus-within>.md-tooltip{max-height:1000%;opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height .25s,z-index 0ms}.focus-visible>.md-tooltip{outline:var(--md-accent-fg-color) auto}.md-tooltip__inner{font-size:.64rem;padding:.8rem}.md-tooltip__inner.md-typeset>:first-child{margin-top:0}.md-tooltip__inner.md-typeset>:last-child{margin-bottom:0}.md-annotation{outline:none;white-space:normal}[dir=rtl] .md-annotation{direction:rtl}.md-annotation:not([hidden]){display:inline-block;line-height:1.325}.md-annotation:focus-within>*{z-index:2}.md-annotation__inner{font-family:var(--md-text-font-family);top:calc(var(--md-tooltip-y) + 1.2ch)}:not(:focus-within)>.md-annotation__inner{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-annotation__index{color:#fff;cursor:pointer;margin:0 1ch;position:relative;transition:z-index .25s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:0}.md-annotation__index:after{background-color:var(--md-default-fg-color--lighter);border-radius:2ch;content:"";height:2.2ch;left:-.126em;margin:0 -.4ch;padding:0 .4ch;position:absolute;transition:color .25s,background-color .25s;width:calc(100% + 1.2ch);width:max(2.2ch,100% + 1.2ch);z-index:-1}@media not all and (prefers-reduced-motion){[data-md-visible]>.md-annotation__index:after{-webkit-animation:pulse 2s infinite;animation:pulse 2s infinite}}:-webkit-any(:focus-within,:hover)>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}:-moz-any(:focus-within,:hover)>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}:is(:focus-within,:hover)>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}:focus-within>.md-annotation__index:after{-webkit-animation:none;animation:none;transition:color .25s,background-color .25s}.md-annotation__index [data-md-annotation-id]{display:inline-block;line-height:90%}.md-annotation__index [data-md-annotation-id]:before{content:attr(data-md-annotation-id);display:inline-block;padding-bottom:.1em;transform:scale(1.15);transition:transform .4s cubic-bezier(.1,.7,.1,1);vertical-align:.065em}@media not print{.md-annotation__index [data-md-annotation-id]:before{content:"+"}:focus-within>.md-annotation__index [data-md-annotation-id]:before{transform:scale(1.25) rotate(45deg)}}:-webkit-any(:focus-within,:hover)>.md-annotation__index{color:var(--md-accent-bg-color)}:-moz-any(:focus-within,:hover)>.md-annotation__index{color:var(--md-accent-bg-color)}:is(:focus-within,:hover)>.md-annotation__index{color:var(--md-accent-bg-color)}:focus-within>.md-annotation__index{-webkit-animation:none;animation:none;transition:none}[dir=ltr] .md-top{margin-left:50%}[dir=rtl] .md-top{margin-right:50%}.md-top{background-color:var(--md-default-bg-color);border-radius:1.6rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color--light);display:block;font-size:.7rem;outline:none;padding:.4rem .8rem;position:fixed;top:3.2rem;transform:translate(-50%);transition:color 125ms,background-color 125ms,transform 125ms cubic-bezier(.4,0,.2,1),opacity 125ms;z-index:2}@media print{.md-top{display:none}}[dir=rtl] .md-top{transform:translate(50%)}.md-top[hidden]{opacity:0;pointer-events:none;transform:translate(-50%,.2rem);transition-duration:0ms}[dir=rtl] .md-top[hidden]{transform:translate(50%,.2rem)}.md-top:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top:is(:focus,:hover){background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top svg{display:inline-block;vertical-align:-.5em}@-webkit-keyframes hoverfix{0%{pointer-events:none}}@keyframes hoverfix{0%{pointer-events:none}}:root{--md-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-version{flex-shrink:0;font-size:.8rem;height:2.4rem}[dir=ltr] .md-version__current{margin-left:1.4rem;margin-right:.4rem}[dir=rtl] .md-version__current{margin-left:.4rem;margin-right:1.4rem}.md-version__current{color:inherit;cursor:pointer;outline:none;position:relative;top:.05rem}[dir=ltr] .md-version__current:after{margin-left:.4rem}[dir=rtl] .md-version__current:after{margin-right:.4rem}.md-version__current:after{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;width:.4rem}.md-version__list{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);list-style-type:none;margin:.2rem .8rem;max-height:0;opacity:0;overflow:auto;padding:0;position:absolute;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;top:.15rem;transition:max-height 0ms .5s,opacity .25s .25s;z-index:3}.md-version:-webkit-any(:focus-within,:hover) .md-version__list{max-height:10rem;opacity:1;-webkit-transition:max-height 0ms,opacity .25s;transition:max-height 0ms,opacity .25s}.md-version:-moz-any(:focus-within,:hover) .md-version__list{max-height:10rem;opacity:1;-moz-transition:max-height 0ms,opacity .25s;transition:max-height 0ms,opacity .25s}.md-version:is(:focus-within,:hover) .md-version__list{max-height:10rem;opacity:1;transition:max-height 0ms,opacity .25s}@media (pointer:coarse){.md-version:hover .md-version__list{-webkit-animation:hoverfix .25s forwards;animation:hoverfix .25s forwards}.md-version:focus-within .md-version__list{-webkit-animation:none;animation:none}}.md-version__item{line-height:1.8rem}[dir=ltr] .md-version__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-version__link{padding-left:1.2rem;padding-right:.6rem}.md-version__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:color .25s,background-color .25s;white-space:nowrap;width:100%}.md-version__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-version__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-version__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-version__link:focus{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--abstract:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--info:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--tip:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--success:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--question:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--warning:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--failure:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--danger:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--bug:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--example:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--quote:url('data:image/svg+xml;charset=utf-8,')}.md-typeset :-webkit-any(.admonition,details){background-color:var(--md-admonition-bg-color);border:0 solid #448aff;border-radius:.1rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid}.md-typeset :-moz-any(.admonition,details){background-color:var(--md-admonition-bg-color);border:0 solid #448aff;border-radius:.1rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid}[dir=ltr] .md-typeset :-webkit-any(.admonition,details){border-left-width:.2rem}[dir=ltr] .md-typeset :-moz-any(.admonition,details){border-left-width:.2rem}[dir=ltr] .md-typeset :is(.admonition,details){border-left-width:.2rem}[dir=rtl] .md-typeset :-webkit-any(.admonition,details){border-right-width:.2rem}[dir=rtl] .md-typeset :-moz-any(.admonition,details){border-right-width:.2rem}[dir=rtl] .md-typeset :is(.admonition,details){border-right-width:.2rem}.md-typeset :is(.admonition,details){background-color:var(--md-admonition-bg-color);border:0 solid #448aff;border-radius:.1rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid}@media print{.md-typeset :-webkit-any(.admonition,details){box-shadow:none}.md-typeset :-moz-any(.admonition,details){box-shadow:none}.md-typeset :is(.admonition,details){box-shadow:none}}.md-typeset :-webkit-any(.admonition,details)>*{box-sizing:border-box}.md-typeset :-moz-any(.admonition,details)>*{box-sizing:border-box}.md-typeset :is(.admonition,details)>*{box-sizing:border-box}.md-typeset :-webkit-any(.admonition,details) :-webkit-any(.admonition,details){margin-bottom:1em;margin-top:1em}.md-typeset :-moz-any(.admonition,details) :-moz-any(.admonition,details){margin-bottom:1em;margin-top:1em}.md-typeset :is(.admonition,details) :is(.admonition,details){margin-bottom:1em;margin-top:1em}.md-typeset :-webkit-any(.admonition,details) .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset :-moz-any(.admonition,details) .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset :is(.admonition,details) .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset :-webkit-any(.admonition,details) .md-typeset__table{padding:0 .6rem}.md-typeset :-moz-any(.admonition,details) .md-typeset__table{padding:0 .6rem}.md-typeset :is(.admonition,details) .md-typeset__table{padding:0 .6rem}.md-typeset :-webkit-any(.admonition,details)>.tabbed-set:only-child{margin-top:0}.md-typeset :-moz-any(.admonition,details)>.tabbed-set:only-child{margin-top:0}.md-typeset :is(.admonition,details)>.tabbed-set:only-child{margin-top:0}html .md-typeset :-webkit-any(.admonition,details)>:last-child{margin-bottom:.6rem}html .md-typeset :-moz-any(.admonition,details)>:last-child{margin-bottom:.6rem}html .md-typeset :is(.admonition,details)>:last-child{margin-bottom:.6rem}.md-typeset :-webkit-any(.admonition-title,summary){background-color:rgba(68,138,255,.1);border:none;font-weight:700;margin-bottom:0;margin-top:0;padding-bottom:.4rem;padding-top:.4rem;position:relative}.md-typeset :-moz-any(.admonition-title,summary){background-color:rgba(68,138,255,.1);border:none;font-weight:700;margin-bottom:0;margin-top:0;padding-bottom:.4rem;padding-top:.4rem;position:relative}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){margin-left:-.8rem;margin-right:-.6rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){margin-left:-.8rem;margin-right:-.6rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){margin-left:-.8rem;margin-right:-.6rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){margin-left:-.6rem;margin-right:-.8rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){margin-left:-.6rem;margin-right:-.8rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){margin-left:-.6rem;margin-right:-.8rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){padding-left:2.2rem;padding-right:.6rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){padding-left:2.2rem;padding-right:.6rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){padding-left:2.2rem;padding-right:.6rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){padding-left:.6rem;padding-right:2.2rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){padding-left:.6rem;padding-right:2.2rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){padding-left:.6rem;padding-right:2.2rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){border-left-width:.2rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){border-left-width:.2rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){border-left-width:.2rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){border-right-width:.2rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){border-right-width:.2rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){border-right-width:.2rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){border-top-left-radius:.1rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){border-top-right-radius:.1rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){border-top-right-radius:.1rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){border-top-left-radius:.1rem}.md-typeset :is(.admonition-title,summary){background-color:rgba(68,138,255,.1);border:none;font-weight:700;margin-bottom:0;margin-top:0;padding-bottom:.4rem;padding-top:.4rem;position:relative}html .md-typeset :-webkit-any(.admonition-title,summary):last-child{margin-bottom:0}html .md-typeset :-moz-any(.admonition-title,summary):last-child{margin-bottom:0}html .md-typeset :is(.admonition-title,summary):last-child{margin-bottom:0}.md-typeset :-webkit-any(.admonition-title,summary):before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset :-moz-any(.admonition-title,summary):before{background-color:#448aff;content:"";height:1rem;mask-image:var(--md-admonition-icon--note);mask-repeat:no-repeat;mask-size:contain;position:absolute;top:.625em;width:1rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary):before{left:.8rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary):before{left:.8rem}[dir=ltr] .md-typeset :is(.admonition-title,summary):before{left:.8rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary):before{right:.8rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary):before{right:.8rem}[dir=rtl] .md-typeset :is(.admonition-title,summary):before{right:.8rem}.md-typeset :is(.admonition-title,summary):before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.note){border-color:#448aff}.md-typeset :-moz-any(.admonition,details):-moz-any(.note){border-color:#448aff}.md-typeset :is(.admonition,details):is(.note){border-color:#448aff}.md-typeset :-webkit-any(.note)>:-webkit-any(.admonition-title,summary){background-color:rgba(68,138,255,.1)}.md-typeset :-moz-any(.note)>:-moz-any(.admonition-title,summary){background-color:rgba(68,138,255,.1)}.md-typeset :is(.note)>:is(.admonition-title,summary){background-color:rgba(68,138,255,.1)}.md-typeset :-webkit-any(.note)>:-webkit-any(.admonition-title,summary):before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.note)>:-moz-any(.admonition-title,summary):before{background-color:#448aff;mask-image:var(--md-admonition-icon--note);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.note)>:is(.admonition-title,summary):before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.abstract,.summary,.tldr){border-color:#00b0ff}.md-typeset :-moz-any(.admonition,details):-moz-any(.abstract,.summary,.tldr){border-color:#00b0ff}.md-typeset :is(.admonition,details):is(.abstract,.summary,.tldr){border-color:#00b0ff}.md-typeset :-webkit-any(.abstract,.summary,.tldr)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,176,255,.1)}.md-typeset :-moz-any(.abstract,.summary,.tldr)>:-moz-any(.admonition-title,summary){background-color:rgba(0,176,255,.1)}.md-typeset :is(.abstract,.summary,.tldr)>:is(.admonition-title,summary){background-color:rgba(0,176,255,.1)}.md-typeset :-webkit-any(.abstract,.summary,.tldr)>:-webkit-any(.admonition-title,summary):before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.abstract,.summary,.tldr)>:-moz-any(.admonition-title,summary):before{background-color:#00b0ff;mask-image:var(--md-admonition-icon--abstract);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.abstract,.summary,.tldr)>:is(.admonition-title,summary):before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.info,.todo){border-color:#00b8d4}.md-typeset :-moz-any(.admonition,details):-moz-any(.info,.todo){border-color:#00b8d4}.md-typeset :is(.admonition,details):is(.info,.todo){border-color:#00b8d4}.md-typeset :-webkit-any(.info,.todo)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,184,212,.1)}.md-typeset :-moz-any(.info,.todo)>:-moz-any(.admonition-title,summary){background-color:rgba(0,184,212,.1)}.md-typeset :is(.info,.todo)>:is(.admonition-title,summary){background-color:rgba(0,184,212,.1)}.md-typeset :-webkit-any(.info,.todo)>:-webkit-any(.admonition-title,summary):before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.info,.todo)>:-moz-any(.admonition-title,summary):before{background-color:#00b8d4;mask-image:var(--md-admonition-icon--info);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.info,.todo)>:is(.admonition-title,summary):before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.tip,.hint,.important){border-color:#00bfa5}.md-typeset :-moz-any(.admonition,details):-moz-any(.tip,.hint,.important){border-color:#00bfa5}.md-typeset :is(.admonition,details):is(.tip,.hint,.important){border-color:#00bfa5}.md-typeset :-webkit-any(.tip,.hint,.important)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,191,165,.1)}.md-typeset :-moz-any(.tip,.hint,.important)>:-moz-any(.admonition-title,summary){background-color:rgba(0,191,165,.1)}.md-typeset :is(.tip,.hint,.important)>:is(.admonition-title,summary){background-color:rgba(0,191,165,.1)}.md-typeset :-webkit-any(.tip,.hint,.important)>:-webkit-any(.admonition-title,summary):before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.tip,.hint,.important)>:-moz-any(.admonition-title,summary):before{background-color:#00bfa5;mask-image:var(--md-admonition-icon--tip);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.tip,.hint,.important)>:is(.admonition-title,summary):before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.success,.check,.done){border-color:#00c853}.md-typeset :-moz-any(.admonition,details):-moz-any(.success,.check,.done){border-color:#00c853}.md-typeset :is(.admonition,details):is(.success,.check,.done){border-color:#00c853}.md-typeset :-webkit-any(.success,.check,.done)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,200,83,.1)}.md-typeset :-moz-any(.success,.check,.done)>:-moz-any(.admonition-title,summary){background-color:rgba(0,200,83,.1)}.md-typeset :is(.success,.check,.done)>:is(.admonition-title,summary){background-color:rgba(0,200,83,.1)}.md-typeset :-webkit-any(.success,.check,.done)>:-webkit-any(.admonition-title,summary):before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.success,.check,.done)>:-moz-any(.admonition-title,summary):before{background-color:#00c853;mask-image:var(--md-admonition-icon--success);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.success,.check,.done)>:is(.admonition-title,summary):before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.question,.help,.faq){border-color:#64dd17}.md-typeset :-moz-any(.admonition,details):-moz-any(.question,.help,.faq){border-color:#64dd17}.md-typeset :is(.admonition,details):is(.question,.help,.faq){border-color:#64dd17}.md-typeset :-webkit-any(.question,.help,.faq)>:-webkit-any(.admonition-title,summary){background-color:rgba(100,221,23,.1)}.md-typeset :-moz-any(.question,.help,.faq)>:-moz-any(.admonition-title,summary){background-color:rgba(100,221,23,.1)}.md-typeset :is(.question,.help,.faq)>:is(.admonition-title,summary){background-color:rgba(100,221,23,.1)}.md-typeset :-webkit-any(.question,.help,.faq)>:-webkit-any(.admonition-title,summary):before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.question,.help,.faq)>:-moz-any(.admonition-title,summary):before{background-color:#64dd17;mask-image:var(--md-admonition-icon--question);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.question,.help,.faq)>:is(.admonition-title,summary):before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.warning,.caution,.attention){border-color:#ff9100}.md-typeset :-moz-any(.admonition,details):-moz-any(.warning,.caution,.attention){border-color:#ff9100}.md-typeset :is(.admonition,details):is(.warning,.caution,.attention){border-color:#ff9100}.md-typeset :-webkit-any(.warning,.caution,.attention)>:-webkit-any(.admonition-title,summary){background-color:rgba(255,145,0,.1)}.md-typeset :-moz-any(.warning,.caution,.attention)>:-moz-any(.admonition-title,summary){background-color:rgba(255,145,0,.1)}.md-typeset :is(.warning,.caution,.attention)>:is(.admonition-title,summary){background-color:rgba(255,145,0,.1)}.md-typeset :-webkit-any(.warning,.caution,.attention)>:-webkit-any(.admonition-title,summary):before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.warning,.caution,.attention)>:-moz-any(.admonition-title,summary):before{background-color:#ff9100;mask-image:var(--md-admonition-icon--warning);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.warning,.caution,.attention)>:is(.admonition-title,summary):before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.failure,.fail,.missing){border-color:#ff5252}.md-typeset :-moz-any(.admonition,details):-moz-any(.failure,.fail,.missing){border-color:#ff5252}.md-typeset :is(.admonition,details):is(.failure,.fail,.missing){border-color:#ff5252}.md-typeset :-webkit-any(.failure,.fail,.missing)>:-webkit-any(.admonition-title,summary){background-color:rgba(255,82,82,.1)}.md-typeset :-moz-any(.failure,.fail,.missing)>:-moz-any(.admonition-title,summary){background-color:rgba(255,82,82,.1)}.md-typeset :is(.failure,.fail,.missing)>:is(.admonition-title,summary){background-color:rgba(255,82,82,.1)}.md-typeset :-webkit-any(.failure,.fail,.missing)>:-webkit-any(.admonition-title,summary):before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.failure,.fail,.missing)>:-moz-any(.admonition-title,summary):before{background-color:#ff5252;mask-image:var(--md-admonition-icon--failure);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.failure,.fail,.missing)>:is(.admonition-title,summary):before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.danger,.error){border-color:#ff1744}.md-typeset :-moz-any(.admonition,details):-moz-any(.danger,.error){border-color:#ff1744}.md-typeset :is(.admonition,details):is(.danger,.error){border-color:#ff1744}.md-typeset :-webkit-any(.danger,.error)>:-webkit-any(.admonition-title,summary){background-color:rgba(255,23,68,.1)}.md-typeset :-moz-any(.danger,.error)>:-moz-any(.admonition-title,summary){background-color:rgba(255,23,68,.1)}.md-typeset :is(.danger,.error)>:is(.admonition-title,summary){background-color:rgba(255,23,68,.1)}.md-typeset :-webkit-any(.danger,.error)>:-webkit-any(.admonition-title,summary):before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.danger,.error)>:-moz-any(.admonition-title,summary):before{background-color:#ff1744;mask-image:var(--md-admonition-icon--danger);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.danger,.error)>:is(.admonition-title,summary):before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.bug){border-color:#f50057}.md-typeset :-moz-any(.admonition,details):-moz-any(.bug){border-color:#f50057}.md-typeset :is(.admonition,details):is(.bug){border-color:#f50057}.md-typeset :-webkit-any(.bug)>:-webkit-any(.admonition-title,summary){background-color:rgba(245,0,87,.1)}.md-typeset :-moz-any(.bug)>:-moz-any(.admonition-title,summary){background-color:rgba(245,0,87,.1)}.md-typeset :is(.bug)>:is(.admonition-title,summary){background-color:rgba(245,0,87,.1)}.md-typeset :-webkit-any(.bug)>:-webkit-any(.admonition-title,summary):before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.bug)>:-moz-any(.admonition-title,summary):before{background-color:#f50057;mask-image:var(--md-admonition-icon--bug);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.bug)>:is(.admonition-title,summary):before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.example){border-color:#7c4dff}.md-typeset :-moz-any(.admonition,details):-moz-any(.example){border-color:#7c4dff}.md-typeset :is(.admonition,details):is(.example){border-color:#7c4dff}.md-typeset :-webkit-any(.example)>:-webkit-any(.admonition-title,summary){background-color:rgba(124,77,255,.1)}.md-typeset :-moz-any(.example)>:-moz-any(.admonition-title,summary){background-color:rgba(124,77,255,.1)}.md-typeset :is(.example)>:is(.admonition-title,summary){background-color:rgba(124,77,255,.1)}.md-typeset :-webkit-any(.example)>:-webkit-any(.admonition-title,summary):before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.example)>:-moz-any(.admonition-title,summary):before{background-color:#7c4dff;mask-image:var(--md-admonition-icon--example);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.example)>:is(.admonition-title,summary):before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.quote,.cite){border-color:#9e9e9e}.md-typeset :-moz-any(.admonition,details):-moz-any(.quote,.cite){border-color:#9e9e9e}.md-typeset :is(.admonition,details):is(.quote,.cite){border-color:#9e9e9e}.md-typeset :-webkit-any(.quote,.cite)>:-webkit-any(.admonition-title,summary){background-color:hsla(0,0%,62%,.1)}.md-typeset :-moz-any(.quote,.cite)>:-moz-any(.admonition-title,summary){background-color:hsla(0,0%,62%,.1)}.md-typeset :is(.quote,.cite)>:is(.admonition-title,summary){background-color:hsla(0,0%,62%,.1)}.md-typeset :-webkit-any(.quote,.cite)>:-webkit-any(.admonition-title,summary):before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.quote,.cite)>:-moz-any(.admonition-title,summary):before{background-color:#9e9e9e;mask-image:var(--md-admonition-icon--quote);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.quote,.cite)>:is(.admonition-title,summary):before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}:root{--md-footnotes-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}[dir=ltr] .md-typeset .footnote>ol{margin-left:0}[dir=rtl] .md-typeset .footnote>ol{margin-right:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:focus-within .footnote-backref{opacity:1;transform:translateX(0);transition:none}.md-typeset .footnote>ol>li:-webkit-any(:hover,:target) .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li:-moz-any(:hover,:target) .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li:is(:hover,:target) .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-ref{font-size:.75em;font-weight:700}html .md-typeset .footnote-ref{outline-offset:.1rem}.md-typeset [id^="fnref:"]:target>.footnote-ref{outline:auto}.md-typeset .footnote-backref{color:var(--md-typeset-a-color);display:inline-block;font-size:0;opacity:0;transform:translateX(.25rem);transition:color .25s,transform .25s .25s,opacity 125ms .25s;vertical-align:text-bottom}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);opacity:1;transform:translateX(0)}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{background-color:currentcolor;content:"";display:inline-block;height:.8rem;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.8rem}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}[dir=ltr] .md-typeset .headerlink{margin-left:.5rem}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem}.md-typeset .headerlink{color:var(--md-default-fg-color--lighter);display:inline-block;opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}.md-typeset .headerlink:focus,.md-typeset :-webkit-any(:hover,:target)>.headerlink{opacity:1;-webkit-transition:color .25s,opacity 125ms;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset :-moz-any(:hover,:target)>.headerlink{opacity:1;-moz-transition:color .25s,opacity 125ms;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset :is(:hover,:target)>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:-webkit-any(:focus,:hover),.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset .headerlink:-moz-any(:focus,:hover),.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset .headerlink:is(:focus,:hover),.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{--md-scroll-margin:3.6rem;--md-scroll-offset:0rem;scroll-margin-top:calc(var(--md-scroll-margin) - var(--md-scroll-offset))}@media screen and (min-width:76.25em){.md-header--lifted~.md-container .md-typeset :target{--md-scroll-margin:6rem}}.md-typeset :-webkit-any(h1,h2,h3):target{--md-scroll-offset:0.2rem}.md-typeset :-moz-any(h1,h2,h3):target{--md-scroll-offset:0.2rem}.md-typeset :is(h1,h2,h3):target{--md-scroll-offset:0.2rem}.md-typeset h4:target{--md-scroll-offset:0.15rem}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.9375em){.md-typeset div.arithmatex{margin:0 -.8rem}}.md-typeset div.arithmatex>*{margin-left:auto!important;margin-right:auto!important;padding:0 .8rem;touch-action:auto;width:-webkit-min-content;width:-moz-min-content;width:min-content}.md-typeset div.arithmatex>* mjx-container{margin:0!important}.md-typeset :-webkit-any(del,ins,.comment).critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset :-moz-any(del,ins,.comment).critic{box-decoration-break:clone}.md-typeset :is(del,ins,.comment).critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{box-shadow:none;display:block;margin:1em 0;overflow:auto;padding-left:.8rem;padding-right:.8rem}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset details{display:flow-root;overflow:visible;padding-top:0}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){box-shadow:none;padding-bottom:0}.md-typeset details:not([open])>summary{border-radius:.1rem}[dir=ltr] .md-typeset summary{padding-right:1.8rem}[dir=rtl] .md-typeset summary{padding-left:1.8rem}[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset summary{cursor:pointer;display:block;min-height:1rem}.md-typeset summary.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset summary:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[dir=ltr] .md-typeset summary:after{right:.4rem}[dir=rtl] .md-typeset summary:after{left:.4rem}.md-typeset summary:after{background-color:currentcolor;content:"";height:1rem;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;transform:rotate(0deg);transition:transform .25s;width:1rem}[dir=rtl] .md-typeset summary:after{transform:rotate(180deg)}.md-typeset summary::marker{display:none}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset :-webkit-any(.emojione,.twemoji,.gemoji){display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset :-moz-any(.emojione,.twemoji,.gemoji){display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset :is(.emojione,.twemoji,.gemoji){display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset :-webkit-any(.emojione,.twemoji,.gemoji) svg{fill:currentcolor;max-height:100%;width:1.125em}.md-typeset :-moz-any(.emojione,.twemoji,.gemoji) svg{fill:currentcolor;max-height:100%;width:1.125em}.md-typeset :is(.emojione,.twemoji,.gemoji) svg{fill:currentcolor;max-height:100%;width:1.125em}.highlight :-webkit-any(.o,.ow){color:var(--md-code-hl-operator-color)}.highlight :-moz-any(.o,.ow){color:var(--md-code-hl-operator-color)}.highlight :is(.o,.ow){color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight :-webkit-any(.cpf,.l,.s,.sb,.sc,.s2,.si,.s1,.ss){color:var(--md-code-hl-string-color)}.highlight :-moz-any(.cpf,.l,.s,.sb,.sc,.s2,.si,.s1,.ss){color:var(--md-code-hl-string-color)}.highlight :is(.cpf,.l,.s,.sb,.sc,.s2,.si,.s1,.ss){color:var(--md-code-hl-string-color)}.highlight :-webkit-any(.cp,.se,.sh,.sr,.sx){color:var(--md-code-hl-special-color)}.highlight :-moz-any(.cp,.se,.sh,.sr,.sx){color:var(--md-code-hl-special-color)}.highlight :is(.cp,.se,.sh,.sr,.sx){color:var(--md-code-hl-special-color)}.highlight :-webkit-any(.m,.mb,.mf,.mh,.mi,.il,.mo){color:var(--md-code-hl-number-color)}.highlight :-moz-any(.m,.mb,.mf,.mh,.mi,.il,.mo){color:var(--md-code-hl-number-color)}.highlight :is(.m,.mb,.mf,.mh,.mi,.il,.mo){color:var(--md-code-hl-number-color)}.highlight :-webkit-any(.k,.kd,.kn,.kp,.kr,.kt){color:var(--md-code-hl-keyword-color)}.highlight :-moz-any(.k,.kd,.kn,.kp,.kr,.kt){color:var(--md-code-hl-keyword-color)}.highlight :is(.k,.kd,.kn,.kp,.kr,.kt){color:var(--md-code-hl-keyword-color)}.highlight :-webkit-any(.kc,.n){color:var(--md-code-hl-name-color)}.highlight :-moz-any(.kc,.n){color:var(--md-code-hl-name-color)}.highlight :is(.kc,.n){color:var(--md-code-hl-name-color)}.highlight :-webkit-any(.no,.nb,.bp){color:var(--md-code-hl-constant-color)}.highlight :-moz-any(.no,.nb,.bp){color:var(--md-code-hl-constant-color)}.highlight :is(.no,.nb,.bp){color:var(--md-code-hl-constant-color)}.highlight :-webkit-any(.nc,.ne,.nf,.nn){color:var(--md-code-hl-function-color)}.highlight :-moz-any(.nc,.ne,.nf,.nn){color:var(--md-code-hl-function-color)}.highlight :is(.nc,.ne,.nf,.nn){color:var(--md-code-hl-function-color)}.highlight :-webkit-any(.nd,.ni,.nl,.nt){color:var(--md-code-hl-keyword-color)}.highlight :-moz-any(.nd,.ni,.nl,.nt){color:var(--md-code-hl-keyword-color)}.highlight :is(.nd,.ni,.nl,.nt){color:var(--md-code-hl-keyword-color)}.highlight :-webkit-any(.c,.cm,.c1,.ch,.cs,.sd){color:var(--md-code-hl-comment-color)}.highlight :-moz-any(.c,.cm,.c1,.ch,.cs,.sd){color:var(--md-code-hl-comment-color)}.highlight :is(.c,.cm,.c1,.ch,.cs,.sd){color:var(--md-code-hl-comment-color)}.highlight :-webkit-any(.na,.nv,.vc,.vg,.vi){color:var(--md-code-hl-variable-color)}.highlight :-moz-any(.na,.nv,.vc,.vg,.vi){color:var(--md-code-hl-variable-color)}.highlight :is(.na,.nv,.vc,.vg,.vi){color:var(--md-code-hl-variable-color)}.highlight :-webkit-any(.ge,.gr,.gh,.go,.gp,.gs,.gu,.gt){color:var(--md-code-hl-generic-color)}.highlight :-moz-any(.ge,.gr,.gh,.go,.gp,.gs,.gu,.gt){color:var(--md-code-hl-generic-color)}.highlight :is(.ge,.gr,.gh,.go,.gp,.gs,.gu,.gt){color:var(--md-code-hl-generic-color)}.highlight :-webkit-any(.gd,.gi){border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight :-moz-any(.gd,.gi){border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight :is(.gd,.gi){border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{background-color:var(--md-code-hl-color);display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em}.highlight span.filename{background-color:var(--md-code-bg-color);border-bottom:.05rem solid var(--md-default-fg-color--lightest);border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flow-root;font-size:.85em;font-weight:700;margin-top:1em;padding:.6617647059em 1.1764705882em;position:relative}.highlight span.filename+pre{margin-top:0}.highlight span.filename+pre>code{border-top-left-radius:0;border-top-right-radius:0}.highlight [data-linenos]:before{background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;color:var(--md-default-fg-color--light);content:attr(data-linenos);float:left;left:-1.1764705882em;margin-left:-1.1764705882em;margin-right:1.1764705882em;padding-left:1.1764705882em;position:-webkit-sticky;position:sticky;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:3}.highlight code a[id]{position:absolute;visibility:hidden}.highlight code[data-md-copying] .hll{display:contents}.highlight code[data-md-copying] .md-annotation{display:none}.highlighttable{display:flow-root}.highlighttable :-webkit-any(tbody,td){display:block;padding:0}.highlighttable :-moz-any(tbody,td){display:block;padding:0}.highlighttable :is(tbody,td){display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable th.filename{flex-grow:1;padding:0;text-align:left}.highlighttable th.filename span.filename{margin-top:0}.highlighttable .linenos{background-color:var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-top-left-radius:.1rem;font-size:.85em;padding:.7720588235em 0 .7720588235em 1.1764705882em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable .linenodiv{box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;padding-right:.5882352941em}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;min-width:0}.linenodiv a{color:inherit}.md-typeset .highlighttable{direction:ltr;margin:1em 0}.md-typeset .highlighttable>tbody>tr>.code>div>pre>code{border-bottom-left-radius:0;border-top-left-radius:0}.md-typeset .highlight+.result{border:.05rem solid var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top-width:.1rem;margin-top:-1.125em;overflow:visible;padding:0 1em}.md-typeset .highlight+.result:after{clear:both;content:"";display:block}@media screen and (max-width:44.9375em){.md-content__inner>.highlight{margin:1em -.8rem}.md-content__inner>.highlight>.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.code>div>pre>code,.md-content__inner>.highlight>.highlighttable>tbody>tr>.filename span.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.linenos,.md-content__inner>.highlight>pre>code{border-radius:0}.md-content__inner>.highlight+.result{border-left-width:0;border-radius:0;border-right-width:0;margin-left:-.8rem;margin-right:-.8rem}}.md-typeset .keys kbd:-webkit-any(:before,:after){-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys kbd:-moz-any(:before,:after){-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys kbd:is(:before,:after){-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys span{color:var(--md-default-fg-color--light);padding:0 .2em}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{content:"⎇";padding-right:.4em}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{content:"⌘";padding-right:.4em}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{content:"⌃";padding-right:.4em}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{content:"◆";padding-right:.4em}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{content:"⌥";padding-right:.4em}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{content:"⇧";padding-right:.4em}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{content:"❖";padding-right:.4em}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{content:"⊞";padding-right:.4em}.md-typeset .keys .key-arrow-down:before{content:"↓";padding-right:.4em}.md-typeset .keys .key-arrow-left:before{content:"←";padding-right:.4em}.md-typeset .keys .key-arrow-right:before{content:"→";padding-right:.4em}.md-typeset .keys .key-arrow-up:before{content:"↑";padding-right:.4em}.md-typeset .keys .key-backspace:before{content:"⌫";padding-right:.4em}.md-typeset .keys .key-backtab:before{content:"⇤";padding-right:.4em}.md-typeset .keys .key-caps-lock:before{content:"⇪";padding-right:.4em}.md-typeset .keys .key-clear:before{content:"⌧";padding-right:.4em}.md-typeset .keys .key-context-menu:before{content:"☰";padding-right:.4em}.md-typeset .keys .key-delete:before{content:"⌦";padding-right:.4em}.md-typeset .keys .key-eject:before{content:"⏏";padding-right:.4em}.md-typeset .keys .key-end:before{content:"⤓";padding-right:.4em}.md-typeset .keys .key-escape:before{content:"⎋";padding-right:.4em}.md-typeset .keys .key-home:before{content:"⤒";padding-right:.4em}.md-typeset .keys .key-insert:before{content:"⎀";padding-right:.4em}.md-typeset .keys .key-page-down:before{content:"⇟";padding-right:.4em}.md-typeset .keys .key-page-up:before{content:"⇞";padding-right:.4em}.md-typeset .keys .key-print-screen:before{content:"⎙";padding-right:.4em}.md-typeset .keys .key-tab:after{content:"⇥";padding-left:.4em}.md-typeset .keys .key-num-enter:after{content:"⌤";padding-left:.4em}.md-typeset .keys .key-enter:after{content:"⏎";padding-left:.4em}:root{--md-tabbed-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-tabbed-icon--next:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .tabbed-set{border-radius:.1rem;display:flex;flex-flow:column wrap;margin:1em 0;position:relative}.md-typeset .tabbed-set>input{height:0;opacity:0;position:absolute;width:0}.md-typeset .tabbed-set>input:target{--md-scroll-offset:0.625em}.md-typeset .tabbed-labels{-ms-overflow-style:none;box-shadow:0 -.05rem var(--md-default-fg-color--lightest) inset;display:flex;max-width:100%;overflow:auto;scrollbar-width:none}@media print{.md-typeset .tabbed-labels{display:contents}}@media screen{.js .md-typeset .tabbed-labels{position:relative}.js .md-typeset .tabbed-labels:before{background:var(--md-accent-fg-color);bottom:0;content:"";display:block;height:2px;left:0;position:absolute;transform:translateX(var(--md-indicator-x));transition:width 225ms,transform .25s;transition-timing-function:cubic-bezier(.4,0,.2,1);width:var(--md-indicator-width)}}.md-typeset .tabbed-labels::-webkit-scrollbar{display:none}.md-typeset .tabbed-labels>label{border-bottom:.1rem solid transparent;border-radius:.1rem .1rem 0 0;color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;font-size:.64rem;font-weight:700;padding:.78125em 1.25em .625em;scroll-margin-inline-start:1rem;transition:background-color .25s,color .25s;white-space:nowrap;width:auto}@media print{.md-typeset .tabbed-labels>label:first-child{order:1}.md-typeset .tabbed-labels>label:nth-child(2){order:2}.md-typeset .tabbed-labels>label:nth-child(3){order:3}.md-typeset .tabbed-labels>label:nth-child(4){order:4}.md-typeset .tabbed-labels>label:nth-child(5){order:5}.md-typeset .tabbed-labels>label:nth-child(6){order:6}.md-typeset .tabbed-labels>label:nth-child(7){order:7}.md-typeset .tabbed-labels>label:nth-child(8){order:8}.md-typeset .tabbed-labels>label:nth-child(9){order:9}.md-typeset .tabbed-labels>label:nth-child(10){order:10}.md-typeset .tabbed-labels>label:nth-child(11){order:11}.md-typeset .tabbed-labels>label:nth-child(12){order:12}.md-typeset .tabbed-labels>label:nth-child(13){order:13}.md-typeset .tabbed-labels>label:nth-child(14){order:14}.md-typeset .tabbed-labels>label:nth-child(15){order:15}.md-typeset .tabbed-labels>label:nth-child(16){order:16}.md-typeset .tabbed-labels>label:nth-child(17){order:17}.md-typeset .tabbed-labels>label:nth-child(18){order:18}.md-typeset .tabbed-labels>label:nth-child(19){order:19}.md-typeset .tabbed-labels>label:nth-child(20){order:20}}.md-typeset .tabbed-labels>label:hover{color:var(--md-accent-fg-color)}.md-typeset .tabbed-content{width:100%}@media print{.md-typeset .tabbed-content{display:contents}}.md-typeset .tabbed-block{display:none}@media print{.md-typeset .tabbed-block{display:block}.md-typeset .tabbed-block:first-child{order:1}.md-typeset .tabbed-block:nth-child(2){order:2}.md-typeset .tabbed-block:nth-child(3){order:3}.md-typeset .tabbed-block:nth-child(4){order:4}.md-typeset .tabbed-block:nth-child(5){order:5}.md-typeset .tabbed-block:nth-child(6){order:6}.md-typeset .tabbed-block:nth-child(7){order:7}.md-typeset .tabbed-block:nth-child(8){order:8}.md-typeset .tabbed-block:nth-child(9){order:9}.md-typeset .tabbed-block:nth-child(10){order:10}.md-typeset .tabbed-block:nth-child(11){order:11}.md-typeset .tabbed-block:nth-child(12){order:12}.md-typeset .tabbed-block:nth-child(13){order:13}.md-typeset .tabbed-block:nth-child(14){order:14}.md-typeset .tabbed-block:nth-child(15){order:15}.md-typeset .tabbed-block:nth-child(16){order:16}.md-typeset .tabbed-block:nth-child(17){order:17}.md-typeset .tabbed-block:nth-child(18){order:18}.md-typeset .tabbed-block:nth-child(19){order:19}.md-typeset .tabbed-block:nth-child(20){order:20}}.md-typeset .tabbed-block>.highlight:first-child>pre,.md-typeset .tabbed-block>pre:first-child{margin:0}.md-typeset .tabbed-block>.highlight:first-child>pre>code,.md-typeset .tabbed-block>pre:first-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child>.filename{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable{margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.filename span.filename,.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.linenos{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.code>div>pre>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child+.result{margin-top:-.125em}.md-typeset .tabbed-block>.tabbed-set{margin:0}.md-typeset .tabbed-button{align-self:center;border-radius:100%;color:var(--md-default-fg-color--light);cursor:pointer;display:block;height:.9rem;margin-top:.1rem;pointer-events:auto;transition:background-color .25s;width:.9rem}.md-typeset .tabbed-button:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .tabbed-button:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-tabbed-icon--prev);mask-image:var(--md-tabbed-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color .25s,transform .25s;width:100%}.md-typeset .tabbed-control{background:linear-gradient(to right,var(--md-default-bg-color) 60%,transparent);display:flex;height:1.9rem;justify-content:start;pointer-events:none;position:absolute;transition:opacity 125ms;width:1.2rem}[dir=rtl] .md-typeset .tabbed-control{transform:rotate(180deg)}.md-typeset .tabbed-control[hidden]{opacity:0}.md-typeset .tabbed-control--next{background:linear-gradient(to left,var(--md-default-bg-color) 60%,transparent);justify-content:end;right:0}.md-typeset .tabbed-control--next .tabbed-button:after{-webkit-mask-image:var(--md-tabbed-icon--next);mask-image:var(--md-tabbed-icon--next)}@media screen and (max-width:44.9375em){[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels{margin:0 -.8rem;max-width:100vw;scroll-padding-inline-start:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels:after{content:""}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-left:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-right:-.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{width:2rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-right:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-left:-.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{width:2rem}}@media screen{.md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){color:var(--md-accent-fg-color)}.md-typeset .no-js .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .no-js .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .no-js .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .no-js .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .no-js .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .no-js .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .no-js .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .no-js .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .no-js .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .no-js .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .no-js .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .no-js .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .no-js .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .no-js .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .no-js .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .no-js .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .no-js .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .no-js .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .no-js .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .no-js .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.no-js .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.no-js .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.no-js .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.no-js .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.no-js .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.no-js .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.no-js .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.no-js .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.no-js .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.no-js .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.no-js .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.no-js .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.no-js .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.no-js .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.no-js .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.no-js .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.no-js .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.no-js .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.no-js .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.no-js .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){border-color:var(--md-accent-fg-color)}}.md-typeset .tabbed-set>input:first-child.focus-visible~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10).focus-visible~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11).focus-visible~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12).focus-visible~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13).focus-visible~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14).focus-visible~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15).focus-visible~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16).focus-visible~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17).focus-visible~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18).focus-visible~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19).focus-visible~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2).focus-visible~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20).focus-visible~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3).focus-visible~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4).focus-visible~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5).focus-visible~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6).focus-visible~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7).focus-visible~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8).focus-visible~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9).focus-visible~.tabbed-labels>:nth-child(9){background-color:var(--md-accent-fg-color--transparent)}.md-typeset .tabbed-set>input:first-child:checked~.tabbed-content>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-content>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-content>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-content>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-content>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-content>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-content>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-content>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-content>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-content>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-content>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-content>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-content>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-content>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-content>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-content>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-content>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-content>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-content>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-content>:nth-child(9){display:block}:root{--md-tasklist-icon:url('data:image/svg+xml;charset=utf-8,');--md-tasklist-icon--checked:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .task-list-item{list-style-type:none;position:relative}[dir=ltr] .md-typeset .task-list-item [type=checkbox]{left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}[dir=ltr] .md-typeset .task-list-indicator:before{left:-1.5em}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em}.md-typeset .task-list-indicator:before{background-color:var(--md-default-fg-color--lightest);content:"";height:1.25em;-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.15em;width:1.25em}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}:root>*{--md-mermaid-font-family:var(--md-text-font-family),sans-serif;--md-mermaid-edge-color:var(--md-code-fg-color);--md-mermaid-node-bg-color:var(--md-accent-fg-color--transparent);--md-mermaid-node-fg-color:var(--md-accent-fg-color);--md-mermaid-label-bg-color:var(--md-default-bg-color);--md-mermaid-label-fg-color:var(--md-code-fg-color)}.mermaid{line-height:normal;margin:1em 0}@media screen and (min-width:45em){[dir=ltr] .md-typeset .inline{margin-right:.8rem}[dir=rtl] .md-typeset .inline{margin-left:.8rem}.md-typeset .inline{float:left;margin-bottom:.8rem;margin-top:0;width:11.7rem}[dir=rtl] .md-typeset .inline{float:right}[dir=ltr] .md-typeset .inline.end{margin-left:.8rem;margin-right:0}[dir=rtl] .md-typeset .inline.end{margin-left:0;margin-right:.8rem}.md-typeset .inline.end{float:right}[dir=rtl] .md-typeset .inline.end{float:left}} \ No newline at end of file diff --git a/assets/stylesheets/main.4a0965b7.min.css.map b/assets/stylesheets/main.4a0965b7.min.css.map new file mode 100644 index 00000000..56beafb5 --- /dev/null +++ b/assets/stylesheets/main.4a0965b7.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["src/assets/stylesheets/main/extensions/pymdownx/_keys.scss","../../../src/assets/stylesheets/main.scss","src/assets/stylesheets/main/_resets.scss","src/assets/stylesheets/main/_colors.scss","src/assets/stylesheets/main/_icons.scss","src/assets/stylesheets/main/_typeset.scss","src/assets/stylesheets/utilities/_break.scss","src/assets/stylesheets/main/layout/_banner.scss","src/assets/stylesheets/main/layout/_base.scss","src/assets/stylesheets/main/layout/_clipboard.scss","src/assets/stylesheets/main/layout/_content.scss","src/assets/stylesheets/main/layout/_dialog.scss","src/assets/stylesheets/main/layout/_footer.scss","src/assets/stylesheets/main/layout/_form.scss","src/assets/stylesheets/main/layout/_header.scss","src/assets/stylesheets/main/layout/_nav.scss","src/assets/stylesheets/main/layout/_search.scss","src/assets/stylesheets/main/layout/_select.scss","src/assets/stylesheets/main/layout/_sidebar.scss","src/assets/stylesheets/main/layout/_source.scss","src/assets/stylesheets/main/layout/_tabs.scss","src/assets/stylesheets/main/layout/_tag.scss","src/assets/stylesheets/main/layout/_tooltip.scss","src/assets/stylesheets/main/layout/_top.scss","src/assets/stylesheets/main/layout/_version.scss","src/assets/stylesheets/main/extensions/markdown/_admonition.scss","node_modules/material-design-color/material-color.scss","src/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/assets/stylesheets/main/extensions/markdown/_toc.scss","src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/assets/stylesheets/main/integrations/_mermaid.scss","src/assets/stylesheets/main/_modifiers.scss"],"names":[],"mappings":"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C+BlnGE,4CACE,mBAAA,CACA,cAAA,CACA,uB/B+mGJ,C+B5mGI,yDAGE,iBAAA,CADA,eAAA,CADA,a/BgnGN,C+BjnGI,sDAGE,iBAAA,CADA,eAAA,CADA,a/BgnGN,C+BjnGI,gDAGE,iBAAA,CADA,eAAA,CADA,a/BgnGN,CgCtnGE,gCACE,sChCynGJ,CgC1nGE,6BACE,sChCynGJ,CgC1nGE,uBACE,sChCynGJ,CgCtnGE,cACE,yChCwnGJ,CgC5mGE,4DACE,oChC8mGJ,CgC/mGE,yDACE,oChC8mGJ,CgC/mGE,mDACE,oChC8mGJ,CgCtmGE,6CACE,qChCwmGJ,CgCzmGE,0CACE,qChCwmGJ,CgCzmGE,oCACE,qChCwmGJ,CgC9lGE,oDACE,oChCgmGJ,CgCjmGE,iDACE,oChCgmGJ,CgCjmGE,2CACE,oChCgmGJ,CgCvlGE,gDACE,qChCylGJ,CgC1lGE,6CACE,qChCylGJ,CgC1lGE,uCACE,qChCylGJ,CgCplGE,gCACE,kChCslGJ,CgCvlGE,6BACE,kChCslGJ,CgCvlGE,uBACE,kChCslGJ,CgChlGE,qCACE,sChCklGJ,CgCnlGE,kCACE,sChCklGJ,CgCnlGE,4BACE,sChCklGJ,CgC3kGE,yCACE,sChC6kGJ,CgC9kGE,sCACE,sChC6kGJ,CgC9kGE,gCACE,sChC6kGJ,CgCtkGE,yCACE,qChCwkGJ,CgCzkGE,sCACE,qChCwkGJ,CgCzkGE,gCACE,qChCwkGJ,CgC/jGE,gDACE,qChCikGJ,CgClkGE,6CACE,qChCikGJ,CgClkGE,uCACE,qChCikGJ,CgCzjGE,6CACE,sChC2jGJ,CgC5jGE,0CACE,sChC2jGJ,CgC5jGE,oCACE,sChC2jGJ,CgChjGE,yDACE,qChCkjGJ,CgCnjGE,sDACE,qChCkjGJ,CgCnjGE,gDACE,qChCkjGJ,CgC7iGE,iCAGE,mBAAA,CAFA,gBAAA,CACA,gBhCgjGJ,CgCljGE,8BAGE,mBAAA,CAFA,gBAAA,CACA,gBhCgjGJ,CgCljGE,wBAGE,mBAAA,CAFA,gBAAA,CACA,gBhCgjGJ,CgC5iGE,eACE,4ChC8iGJ,CgC3iGE,eACE,4ChC6iG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","file":"main.css"} \ No newline at end of file diff --git a/assets/stylesheets/palette.cbb835fc.min.css b/assets/stylesheets/palette.cbb835fc.min.css new file mode 100644 index 00000000..30f9264c --- /dev/null +++ b/assets/stylesheets/palette.cbb835fc.min.css @@ -0,0 +1 @@ +@media screen{[data-md-color-scheme=slate]{--md-hue:232;--md-default-fg-color:hsla(var(--md-hue),75%,95%,1);--md-default-fg-color--light:hsla(var(--md-hue),75%,90%,0.62);--md-default-fg-color--lighter:hsla(var(--md-hue),75%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),75%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,21%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,21%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,21%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,21%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,1);--md-code-bg-color:hsla(var(--md-hue),15%,15%,1);--md-code-hl-color:rgba(66,135,255,.15);--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(66,135,255,.3);--md-typeset-kbd-color:hsla(var(--md-hue),15%,94%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,94%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-typeset-table-color:hsla(var(--md-hue),75%,95%,0.12);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-bg-color:hsla(var(--md-hue),15%,12%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,10%,1);--md-shadow-z1:0 0.2rem 0.5rem rgba(0,0,0,.2),0 0 0.05rem rgba(0,0,0,.1);--md-shadow-z2:0 0.2rem 0.5rem rgba(0,0,0,.3),0 0 0.05rem rgba(0,0,0,.25);--md-shadow-z3:0 0.2rem 0.5rem rgba(0,0,0,.4),0 0 0.05rem rgba(0,0,0,.35)}[data-md-color-scheme=slate] img[src$="#gh-light-mode-only"],[data-md-color-scheme=slate] img[src$="#only-light"]{display:none}[data-md-color-scheme=slate] img[src$="#gh-dark-mode-only"],[data-md-color-scheme=slate] img[src$="#only-dark"]{display:initial}[data-md-color-scheme=slate][data-md-color-primary=pink]{--md-typeset-a-color:#ed5487}[data-md-color-scheme=slate][data-md-color-primary=purple]{--md-typeset-a-color:#bd78c9}[data-md-color-scheme=slate][data-md-color-primary=deep-purple]{--md-typeset-a-color:#a682e3}[data-md-color-scheme=slate][data-md-color-primary=indigo]{--md-typeset-a-color:#6c91d5}[data-md-color-scheme=slate][data-md-color-primary=teal]{--md-typeset-a-color:#00ccb8}[data-md-color-scheme=slate][data-md-color-primary=green]{--md-typeset-a-color:#71c174}[data-md-color-scheme=slate][data-md-color-primary=deep-orange]{--md-typeset-a-color:#ff9575}[data-md-color-scheme=slate][data-md-color-primary=brown]{--md-typeset-a-color:#c7846b}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=blue-grey],[data-md-color-scheme=slate][data-md-color-primary=grey],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#6c91d5}[data-md-color-switching] *,[data-md-color-switching] :after,[data-md-color-switching] :before{transition-duration:0ms!important}}[data-md-color-accent=red]{--md-accent-fg-color:#ff1947;--md-accent-fg-color--transparent:rgba(255,25,71,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:rgba(245,0,86,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:rgba(223,65,251,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:rgba(124,77,255,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(82,108,254,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:rgba(66,135,255,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:rgba(0,145,235,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:rgba(0,186,214,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:rgba(0,189,164,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:rgba(0,199,83,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:rgba(99,222,23,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:rgba(176,235,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:rgba(255,213,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:rgba(255,170,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:rgba(255,145,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:rgba(255,110,66,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=light-green]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#72ad2e}[data-md-color-primary=lime]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#8b990a}[data-md-color-primary=yellow]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#b8a500}[data-md-color-primary=amber]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#d19d00}[data-md-color-primary=orange]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#e68a00}[data-md-color-primary=white]{--md-primary-fg-color:#fff;--md-primary-fg-color--light:hsla(0,0%,100%,.7);--md-primary-fg-color--dark:rgba(0,0,0,.07);--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54);--md-typeset-a-color:#4051b5}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__form{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__form:hover{background-color:rgba(0,0,0,.32)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:rgba(0,0,0,.87)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07)}}[data-md-color-primary=black]{--md-primary-fg-color:#000;--md-primary-fg-color--light:rgba(0,0,0,.54);--md-primary-fg-color--dark:#000;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-header{background-color:#000}@media screen and (max-width:59.9375em){[data-md-color-primary=black] .md-nav__source{background-color:rgba(0,0,0,.87)}}@media screen and (min-width:60em){[data-md-color-primary=black] .md-search__form{background-color:hsla(0,0%,100%,.12)}[data-md-color-primary=black] .md-search__form:hover{background-color:hsla(0,0%,100%,.3)}}@media screen and (max-width:76.1875em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:#000}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:#000}} \ No newline at end of file diff --git a/assets/stylesheets/palette.cbb835fc.min.css.map b/assets/stylesheets/palette.cbb835fc.min.css.map new file mode 100644 index 00000000..96e380c8 --- /dev/null +++ b/assets/stylesheets/palette.cbb835fc.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["src/assets/stylesheets/palette/_scheme.scss","../../../src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"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","file":"palette.css"} \ No newline at end of file diff --git a/basic_usage/index.html b/basic_usage/index.html new file mode 100644 index 00000000..bc5a7754 --- /dev/null +++ b/basic_usage/index.html @@ -0,0 +1,72 @@ + Basic usage - gNMIc

    Basic usage

    The following examples demonstrate the basic usage of gnmic in a scenario where the remote target runs an unsecured (without TLS enabled) gNMI server. The admin:admin credentials are used to connect to the gNMI server running at 10.1.0.11:57400 address.

    Info

    For the complete command usage examples, refer to the "Command reference" menu.

    Capabilities RPC#

    Getting the device's capabilities is done with capabilities command:

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure capabilities
    +gNMI_Version: 0.7.0
    +supported models:
    +  - nokia-conf, Nokia, 19.10.R2
    +  - nokia-state, Nokia, 19.10.R2
    +  - nokia-li-state, Nokia, 19.10.R2
    +  - nokia-li-conf, Nokia, 19.10.R2
    +<< SNIPPED >>
    +supported encodings:
    +  - JSON
    +  - BYTES
    +

    Get RPC#

    Retrieving the data snapshot from the target device is done with get command:

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \
    +      get --path /state/system/platform
    +
    +{
    +  "source": "10.1.0.11:57400",
    +  "timestamp": 1592829586901061761,
    +  "time": "2020-06-22T14:39:46.901061761+02:00",
    +  "updates": [
    +    {
    +      "Path": "state/system/platform",
    +      "values": {
    +        "state/system/platform": "7750 SR-1s"
    +      }
    +    }
    +  ]
    +}
    +

    Set RPC#

    Modifying state of the target device is done with set command:

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \
    +      set --update-path /configure/system/name \
    +          --update-value gnmic_demo
    +
    +{
    +  "source": "0.tcp.eu.ngrok.io:12267",
    +  "timestamp": 1592831593821038738,
    +  "time": "2020-06-22T15:13:13.821038738+02:00",
    +  "results": [
    +    {
    +      "operation": "UPDATE",
    +      "path": "configure/system/name"
    +    }
    +  ]
    +}
    +

    Subscribe RPC#

    Subscription to the gNMI telemetry data can be done with subscribe command:

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \
    +      sub --path "/state/port[port-id=1/1/c1/1]/statistics/in-packets"
    +
    +{
    +  "source": "0.tcp.eu.ngrok.io:12267",
    +  "timestamp": 1592832965197288856,
    +  "time": "2020-06-22T15:36:05.197288856+02:00",
    +  "prefix": "state/port[port-id=1/1/c1/1]/statistics",
    +  "updates": [
    +    {
    +      "Path": "in-packets",
    +      "values": {
    +        "in-packets": "12142"
    +      }
    +    }
    +  ]
    +}
    +

    YANG path browser#

    gnmic can produce a list of XPATH/gNMI paths for a given YANG model with its path command. The paths in that list can be used as the --path values for the Get/Set/Subscribe commands.

    # nokia model
    +gnmic path -m nokia-state --file nokia-state-combined.yang | head -10
    +/state/aaa/radius/statistics/coa/dropped/bad-authentication
    +/state/aaa/radius/statistics/coa/dropped/missing-auth-policy
    +/state/aaa/radius/statistics/coa/dropped/invalid
    +/state/aaa/radius/statistics/coa/dropped/missing-resource
    +/state/aaa/radius/statistics/coa/received
    +/state/aaa/radius/statistics/coa/accepted
    +/state/aaa/radius/statistics/coa/rejected
    +/state/aaa/radius/statistics/disconnect-messages/dropped/bad-authentication
    +/state/aaa/radius/statistics/disconnect-messages/dropped/missing-auth-policy
    +/state/aaa/radius/statistics/disconnect-messages/dropped/invalid
    +
    \ No newline at end of file diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 00000000..9721a2e1 --- /dev/null +++ b/blog/index.html @@ -0,0 +1 @@ + Index - gNMIc

    Index

    Coming soon

    \ No newline at end of file diff --git a/changelog/index.html b/changelog/index.html new file mode 100644 index 00000000..c9b49634 --- /dev/null +++ b/changelog/index.html @@ -0,0 +1,3 @@ + Changelog - gNMIc

    Changelog

    Changelog#

    v 0.35.0 - January 20th 2024#

    • Processors

      • Added a plugin process type that allows users to write their own custom processors: examples
    • gRPC metadata

      • A new flag --metadata | -H is introduced. It allows users to add custom gRPC metadata headers to any request.
    • Outputs:

      • Kafka output:

        • Added support for custom topics per target/subscription.

        • Added support for both Async and Sync Kafka producers.

    • Commands:

      • Listen command: When using the listen command outputs internal metrics are properly initialized and exposed to prometheus for scraping.

    v 0.34.0 - November 11th 2023#

    • Prometheus Write Output

      • The number of prometheus_write writers can now be configured.
    • Subscription Encoding

      • A subscription encoding can now be set per target. Before, it was either a global attribute or set per subscription. With this change, it can be set globally, per target or per subscription.
    • Processors:

      • New event-combine processor: A convenience processor that allows combining other processors into a single one.

      • New event-rate-limit processor: A processor that rate-limits each event with matching tags to the configured amount per-seconds.

    • Outputs:

    • Clustering:

      • New redis locker: For leader election, service discovery and target distribution gNMIc supports both Consul and Kubernetes. It is now possible to use redis for the same purpose.

    v 0.33.0 - October 8th 2023#

    • Rest API

      • Added a kubernetes friendly api/v1/healthz endpoint.
    • Set Command

    • Outputs

      • Allow the number of workers used by the prometheus and prometheus_write outputs to be configurable to improve performance.
    • Go version

      • Upgrade to Golang v1.21.1.

    v0.32.0 - August 31st 2023#

    • TLS

      • It is now possible to override the serverName used by gNMIc when verifying the server name present in the certificate sent by the gNMI server. PR
    • Subscription

      • Added support for mixing on-change and sample stream subscription in the same gRPC stream. PR
      • Added support for attaching specific outputs to a subscription. PR
    • REST API

      • Added a health chek endpoint to be used by kubernetes. PR
    • Kafka Output

      • Added support for Kafka compression. PR
    • Generate Path

      • Added enum-values to the JSON output of generate path command. PR

    v0.31.0 - May 17th 2023#

    • Prometheus output

      • When using the Consul auto discovery feature of the Proemtheus output, it is now possible to configure different service and listen addresses. This is useful when gNMIc is running as a container of behind a NAT device
    • Set Request file

      • The CLI origin is now allowed in the path field of updates, replaces and deletes in a set request file. If the path field has the cli:/ origin, the value field is expected to be a string and will be set in an ascii TypedValue.

    v0.30.0 - April 18th 2023#

    • Set Command

      • The set command now supports the flags --replace-cli, --replace-cli-file, --update-cli and --update-cli-file, these flags can be used to send gNMI set requests with the CLI origin.
    • Logging:

      • Reduce log verbosity of File and HTTP target discovery mechanisms.
    • Processors:

      • The Drop event processor completely removes the message to be dropped instead of replacing it with an empty message.
    • Inputs:

    • Outputs:

      • Kafka output now has a configuration attribute called insert-key, if true, the messages written will include a key built from the gNMI message source and subscription name.

      • TCP output now has a configuration attribute called delimiter, it allows to set user defined string to be sent between each message. This allows the receiving end to properly split JSON objects. It it particularly useful with Logstash when writing gNMI events to an ELK stack.

    • TLS:

      • When using gNMIc's components that expose a TLS server (gNMI server, Tunnel server, Rest API and Prometheus output) it's possible to fine tune the how the server requests and validates a client certificate.

        This is done using the configuration attribute client-auth under each server's TLS section, it takes 4 different values:

        • request: The server requests a certificate from the client but does not require the client to send a certificate. If the client sends a certificate, it is not required to be valid.

        • require: The server requires the client to send a certificate and does not fail if the client certificate is not valid.

        • verify-if-given: The server requests a certificate, does not fail if no certificate is sent. If a certificate is sent it is required to be valid.

        • require-verify: The server requires the client to send a valid certificate.

    • Diff Command:

      • The diff command has 2 new sub commands:

        • setrequest: compares the intent between two SetRequest messages encoded in textproto format.

        • set-to-notifs: verifies whether a set of notifications from a GetResponse or a stream of SubscribeResponse messages comply with a SetRequest messages in textproto format. The envisioned use case is to check whether a stored snapshot of device state matches that of the intended state as specified by a SetRequest.

    • Outputs:

      • When using the event format with certain outputs (file, nats, jetstream, kafka, tcp or udp) it's possible to send event message individually as opposed to sending them in an array. This is done using the attribute split-events: true under each of the outputs configuration sections.

      • Prometheus output now supports a custom service address field under service-registration, it specifies the address to be registered in Consul for discovery. It can be a hostname, an IP address or a IP/Host:Port socket address. It it does not contain a port number, the port number from the listen field is used.

    • Set Request file

      • The Set request file can be used with Origin cli, gNMIc will properly format the commands as string, not as JSON value.

    v0.29.0 - February 20th 2023#

    • Generate Path

      • The generate path command with the flag --json shows the features the path depends on. The list of features is built recursively from the YANG attribute if-feature.
    • Processors:

    • Loaders

      • The HTTP loader now supports different authentication schemas as well as setting a template from a local file.

    v0.28.0 - December 7th 2022#

    • Targets

      • Targets static tags are now properly propagated to outputs when a cache is used.
    • Listen Command:

      • The system-name HTTP2 header is now used as a tag in exported metrics.
    • Outputs:

      • The timestamp precision under gNMIc's InfluxDB output is now configurable.

      • Added a new snmp output type, it allows to dynamically convert gNMI updates into SNMP traps.

    v0.27.0 - October 8th 2022#

    • Targets

      • Add supports for socks5 proxies per target.
    • Logging

      • Support for log rotation via the flags --log-max-size, log-max-backups and --log-compress

    v0.26.0 - June 28th 2022#

    v0.25.1 - June 13th 2022#

    • Upgrade Go version to go1.18.1.

    • Fix running gnmic subscribe with only Inputs and Outputs configured (no subscriptions or targets).

    v0.25.0 - June 11th 2022#

    • Processors

    • New Processors

    • Clustering

      • gNMIc supports kubernetes based clustering, i.e you can build gNMIc clusters on kubernetes without the need for Consul cluster.
    • Yang path generation

      • The command gnmic generate path supports generating paths for YANG containers. In earlier versions, the paths generation was done for YANG leaves only.
    • Internal gNMIc Prometheus metrics

      gNMIc exposes additional internal metrics available to be scraped using Prometheus.

    • Static tags from target configuration

      • It is now possible to set static tags on events by configuring them under each target.
    • Influxdb cache

      The InfluxDB output now supports gNMI based caching, allowing to apply processors on multiple event messages at once and batching the written points to InfluxDB.

    v0.24.0 - March 13th 2022#

    • gRPC Tunnel Support

      Add support for gNMI RPC using a gRPC tunnel, gNMIc runs as a collector with an embedded tunnel server.

    v0.23.0 - February 24th 2022#

    • Docker image:

      • The published gnmic docker image is now based on alpine instead of an empty container.
      • A from scratch image is published and can be obtained using the command:
        docker pull ghcr.io/karimra/gnmic:latest-scratch
        +docker pull ghcr.io/karimra/gnmic:v0.23.0-scratch
        +
    • gNMIc Golang API:

      • Add gNMI responses constructors
      • Add gRPC tunnel proto messages constructors
    • Target Discovery:

      • Add the option to transform the loaded targets format using a Go text template for file and HTTP loaders
      • Poll based target loaders (file, HTTP and docker) now support a startup delay timer

    v0.22.1 - February 2nd 2022#

    • Fix a Prometheus output issue when using gNMI cache that causes events to be missing from the metrics.

    v0.22.0 - February 1st 2022#

    • gNMIc Golang API:

      Added the github.com/karimra/gnmic/api golang package. It can be imported by other Golang programs to ease the creation of gNMI targets and gNMI Requests.

    v0.21.0 - January 23rd 2022#

    • Generate Cmd:

      Add YANG module namespace to generated paths.

    • Outputs:

      Outputs File, NATS and Kafka now support a msg-template field to customize the written messages using Go templates.

    • API:

      Add Cluster API endpoints.

    • Actions:

      Add Template action.

      Add Subscribe ONCE RPC to gNMI action.

      Allow gNMI action on multiple targets.

      Add Script action.

    • Get Cmd:

      Implement Format event for GetResponse messages.

      Add the ability to execute processors with Get command flag --processor on GetResponse messages.

    • Target Discovery:

      Add the ability to run actions on target discovery or deletion.

    • Set Cmd:

      Add --dry-run flag which runs the set request templates and prints their output without sending the SetRequest to the targets.

    • TLS:

      Add pre-master key logging for TLS connections using the flag --log-tls-secret. The key can be used to decrypt encrypted gNMI messages using wireshark.

    • Target:

      Add target.Stop() method to gracefully close the target underlying gRPC connection.

    v0.20.0 - October 19th 2021#

    • Add gomplate template functions to all templates rendered by gnmic.

    • Path generation:

      gnmic generate path supports generating paths with type and description in JSON format.

    • Set RPC template:

      Set RPC supports multiple template files in a single command.

    • Clustering:

      gnmic clusters can be formed using secure (HTTPS) API endpoints.

    • Configuration payload generation:

      Configuration keys can now be formatted as camelCase or snake_case strings

    v0.19.1 - October 7th 2021#

    • Path search

    Do not enter search mode if not paths are found.

    Change the default service name when registering with a Consul server

    v0.19.0 - September 16th 2021#

    • Event Processors

      Event Convert now converts binary float notation to float

    • Target Loaders:

      gNMIc can now dynamically discover targets from a remote HTTP server.

      HTTP Loader is now properly instrumented using Prometheus metrics.

      Supports remote files (ftp, sftp, http(s)) in addition to local file system files.

      File loader is now properly instrumented using Prometheus metrics.

      Consul Loader is now properly instrumented using Prometheus metrics.

      Docker Loader is now properly instrumented using Prometheus metrics.

    • gRPC

      gNMIc now adds its version as part of the user-agent HTTP header.

    v0.18.0 - August 17th 2021#

    • gNMI Server:

      Add support for a global gNMI server. It supports all types of subscriptions, ran against a local cache build out the configured subscriptions. It support Get and Set RPCs as well, those are run against the configured targets.

      The gNMI server supports Consul based service registration.

    • Outputs:

      Add support for gNMI server output type

    • Target configuration:

      Support multiple IP addresses per target, all addresses are tried simultaneously. The first successful gRPC connection is used.

    • Prometheus Output:

      Add the option of generating Prometheus metrics on-scrape, instead of on-reception. The gNMI notifications are stored in a local cache and used to generate metrics when a Prometheus server sends a scrape request.

    • Event Processors:

      Add group-by processor, it groups events together based on a given criteria. The events can belong to different gNMI notifications or even to different subscriptions.

    • Event Processor Convert:

      Add support for boolean conversion

    • Deployment Examples:

      Add containerlab based deployment examples. These deployment come with a router fabric built using Nokia's SRL

    • API server:

      Add Secure API server configuration options

    • Target Loaders:

      Consul loader update: Add support for gNMI target discovery from Consul services.

    • Get Request:

      Add printing of Target as part of Path Prefix

    • Set Request:

      Add printing of Target as part of Path Prefix

    v0.17.0 - July 14th 2021#

    • Event Trigger:

      Enhance event-trigger to run multiple actions sequentially when an event occurs.

      The output of an action can be used in the following ones.

    • Kafka output:

      Add SASL_SSL and SSL security protocols to kafka output.

    • gRPC authentication:

      Add support for token based gRPC authentication.

    v0.16.2 - July 13th 2021#

    • Fix nil pointer dereference in case a subscription has suppress-redundant but no heartbeat-interval.

    v0.16.1 - July 12th 2021#

    • Bump github.com/openconfig/goyang version to v0.2.7

    v0.16.0 - June 14th 2021#

    • Target Discovery:

      Add Docker Engine target loader, gnmic can dynamically discover gNMI targets running as docker containers.

    • Event Trigger: gNMI action

      Enhance gNMI action to take external variables as input, in addition to the received gNMI update.

    v0.15.0 - June 7th 2021#

    • Subscription:

    Add field set-target under subscription config, a boolean that enables setting the target name as a gNMI prefix target.

    • Outputs:

    Add add-target and target-template fields under all outputs, Enables adding the target value as a tag/label based on the subscription and target metadata

    v0.14.3 - June 6th 2021#

    • Set command:

      Fix ascii values encoding if used with --request-file flag.

    v0.14.2 - June 3rd 2021#

    • Fix event-convert processor when the conversion is between integer types.
    • Add an implicit conversion of uint to int if the influxdb output version is 1.8.x. This is a workaround for the limited support of influx APIv2 by influxDB1.8

    v0.14.1 - May 31st 2021#

    • Fix OverrideTS processor
    • Add override-timestamps option under outputs, to override the message timestamps regardless of the message output format

    v0.14.0 - May 28th 2021#

    • New Output format flat

      • This format prints the Get and Subscribe RPCs as a list of xpath: value, where the xpath points to a leaf value.
    • New gnmic diff command:

      • This command prints the difference in responses between a reference target --ref and one or more targets to be compared to the reference --compare.
      • The output is printed as flat format results.

    v0.13.0 - May 10th 2021#

    • New gnmic generate Command:

      • Given a set of yang models and an xpath, gnmic generate generates a JSON/YAML representation of the YANG object the given path points to.
      • Given a set of yang models and an set of xpaths (with --update or --replace), gnmic generate set-request generates a set request file that can be filled with the desired values and used with gnmic set --request-file
      • The sub-command gnmic generate path is an alias to gnmic path
    • Path Command:

      • add flag --desc which, if present, prints the YANG leaf description together with the generated paths.
      • add flag --config-only which, if present, only generates paths pointing to YANG leaves representing config data.
      • add flag --state-only which, if present, only generates paths pointing to a YANG leaf representing state data.

    v0.12.2 - April 24th 2021#

    • Fix a bug that cause gNMIc to crash if certain processors are used.

    v0.12.1 - April 21st 2021#

    • Fix parsing of stringArray flags containing a space.

    v0.12.0 - April 20th 2021#

    • Outputs:
      • InfluxDB and Prometheus outputs: Convert gNMI Decimal64 values to Float64.
    • Set Command:
      • Add the ability to run a Set command using a single file, including replaces, updates and deletes.
      • The request file --request-file is either a static file or a Golang Text Template rendered separately for each target.
      • The template input is read from a file referenced by the flag --request-vars.

    v0.11.0 - April 15th 2021#

    • Processors:
      • Add event-allow processor, basically an allow ACL based on jq condition or regular expressions.
      • Add event-extract-tags processor, it adds tags based on regex named groups from tag names, tag values, value names, or values.
      • Add gnmi-action to event-trigger processor, the action runs a gNMI Set or Get if the trigger condition is met.
    • Set Command:
      • Improve usability by supporting reading values (--update-file and --replace-file) from standard input.

    v0.10.0 - April 8th 2021#

    • New command:
      • getset command: This command conditionally executes both a Get and a Set RPC, the GetResponse is used to evaluate a condition which if met triggers the execution of the Set RPC.
    • Processors:
      • Some processors' apply condition can be expressed using jq instead of regular expressions.

    v0.9.1 - March 23rd 2021#

    • Processors:

      • Add event-trigger processor: This processor is used to trigger a predefined action if a condition is met.
      • New processor event-jq which applies a transformation on the messages expressed as a jq expression.
    • Shell autocompletion:

      • Shell (bash, zsh and fish) autocompletion scripts can be generated using gnmic completion [bash|zsh|fish].
    • gRPC gzip compression:
      • gnmic supports gzip compression on gRPC connections.

    v0.9.0 - March 11th 2021#

    • Clustered Prometheus output:
      • When deployed as a cluster, it is possible to register only one of the prometheus outputs in Consul. This is handy in the case of a cluster with data replication.
    • Proto file loading at runtime (Nokia SROS):
      • gnmic supports loading SROS proto files at runtime to decode gNMI updates with proto encoding
    • Kafka Output:
      • Kafka SASL support: PLAIN, SCRAM SHA256/SHA512 OAuth mechanisms are supported.
    • Configuration:
      • gnmic supports configuration using environment variables.
    • Processors:
      • add event-merge processor.
    • Target Loaders:
      • gnmic supports target loaders at runtime, new targets can be added to the configuration from a file that gnmic watches or from Consul

    v0.8.0 - March 2nd 2021#

    • Inputs:
      • Processors can now be applied by the input plugins.
    • Prometheus output:
      • The Prometheus output can now register as a service in Consul, a Prometheus client can discover the output using consul service discovery.
    • Clustering:
      • gnmic can now run as a cluster, this requires a running Consul instance that will be used by the gnmic instance for leader election and target load sharing.
    • Configuration file:
      • The default configuration file placement now follows XDG recommendations
    • CLI exit status:
      • Failure of most commands is properly reflected in the cli exit status.
    • Configuration:
      • Configuration fields that are OS paths are expanded by gnmic
    • Deployment examples:
      • A set of deployment examples is added to the repo and the docs.

    v0.7.0 - January 28th 2021#

    • Prometheus output metrics customization:

      • metric-prefix and append-subscription-name can be used to change the default metric prefix and append the subscription name to the metric name.
      • export-timestamps: enables/disables the export of timestamps together with the metric.
      • strings-as-labels: enables/disables automatically adding paths with a value of type string as a metric label.
    • NATS output:

      • allow multiple NATS workers under NATS output via field num-workers.
      • add NATS prometheus internal metrics.
    • STAN output:

      • allow multiple STAN workers under STAN output via field num-workers.
      • add NATS prometheus internal metrics.
    • File output:

      • add File prometheus metrics.
    • Inputs:

      • support ingesting gNMI data from NATS, STAN or a Kafka message bus.

    v0.6.0 - December 14th 2020#

    • Processors:

      • Added processors to gnmic, a set of basic processors can be used to manipulate gNMI data flowing through gnmic. These processors are applied by the output plugins
    • Upgrade command: gnmic can be upgraded using gnmic version upgrade command.

    v0.5.2 - December 1st 2020#

    • Outputs:
      • Improve outputs logging
      • Add Prometheus metrics to Kafka output

    v0.5.1 - November 28th 2020#

    • Prompt Mode:
      • Fix subscribe RPC behavior
    • QoS:
      • Do not populate QoS field if not set via config file or flag. Outputs:
      • add configurable number of workers to some outputs.

    v0.5.0 - November 25th 2020#

    • Prompt Mode:
      • Add prompt sub commands.
    • XPATH parsing:
      • Add custom xpath parsingto gnmi.Path to allow for paths including column :.
    • TLS:
      • Allow configurable TLS versions per target, the minimum, the maximum and the preferred TLS versions ca be configured.

    v0.4.3 - November 10th 2020#

    • Missing path:
      • Initialize the path field if not present in SubscribeResponse

    v0.4.2 - November 5th 2020#

    • YANG:
      • Prompt command flags --file and --dir support globs.
    • Subscribe:
      • added flags --output that allows to choose a single output for subscribe updates
    • Prompt:
      • Max suggestions is automatically adjusted based on the terminal height.
      • Add suggestions for address and subscriptions.

    v0.4.1 - October 22nd 2020#

    • Prompt:
      • Add suggestions of xpath with origin, --suggest-with-origin.

    v0.4.0 - October 21st 2020#

    • New Command:
      • Add new command prompt
    • Prompt:

      • Add ctrl+z key bind to delete a single path element.
      • Add YANG info to xpath suggestions.
      • Add GoLeft, GoRight key binds.
      • Sort xpaths and prefixes suggestions.
      • xpaths suggestions are properly generated if a prefix is present.
      • flag --suggest-all-flags allows adding global flags suggestion in prompt mode.
    • Prometheus output:

      • Add support for Prometheus output plugin.

    v0.3.0 - October 1st 2020#

    • InfluxDB output:
      • Add support for influxDB output plugin.

    v0.2.3 - September 18th 2020#

    • Retry
      • Add basic RPC retry mechanism.
    • ONCE mode subscription:
      • Handle targets that send an EOF error instead of a SyncResponse to signify the end of ONCE subscriptions.
    • Docker image:
      • Docker images added to ghcr.io as well as docker hub.

    v0.2.2 - September 3rd 2020#

    • CLI:
      • Properly handle paths that include quotes.
    • Unix Socket:
      • Allow send/rcv of gNMI data to/from a unix socket.
    • Outputs:
      • Add TCP output plugin.

    v0.2.1 - August 11th 2020#

    • Releases:
      • Add .deb. and .rpm packages to releases.
    • Outputs:
      • Add UDP output plugin.

    v0.2.0 - August 7th 2020#

    • Releases:
      • Add ARM releases.
      • Push docker image to docker hub.

    v0.1.1 - July 23rd 2020#

    • Set Cmd:
      • Support json_ietf encoding when the value is specified from a file.

    v0.1.0 - July 16th 2020#

    • Outputs:
      • Allow NATS/STAN output subject customization.

    v0.0.7 - July 16th 2020#

    • gNMI Target:
      • Add support for gNMI Target field.
    • gNMI Origin:
      • Add support for gNMI Origin field.
    • Prometheus internal metrics:
      • Add support for gnmic internal metrics via a Prometheus server.
    • Outputs:
      • Add support for multiple output plugins (file, NATS, STAN, Kafka)
    • Targets:
      • Support target specific configuration.
    • Poll Subscription:
      • Allow selecting polled targets and subscription using a CLI select menu.
    • gNMI Models:
      • Support multiple Models in Get and Subscribe RPCs.

    v0.0.6 - June 2nd 2020#

    • Nokia Dialout:
      • Add Support for Nokia Dialout telemetry.
    • Printing:
      • Convert timestamps to Time.

    v0.0.5 - May 18th 2020#

    • Formatting:
      • Add textproto format.

    v0.0.4 - May 11th 2020#

    • Logging:
      • Support logging to file instead of Stderr.
    • Set Command:
      • support Set values from YAML file.

    v0.0.3 - April 23rd 2020#

    • Proxy:
      • Allow usage of ENV proxy values for gRPC connections.
    • Installation:
      • Add installation script.

    v0.0.2 - April 13th 2020#

    • Terminal printing clean up.
    • Path Command: Add search option.

    v0.0.1 - March 24th 2020#

    • Capabilities RPC Command.
    • Get RPC Command.
    • Subscribe RPC Command.
    • Set RPC Command.
    • TLS support.
    • Version Command.
    • Path Commnd.

    initial Commit - February 20th 2020#

    \ No newline at end of file diff --git a/cmd/capabilities/index.html b/cmd/capabilities/index.html new file mode 100644 index 00000000..7e549751 --- /dev/null +++ b/cmd/capabilities/index.html @@ -0,0 +1,16 @@ + Capabilities - gNMIc

    Capabilities

    Description#

    The [cap | capabilities] command represents the gNMI Capabilities RPC.

    It is used to send a Capability Request to the specified target(s) and expects one Capability Response per target.

    Capabilities allows the client to retrieve the set of capabilities that is supported by the target:

    • gNMI version
    • available data models
    • supported encodings
    • gNMI extensions

    This allows the client to, for example, validate the service version that is implemented and retrieve the set of models that the target supports. The models can then be specified in subsequent Get/Subscribe RPCs to precisely tell the target which models to use.

    Usage#

    gnmic [global-flags] capabilities [local-flags]

    Examples#

    single host#

    gnmic -a <ip:port> --username <user> --password <password> \
    +      --insecure capabilities
    +
    +gNMI_Version: 0.7.0
    +supported models:
    +  - nokia-conf, Nokia, 19.10.R2
    +  - nokia-state, Nokia, 19.10.R2
    +  - nokia-li-state, Nokia, 19.10.R2
    +  - nokia-li-conf, Nokia, 19.10.R2
    +<< SNIPPED >>
    +supported encodings:
    +  - JSON
    +  - BYTES
    +

    multiple hosts#

    gnmic -a <ip:port>,<ip:port> -u <user> -p <password> \
    +      --insecure cap
    +
    \ No newline at end of file diff --git a/cmd/diff/diff/index.html b/cmd/diff/diff/index.html new file mode 100644 index 00000000..26693598 --- /dev/null +++ b/cmd/diff/diff/index.html @@ -0,0 +1,39 @@ + Diff - gNMIc

    Diff

    Description#

    The diff command is similar to a get or subscribe (mode ONCE) commands ran against at least 2 targets, a reference and one or more compared targets. The command will compare the returned responses from the compared targets to the ones returned from the reference target and only print the difference between them.

    The output is printed as a list "flattened" gNMI updates, each line containing an XPath pointing to a leaf followed by its value.

    Each line is preceded with either signs + or -:

    • + means the leaf and its value are present in the compared target but not in the reference target.
    • - means the leaf and its value are present in the reference target but not in the compared target.

    e.g:

    +   network-instance[name=default]/interface[name=ethernet-1/36.0]: {}
    +-   network-instance[name=default]/protocols/bgp/autonomous-system: 101
    +

    The output above indicates:

    • The compared target has interface ethernet-1/36.0 added to network instance default while the reference doesn't.
    • The compared target is missing the autonomous-system 101 configuration under network-instance default protocols/bgp compared to the reference.

    The data to be compared is specified with the flag --path, which can be set multiple times to compare multiple data sets. By default, the data it is retrieved using a Get RPC, if the flag --sub is present, a Subscribe RPC with mode ONCE is used instead.

    Each of the get and subscribe methods has pros and cons, with the get method you can choose to compare CONFIG or STATE only, via the flag --type. The subscribe method allows to stream the response(s) in case a larger data set needs to be compared. In addition to that, some routers support more encoding options when using the subscribe RPC

    Multiple targets can be compared to the reference at once, the printed output of each difference will start with the line "$reference" vs "$compared"

    Aliases: compare

    Usage#

    gnmic [global-flags] diff [local-flags]

    Flags#

    ref#

    The --ref flag is a mandatory flag that specifies the target to used as reference to compare other targets to.

    compare#

    The --compare flag is a mandatory flag that specifies the targets to compare to the reference target.

    prefix#

    As per path prefixes, the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --path flag. Defaults to "".

    path#

    The mandatory path flag [--path] is used to specify the path(s) the client wants to receive a snapshot of.

    Multiple paths can be specified by using multiple --path flags:

    gnmic --insecure \
    +      --ref router1
    +      --compare router2,router3
    +      diff --path "/state/ports[port-id=*]" \
    +           --path "/state/router[router-name=*]/interface[interface-name=*]"
    +

    If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: "origin:path":

    model#

    The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported.

    target#

    With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message.

    type#

    The type flag [--type] is used to specify the data type requested from the server.

    One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to "ALL")

    sub#

    When the flag --sub is present, gnmic will use a Subscribe RPC with mode ONCE, instead of a Get RPC to retrieve the data to be compared.

    Examples#

    gnmic diff -t config --skip-verify -e ascii \
    +           --ref clab-te-leaf1 \
    +           --compare clab-te-leaf2 \
    +           --path /network-instance
    +
    "clab-te-leaf1:57400" vs "clab-te-leaf2:57400"
    ++   network-instance[name=default]/interface[name=ethernet-1/36.0]                                    : {}
    +-   network-instance[name=default]/protocols/bgp/autonomous-system                                    : 101
    ++   network-instance[name=default]/protocols/bgp/autonomous-system                                    : 102
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:11:1]            : {}
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:11:1]/admin-state: enable
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:11:1]/peer-as    : 201
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:11:1]/peer-group : eBGPv6
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:12:1]            : {}
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:12:1]/admin-state: enable
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:12:1]/peer-as    : 202
    +-   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:12:1]/peer-group : eBGPv6
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:21:1]            : {}
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:21:1]/admin-state: enable
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:21:1]/peer-as    : 201
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:21:1]/peer-group : eBGPv6
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:22:1]            : {}
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:22:1]/admin-state: enable
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:22:1]/peer-as    : 202
    ++   network-instance[name=default]/protocols/bgp/neighbor[peer-address=2002::192:168:22:1]/peer-group : eBGPv6
    ++   network-instance[name=default]/protocols/bgp/router-id                                            : 10.0.1.2
    +-   network-instance[name=default]/protocols/bgp/router-id                                            : 10.0.1.1
    +-   network-instance[name=myins]                                                                      : {}
    +-   network-instance[name=myins]/admin-state                                                          : enable
    +-   network-instance[name=myins]/description                                                          : desc1
    +-   network-instance[name=myins]/interface[name=ethernet-1/36.0]                                      : {}
    +-   network-instance[name=myins]/type                                                                 : ip-vrf
    +
    \ No newline at end of file diff --git a/cmd/diff/diff_set_to_notifs/index.html b/cmd/diff/diff_set_to_notifs/index.html new file mode 100644 index 00000000..a5fc0adb --- /dev/null +++ b/cmd/diff/diff_set_to_notifs/index.html @@ -0,0 +1,12 @@ + Diff Set-To-Notifs - gNMIc

    Diff Set-To-Notifs

    Description#

    The diff set-to-notifs command is used to verify whether a set of notifications from a GetResponse or a stream of SubscribeResponse messages comply with a SetRequest messages in textproto format. The envisioned use case is to check whether a stored snapshot of device state matches that of the intended state as specified by a SetRequest.

    The output is printed as a list of "flattened" gNMI updates, each line containing an XPath pointing to a leaf followed by its value.

    Each line is preceded with either signs + or -:

    • + means the leaf and its value are present in the new SetRequest but not in the reference SetRequest.
    • - means the leaf and its value are present in the reference SetRequest but not in the new SetRequest.

    e.g:

    SetToNotifsDiff(-want/SetRequest, +got/Notifications):
    +- /lacp/interfaces/interface[name=Port-Channel9]/config/interval: "FAST"
    +- /lacp/interfaces/interface[name=Port-Channel9]/config/name: "Port-Channel9"
    +- /lacp/interfaces/interface[name=Port-Channel9]/name: "Port-Channel9"
    +- /network-instances/network-instance[name=VrfBlue]/config/name: "VrfBlue"
    +- /network-instances/network-instance[name=VrfBlue]/config/type: "openconfig-network-instance-types:L3VRF"
    +- /network-instances/network-instance[name=VrfBlue]/name: "VrfBlue"
    +m /system/config/hostname:
    +  - "violetsareblue"
    +  + "rosesarered"
    +

    The output above indicates:

    • The set of paths starting with /lacp/interfaces/interface[name=Port-Channel9]/config/interval: "FAST" are present in the SetRequest but missing in the response from the device.
    • The value at path /system/config/hostname does not match that of the SetRequest.

    When --full is specified, values common between the SetRequest and the response messages are also shown.

    How to obtain a GetResponse or SubscribeResponse#

    To obtain GetRespnse/SubscribeResponse in textproto format, simply run gnmic's subscribe or get functions and pass in the flag --format prototext.

    Responses retrieved from either GetRequest or SubscribeRequest are supported by this command's --response flag.

    Usage#

    gnmic [global-flags] diff set-to-notifs [local-flags]

    Flags#

    setrequest#

    The --setrequest flag is a mandatory flag that specifies the reference gNMI SetRequest textproto file for comparing against the new SetRequest.

    response#

    The --response flag is a mandatory flag that specifies the gNMI Notifications textproto file (can contain a GetResponse or SubscribeResponse stream) for comparing against the reference SetRequest.

    Examples#

    $ gnmic diff set-to-notifs --setrequest cmd/demo/setrequest.textproto --response cmd/demo/subscriberesponses.textproto
    +
    \ No newline at end of file diff --git a/cmd/diff/diff_setrequest/index.html b/cmd/diff/diff_setrequest/index.html new file mode 100644 index 00000000..2d5447d4 --- /dev/null +++ b/cmd/diff/diff_setrequest/index.html @@ -0,0 +1,9 @@ + Diff Setrequest - gNMIc

    Diff Setrequest

    Description#

    The diff setrequest command is used to compare the intent between two SetRequest messages encoded in textproto format.

    The output is printed as a list of "flattened" gNMI updates, each line containing an XPath pointing to a leaf followed by its value.

    Each line is preceded with either signs + or -:

    • + means the leaf and its value are present in the new SetRequest but not in the reference SetRequest.
    • - means the leaf and its value are present in the reference SetRequest but not in the new SetRequest.

    e.g:

    SetRequestIntentDiff(-A, +B):
    +-------- deletes/replaces --------
    ++ /network-instances/network-instance[name=VrfBlue]: deleted or replaced only in B
    +-------- updates --------
    +m /system/config/hostname:
    +  - "violetsareblue"
    +  + "rosesarered"
    +

    The output above indicates:

    • The new target deletes or replaces the path /network-instances/network-instance[name=VrfBlue] while the reference doesn't.
    • The new target changes the value of /system/config/hostname compared to the reference from "violetsareblue" to "rosesarered".

    When --full is specified, values common between the two SetRequest are also shown.

    SetRequest Intent#

    It is possible for two SetRequests to be different but which are semantically equivalent -- i.e. they both modify the same leafs in the same ways. In other words, their overall effects are the same.

    For example, a replace on the leaf /system/config/hostname with the value "foo" is the same as an update on the same leaf with the same value. A replace on the container /system/ with the value { config: { hostname: "foo" } } is the same as a delete on that container followed by a replace to the leaf. Overwrites are also possible, although this is currently unsupported.

    In order to compare equivalent SetRequests correctly, this tool breaks down a SetRequest into its "minimal intent" (deletes followed by updates) prior to the diff computation. This is why the output groups deletes/replaces into the same section.

    Usage#

    gnmic [global-flags] diff setrequest [local-flags]

    Flags#

    ref#

    The --ref flag is a mandatory flag that specifies the reference gNMI SetRequest textproto file for comparing against the new SetRequest.

    new#

    The --new flag is a mandatory flag that specifies the new gNMI SetRequest textproto file for comparing against the reference SetRequest.

    Examples#

    $ gnmic diff setrequest --ref cmd/demo/setrequest.textproto --new cmd/demo/setrequest2.textproto
    +
    \ No newline at end of file diff --git a/cmd/generate/generate_path/index.html b/cmd/generate/generate_path/index.html new file mode 100644 index 00000000..7070df67 --- /dev/null +++ b/cmd/generate/generate_path/index.html @@ -0,0 +1 @@ + Generate Path - gNMIc

    Generate Path

    Description#

    The path sub command is an alias for the gnmic path command.

    \ No newline at end of file diff --git a/cmd/generate/generate_set_request/index.html b/cmd/generate/generate_set_request/index.html new file mode 100644 index 00000000..bfc3dd29 --- /dev/null +++ b/cmd/generate/generate_set_request/index.html @@ -0,0 +1,255 @@ + Generate Set-Request - gNMIc

    Generate Set-Request

    Description#

    The set-request sub command generates a Set request file given a list of update and/or replace paths.

    If no paths are supplied, a root (/) replace path is used as a default.

    The generated file can be manually edited and used with gnmic set command:

    gnmic set --request-file <path_to_generated_file>

    Aliases: sreq, srq, sr

    Usage#

    gnmic [global-flags] generate [generate-flags] set-request [sub-command-flags]

    Flags#

    update#

    The --update flag specifies a valid xpath, used to generate an updates section of the set request file.

    Multiple --update flags can be supplied.

    replace#

    The --replace flag specifies a valid xpath, used to generate a replaces section of the set request file.

    Multiple --replace flags can be supplied.

    Examples#

    Openconfig#

    YANG repo: openconfig/public

    Clone the OpenConfig repository:

    git clone https://github.com/openconfig/public
    +cd public
    +
    gnmic --encoding json_ietf \
    +          generate  \
    +          --file release/models \
    +          --dir third_party \
    +          --exclude ietf-interfaces \
    +          set-request \
    +          --replace /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address
    +

    The above command generates the below YAML output (JSON if --json flag is supplied)

    replaces:
    +- path: /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address
    +  value:
    +  - config:
    +      ip: ""
    +      prefix-length: ""
    +    ip: ""
    +    vrrp:
    +      vrrp-group:
    +      - config:
    +          accept-mode: "false"
    +          advertisement-interval: "100"
    +          preempt: "true"
    +          preempt-delay: "0"
    +          priority: "100"
    +          virtual-address: ""
    +          virtual-router-id: ""
    +        interface-tracking:
    +          config:
    +            priority-decrement: "0"
    +            track-interface: ""
    +        virtual-router-id: ""
    +  encoding: JSON_IETF
    +

    The value section can be filled with the desired configuration variables.

    Nokia SR OS#

    git clone https://github.com/nokia/7x50_YangModels
    +cd 7x50_YangModels
    +git checkout sros_21.2.r2
    +
    gnmic generate \
    +        --file YANG/nokia-combined \
    +        --dir YANG \
    +        set-request \
    +        --replace /configure/service/vprn/bgp/family
    +

    The above command generates the below YAML output (JSON if --json flag is supplied)

    replaces:
    +- path: /configure/service/vprn/bgp/family
    +  value:
    +    flow-ipv4: "false"
    +    flow-ipv6: "false"
    +    ipv4: "true"
    +    ipv6: "false"
    +    label-ipv4: "false"
    +    mcast-ipv4: "false"
    +    mcast-ipv6: "false"
    +

    Cisco#

    YANG repo: YangModels/yang

    Clone the YangModels/yang repo and change into the main directory of the repo:

    git clone https://github.com/YangModels/yang
    +cd yang/vendor
    +
    gnmic --encoding json_ietf \
    +          generate  \
    +          --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \
    +          --file vendor/cisco/xr/721/Cisco-IOS-XR-ipv4-bgp-oper.yang \
    +          --dir standard/ietf \
    +          set-request \
    +          --path /active-nodes
    +

    The above command generates the below YAML output (JSON if --json flag is supplied)

    replaces:
    +- path: /active-nodes
    +  value:
    +    active-node:
    +    - node-name: ""
    +      selective-vrf-download:
    +        role:
    +          address-family:
    +            ipv4:
    +              unicast: ""
    +            ipv6:
    +              unicast: ""
    +        vrf-groups:
    +          vrf-group:
    +          - vrf-group-name: ""
    +  encoding: JSON_IETF
    +

    Juniper#

    YANG repo: Juniper/yang

    Clone the Juniper YANG repository and change into the release directory:

    git clone https://github.com/Juniper/yang
    +cd yang/20.3/20.3R1
    +
    gnmic --encoding json_ietf \
    +          generate
    +          --file junos/conf \
    +          --dir common 
    +          set-request \
    +          --replace /configuration/interfaces/interface/unit/family/inet/address
    +

    The above command generates the below YAML output (JSON if --json flag is supplied)

    replaces:
    +- path: /configuration/interfaces/interface/unit/family/inet/address
    +  value:
    +  - apply-groups: ""
    +    apply-groups-except: ""
    +    apply-macro:
    +    - data:
    +      - name: ""
    +        value: ""
    +      name: ""
    +    arp:
    +    - case_1: ""
    +      case_2: ""
    +      l2-interface: ""
    +      name: ""
    +      publish: ""
    +    broadcast: ""
    +    destination: ""
    +    destination-profile: ""
    +    master-only: ""
    +    multipoint-destination:
    +    - apply-groups: ""
    +      apply-groups-except: ""
    +      apply-macro:
    +      - data:
    +        - name: ""
    +          value: ""
    +        name: ""
    +      case_1: ""
    +      case_2: ""
    +      epd-threshold:
    +        apply-groups: ""
    +        apply-groups-except: ""
    +        apply-macro:
    +        - data:
    +          - name: ""
    +            value: ""
    +          name: ""
    +        epd-threshold-plp0: ""
    +        plp1: ""
    +      inverse-arp: ""
    +      name: ""
    +      oam-liveness:
    +        apply-groups: ""
    +        apply-groups-except: ""
    +        apply-macro:
    +        - data:
    +          - name: ""
    +            value: ""
    +          name: ""
    +        down-count: ""
    +        up-count: ""
    +      oam-period:
    +        disable: {}
    +        oam_period: ""
    +      shaping:
    +        apply-groups: ""
    +        apply-groups-except: ""
    +        apply-macro:
    +        - data:
    +          - name: ""
    +            value: ""
    +          name: ""
    +        cbr:
    +          cbr-value: ""
    +          cdvt: ""
    +        queue-length: ""
    +        rtvbr:
    +          burst: ""
    +          cdvt: ""
    +          peak: ""
    +          sustained: ""
    +        vbr:
    +          burst: ""
    +          cdvt: ""
    +          peak: ""
    +          sustained: ""
    +      transmit-weight: ""
    +    name: ""
    +    preferred: ""
    +    primary: ""
    +    virtual-gateway-address: ""
    +    vrrp-group:
    +    - advertisements-threshold: ""
    +      apply-groups: ""
    +      apply-groups-except: ""
    +      apply-macro:
    +      - data:
    +        - name: ""
    +          value: ""
    +        name: ""
    +      authentication-key: ""
    +      authentication-type: ""
    +      case_1: ""
    +      case_2: ""
    +      case_3: ""
    +      name: ""
    +      preferred: ""
    +      priority: ""
    +      track:
    +        apply-groups: ""
    +        apply-groups-except: ""
    +        apply-macro:
    +        - data:
    +          - name: ""
    +            value: ""
    +          name: ""
    +        interface:
    +        - apply-groups: ""
    +          apply-groups-except: ""
    +          apply-macro:
    +          - data:
    +            - name: ""
    +              value: ""
    +            name: ""
    +          bandwidth-threshold:
    +          - name: ""
    +            priority-cost: ""
    +          name: ""
    +          priority-cost: ""
    +        priority-hold-time: ""
    +        route:
    +        - priority-cost: ""
    +          route_address: ""
    +          routing-instance: ""
    +      virtual-link-local-address: ""
    +      vrrp-inherit-from:
    +        active-group: ""
    +        active-interface: ""
    +        apply-groups: ""
    +        apply-groups-except: ""
    +        apply-macro:
    +        - data:
    +          - name: ""
    +            value: ""
    +          name: ""
    +    web-authentication:
    +      apply-groups: ""
    +      apply-groups-except: ""
    +      apply-macro:
    +      - data:
    +        - name: ""
    +          value: ""
    +        name: ""
    +      http: ""
    +      https: ""
    +      redirect-to-https: ""
    +  encoding: JSON_IETF
    +

    Arista#

    YANG repo: aristanetworks/yang

    Arista uses a subset of OpenConfig modules and does not provide IETF modules inside their repo. So make sure you have IETF models available so you can reference it, a openconfig/public is a good candidate.

    Clone the Arista YANG repo:

    git clone https://github.com/aristanetworks/yang
    +cd yang
    +

    The above command generates the below YAML output (JSON if --json flag is supplied)

    gnmic --encoding json_ietf \
    +          generate
    +          --file EOS-4.23.2F/openconfig/public/release/models \
    +          --dir ../openconfig/public/third_party/ietf \
    +          --exclude ietf-interfaces \
    +          set-request \
    +          --replace bgp/neighbors/neighbor/config
    +
    replaces:
    +- path: bgp/neighbors/neighbor/config
    +  value:
    +    auth-password: ""
    +    description: ""
    +    enabled: "true"
    +    local-as: ""
    +    neighbor-address: ""
    +    peer-as: ""
    +    peer-group: ""
    +    peer-type: ""
    +    remove-private-as: ""
    +    route-flap-damping: "false"
    +    send-community: NONE
    +
    \ No newline at end of file diff --git a/cmd/generate/index.html b/cmd/generate/index.html new file mode 100644 index 00000000..287d4b7d --- /dev/null +++ b/cmd/generate/index.html @@ -0,0 +1,28 @@ + Generate - gNMIc

    Generate

    Description#

    Most gNMI targets use YANG as a modeling language for their datastores. It order to access and manipulate the stored data (Get, Set, Subscribe), a tool should be aware of the underlying YANG model, be able to generate paths pointing to the desired gNMI objects as well as building configuration payloads matching data instances on the targets.

    The generate command takes the target's YANG models as input and generates:

    • Paths in xpath or gNMI formats.
    • Configuration payloads that can be used as update or replace input files for the Set command.
    • A Set request file that can be used as a template with the Set command.

    Aliases: gen

    Usage#

    gnmic [global-flags] generate [local-flags]

    or

    gnmic [global-flags] generate [local-flags] sub-command [sub-command-flags]

    Persistent Flags#

    output#

    The --output flag specifies the file to which the generated output will be written, defaults to stdout

    json#

    When used with generate command, the --json flag, if present changes the output format from YAML to JSON.

    When used with generate path command, it outputs the path, the leaf type, its description, its default value and if it is a state leaf or not in an array of JSON objects.

    Local Flags#

    path#

    The --path flag specifies the path whose payload (JSON/YAML) will be generated.

    Defaults to /

    config-only#

    The --config-only flag, if present instruct gnmic to generate JSON/YAML payloads from YANG nodes not marked as config false.

    camel-case#

    The --camel-case flag, if present allows to convert all the keys in the generated JSON/YAML paylod to CamelCase

    snake-case#

    The --snake-case flag, if present allows to convert all the keys in the generated JSON/YAML paylod to snake_case

    Sub Commands#

    Path#

    The path sub command is an alias for the gnmic path command.

    Set-request#

    The set-request sub command generates a Set request file given a list of update and/or replace paths.

    Examples#

    Openconfig#

    YANG repo: openconfig/public

    Clone the OpenConfig repository:

    git clone https://github.com/openconfig/public
    +cd public
    +
    gnmic --encoding json_ietf \
    +          generate  \
    +          --file release/models \
    +          --dir third_party \
    +          --exclude ietf-interfaces \
    +          --path /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address
    +
    - config:
    +    ip: ""
    +    prefix-length: ""
    +  ip: ""
    +  vrrp:
    +    vrrp-group:
    +    - config:
    +        accept-mode: "false"
    +        advertisement-interval: "100"
    +        preempt: "true"
    +        preempt-delay: "0"
    +        priority: "100"
    +        virtual-address: ""
    +        virtual-router-id: ""
    +      interface-tracking:
    +        config:
    +          priority-decrement: "0"
    +          track-interface: ""
    +      virtual-router-id: ""
    +
    \ No newline at end of file diff --git a/cmd/get/index.html b/cmd/get/index.html new file mode 100644 index 00000000..b0435913 --- /dev/null +++ b/cmd/get/index.html @@ -0,0 +1,18 @@ + Get - gNMIc

    Get

    Description#

    The get command represents the gNMI Get RPC.

    It is used to send a GetRequest to the specified target(s) (using the global flag --address and expects one GetResponse per target, per path.

    The Get RPC is used to retrieve a snapshot of data from the target. It requests that the target snapshots a subset of the data tree as specified by the paths included in the message and serializes this to be returned to the client using the specified encoding.

    Usage#

    gnmic [global-flags] get [local-flags]

    Flags#

    prefix#

    As per path prefixes, the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --path flag. Defaults to "".

    path#

    The mandatory path flag [--path] is used to specify the path(s) the client wants to receive a snapshot of.

    Multiple paths can be specified by using multiple --path flags:

    gnmic -a <ip:port> --insecure \
    +      get --path "/state/ports[port-id=*]" \
    +          --path "/state/router[router-name=*]/interface[interface-name=*]"
    +

    If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: "origin:path":

    Note

    The path after the origin value has to start with a /

    gnmic -a <ip:port> --insecure \
    +      get --path "openconfig-interfaces:/interfaces/interface"
    +

    model#

    The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported.

    target#

    With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message.

    values-only#

    The flag [--values-only] allows to print only the values returned in a GetResponse. This is useful when only the value of a leaf is of interest, like check if a value was set correctly.

    type#

    The type flag [--type] is used to specify the data type requested from the server.

    One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to "ALL")

    processor#

    The [--processor] flag allow to list event processor names to be run as a result of receiving the GetReponse messages.

    The processors are run in the order they are specified (--processor proc1,proc2 or --processor proc1 --processor proc2).

    Examples#

    # simple Get RPC
    +gnmic -a <ip:port> get --path "/state/port[port-id=*]"
    +
    +# Get RPC with multiple paths
    +gnmic -a <ip:port> get --path "/state/port[port-id=*]" \
    +      --path "/state/router[router-name=*]/interface[interface-name=*]"
    +
    +# Get RPC with path prefix
    +gnmic -a <ip:port> get --prefix "/state" \
    +      --path "port[port-id=*]" \
    +      --path "router[router-name=*]/interface[interface-name=*]"
    +

    \ No newline at end of file diff --git a/cmd/getset/index.html b/cmd/getset/index.html new file mode 100644 index 00000000..25938048 --- /dev/null +++ b/cmd/getset/index.html @@ -0,0 +1,6 @@ + GetSet - gNMIc

    GetSet

    Description#

    The getset command is a combination of the gNMI Get RPC and the gNMI Set RPC.

    It allows to conditionally execute a Set RPC based on a condition evaluated against a GetResponse.

    The condition written as a jq expression, is specified using the flag --condition.

    The SetRPC is executed only if the condition evaluates to true

    Usage#

    gnmic [global-flags] getset [local-flags]

    gnmic [global-flags] gas [local-flags]

    gnmic [global-flags] gs [local-flags]

    Flags#

    prefix#

    As per path prefixes, the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --get, --update, --replace and --delete flags.

    Defaults to "".

    get#

    The mandatory get flag [--get] is used to specify the single path used in the Get RPC.

    model#

    The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported.

    target#

    With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message.

    type#

    The type flag [--type] is used to specify the data type requested from the server.

    One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to "ALL")

    condition#

    The [--condition] is a jq expression that can be used to determine if the Set Request is executed based on the Get Response values.

    update#

    The [--update] specifies a jq expression used to build the Set Request update path.

    replace#

    The [--replace] specifies a jq expression used to build the Set Request replace path.

    delete#

    The [--delete] specifies a jq expression used to build the Set Request delete path.

    value#

    The [--value] specifies a jq expression used to build the Set Request value.

    Examples#

    The command in the below example does the following:

    • gets the list of interface indexes to interface name mapping,

    • checks if the interface index (ifindex) 70 exists,

    • if it does, the set request changes the interface state to enable using the interface name.

    gnmic getset -a <ip:port> \
    +    --get /interface/ifindex \
    +    --condition '.[] | .updates[].values[""]["srl_nokia-interfaces:interface"][] | select(.ifindex==70) | (.name != "" or .name !=null)' \
    +    --update '.[] | .updates[].values[""]["srl_nokia-interfaces:interface"][] | select(.ifindex==70) | "interface[name=" + .name + "]/admin-state"' \
    +    --value enable
    +
    \ No newline at end of file diff --git a/cmd/listen/index.html b/cmd/listen/index.html new file mode 100644 index 00000000..bdc83ebb --- /dev/null +++ b/cmd/listen/index.html @@ -0,0 +1,37 @@ + Listen - gNMIc

    Listen

    Description#

    gnmic can be used in a "dial-out telemetry" mode by means of the listen command. In the dial-out mode:

    • a network element is configured with the telemetry paths
    • a network element initiates a connection towards the server/collector (gnmic acts as a server in that case)

    Info

    Currently gnmic only implements the dial-out support for Nokia1 SR OS 20.5.r1+ routers.

    Usage#

    gnmic listen [global flags] [local flags]
    +

    Flags#

    address#

    The address flag [-a | --address] tells gnmic which address to bind an internal server to in an address:port format, e.g.: 0.0.0.0:57400.

    tls-cert#

    Path to the TLS certificate can be supplied with --tls-cert flag.

    tls-key#

    Path to the private key can be supplied with --tls-key flag.

    max-concurrent-streams#

    To limit the maximum number of concurrent HTTP2 streams use the --max-concurrent-streams flag, the default value is 256.

    prometheus-address#

    The prometheus-address flag [--prometheus-address] allows starting a prometheus server that can be scraped by a prometheus client. It exposes metrics like memory, CPU and file descriptor usage.

    Examples#

    TLS disabled server#

    To start gnmic as a server listening on all interfaces without TLS support is as simple as:

    gnmic listen -a 0.0.0.0:57400
    +
    SR OS configuration for non TLS dialout connections
    /configure system telemetry destination-group "dialout" allow-unsecure-connection
    +/configure system telemetry destination-group "dialout" destination 10.2.0.99 port 57400 router-instance "management"
    +/configure system telemetry persistent-subscriptions { }
    +/configure system telemetry persistent-subscriptions subscription "dialout" admin-state enable
    +/configure system telemetry persistent-subscriptions subscription "dialout" sensor-group "port_stats"
    +/configure system telemetry persistent-subscriptions subscription "dialout" mode sample
    +/configure system telemetry persistent-subscriptions subscription "dialout" sample-interval 1000
    +/configure system telemetry persistent-subscriptions subscription "dialout" destination-group "dialout"
    +/configure system telemetry persistent-subscriptions subscription "dialout" encoding bytes
    +/configure system telemetry sensor-groups { }
    +/configure system telemetry sensor-groups { sensor-group "port_stats" }
    +/configure system telemetry sensor-groups { sensor-group "port_stats" path "/state/port[port-id=1/1/c1/1]/statistics/in-octets" }
    +

    TLS enabled server#

    By using tls-cert and tls-key flags it is possible to run gnmic with TLS.

    gnmic listen -a 0.0.0.0:57400 --tls-cert gnmic.pem --tls-key gnmic-key.pem
    +
    SR OS configuration for a TLS enabled dialout connections

    The configuration below does not utilise router-side certificates and uses the certificate provided by the server (gnmic). The router will also not verify the certificate.

    /configure system telemetry destination-group "dialout" tls-client-profile "client-tls"
    +/configure system telemetry destination-group "dialout" destination 10.2.0.99 port 57400 router-instance "management"
    +/configure system telemetry persistent-subscriptions { }
    +/configure system telemetry persistent-subscriptions subscription "dialout" admin-state enable
    +/configure system telemetry persistent-subscriptions subscription "dialout" sensor-group "port_stats"
    +/configure system telemetry persistent-subscriptions subscription "dialout" mode sample
    +/configure system telemetry persistent-subscriptions subscription "dialout" sample-interval 1000
    +/configure system telemetry persistent-subscriptions subscription "dialout" destination-group "dialout"
    +/configure system telemetry persistent-subscriptions subscription "dialout" encoding bytes
    +/configure system telemetry sensor-groups { }
    +/configure system telemetry sensor-groups { sensor-group "port_stats" }
    +/configure system telemetry sensor-groups { sensor-group "port_stats" path "/state/port[port-id=1/1/c1/1]/statistics/in-octets" }
    +
    +/configure system security tls client-cipher-list "client-ciphers" { }
    +/configure system security tls client-cipher-list "client-ciphers" cipher 1 name tls-rsa-with-aes128-cbc-sha
    +/configure system security tls client-cipher-list "client-ciphers" cipher 2 name tls-rsa-with-aes128-cbc-sha256
    +/configure system security tls client-cipher-list "client-ciphers" cipher 3 name tls-rsa-with-aes256-cbc-sha
    +/configure system security tls client-cipher-list "client-ciphers" cipher 4 name tls-rsa-with-aes256-cbc-sha256
    +
    +/configure system security tls client-tls-profile "client-tls" admin-state enable
    +/configure system security tls client-tls-profile "client-tls" cipher-list "client-ciphers"
    +


    1. Nokia dial-out proto definition can be found in karimra/sros-dialout 

    \ No newline at end of file diff --git a/cmd/path/index.html b/cmd/path/index.html new file mode 100644 index 00000000..9916b01b --- /dev/null +++ b/cmd/path/index.html @@ -0,0 +1,31 @@ + Path - gNMIc

    Path

    Description#

    With path command it is possible to generate and search through the XPATH style paths extracted from a YANG file.

    By extracting the XPATH styled paths from a YANG model it is made possible to utilize CLI search tools like awk, sed and alike to find the paths satisfying specific matching rules.

    The embedded search capability allows to perform a quick and simple search through the model's paths using simple inclusion/exclusion operators.

    Flags#

    types#

    When --types flag is present the extracted paths will also have a corresponding type printed out.

    path-type#

    The --path-type flag governs which style is used to display the path information. The default value is xpath which will produce the XPATH compatible paths.

    The other option is gnmi which will result in the paths to be formatted using the gNMI Path Conventions.

    /state/sfm[sfm-slot=*]/hardware-data/firmware-revision-status
    +
    elem:{name:"state"}  elem:{name:"sfm"  key:{key:"sfm-slot"  value:"*"}}  elem:{name:"hardware-data"}  elem:{name:"firmware-revision-status"}
    +

    With the --search flag present an interactive CLI search dialog is displayed that allows to navigate through the paths list and perform a search.

    ❯ gnmic path --file _test/nokia-state-combined.yang --search
    +Use the arrow keys to navigate: ↓ ↑ → ←  and : toggles search
    +? select path: 
    +    /state/aaa/radius/statistics/coa/dropped/bad-authentication
    +    /state/aaa/radius/statistics/coa/dropped/missing-auth-policy
    +  ▸ /state/aaa/radius/statistics/coa/dropped/invalid
    +    /state/aaa/radius/statistics/coa/dropped/missing-resource
    +    /state/aaa/radius/statistics/coa/received
    +    /state/aaa/radius/statistics/coa/accepted
    +    /state/aaa/radius/statistics/coa/rejected
    +    /state/aaa/radius/statistics/disconnect-messages/dropped/bad-authentication
    +    /state/aaa/radius/statistics/disconnect-messages/dropped/missing-auth-policy
    +↓   /state/aaa/radius/statistics/disconnect-messages/dropped/invalid
    +

    descr#

    When the --descr flag is present, the leaf description is printed after the path, indented with a \t.

    config-only#

    When the --config-only flag is present, paths are generated only for YANG leaves representing config data.

    state-only#

    When the --state-only flag is present, paths are generated only for YANG leaves representing state data.

    with-non-leaves#

    When the --with-non-leaves flag is present, paths are generated not only for YANG leaves.

    Examples#

    # output to stdout the XPATH styled paths
    +# from the nokia-state module of nokia-state-combined.yang file
    +gnmic path --file nokia-state-combined.yang
    +
    +# from the nokia-conf module
    +gnmic path -m nokia-conf --file nokia-conf-combined.yang
    +
    +# with the gNMI styled paths
    +gnmic path --file nokia-state-combined.yang --path-type gnmi
    +
    +# with path types
    +gnmic path --file nokia-state-combined.yang --types
    +
    +# entering the interactive navigation prompt
    +gnmic path --file nokia-state-combined.yang --search
    +

    1. Nokia combined models can be found in nokia/7x50_YangModels repo. 

    \ No newline at end of file diff --git a/cmd/prompt/index.html b/cmd/prompt/index.html new file mode 100644 index 00000000..cff0981a --- /dev/null +++ b/cmd/prompt/index.html @@ -0,0 +1 @@ + Prompt - gNMIc

    Prompt

    Description#

    The prompt command starts gnmic in an interactive prompt mode with the following auto-completion features:

    Usage#

    gnmic [global-flags] prompt [local-flags]

    Flags#

    description-with-prefix#

    When set, the description of the path elements in the suggestion box will contain module's prefix.

    description-with-types#

    When set, the description of the path elements in the suggestion box will contain element's type information.

    max-suggestions#

    The --max-suggestions flag sets the number of lines that the suggestion box will display without scrolling.

    Defaults to 10. Note, the terminal height might limit the number of lines in the suggestions box.

    suggest-all-flags#

    The --suggest-all-flags makes gnmic prompt suggest both global and local flags for a sub-command.

    The default behavior (when this flag is not set) is to suggest only local flags for any typed sub-command.

    suggest-with-origin#

    The --suggest-with-origin flag prepends the suggested path with the module name to which this path belongs.

    The path becomes rendered as <module_name>:/<suggested-container>. The module name will be used as the origin of the gNMI path.

    suggestions-bg-color#

    The --suggestions-bg-color flag sets the background color of the left part of the suggestion box.

    Defaults to dark blue.

    description-bg-color#

    The --description-bg-color flag sets the background color of the right part of the suggestion box.

    Defaults to dark gray.

    prefix-color#

    The --prefix-color flag sets the gnmic prompt prefix color gnmic>.

    Defaults to dark blue.

    Examples#

    The detailed explanation of the prompt command the the YANG-completions is provided on the Prompt mode and auto-suggestions page.

    \ No newline at end of file diff --git a/cmd/set/index.html b/cmd/set/index.html new file mode 100644 index 00000000..5cc6f75d --- /dev/null +++ b/cmd/set/index.html @@ -0,0 +1,285 @@ + Set - gNMIc

    Set

    Description#

    The set command represents the gNMI Set RPC.

    It is used to send a Set Request to the specified target(s) and expects one Set Response per target.

    Set RPC allows the client to modify the state of data on the target. The data specified referenced by a path can be updated, replaced or deleted.

    Note

    It is possible to combine update, replace and delete in a single gnmic set command.

    Usage#

    gnmic [global-flags] set [local-flags]

    The Set Request can be any of (or a combination of) update, replace or/and delete operations.

    Flags#

    prefix#

    The --prefix flag sets a common prefix to all paths specified using the local --path flag. Defaults to "".

    If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: "origin:path":

    Note

    The path after the origin value has to start with a /

    gnmic set --update "openconfig-interfaces:/interfaces/interface:::<type>:::<value>"
    +

    target#

    With the optional [--target] flag it is possible to supply the path target information in the prefix field of a SetRequest message.

    dry-run#

    The --dry-run flag allow to run a Set request without sending it to the targets. This is useful while developing templated Set requests.

    delete#

    The --delete flag allows creating a SetRequest.Delete as part of teh SetRequest message.

    replace#

    The --replace flag allows creating a SetRequest.Replace as part of a SetRequest message. It is expected to be in the format $path:::$type:::$value, where $path is the gNMI path of the object to replace, $type is the type of the value and $value is the replacement value.

    update#

    The --update flag allows creating a SetRequest.Update as part of a SetRequest message. It is expected to be in the format $path:::$type:::$value, where $path is the gNMI path of the object to update, $type is the type of the value and $value is the update value.

    replace-path and replace-value#

    The --replace-path and --replace-value flags are equivalent to the --replace flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.

    update-path and update-value#

    The --update-path and --update-value flags are equivalent to the --update flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.

    replace-path and replace-file#

    The --replace-path and --replace-file flags are equivalent to the --replace flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.

    update-path and update-file#

    The --update-path and --update-file flags are equivalent to the --update flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.

    replace-cli#

    The --replace-cli flag allows setting a SetRequest.Replace as part of a SetRequest message. It expects a single CLI command which will form the value path of the Replace, the path will be set to the CLI origin cli.

    replace-cli-file#

    The --replace-cli flag allows setting a SetRequest.Replace as part of a SetRequest message. It expects a file containing one or multiple CLI commands which will form the value path of the Replace, the path will be set to the CLI origin cli.

    update-cli#

    The --update-cli flag allows setting a SetRequest.Update as part of a SetRequest message. It expects a single CLI command which will form the value path of the Replace, the path will be set to the CLI origin cli.

    update-cli-file#

    The --update-cli flag allows setting a SetRequest.Update as part of a SetRequest message. It expects a file containing one or multiple CLI commands which will form the value path of the Replace, the path will be set to the CLI origin cli.

    request-file and request-vars#

    See this section below.

    Update Request#

    There are several ways to perform an update operation with gNMI Set RPC:

    1. in-line update, implicit type#

    Using both --update-path and --update-value flags, a user can update a value for a given path.

    gnmic set --update-path /configure/system/name --update-value router1
    +
    +gnmic set --update-path /configure/router[router-name=Base]/interface[interface-name=system]/admin-state \
    +          --update-value enable
    +

    The above 2 updates can be combined in the same CLI command:

    gnmic set --update-path /configure/system/name \
    +          --update-value router1 \
    +          --update-path /configure/router[router-name=Base]/interface[interface-name=system]/admin-state \
    +          --update-value enable
    +

    2. in-line update, explicit type#

    Using the update flag --update, one can specify the path, value type and value in a single parameter using a delimiter --delimiter. Delimiter string defaults to ":::".

    Supported types: json, json_ietf, string, int, uint, bool, decimal, float, bytes, ascii.

    # path:::value-type:::value
    +gnmic set --update /configure/system/name:::json:::router1
    +
    +gnmic set --update /configure/router[router-name=Base]/interface[interface-name=system]/admin-state:::json:::enable
    +
    +gnmic set --update /configure/router[router-name=Base]/interface[interface-name=system]:::json:::'{"admin-state":"enable"}'
    +

    3. update with a value from JSON or YAML file#

    It is also possible to specify the values from a local JSON or YAML file using --update-file flag for the value and --update-path for the path.

    In which case the value encoding will be determined by the global flag [ -e | --encoding ], both JSON and JSON_IETF are supported

    The file's format is identified by its extension, json: .json and yaml .yaml or .yml.

    {
    +    "admin-state": "enable",
    +    "ipv4": {
    +        "primary": {
    +            "address": "1.1.1.1",
    +            "prefix-length": 32
    +        }
    +    }
    +}
    +
    gnmic set --update-path /configure/router[router-name=Base]/interface[interface-name=system] \
    +          --update-file interface.json
    +

    "admin-state": enable
    +"ipv4":
    +"primary":
    +    "address": 1.1.1.1
    +    "prefix-length": 32
    +
    gnmic set --update-path /configure/router[router-name=Base]/interface[interface-name=system] \
    +          --update-file interface.yml
    +

    Replace Request#

    There are 3 main ways to specify a replace operation:

    1. in-line replace, implicit type#

    Using both --replace-path and --replace-value flags, a user can replace a value for a given path. The type of the value is implicitly set to JSON:

    gnmic set --replace-path /configure/system/name --replace-value router1
    +
    gnmic set --replace-path /configure/router[router-name=Base]/interface[interface-name=system]/admin-state \
    +          --replace-value enable
    +

    The above 2 commands can be packed in the same CLI command:

    gnmic set --replace-path /configure/system/name \
    +          --replace-value router1 \
    +          --replace-path /configure/router[router-name=Base]/interface[interface-name=system]/admin-state \
    +          --replace-value enable
    +

    2. in-line replace, explicit type#

    Using the replace flag --replace, you can specify the path, value type and value in a single parameter using a delimiter --delimiter. Delimiter string defaults to ":::".

    Supported types: json, json_ietf, string, int, uint, bool, decimal, float, bytes, ascii.

    gnmic set --replace /configure/system/name:::json:::router1
    +
    gnmic set --replace /configure/router[router-name=Base]/interface[interface-name=system]/admin-state:::json:::enable
    +

    3. replace with a value from JSON or YAML file#

    It is also possible to specify the values from a local JSON or YAML file using flag --replace-file for the value and --replace-path for the path.

    In which case the value encoding will be determined by the global flag [ -e | --encoding ], both JSON and JSON_IETF are supported

    The file is identified by its extension, json: .json and yaml .yaml or .yml.

    {
    +    "admin-state": "enable",
    +    "ipv4": {
    +        "primary": {
    +            "address": "1.1.1.1",
    +            "prefix-length": 32
    +        }
    +    }
    +}
    +
    "admin-state": enable
    +"ipv4":
    +"primary":
    +    "address": 1.1.1.1
    +    "prefix-length": 32
    +

    Then refer to the file with --replace-file flag

    gnmic set --replace-path /configure/router[router-name=Base]/interface[interface-name=system] \
    +          --replace-file interface.json
    +

    Delete Request#

    A deletion operation within the Set RPC is specified using the delete flag --delete.

    It takes an XPATH pointing to the config node to be deleted:

    gnmic set --delete "/configure/router[router-name=Base]/interface[interface-name=dummy_interface]"
    +

    Templated Set Request file#

    A Set Request can also be built based on one or multiple templates and (optionally) a set of variables.

    The variables allow to generate a Set Request file on per target basis.

    If no variable file is found, the execution continues and the template is assumed to be a static string.

    Each template specified with the flag --request-file is rendered against the variables defined in the file set with --request-vars. Each template results in a single gNMI Set Request.

    gnmic set --request-file <template1> --request-file <template2> --request-vars <vars_file>
    +

    Template Format#

    The rendered template data can be a JSON or YAML valid string.

    It has 3 sections, updates, replaces and deletes.

    In each of the updates and replaces, a path, a value and an encoding can be configured.

    If not specified, path defaults to /, while encoding defaults to the value set with --encoding flag.

    updates and replaces result in a set of gNMI Set Updates in the Set RPC, deletes result in a set of gNMI paths to be deleted.

    The value can be any arbitrary data format that the target accepts, it will be encoded based on the value of "encoding".

    {
    +  "updates": [
    +      {
    +          "path": "/interface[name=ethernet-1/1]",
    +          "value": {
    +              "admin-state": "enable",
    +              "description": "to_spine1"
    +           },
    +           "encoding": "json_ietf"
    +      },
    +      {
    +          "path": "/interface[name=ethernet-1/2]",
    +          "value": {
    +              "admin-state": "enable",
    +              "description": "to_spine2"
    +           },
    +           "encoding": "json_ietf"
    +      }
    +  ],
    +  "replaces": [
    +      {
    +          "path": "/interface[name=ethernet-1/3]",
    +          "value": {
    +              "admin-state": "enable",
    +              "description": "to_spine3"
    +           }
    +      },
    +       {
    +          "path": "/interface[name=ethernet-1/4]",
    +          "value": {
    +              "admin-state": "enable",
    +              "description": "to_spine4"
    +           }
    +      }
    +  ],
    +  "deletes" : [
    +      "/interface[name=ethernet-1/5]",
    +      "/interface[name=ethernet-1/6]"
    +  ]
    +}
    +
    updates:
    +  - path: "/interface[name=ethernet-1/1]"
    +    value:
    +      admin-state: enable
    +      description: "to_spine1"
    +    encoding: "json_ietf"
    +  - path: "/interface[name=ethernet-1/2]"
    +    value:
    +      admin-state: enable
    +      description: "to_spine2"
    +    encoding: "json_ietf"
    +replaces:
    +  - path: "/interface[name=ethernet-1/3]"
    +    value:
    +      admin-state: enable
    +      description: "to_spine3"
    +  - path: "/interface[name=ethernet-1/4]"
    +    value:
    +      admin-state: enable
    +      description: "to_spine4"
    +deletes:
    +  - "/interface[name=ethernet-1/5]"
    +  - "/interface[name=ethernet-1/6]"
    +

    Per Target Template Variables#

    The file --request-file can be written as a Go Text template.

    The parsed template is loaded with additional functions from gomplate.

    gnmic generates one gNMI Set request per target.

    The template will be rendered using variables read from the file --request-vars. Just like the template file, the variables file can either be a JSON or YAML formatted file.

    If the flag --request-vars is not set, gnmic looks for a file with the same path, name and extension as the request-file, appended with _vars.

    Within the template, the variables defined in the --request-vars file are accessible using the .Vars notation, while the target name is accessible using the .TargetName notation.

    Example request template:

    replaces:
    +{{ $target := index .Vars .TargetName }}
    +{{- range $interface := index $target "interfaces" }}
    +  - path: "/interface[name={{ index $interface "name" }}]"
    +    encoding: "json_ietf"
    +    value: 
    +      admin-state: {{ index $interface "admin-state" | default "disable" }}
    +      description: {{ index $interface "description" | default "" }}
    +    {{- range $index, $subinterface := index $interface "subinterfaces" }}
    +      subinterface:
    +        - index: {{ $index }}
    +          admin-state: {{ index $subinterface "admin-state" | default "disable" }}
    +          {{- if has $subinterface "ipv4-address" }}
    +          ipv4:
    +            address:
    +              - ip-prefix: {{ index $subinterface "ipv4-address" | toString }}
    +          {{- end }}
    +          {{- if has $subinterface "ipv6-address" }}
    +          ipv6:
    +            address:
    +              - ip-prefix: {{ index $subinterface "ipv6-address" | toString }}
    +          {{- end }}
    +    {{- end }}
    +{{- end }}
    +

    The below variables file defines the input for 3 leafs:

    leaf1:57400:
    +  interfaces:
    +    - name: ethernet-1/1
    +      admin-state: "enable"
    +      description: "leaf1_to_spine1"
    +      subinterfaces:
    +        - admin-state: enable
    +          ipv4-address: 192.168.78.1/30
    +    - name: ethernet-1/2
    +      admin-state: "enable"
    +      description: "leaf1_to_spine2"
    +      subinterfaces:
    +        - admin-state: enable
    +          ipv4-address: 192.168.79.1/30
    +
    +leaf2:57400:
    +  interfaces:
    +    - name: ethernet-1/1
    +      admin-state: "enable"
    +      description: "leaf2_to_spine1"
    +      subinterfaces:
    +        - admin-state: enable
    +          ipv4-address: 192.168.88.1/30
    +    - name: ethernet-1/2
    +      admin-state: "enable"
    +      description: "leaf2_to_spine2"
    +      subinterfaces:
    +        - admin-state: enable
    +          ipv4-address: 192.168.89.1/30
    +
    +leaf3:57400:
    +  interfaces:
    +    - name: ethernet-1/1
    +      admin-state: "enable"
    +      description: "leaf3_to_spine1"
    +      subinterfaces:
    +        - admin-state: enable
    +          ipv4-address: 192.168.98.1/30
    +    - name: ethernet-1/2
    +      admin-state: "enable"
    +      description: "leaf3_to_spine2"
    +      subinterfaces:
    +        - admin-state: enable
    +          ipv4-address: 192.168.99.1/30
    +

    Result Request file per target:

    updates:
    +  - path: /interface[name=ethernet-1/1]
    +    encoding: "json_ietf"
    +    value: 
    +      admin-state: enable
    +      description: leaf1_to_spine1
    +      subinterface:
    +        - index: 0
    +          admin-state: enable
    +          ipv4:
    +            address:
    +              - ip-prefix: 192.168.78.1/30
    +  - path: /interface[name=ethernet-1/2]
    +    encoding: "json_ietf"
    +    value: 
    +      admin-state: enable
    +      description: leaf1_to_spine2
    +      subinterface:
    +        - index: 0
    +          admin-state: enable
    +          ipv4:
    +            address:
    +              - ip-prefix: 192.168.79.1/30
    +
    updates:
    +  - path: /interface[name=ethernet-1/1]
    +    encoding: "json_ietf"
    +    value: 
    +      admin-state: enable
    +      description: leaf2_to_spine1
    +      subinterface:
    +        - index: 0
    +          admin-state: enable
    +          ipv4:
    +            address:
    +              - ip-prefix: 192.168.88.1/30
    +  - path: /interface[name=ethernet-1/2]
    +    encoding: "json_ietf"
    +    value: 
    +      admin-state: enable
    +      description: leaf2_to_spine2
    +      subinterface:
    +        - index: 0
    +          admin-state: enable
    +          ipv4:
    +            address:
    +              - ip-prefix: 192.168.89.1/30
    +
    updates:
    +  - path: /interface[name=ethernet-1/1]
    +    encoding: "json_ietf"
    +    value: 
    +      admin-state: enable
    +      description: leaf3_to_spine1
    +      subinterface:
    +        - index: 0
    +          admin-state: enable
    +          ipv4:
    +            address:
    +              - ip-prefix: 192.168.98.1/30
    +  - path: /interface[name=ethernet-1/2]
    +    encoding: "json_ietf"
    +    value: 
    +      admin-state: enable
    +      description: leaf3_to_spine2
    +      subinterface:
    +        - index: 0
    +          admin-state: enable
    +          ipv4:
    +            address:
    +              - ip-prefix: 192.168.99.1/30
    +

    Examples#

    1. update#

    in-line value#

    gnmic -a <ip:port> set --update-path /configure/system/name \
    +                       --update-value <system_name>
    +

    value from JSON file#

    cat jsonFile.json
    +{"name": "router1"}
    +
    +gnmic -a <ip:port> set --update-path /configure/system \
    +                       --update-file <jsonFile.json>
    +
    echo '{"name": "router1"}' | gnmic -a <ip:port> set \
    +                             --update-path /configure/system \
    +                             --update-file -
    +

    specify value type#

    gnmic -a <ip:port> set --update /configure/system/name:::json:::router1
    +gnmic -a <ip:port> set --update /configure/system/name@json@router1 \
    +                       --delimiter @
    +

    2. replace#

    cat interface.json
    +{"address": "1.1.1.1", "prefix-length": 32}
    +
    +gnmic -a <ip:port> --insecure \
    +      set --replace-path /configure/router[router-name=Base]/interface[interface-name=interface1]/ipv4/primary \
    +          --replace-file interface.json
    +
    echo '{"address": "1.1.1.1", "prefix-length": 32}' | gnmic -a <ip:port> --insecure \
    +      set --replace-path /configure/router[router-name=Base]/interface[interface-name=interface1]/ipv4/primary \
    +          --replace-file -
    +

    3. delete#

    gnmic -a <ip:port> --insecure set --delete /configure/router[router-name=Base]/interface[interface-name=interface1]
    +

    \ No newline at end of file diff --git a/cmd/subscribe/index.html b/cmd/subscribe/index.html new file mode 100644 index 00000000..41adaf2e --- /dev/null +++ b/cmd/subscribe/index.html @@ -0,0 +1,13 @@ + Subscribe - gNMIc

    Subscribe

    Description#

    The [subscribe | sub] command represents the gNMI Subscribe RPC.

    It is used to send a Subscribe Request to the specified target(s) and expects one or multiple Subscribe Response

    Usage#

    gnmic [global-flags] subscribe [local-flags]

    Local Flags#

    The subscribe command supports the following local flags:

    prefix#

    The [--prefix] flag sets a common prefix to all paths specified using the local --path flag. Defaults to "".

    path#

    The path flag [--path] is used to specify the path(s) to which the client wants to subscribe.

    Multiple paths can be specified by using repeated --path flags:

    gnmic sub --path "/state/ports[port-id=*]" \
    +          --path "/state/router[router-name=*]/interface[interface-name=*]"
    +

    If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: "origin:path":

    Note

    The path after the origin value has to start with a /

    gnmic sub --path "openconfig-interfaces:/interfaces/interface"
    +

    target#

    With the optional [--target] flag it is possible to supply the path target information in the prefix field of the SubscriptionList message.

    set-target#

    The [--set-target] flag is used to set the SubscribeRequest Prefix target value to the configured target name stripped of the port number.

    model#

    The [--model] flag is used to specify the schema definition modules that the target should use when extracting the data to stream back.

    qos#

    The [--qos] flag specifies the packet marking that is to be used for the responses to the subscription request. Default marking is set to 20. If qos marking is not supported by a target the marking can be disabled by setting the value to 0.

    mode#

    The [--mode] mode flag specifies the mode of subscription to be created.

    This may be one of: ONCE, STREAM or POLL.

    It is case insensitive and defaults to STREAM.

    stream subscription mode#

    The [--stream-mode] flag is used to specify the stream subscription mode.

    This may be one of: ON_CHANGE, SAMPLE or TARGET_DEFINED

    This flag applies only if --mode is set to STREAM. It is case insensitive and defaults to SAMPLE.

    sample interval#

    The [--sample-interval] flag is used to specify the sample interval to be used by the target to send samples to the client.

    This flag applies only in case --mode is set to STREAM and --stream-mode is set to SAMPLE.

    Valid formats: 1s, 1m30s, 1h. Defaults to 0s which is the lowest interval supported by a target.

    heartbeat interval#

    The [--heartbeat-interval] flag is used to specify the server heartbeat interval.

    The heartbeat interval value can be specified along with ON_CHANGE or SAMPLE stream subscriptions modes.

    • ON_CHANGE: The value of the data item(s) MUST be re-sent once per heartbeat interval regardless of whether the value has changed or not.
    • SAMPLE: The target MUST generate one telemetry update per heartbeat interval, regardless of whether the --suppress-redundant flag is set to true.

    quiet#

    With [--quiet] flag set gnmic will not output subscription responses to stdout. The --quiet flag is useful when gnmic exports the received data to one of the export providers.

    suppress redundant#

    When the [--suppress-redundant] flag is set to true, the target SHOULD NOT generate a telemetry update message unless the value of the path being reported on has changed since the last update was generated.

    This flag applies only in case --mode is set to STREAM and --stream-mode is set to SAMPLE.

    updates only#

    When the [--updates-only] flag is set to true, the target MUST not transmit the current state of the paths that the client has subscribed to, but rather should send only updates to them.

    name#

    The [--name] flag is used to trigger one or multiple subscriptions already defined in the configuration file see defining subscriptions

    output#

    The [--output] flag is used to select one or multiple output already defined in the configuration file.

    Outputs defined under target take precedence over this flag, see defining outputs and defining targets

    watch-config#

    The [--watch-config] flag is used to enable automatic target loading from the configuration source at runtime.

    On each configuration change, gnmic reloads the list of targets, subscribes to new targets and/or deletes subscriptions to the deleted ones.

    Only addition and deletion of targets are currently supported, changes in an existing target config are not possible.

    backoff#

    The [--backoff] flag is used to specify a duration between consecutive subscription towards targets. It defaults to 0s meaning all subscription are started in parallel.

    If a locker is configured, the backoff timer is set to 100ms by default.

    lock-retry#

    The [--lock-retry] flag is a duration used to set the wait time between consecutive lock attempts. Defaults to 5s.

    history-snapshot#

    The [--history-snapshot] flag sets the snapshot value in the subscribe request gNMI History extension.

    The value can be either nanoseconds since Unix epoch or a date in RFC3339 format.

    history-start#

    The [--history-start] flag sets the start value in the subscribe request Time Range gNMI History extension.

    The value can be either nanoseconds since Unix epoch or a date in RFC3339 format.

    history-end#

    The [--history-end] flag sets the end value in the subscribe request Time Range gNMI History extension.

    Examples#

    1. streaming, target-defined, 10s interval#

    gnmic -a <ip:port> sub --path /state/port[port-id=*]/statistics
    +

    2. streaming, sample, 30s interval#

    gnmic -a <ip:port> sub --path "/state/port[port-id=*]/statistics" \
    +                       --sample-interval 30s
    +

    3. streaming, on-change, heartbeat interval 1min#

    gnmic -a <ip:port> sub --path "/state/port[port-id=*]/statistics" \
    +                       --stream-mode on-change \
    +                       --heartbeat-interval 1m
    +

    4. once subscription#

    gnmic -a <ip:port> sub --path "/state/port[port-id=*]/statistics" \
    +                       --mode once
    +

    \ No newline at end of file diff --git a/deployments/clusters/containerlab/cluster_with_gnmi_server_and_prometheus_output/index.html b/deployments/clusters/containerlab/cluster_with_gnmi_server_and_prometheus_output/index.html new file mode 100644 index 00000000..17ca4050 --- /dev/null +++ b/deployments/clusters/containerlab/cluster_with_gnmi_server_and_prometheus_output/index.html @@ -0,0 +1,29 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to achieve redundancy, high-availability and data aggregation via clustering.

    This deployment example includes:

    The leader election and target distribution is done with the help of a Consul server

    All members of the cluster expose a gNMI Server that the single gNMIc instance will use to aggregate the collected data.

    The aggregation gNMIc instance exposes a Prometheus output that is registered in Consul and is discoverable by the Prometheus server.

    The whole lab is pretty much self organising:

    • The gNMIc cluster instances discover the targets dynamically using a Docker Loader
    • The gNMIc standalone instance, discovers the cluster instance using a Consul Loader
    • The Prometheus server discovers gNMIc's Prometheus output using Consul Service Discovery

    Deployment files:

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/2.clusters/4.gnmi-server/containerlab
    +sudo clab deploy -t gnmi-server.clab.yaml
    +

    +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +| #  |          Name           | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address      |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +|  1 | clab-lab24-agg-gnmic    | 2e9cc2821b07 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64  |
    +|  2 | clab-lab24-consul-agent | c17d31d5f41b | consul:latest                | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64  |
    +|  3 | clab-lab24-gnmic1       | 3d56e09955f2 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64  |
    +|  4 | clab-lab24-gnmic2       | eba24dacea36 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64  |
    +|  5 | clab-lab24-gnmic3       | caf473f500f6 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64  |
    +|  6 | clab-lab24-grafana      | eaa224e62243 | grafana/grafana:latest       | linux |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64  |
    +|  7 | clab-lab24-leaf1        | 6771dc8d3786 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64  |
    +|  8 | clab-lab24-leaf2        | 5cfb1cf68958 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.14/24 | 2001:172:20:20::e/64  |
    +|  9 | clab-lab24-leaf3        | c438f734e44d | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.19/24 | 2001:172:20:20::13/64 |
    +| 10 | clab-lab24-leaf4        | ae4321825a03 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.17/24 | 2001:172:20:20::11/64 |
    +| 11 | clab-lab24-leaf5        | ee7a520fd844 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.18/24 | 2001:172:20:20::12/64 |
    +| 12 | clab-lab24-leaf6        | 59c3c515ef35 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64  |
    +| 13 | clab-lab24-leaf7        | 111f858b19fd | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.22/24 | 2001:172:20:20::16/64 |
    +| 14 | clab-lab24-leaf8        | 0ecc69891eb4 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.20/24 | 2001:172:20:20::14/64 |
    +| 15 | clab-lab24-prometheus   | 357821ec726e | prom/prometheus:latest       | linux |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64  |
    +| 16 | clab-lab24-spine1       | 0f5f6f6dc5fa | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.13/24 | 2001:172:20:20::d/64  |
    +| 17 | clab-lab24-spine2       | b718503d3b3f | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.15/24 | 2001:172:20:20::f/64  |
    +| 18 | clab-lab24-spine3       | e02f18d0e3ff | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.11/24 | 2001:172:20:20::b/64  |
    +| 19 | clab-lab24-spine4       | 3347cba3f277 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.12/24 | 2001:172:20:20::c/64  |
    +| 20 | clab-lab24-super-spine1 | 4abc7bcaf43c | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.16/24 | 2001:172:20:20::10/64 |
    +| 21 | clab-lab24-super-spine2 | 5b2f5f153d43 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.21/24 | 2001:172:20:20::15/64 |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +
    Check the Prometheus Output and gNMI Server documentation pages for more configuration options

    \ No newline at end of file diff --git a/deployments/clusters/containerlab/cluster_with_influxdb_output/index.html b/deployments/clusters/containerlab/cluster_with_influxdb_output/index.html new file mode 100644 index 00000000..4834a6d4 --- /dev/null +++ b/deployments/clusters/containerlab/cluster_with_influxdb_output/index.html @@ -0,0 +1,28 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to achieve redundancy, high-availability via clustering.

    This deployment example includes:

    The leader election and target distribution is done with the help of a Consul server

    Deployment files:

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/2.clusters/1.influxdb-output/containerlab
    +sudo clab deploy -t lab21.clab.yaml
    +
    +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +| #  |          Name           | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address      |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +|  1 | clab-lab21-consul-agent | a6f6eb70965f | consul:latest                | linux |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64  |
    +|  2 | clab-lab21-gnmic1       | 9758b0761431 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64  |
    +|  3 | clab-lab21-gnmic2       | 6d6ae91c64bf | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64  |
    +|  4 | clab-lab21-gnmic3       | 5df100a9fa73 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64  |
    +|  5 | clab-lab21-grafana      | fe51bda1830c | grafana/grafana:latest       | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64  |
    +|  6 | clab-lab21-influxdb     | 20712484d835 | influxdb:latest              | linux |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64  |
    +|  7 | clab-lab21-leaf1        | ce084f636942 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.14/24 | 2001:172:20:20::e/64  |
    +|  8 | clab-lab21-leaf2        | 5cbaba4bc9ff | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.11/24 | 2001:172:20:20::b/64  |
    +|  9 | clab-lab21-leaf3        | a5e92ca08c7e | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64  |
    +| 10 | clab-lab21-leaf4        | 1ccfe0082b15 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.12/24 | 2001:172:20:20::c/64  |
    +| 11 | clab-lab21-leaf5        | 7fd4144277a0 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64  |
    +| 12 | clab-lab21-leaf6        | cb4df0d609db | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.13/24 | 2001:172:20:20::d/64  |
    +| 13 | clab-lab21-leaf7        | 8f09b622365f | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.19/24 | 2001:172:20:20::13/64 |
    +| 14 | clab-lab21-leaf8        | 0ab91010b4a7 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.18/24 | 2001:172:20:20::12/64 |
    +| 15 | clab-lab21-spine1       | 86d00f11b944 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.15/24 | 2001:172:20:20::f/64  |
    +| 16 | clab-lab21-spine2       | 90cf49595ad2 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.20/24 | 2001:172:20:20::14/64 |
    +| 17 | clab-lab21-spine3       | 1c694820eb88 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.16/24 | 2001:172:20:20::10/64 |
    +| 18 | clab-lab21-spine4       | 1e3eac3de55f | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64  |
    +| 19 | clab-lab21-super-spine1 | aafc478de31d | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.21/24 | 2001:172:20:20::15/64 |
    +| 20 | clab-lab21-super-spine2 | bb27b743c97f | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.17/24 | 2001:172:20:20::11/64 |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +

    Check the InfluxDB Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/clusters/containerlab/cluster_with_nats_input_and_prometheus_output/index.html b/deployments/clusters/containerlab/cluster_with_nats_input_and_prometheus_output/index.html new file mode 100644 index 00000000..9eff86e9 --- /dev/null +++ b/deployments/clusters/containerlab/cluster_with_nats_input_and_prometheus_output/index.html @@ -0,0 +1,35 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to achieve redundancy, high-availability as well as data replication.

    The redundancy and high-availability are guaranteed by deploying a gnmic cluster.

    The data replication is achieved using a NATS server acting as both a gnmic input and output.

    This deployment example includes a:

    The leader election and target distribution is done with the help of a Consul server

    Each gnmic instance outputs the streamed gNMI data to NATS, and reads back all the data from the same NATS server (including its own),

    This effectively guarantees that each instance holds the data streamed by the whole cluster.

    Like in the previous examples, each gnmic instance will also register its Prometheus output service in Consul.

    But before doing so, it will attempt to acquire a key lock gnmic/$CLUSTER_NAME/prometheus-output, (use-lock: true)

    prom-output:
    +  type: prometheus
    +  listen: ":9804"
    +  service-registration:
    +    address: consul-agent:8500
    +    use-lock: true # <===
    +
    Since only one instance can hold a lock, only one prometheus output is registered, so only one output is scraped by Prometheus.

    Deployment files:

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/2.clusters/3.nats-input-prometheus-output/containerlab
    +sudo clab deploy -t lab23.clab.yaml
    +

    +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +| #  |          Name           | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address      |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +|  1 | clab-lab23-consul-agent | cfdaf19e9435 | consul:latest                | linux |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64  |
    +|  2 | clab-lab23-gnmic1       | 7e2a4060a1ae | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64  |
    +|  3 | clab-lab23-gnmic2       | 9e27e4620104 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64  |
    +|  4 | clab-lab23-gnmic3       | bb7471eb5f49 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64  |
    +|  5 | clab-lab23-grafana      | 3fbf7755c49e | grafana/grafana:latest       | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64  |
    +|  6 | clab-lab23-leaf1        | a61624d5312b | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.21/24 | 2001:172:20:20::15/64 |
    +|  7 | clab-lab23-leaf2        | ef86f701b379 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.14/24 | 2001:172:20:20::e/64  |
    +|  8 | clab-lab23-leaf3        | 352433a2ab3b | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.22/24 | 2001:172:20:20::16/64 |
    +|  9 | clab-lab23-leaf4        | 5ddba813d36f | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.19/24 | 2001:172:20:20::13/64 |
    +| 10 | clab-lab23-leaf5        | aad20f4b9969 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.11/24 | 2001:172:20:20::b/64  |
    +| 11 | clab-lab23-leaf6        | 757c76527a75 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.15/24 | 2001:172:20:20::f/64  |
    +| 12 | clab-lab23-leaf7        | d85e94aaa0dd | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64  |
    +| 13 | clab-lab23-leaf8        | ef6210c0e5aa | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.20/24 | 2001:172:20:20::14/64 |
    +| 14 | clab-lab23-nats         | f1a1f351bbf8 | nats:latest                  | linux |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64  |
    +| 15 | clab-lab23-prometheus   | f7f194a934c5 | prom/prometheus:latest       | linux |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64  |
    +| 16 | clab-lab23-spine1       | ddbf4e804097 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.16/24 | 2001:172:20:20::10/64 |
    +| 17 | clab-lab23-spine2       | f48323a4de88 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.17/24 | 2001:172:20:20::11/64 |
    +| 18 | clab-lab23-spine3       | 2a65eed26a7e | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.18/24 | 2001:172:20:20::12/64 |
    +| 19 | clab-lab23-spine4       | ea59d0e5d9ed | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.12/24 | 2001:172:20:20::c/64  |
    +| 20 | clab-lab23-super-spine1 | 37af6cd04dd8 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64  |
    +| 21 | clab-lab23-super-spine2 | 3408891a0718 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.13/24 | 2001:172:20:20::d/64  |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +
    Check the NATS Output, NATS Input and Prometheus Output documentation pages for more configuration options.

    \ No newline at end of file diff --git a/deployments/clusters/containerlab/cluster_with_prometheus_output/index.html b/deployments/clusters/containerlab/cluster_with_prometheus_output/index.html new file mode 100644 index 00000000..7d248988 --- /dev/null +++ b/deployments/clusters/containerlab/cluster_with_prometheus_output/index.html @@ -0,0 +1,28 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to achieve redundancy, high-availability via clustering.

    This deployment example includes:

    The leader election and target distribution is done with the help of a Consul server

    gnmic will also register its Prometheus output service in Consul so that Prometheus can discover which Prometheus servers are available to be scraped.

    Deployment files:

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/2.clusters/2.prometheus-output/containerlab
    +sudo clab deploy -t lab22.clab.yaml
    +

    +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +| #  |          Name           | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address      |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +|  1 | clab-lab22-consul-agent | 542169159f8b | consul:latest                | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64  |
    +|  2 | clab-lab22-gnmic1       | c04b2b597e7a | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64  |
    +|  3 | clab-lab22-gnmic2       | 49604280d82d | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64  |
    +|  4 | clab-lab22-gnmic3       | 49e910460cad | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64  |
    +|  5 | clab-lab22-grafana      | c0a37b012d29 | grafana/grafana:latest       | linux |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64  |
    +|  6 | clab-lab22-leaf1        | c6429b499c11 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.19/24 | 2001:172:20:20::13/64 |
    +|  7 | clab-lab22-leaf2        | 62f235b39a62 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.17/24 | 2001:172:20:20::11/64 |
    +|  8 | clab-lab22-leaf3        | 78d3b4e62a6b | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.11/24 | 2001:172:20:20::b/64  |
    +|  9 | clab-lab22-leaf4        | 8c5d80b4d916 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.13/24 | 2001:172:20:20::d/64  |
    +| 10 | clab-lab22-leaf5        | 508d4d2389b4 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.16/24 | 2001:172:20:20::10/64 |
    +| 11 | clab-lab22-leaf6        | 14ce19a8c5da | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64  |
    +| 12 | clab-lab22-leaf7        | c4f6e586baa3 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.20/24 | 2001:172:20:20::14/64 |
    +| 13 | clab-lab22-leaf8        | 1e00e6346bf1 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.12/24 | 2001:172:20:20::c/64  |
    +| 14 | clab-lab22-prometheus   | 5ed38ce63113 | prom/prometheus:latest       | linux |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64  |
    +| 15 | clab-lab22-spine1       | 38247b0f81e7 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64  |
    +| 16 | clab-lab22-spine2       | 76bf66748acd | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.21/24 | 2001:172:20:20::15/64 |
    +| 17 | clab-lab22-spine3       | 5c8776e2fc77 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.15/24 | 2001:172:20:20::f/64  |
    +| 18 | clab-lab22-spine4       | de67e5b92f36 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.14/24 | 2001:172:20:20::e/64  |
    +| 19 | clab-lab22-super-spine1 | 00f0aee0265a | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.18/24 | 2001:172:20:20::12/64 |
    +| 20 | clab-lab22-super-spine2 | 418888eb7325 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64  |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+
    +
    Check the Prometheus Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/clusters/docker-compose/cluster_with_influxdb_output/index.html b/deployments/clusters/docker-compose/cluster_with_influxdb_output/index.html new file mode 100644 index 00000000..3042c78c --- /dev/null +++ b/deployments/clusters/docker-compose/cluster_with_influxdb_output/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to achieve redundancy, high-availability via clustering.

    This deployment example includes:

    The leader election and target distribution is done with the help of a Consul server

    Deployment files:

    Download the files, update the gnmic config files with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the InfluxDB Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/clusters/docker-compose/cluster_with_nats_input_and_prometheus_output/index.html b/deployments/clusters/docker-compose/cluster_with_nats_input_and_prometheus_output/index.html new file mode 100644 index 00000000..4741e127 --- /dev/null +++ b/deployments/clusters/docker-compose/cluster_with_nats_input_and_prometheus_output/index.html @@ -0,0 +1,8 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to achieve redundancy, high-availability as well as data replication.

    The redundancy and high-availability are guaranteed by deploying a gnmic cluster.

    The data replication is achieved using a NATS server acting as both a gnmic input and output.

    This deployment example includes a:

    The leader election and target distribution is done with the help of a Consul server

    Each gnmic instance outputs the streamed gNMI data to NATS, and reads back all the data from the same NATS server (including its own),

    This effectively guarantees that each instance holds the data streamed by the whole cluster.

    Like in the previous examples, each gnmic instance will also register its Prometheus output service in Consul.

    But before doing so, it will attempt to acquire a key lock gnmic/$CLUSTER_NAME/prometheus-output, (use-lock: true)

    prom-output:
    +  type: prometheus
    +  listen: ":9804"
    +  service-registration:
    +    address: consul-agent:8500
    +    use-lock: true # <===
    +

    Since only one instance can hold a lock, only one prometheus output is registered, so only one output is scraped by Prometheus.

    Deployment files:

    Download the files, update the gnmic config files with the desired subscriptions and targets.

    Note

    The targets outputs list should include the nats output name

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the NATS Output, NATS Input and Prometheus Output documentation pages for more configuration options.

    \ No newline at end of file diff --git a/deployments/clusters/docker-compose/cluster_with_prometheus_output/index.html b/deployments/clusters/docker-compose/cluster_with_prometheus_output/index.html new file mode 100644 index 00000000..9ba4c4ae --- /dev/null +++ b/deployments/clusters/docker-compose/cluster_with_prometheus_output/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to achieve redundancy, high-availability via clustering.

    This deployment example includes:

    The leader election and target distribution is done with the help of a Consul server

    gnmic will also register its Prometheus output service in Consul so that Prometheus can discover which Prometheus servers are available to be scraped

    Deployment files:

    Download the files, update the gnmic config files with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the Prometheus Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/clusters/kubernetes/cluster_with_prometheus_output/index.html b/deployments/clusters/kubernetes/cluster_with_prometheus_output/index.html new file mode 100644 index 00000000..8d79748a --- /dev/null +++ b/deployments/clusters/kubernetes/cluster_with_prometheus_output/index.html @@ -0,0 +1,8 @@ + Kubernetes - gNMIc

    Kubernetes

    The purpose of this deployment is to achieve redundancy, high-availability using Kubernetes and gnmic's internal clustering mechanism.

    This deployment example includes:

    The leader election and target distribution is done with the help of a Consul server

    gnmic can be discovered by Prometheus using Kubernetes service discovery. Kubernetes uses a headless service with a StatefulSet to disable the internal load balancing across multiple pods of the same StatefulSet and allow Prometheus to discover all instances of gnmic.

    Prometheus Operator must be installed prior to gnmic deployment. (Can also be installed via kube-prometheus-stack helm chart or kube-prometheus)

    Deployment files:

    Download the files, update the gnmic ConfigMap with the desired subscriptions and targets and make sure that prometheus servicemonitor is in a namespace or has a label that Prometheus operator is watching.

    Deploy it with:

    kubectl create ns gnmic
    +kubectl apply -n gnmic -f kubernetes/consul
    +kubectl apply -n gnmic -f kubernetes/gnmic-app
    +# Before deploying the Prometheus ServiceMonitor
    +# Install Prometheus operator or kube-prometheus or kube-prometheus-stack helm chart
    +# Otherwise the command will fail
    +kubectl apply -f kubernetes/prometheus
    +

    Check the Prometheus Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/deployments_intro/index.html b/deployments/deployments_intro/index.html new file mode 100644 index 00000000..c55afde8 --- /dev/null +++ b/deployments/deployments_intro/index.html @@ -0,0 +1 @@ + Deployments - gNMIc

    Deployments

    There are numerous ways gnmic can be deployed, each fulfilling a specific use case.

    Whether it is gNMI telemetry collection and export to a single output, or clustered data pipelines with high availability and redundancy, the below examples should cover the most common use cases.

    In this section you will find multiple deployment examples, using docker-compose or containerlab. Each deployment comes with:

    • a docker-compose or clab file
    • one or multiple gnmic configuration file(s)
    • extra configuration files if required by the use case (e.g: prometheus, grafana,...)

    The containerlab examples come with a fabric deployed using Nokia's SR Linux

    If you don't find an example that fits your needs, feel free to open an issue on github

    Single Instance#

    These examples showcase single gnmic instance deployments with the most commonly used outputs

    Clusters#

    gnmic can also be deployed in clustered mode to either load share the targets connections between multiple instances and offer connection resiliency, and/or replicate the collected data among all the cluster members

    Pipelines#

    Building data pipelines using gnmic is achieved using the outputs and inputs plugins.

    You will be able to process the data in a serial fashion, split it for parallel processing or mirror it to create a forked pipeline.

    \ No newline at end of file diff --git a/deployments/pipelines/docker-compose/forked_pipeline/index.html b/deployments/pipelines/docker-compose/forked_pipeline/index.html new file mode 100644 index 00000000..b6c62af2 --- /dev/null +++ b/deployments/pipelines/docker-compose/forked_pipeline/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to create a forked data pipeline using NATS , Influxdb and Prometheus

    The example includes 3 gnmic instances.

    • The first, called collector, is responsible for streaming the gNMI data from the targets and output it to a NATS server.
    • The second and third, called relay1 and relay2, reads the data from NATS and writes it to either InfluxDB or Prometheus

    This deployment enables a few use cases:

    • Apply different processors by the collector and relay.
    • Scale the collector and relay separately, see this example for a scaled-out version.
    • Fork the data into a separate pipeline for a different use case.

    Deployment files:

    Download the files, update the gnmic collector config files with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the Prometheus Output and NATS Input documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/pipelines/docker-compose/gnmic_cluster_nats_prometheus/index.html b/deployments/pipelines/docker-compose/gnmic_cluster_nats_prometheus/index.html new file mode 100644 index 00000000..ed96b6eb --- /dev/null +++ b/deployments/pipelines/docker-compose/gnmic_cluster_nats_prometheus/index.html @@ -0,0 +1,3 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to create a clustered data pipeline using NATS and Prometheus. Achieving redundancy, high-availability and data replication, all in clustered data pipeline.

    The example is divided in 2 parts:

    • Clustered collectors and single relay
    • Clustered collectors and clustered relays

    These 2 examples are essentially scaled-out versions of this example

    Clustered collectors and single relay#

    Deployment files:

    Download the files, update the gnmic collectors config files with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the Prometheus Output and NATS Input documentation page for more configuration options

    Clustered collectors and clustered relays#

    Deployment files:

    Download the files, update the gnmic collectors config files with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the Prometheus Output and NATS Input documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/pipelines/docker-compose/nats_influxdb/index.html b/deployments/pipelines/docker-compose/nats_influxdb/index.html new file mode 100644 index 00000000..9e3d22b7 --- /dev/null +++ b/deployments/pipelines/docker-compose/nats_influxdb/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to create data pipeline using NATS and InfluxDB

    The example includes 2 gnmic instances.

    • The first, called collector, is responsible for streaming the gNMI data from the targets and output it to a NATS server.
    • The second, called relay, reads the data from NATS and writes it to InfluxDB

    This deployment enables a few use cases:

    • Apply different processors by the collector and relay.
    • Scale the collector and relay separately, see this example for a scaled-out version.
    • Fork the data into a separate pipeline for a different use case.

    Deployment files:

    Download the files, update the gnmic collector config files with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the InfluxDB Output and NATS Input documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/pipelines/docker-compose/nats_prometheus/index.html b/deployments/pipelines/docker-compose/nats_prometheus/index.html new file mode 100644 index 00000000..977113e5 --- /dev/null +++ b/deployments/pipelines/docker-compose/nats_prometheus/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to create data pipeline using NATS and Prometheus

    The example includes 2 gnmic instances.

    • The first, called collector, is responsible for streaming the gNMI data from the targets and output it to a NATS server.
    • The second, called relay, reads the data from NATS and writes it to Prometheus

    This deployment enables a few use cases:

    • Apply different processors by the collector and relay.
    • Scale the collector and relay separately, see this example for a scaled-out version.
    • Fork the data into a separate pipeline for a different use case.

    Deployment files:

    Download the files, update the gnmic collector config files with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the Prometheus Output and NATS Input documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/single-instance/containerlab/influxdb-output/index.html b/deployments/single-instance/containerlab/influxdb-output/index.html new file mode 100644 index 00000000..128f8c0a --- /dev/null +++ b/deployments/single-instance/containerlab/influxdb-output/index.html @@ -0,0 +1,17 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to collect gNMI data and write it to an InfluxDB instance.

    This deployment example includes a single gnmic instance, a single InfluxDB server acting as an InfluxDB output and a Grafana server

    Deployment files:

    The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed.

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/1.single-instance/3.influxdb-output/containerlab
    +sudo clab deploy -t influxdb.clab.yaml
    +
    +---+---------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +| # |        Name         | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address     |
    ++---+---------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +| 1 | clab-lab13-gnmic    | 1ee4c75ff443 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64 |
    +| 2 | clab-lab13-grafana  | a932207780bb | grafana/grafana:latest       | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64 |
    +| 3 | clab-lab13-influxdb | 0768ba6ca10b | influxdb:latest              | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64 |
    +| 4 | clab-lab13-leaf1    | e0e2045fca7f | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64 |
    +| 5 | clab-lab13-leaf2    | 75b8978e734c | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64 |
    +| 6 | clab-lab13-leaf3    | 7b03eed78f5d | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64 |
    +| 7 | clab-lab13-leaf4    | 19007ce81e04 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64 |
    +| 8 | clab-lab13-spine1   | c044fc51196d | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64 |
    +| 9 | clab-lab13-spine2   | bcfa52ad2772 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64 |
    ++---+---------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +

    Check the InfluxDB Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/single-instance/containerlab/kafka-output/index.html b/deployments/single-instance/containerlab/kafka-output/index.html new file mode 100644 index 00000000..1f53f1c5 --- /dev/null +++ b/deployments/single-instance/containerlab/kafka-output/index.html @@ -0,0 +1,17 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to collect gNMI data and write it to a Kafka broker.

    Multiple 3rd Party systems (acting as a Kafka consumers) can then read the data from the Kafka broker for further processing.

    This deployment example includes a single gnmic instance and a single Kafka output

    Deployment files:

    The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed.

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/1.single-instance/2.kafka-output/containerlab
    +sudo clab deploy -t kafka.clab.yaml
    +
    +---+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +| # |            Name             | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address     |
    ++---+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +| 1 | clab-lab12-gnmic            | e79d31f92a7a | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64 |
    +| 2 | clab-lab12-kafka-server     | 004a338cdb3d | bitnami/kafka:latest         | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64 |
    +| 3 | clab-lab12-leaf1            | b9269bac3adf | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64 |
    +| 4 | clab-lab12-leaf2            | baaeea0ad1a6 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64 |
    +| 5 | clab-lab12-leaf3            | 08127014b3cd | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64 |
    +| 6 | clab-lab12-leaf4            | da037997c5ff | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64 |
    +| 7 | clab-lab12-spine1           | c3bcfe40fcc7 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64 |
    +| 8 | clab-lab12-spine2           | 842b259d01b0 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64 |
    +| 9 | clab-lab12-zookeeper-server | 5c89e48fdff1 | bitnami/zookeeper:latest     | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64 |
    ++---+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +

    Check the Kafka Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/single-instance/containerlab/multiple-outputs/index.html b/deployments/single-instance/containerlab/multiple-outputs/index.html new file mode 100644 index 00000000..7acee84b --- /dev/null +++ b/deployments/single-instance/containerlab/multiple-outputs/index.html @@ -0,0 +1,22 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to collect gNMI data and write it to multiple outputs.

    This deployment example includes:

    Deployment files:

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/1.single-instance/5.multiple-outputs/containerlab
    +sudo clab deploy -t multiple-outputs.clab.yaml
    +
    +----+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +| #  |            Name             | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address     |
    ++----+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +|  1 | clab-lab15-consul-agent     | 14f864fb1da9 | consul:latest                | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64 |
    +|  2 | clab-lab15-gnmic            | cfb8bfca7547 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64 |
    +|  3 | clab-lab15-grafana          | 56c19565e27c | grafana/grafana:latest       | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64 |
    +|  4 | clab-lab15-influxdb         | f2d0b2186e10 | influxdb:latest              | linux |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64 |
    +|  5 | clab-lab15-kafka-server     | efe445dbf0f0 | bitnami/kafka:latest         | linux |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64 |
    +|  6 | clab-lab15-leaf1            | 42d57c79385e | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64 |
    +|  7 | clab-lab15-leaf2            | e4b041046779 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.11/24 | 2001:172:20:20::b/64 |
    +|  8 | clab-lab15-leaf3            | ba87204f2678 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.13/24 | 2001:172:20:20::d/64 |
    +|  9 | clab-lab15-leaf4            | 327461ee913e | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.15/24 | 2001:172:20:20::f/64 |
    +| 10 | clab-lab15-nats             | 0363dae05edf | nats:latest                  | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64 |
    +| 11 | clab-lab15-prometheus       | 44611ebe4a03 | prom/prometheus:latest       | linux |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64 |
    +| 12 | clab-lab15-spine1           | 8b2b430eea87 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.12/24 | 2001:172:20:20::c/64 |
    +| 13 | clab-lab15-spine2           | 425bea3a243e | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.14/24 | 2001:172:20:20::e/64 |
    +| 14 | clab-lab15-zookeeper-server | 91b546eb7bf9 | bitnami/zookeeper:latest     | linux |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64 |
    ++----+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +

    Check the gnmic outputs documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/single-instance/containerlab/nats-output/index.html b/deployments/single-instance/containerlab/nats-output/index.html new file mode 100644 index 00000000..3662859b --- /dev/null +++ b/deployments/single-instance/containerlab/nats-output/index.html @@ -0,0 +1,16 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to collect gNMI data and write it to a NATS server.

    Multiple 3rd Party systems (acting as a NATS clients) can then read the data from the NATS server for further processing.

    This deployment example includes a single gnmic instance and a single NATS output

    Deployment files:

    The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed.

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/1.single-instance/1.nats-output/containerlab
    +sudo clab deploy -t nats.clab.yaml
    +
    +---+-------------------+--------------+------------------------------+-------+-------+---------+----------------+----------------------+
    +| # |       Name        | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address  |     IPv6 Address     |
    ++---+-------------------+--------------+------------------------------+-------+-------+---------+----------------+----------------------+
    +| 1 | clab-lab11-gnmic  | 955eaa35b730 | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.3/24 | 2001:172:20:20::3/64 |
    +| 2 | clab-lab11-leaf1  | f0f61a79124e | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.4/24 | 2001:172:20:20::4/64 |
    +| 3 | clab-lab11-leaf2  | de714ee79856 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24 | 2001:172:20:20::9/64 |
    +| 4 | clab-lab11-leaf3  | c674b7bbb898 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.8/24 | 2001:172:20:20::8/64 |
    +| 5 | clab-lab11-leaf4  | c37033f30e99 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.7/24 | 2001:172:20:20::7/64 |
    +| 6 | clab-lab11-nats   | ebbd346d2aee | nats:latest                  | linux |       | running | 172.20.20.2/24 | 2001:172:20:20::2/64 |
    +| 7 | clab-lab11-spine1 | 0fe91271bdfe | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.6/24 | 2001:172:20:20::6/64 |
    +| 8 | clab-lab11-spine2 | 6b05f4e42cc4 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.5/24 | 2001:172:20:20::5/64 |
    ++---+-------------------+--------------+------------------------------+-------+-------+---------+----------------+----------------------+
    +

    Check the NATS Output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/single-instance/containerlab/prometheus-output/index.html b/deployments/single-instance/containerlab/prometheus-output/index.html new file mode 100644 index 00000000..3f797eaa --- /dev/null +++ b/deployments/single-instance/containerlab/prometheus-output/index.html @@ -0,0 +1,18 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to collect gNMI data and make it available for scraping by a Prometheus client.

    This deployment example includes a single gnmic instance, a Prometheus Server, a Consul agent used by Prometheus to discover gNMIc's Prometheus output and a Grafana server.

    Deployment files:

    The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed.

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/1.single-instance/4.prometheus-output/containerlab
    +sudo clab deploy -t prometheus.clab.yaml
    +
    +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +| #  |          Name           | Container ID |            Image             | Kind  | Group |  State  |  IPv4 Address   |     IPv6 Address     |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +|  1 | clab-lab14-consul-agent | e402b0516753 | consul:latest                | linux |       | running | 172.20.20.4/24  | 2001:172:20:20::4/64 |
    +|  2 | clab-lab14-gnmic        | 53943cdb8cde | ghcr.io/openconfig/gnmic:latest | linux |       | running | 172.20.20.3/24  | 2001:172:20:20::3/64 |
    +|  3 | clab-lab14-grafana      | 1a57efb74f37 | grafana/grafana:latest       | linux |       | running | 172.20.20.2/24  | 2001:172:20:20::2/64 |
    +|  4 | clab-lab14-leaf1        | 8343848fbd7a | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.9/24  | 2001:172:20:20::9/64 |
    +|  5 | clab-lab14-leaf2        | 9986ff987048 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.8/24  | 2001:172:20:20::8/64 |
    +|  6 | clab-lab14-leaf3        | 25a212fcb7a1 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.11/24 | 2001:172:20:20::b/64 |
    +|  7 | clab-lab14-leaf4        | 025373e9f192 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.10/24 | 2001:172:20:20::a/64 |
    +|  8 | clab-lab14-prometheus   | ae9b47c49c8d | prom/prometheus:latest       | linux |       | running | 172.20.20.5/24  | 2001:172:20:20::5/64 |
    +|  9 | clab-lab14-spine1       | fb9abd5b4c5c | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.7/24  | 2001:172:20:20::7/64 |
    +| 10 | clab-lab14-spine2       | f32906f19d55 | ghcr.io/nokia/srlinux        | srl   |       | running | 172.20.20.6/24  | 2001:172:20:20::6/64 |
    ++----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+
    +

    Check the Prometheus output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/single-instance/containerlab/prometheus-remote-write-output/index.html b/deployments/single-instance/containerlab/prometheus-remote-write-output/index.html new file mode 100644 index 00000000..4191b94c --- /dev/null +++ b/deployments/single-instance/containerlab/prometheus-remote-write-output/index.html @@ -0,0 +1,18 @@ + Containerlab - gNMIc

    Containerlab

    The purpose of this deployment is to collect gNMI data and use Prometheus remote write API to push it to different monitoring systems like Prometheus, Mimir, CortexMetrics, VictoriaMetrics, Thanos...

    This deployment example includes a single gnmic instance, a Prometheus Server, and a Grafana server.

    Deployment files:

    The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed.

    Deploy it with:

    git clone https://github.com/openconfig/gnmic.git
    +cd gnmic/examples/deployments/1.single-instance/6.prometheus-write-output/containerlab
    +sudo clab deploy -t prometheus.clab.yaml
    +
    +----+-------------------------+--------------+------------------------------+-------+---------+-----------------+--------------+
    +| #  |          Name           | Container ID |            Image             | Kind  |  State  |  IPv4 Address   | IPv6 Address |
    ++----+-------------------------+--------------+------------------------------+-------+---------+-----------------+--------------+
    +|  1 | clab-lab16-consul-agent | 10054b55e722 | consul:latest                | linux | running | 172.19.19.3/24  | N/A          |
    +|  2 | clab-lab16-gnmic        | 1eeab0771731 | ghcr.io/openconfig/gnmic:latest | linux | running | 172.19.19.5/24  | N/A          |
    +|  3 | clab-lab16-grafana      | fd09146937ef | grafana/grafana:latest       | linux | running | 172.19.19.2/24  | N/A          |
    +|  4 | clab-lab16-leaf1        | 0c8f5bf7bafb | ghcr.io/nokia/srlinux        | srl   | running | 172.19.19.11/24 | N/A          |
    +|  5 | clab-lab16-leaf2        | a33868bef0a3 | ghcr.io/nokia/srlinux        | srl   | running | 172.19.19.9/24  | N/A          |
    +|  6 | clab-lab16-leaf3        | 3fb3b459cd48 | ghcr.io/nokia/srlinux        | srl   | running | 172.19.19.10/24 | N/A          |
    +|  7 | clab-lab16-leaf4        | bb2cbc064b05 | ghcr.io/nokia/srlinux        | srl   | running | 172.19.19.6/24  | N/A          |
    +|  8 | clab-lab16-prometheus   | 63b6fb1551de | prom/prometheus:latest       | linux | running | 172.19.19.4/24  | N/A          |
    +|  9 | clab-lab16-spine1       | 76853ab9c4a8 | ghcr.io/nokia/srlinux        | srl   | running | 172.19.19.8/24  | N/A          |
    +| 10 | clab-lab16-spine2       | fdf42ca0fec1 | ghcr.io/nokia/srlinux        | srl   | running | 172.19.19.7/24  | N/A          |
    ++----+-------------------------+--------------+------------------------------+-------+---------+-----------------+--------------+
    +

    Check the Prometheus Remote Write output documentation page for more configuration options.

    \ No newline at end of file diff --git a/deployments/single-instance/docker-compose/influxdb-output/index.html b/deployments/single-instance/docker-compose/influxdb-output/index.html new file mode 100644 index 00000000..027701e8 --- /dev/null +++ b/deployments/single-instance/docker-compose/influxdb-output/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to collect gNMI data and write it to an InfluxDB instance.

    This deployment example includes a single gnmic instance and a single InfluxDB output

    Deployment files:

    Download both files, update the gnmic config file with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the InfluxDB Output documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/single-instance/docker-compose/kafka-output/index.html b/deployments/single-instance/docker-compose/kafka-output/index.html new file mode 100644 index 00000000..7f0fdca3 --- /dev/null +++ b/deployments/single-instance/docker-compose/kafka-output/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to collect gNMI data and write it to a Kafka broker.

    Multiple 3rd Party systems (acting as a Kafka consumers) can then read the data from the Kafka broker for further processing.

    This deployment example includes a single gnmic instance and a single Kafka output

    Deployment files:

    Download both files, update the gnmic config file with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the Kafka Output documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/single-instance/docker-compose/multiple-outputs/index.html b/deployments/single-instance/docker-compose/multiple-outputs/index.html new file mode 100644 index 00000000..58ecade3 --- /dev/null +++ b/deployments/single-instance/docker-compose/multiple-outputs/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to collect gNMI data and write it to multiple outputs.

    This deployment example includes:

    Deployment files:

    Download both files, update the gnmic config file with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the gnmic outputs documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/single-instance/docker-compose/nats-output/index.html b/deployments/single-instance/docker-compose/nats-output/index.html new file mode 100644 index 00000000..fcd6433e --- /dev/null +++ b/deployments/single-instance/docker-compose/nats-output/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to collect gNMI data and write it to a NATS server.

    Multiple 3rd Party systems (acting as a NATS clients) can then read the data from the NATS server for further processing.

    This deployment example includes a single gnmic instance and a single NATS output

    Deployment files:

    Download both files, update the gnmic config file with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the NATS Output documentation page for more configuration options

    \ No newline at end of file diff --git a/deployments/single-instance/docker-compose/prometheus-output/index.html b/deployments/single-instance/docker-compose/prometheus-output/index.html new file mode 100644 index 00000000..d60f8298 --- /dev/null +++ b/deployments/single-instance/docker-compose/prometheus-output/index.html @@ -0,0 +1,2 @@ + Docker Compose - gNMIc

    Docker Compose

    The purpose of this deployment is to collect gNMI data and make it available for scraping by a Prometheus client.

    This deployment example includes a single gnmic instance and a single Prometheus output

    Deployment files:

    Download both files, update the gnmic config file with the desired subscriptions and targets.

    Deploy it with:

    sudo docker-compose up -d
    +

    Check the Prometheus output documentation page for more configuration options

    \ No newline at end of file diff --git a/global_flags/index.html b/global_flags/index.html new file mode 100644 index 00000000..d26dbb89 --- /dev/null +++ b/global_flags/index.html @@ -0,0 +1,87 @@ + Global flags - gNMIc

    Global flags

    address#

    The address flag [-a | --address] is used to specify the target's gNMI server address in address:port format, for e.g: 192.168.113.11:57400

    Multiple target addresses can be specified, either as comma separated values:

    gnmic --address 192.168.113.11:57400,192.168.113.12:57400 
    +

    or by using the --address flag multiple times:

    gnmic -a 192.168.113.11:57400 --address 192.168.113.12:57400
    +

    auth-scheme#

    The auth-scheme flag --auth-scheme is used to specify the authorization header type. For example, if auth-scheme is set to Basic, the gNMI requests headers will include an Authorization header with value Basic base64enc(username:password).

    cluster-name#

    The [--cluster-name] flag is used to specify the cluster name the gnmic instance will join.

    The cluster name is used as part of the locked keys to share targets between multiple gnmic instances.

    Defaults to default-cluster

    config#

    The --config flag specifies the location of a configuration file that gnmic will read.

    If not specified, gnmic searches for a file named .gnmic with extensions yaml, yml, toml or json in the following locations:

    • $PWD
    • $HOME
    • $XDG_CONFIG_HOME
    • $XDG_CONFIG_HOME/gnmic

    debug#

    The debug flag [-d | --debug] enables the printing of extra information when sending/receiving an RPC

    dir#

    A path to a directory which gnmic would recursively traverse in search for the additional YANG files which may be required by YANG files specified with --file to build the YANG tree.

    Can also point to a single YANG file instead of a directory.

    Multiple --dir flags can be supplied.

    encoding#

    The encoding flag [-e | --encoding] is used to specify the gNMI encoding of the Update part of a Notification message.

    It is case insensitive and must be one of: JSON, BYTES, PROTO, ASCII, JSON_IETF

    exclude#

    The --exclude flag specifies the YANG module names to be excluded from the tree generation when YANG modules names clash.

    Multiple --exclude flags can be supplied.

    file#

    A path to a YANG file or a directory with YANG files which gnmic will use with prompt, generate and path commands.

    Multiple --file flags can be supplied.

    format#

    Five output formats can be configured by means of the --format flag. [proto, protojson, prototext, json, event] The default format is json.

    The proto format outputs the gnmi message as raw bytes, this value is not allowed when the output type is file (file system, stdout or stderr) see outputs

    The prototext and protojson formats are the message representation as defined in prototext and protojson

    The event format emits the received gNMI SubscribeResponse updates and deletes as a list of events tagged with the keys present in the subscribe path (as well as some metadata) and a timestamp

    Here goes an example of the same response emitted to stdout in the respective formats:

    {
    +  "update": {
    +  "timestamp": "1595584408456503938",
    +  "prefix": {
    +    "elem": [
    +      {
    +        "name": "state"
    +      },
    +      {
    +        "name": "system"
    +      },
    +      {
    +        "name": "version"
    +      }
    +    ]
    +  },
    +    "update": [
    +      {
    +        "path": {
    +          "elem": [
    +            {
    +             "name": "version-string"
    +           }
    +          ]
    +        },
    +        "val": {
    +          "stringVal": "TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\r\nAll rights reserved. All use subject to applicable license agreements.\r\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros"
    +        }
    +      }
    +    ]
    +  }
    +}
    +
    update: {
    +  timestamp: 1595584168675434221
    +  prefix: {
    +    elem: {
    +      name: "state"
    +    }
    +    elem: {
    +      name: "system"
    +    }
    +    elem: {
    +      name: "version"
    +    }
    +  }
    +  update: {
    +    path: {
    +      elem: {
    +        name: "version-string"
    +      }
    +    }
    +    val: {
    +      string_val: "TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\r\nAll rights reserved. All use subject to applicable license agreements.\r\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros"
    +    }
    +  }
    +}
    +
    {
    +  "source": "172.17.0.100:57400",
    +  "subscription-name": "default",
    +  "timestamp": 1595584326775141151,
    +  "time": "2020-07-24T17:52:06.775141151+08:00",
    +  "prefix": "state/system/version",
    +  "updates": [
    +    {
    +      "Path": "version-string",
    +      "values": {
    +        "version-string": "TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\r\nAll rights reserved. All use subject to applicable license agreements.\r\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros"
    +      }
    +    }
    +  ]
    +}
    +
    [
    +  {
    +    "name": "default",
    +    "timestamp": 1595584587725708234,
    +    "tags": {
    +      "source": "172.17.0.100:57400",
    +      "subscription-name": "default"
    +    },
    +    "values": {
    +      "/state/system/version/version-string": "TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\r\nAll rights reserved. All use subject to applicable license agreements.\r\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros"
    +    }
    +  }
    +]
    +

    gzip#

    The [--gzip] flag enables gRPC gzip compression.

    insecure#

    The insecure flag [--insecure] is used to indicate that the client wishes to establish an non-TLS enabled gRPC connection.

    To disable certificate validation in a TLS-enabled connection use skip-verify flag.

    instance-name#

    The [--instance-name] flag is used to give a unique name to the running gnmic instance. This is useful when there are multiple instances of gnmic running at the same time, either for high-availability and/or scalability

    log#

    The --log flag enables log messages to appear on stderr output. By default logging is disabled.

    log-file#

    The log-file flag [--log-file <path>] sets the log output to a file referenced by the path. This flag supersede the --log flag

    log-max-size#

    The [--log-max-size] flag enables log rotation and sets the maximum size of the log file in megabytes before it gets rotated.

    log-max-backups#

    The [--log-max-backups] flag sets the maximum number of old log files to retain. The default is to retain all old log files.

    log-compress#

    The [--log-compress] flag determines if the rotated log files should be compressed using gzip. The default is not to perform compression.

    no-prefix#

    The no prefix flag [--no-prefix] disables prefixing the json formatted responses with [ip:port] string.

    Note that in case a single target is specified, the prefix is not added.

    password#

    The password flag [-p | --password] is used to specify the target password as part of the user credentials.

    Note that in case multiple targets are used, all should use the same credentials.

    proto-dir#

    The [--proto-dir] flag is used to specify a list of directories where gnmic will search for the proto file names specified with --proto-file.

    proto-file#

    The [--proto-file] flag is used to specify a list of proto file names that gnmic will use to decode ProtoBytes values. only Nokia SROS proto is currently supported.

    proxy-from-env#

    The proxy-from-env flag [--proxy-from-env] indicates that the gnmic should use the HTTP/HTTPS proxy addresses defined in the environment variables http_proxy and https_proxy to reach the targets specified using the --address flag.

    retry#

    The retry flag [--retry] specifies the wait time before each retry.

    Valid formats: 10s, 1m30s, 1h. Defaults to 10s

    skip-verify#

    The skip verify flag [--skip-verify] indicates that the target should skip the signature verification steps, in case a secure connection is used.

    targets-file#

    The [--targets-file] flag is used to configure a file target loader

    timeout#

    The timeout flag [--timeout] specifies the gRPC timeout after which the connection attempt fails.

    Valid formats: 10s, 1m30s, 1h. Defaults to 10s

    tls-ca#

    The TLS CA flag [--tls-ca] specifies the root certificates for verifying server certificates encoded in PEM format.

    tls-cert#

    The TLS cert flag [--tls-cert] specifies the public key for the client encoded in PEM format.

    tls-key#

    The TLS key flag [--tls-key] specifies the private key for the client encoded in PEM format.

    tls-max-version#

    The TLS max version flag [--tls-max-version] specifies the maximum supported TLS version supported by gNMIc when creating a secure gRPC connection.

    tls-min-version#

    The tls min version flag [--tls-min-version] specifies the minimum supported TLS version supported by gNMIc when creating a secure gRPC connection.

    tls-server-name#

    The TLS server name flag [--tls-server-name] sets the server name to be used when verifying the hostname on the returned certificates unless --skip-verify is set.

    This global flag applies to all targets.

    tls-version#

    The tls version flag [--tls-version] specifies a single supported TLS version gNMIc when creating a secure gRPC connection.

    This flag overwrites the previously listed flags --tls-max-version and --tls-min-version.

    log-tls-secret#

    The log TLS secret flag [--log-tls-secret] makes gnmic to log the per-session pre-master secret so that it can be used to decrypt TLS secured gNMI communications with, for example, Wireshark.

    The secret will be saved to a file named <target-name>.tlssecret.log.

    token#

    The token flag [--token] sets a token value to be added to each RPC as an Authorization Bearer Token.

    Applied only in the case of a secure gRPC connection.

    username#

    The username flag [-u | --username] is used to specify the target username as part of the user credentials.

    calculate-latency#

    The --calculate-latency flag augments subscribe et get responses by calculating the delta between the message timestamp and the receive timestamp. The resulting message will include 4 extra fields:

    • recv-timestamp:The receive timestamp in nanoseconds.
    • recv-time: The receive time in ISO 8601 date and time representation, extended to include fractional seconds and a time zone offset..
    • latency-nano: The difference between the message timestamp and the receive time in nanoseconds.
    • latency-milli: The difference between the message timestamp and the receive time in milliseconds.

    metadata#

    The [-H | --metadata] flag adds custom headers to any gRPC request. gnmic -H header1=value1 -H header2=value2

    \ No newline at end of file diff --git a/images/gnmi-headline-1.svg b/images/gnmi-headline-1.svg new file mode 100644 index 00000000..c5ac1d3f --- /dev/null +++ b/images/gnmi-headline-1.svg @@ -0,0 +1,176 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/gnmic-headline.svg b/images/gnmic-headline.svg new file mode 100644 index 00000000..9563a701 --- /dev/null +++ b/images/gnmic-headline.svg @@ -0,0 +1,189 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/gnmic-logo-ink.svg b/images/gnmic-logo-ink.svg new file mode 100644 index 00000000..e1ccc247 --- /dev/null +++ b/images/gnmic-logo-ink.svg @@ -0,0 +1,133 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + gNMIc + + diff --git a/images/gnmic-logo.pdf b/images/gnmic-logo.pdf new file mode 100644 index 00000000..21afdc3c Binary files /dev/null and b/images/gnmic-logo.pdf differ diff --git a/images/gnmic.prompt-mode.demo.gif b/images/gnmic.prompt-mode.demo.gif new file mode 100644 index 00000000..18d9325c Binary files /dev/null and b/images/gnmic.prompt-mode.demo.gif differ diff --git a/images/outputs.svg b/images/outputs.svg new file mode 100644 index 00000000..c2fe13eb --- /dev/null +++ b/images/outputs.svg @@ -0,0 +1,3 @@ + + +
    LocalFS
    LocalFS
    gNMI subscription
    gNMI subscription
    Json/Proto/Event
    Json/Proto/Event
    InfluxDB
    InfluxDB
    NATS / NATS Streaming
    NATS / NATS Streaming
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/images/pulse.svg b/images/pulse.svg new file mode 100644 index 00000000..d2d55a95 --- /dev/null +++ b/images/pulse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..945a9ced --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + gNMIc

    Home

    github release Github all releases


    gnmic (pronoun.: gee·en·em·eye·see) is a gNMI CLI client that provides full support for Capabilities, Get, Set and Subscribe RPCs with collector capabilities.

    Features#

    • Full support for gNMI RPCs
      Every gNMI RPC has a corresponding command with all of the RPC options configurable by means of the local and global flags.
    • Flexible collector deployment
      gnmic can be deployed as a gNMI collector that supports multiple output types (NATS, Kafka, Prometheus, InfluxDB,...).
      The collector can be deployed either as a single instance, as part of a cluster, or used to form data pipelines.
    • gNMI data manipulation
      gnmic collector supports data transformation capabilities that can be used to adapt the collected data to your specific use case.
    • Dynamic targets loading
      gnmic support target loading at runtime based on input from external systems.
    • YANG-based path suggestions
      Your CLI magically becomes a YANG browser when gnmic is executed in prompt mode. In this mode the flags that take XPATH values will get auto-suggestions based on the provided YANG modules. In other words - voodoo magic 🤯
    • Multiple configuration sources
      gnmic supports flags, environment variables as well as file based configurations.
    • Multi-target operations
      Commands can operate on multiple gNMI targets for bulk configuration/retrieval/subscription.
    • Multiple subscriptions
      With file based configuration it is possible to define and configure multiple subscriptions which can be independently associated with gNMI targets.
    • Inspect gNMI messages
      With the textproto output format and the logging capabilities of gnmic you can see the actual gNMI messages being sent/received. Its like having a gNMI looking glass!
    • Configurable TLS enforcement
      gNMI client supports both TLS and non-TLS transports so you can start using it in a lab environment without having to care about the PKI.
    • Dial-out telemetry
      The dial-out telemetry server is provided for Nokia SR OS.
    • Pre-built multi-platform binaries
      Statically linked binaries made in our release pipeline are available for major operating systems and architectures. Making installation a breeze!
    • Extensive and friendly documentation
      You won't be in need to dive into the source code to understand how gnmic works, our documentation site has you covered.

    Quick start guide#

    Installation#

    bash -c "$(curl -sL https://get-gnmic.openconfig.net)"
    +

    Capabilities request#

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure capabilities
    +

    Get request#

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \
    +      get --path /state/system/platform
    +

    Set request#

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \
    +      set --update-path /configure/system/name \
    +          --update-value gnmic_demo
    +

    Subscribe request#

    gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \
    +      sub --path "/state/port[port-id=1/1/c1/1]/statistics/in-packets"
    +
    \ No newline at end of file diff --git a/install/index.html b/install/index.html new file mode 100644 index 00000000..a22b97e1 --- /dev/null +++ b/install/index.html @@ -0,0 +1,46 @@ + Installation - gNMIc

    Installation

    gnmic is a single binary built for the Linux, Mac OS and Windows operating systems distributed via Github releases.

    Linux/Mac OS#

    To download & install the latest release the following automated installation script can be used:

    bash -c "$(curl -sL https://get-gnmic.openconfig.net)"
    +

    As a result, the latest gnmic version will be installed in the /usr/local/bin directory and the version information will be printed out.

    Downloading gnmic_0.0.3_Darwin_x86_64.tar.gz...
    +Moving gnmic to /usr/local/bin
    +
    +version : 0.0.3
    + commit : f541948
    +   date : 2020-04-23T12:06:07Z
    + gitURL : https://github.com/openconfig/gnmic.git
    +   docs : https://gnmic.openconfig.net
    +
    +Installation complete!
    +

    To install a specific version of gnmic, provide the version with -v flag to the installation script:

    bash -c "$(curl -sL https://get-gnmic.openconfig.net)" -- -v 0.5.0
    +

    Packages#

    Linux users running distributions with support for deb/rpm packages can install gnmic using pre-built packages:

    bash -c "$(curl -sL https://get-gnmic.openconfig.net)" -- --use-pkg
    +

    Upgrade#

    To upgrade gnmic to the latest version use the upgrade command:

    # upgrade using binary file
    +gnmic version upgrade
    +
    +# upgrade using package
    +gnmic version upgrade --use-pkg
    +

    Windows#

    Windows users should use WSL on Windows and install the linux version of the tool.

    Docker#

    The gnmic container image can be pulled from Dockerhub or GitHub container registries. The tag of the image corresponds to the release version and latest tag points to the latest available release:

    # pull latest release from dockerhub
    +docker pull gnmic/gnmic:latest
    +# pull a specific release from dockerhub
    +docker pull gnmic/gnmic:0.7.0
    +
    +# pull latest release from github registry
    +docker pull ghcr.io/openconfig/gnmic:latest
    +# pull a specific release from github registry
    +docker pull ghcr.io/openconfig/gnmic:0.5.2
    +

    Example running gnmic get command using the docker image:

    docker run \
    +       --network host \
    +       --rm ghcr.io/openconfig/gnmic get --log --username admin --password admin --insecure --address router1.local --path /interfaces
    +

    Docker Compose#

    gnmic docker-compose file example:

    version: '2'
    +
    +networks:
    +  gnmic-net:
    +    driver: bridge
    +
    +services:
    +  gnmic-1:
    +    image: ghcr.io/openconfig/gnmic:latest
    +    container_name: gnmic-1
    +    networks:
    +      - gnmic-net
    +    volumes:
    +      - ./gnmic.yaml:/app/gnmic.yaml
    +    command: "subscribe --config /app/gnmic.yaml"
    +

    See here for more deployment options

    \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 00000000..70053913 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"gnmic (pronoun.: gee\u00b7en\u00b7em\u00b7eye\u00b7see) is a gNMI CLI client that provides full support for Capabilities, Get, Set and Subscribe RPCs with collector capabilities. Features # Full support for gNMI RPCs Every gNMI RPC has a corresponding command with all of the RPC options configurable by means of the local and global flags. Flexible collector deployment gnmic can be deployed as a gNMI collector that supports multiple output types ( NATS , Kafka , Prometheus , InfluxDB ,...). The collector can be deployed either as a single instance , as part of a cluster , or used to form data pipelines . gNMI data manipulation gnmic collector supports data transformation capabilities that can be used to adapt the collected data to your specific use case. Dynamic targets loading gnmic support target loading at runtime based on input from external systems. YANG-based path suggestions Your CLI magically becomes a YANG browser when gnmic is executed in prompt mode. In this mode the flags that take XPATH values will get auto-suggestions based on the provided YANG modules. In other words - voodoo magic Multiple configuration sources gnmic supports flags , environment variables as well as file based configurations. Multi-target operations Commands can operate on multiple gNMI targets for bulk configuration/retrieval/subscription. Multiple subscriptions With file based configuration it is possible to define and configure multiple subscriptions which can be independently associated with gNMI targets. Inspect gNMI messages With the textproto output format and the logging capabilities of gnmic you can see the actual gNMI messages being sent/received. Its like having a gNMI looking glass! Configurable TLS enforcement gNMI client supports both TLS and non-TLS transports so you can start using it in a lab environment without having to care about the PKI. Dial-out telemetry The dial-out telemetry server is provided for Nokia SR OS. Pre-built multi-platform binaries Statically linked binaries made in our release pipeline are available for major operating systems and architectures. Making installation a breeze! Extensive and friendly documentation You won't be in need to dive into the source code to understand how gnmic works, our documentation site has you covered. Quick start guide # Installation # bash -c \"$(curl -sL https://get-gnmic.openconfig.net)\" Capabilities request # gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure capabilities Get request # gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \\ get --path /state/system/platform Set request # gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \\ set --update-path /configure/system/name \\ --update-value gnmic_demo Subscribe request # gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \\ sub --path \"/state/port[port-id=1/1/c1/1]/statistics/in-packets\"","title":"Home"},{"location":"#features","text":"Full support for gNMI RPCs Every gNMI RPC has a corresponding command with all of the RPC options configurable by means of the local and global flags. Flexible collector deployment gnmic can be deployed as a gNMI collector that supports multiple output types ( NATS , Kafka , Prometheus , InfluxDB ,...). The collector can be deployed either as a single instance , as part of a cluster , or used to form data pipelines . gNMI data manipulation gnmic collector supports data transformation capabilities that can be used to adapt the collected data to your specific use case. Dynamic targets loading gnmic support target loading at runtime based on input from external systems. YANG-based path suggestions Your CLI magically becomes a YANG browser when gnmic is executed in prompt mode. In this mode the flags that take XPATH values will get auto-suggestions based on the provided YANG modules. In other words - voodoo magic Multiple configuration sources gnmic supports flags , environment variables as well as file based configurations. Multi-target operations Commands can operate on multiple gNMI targets for bulk configuration/retrieval/subscription. Multiple subscriptions With file based configuration it is possible to define and configure multiple subscriptions which can be independently associated with gNMI targets. Inspect gNMI messages With the textproto output format and the logging capabilities of gnmic you can see the actual gNMI messages being sent/received. Its like having a gNMI looking glass! Configurable TLS enforcement gNMI client supports both TLS and non-TLS transports so you can start using it in a lab environment without having to care about the PKI. Dial-out telemetry The dial-out telemetry server is provided for Nokia SR OS. Pre-built multi-platform binaries Statically linked binaries made in our release pipeline are available for major operating systems and architectures. Making installation a breeze! Extensive and friendly documentation You won't be in need to dive into the source code to understand how gnmic works, our documentation site has you covered.","title":"Features"},{"location":"#quick-start-guide","text":"","title":"Quick start guide"},{"location":"#installation","text":"bash -c \"$(curl -sL https://get-gnmic.openconfig.net)\"","title":"Installation"},{"location":"#capabilities-request","text":"gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure capabilities","title":"Capabilities request"},{"location":"#get-request","text":"gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \\ get --path /state/system/platform","title":"Get request"},{"location":"#set-request","text":"gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \\ set --update-path /configure/system/name \\ --update-value gnmic_demo","title":"Set request"},{"location":"#subscribe-request","text":"gnmic -a 10.1.0.11:57400 -u admin -p admin --insecure \\ sub --path \"/state/port[port-id=1/1/c1/1]/statistics/in-packets\"","title":"Subscribe request"},{"location":"basic_usage/","text":"The following examples demonstrate the basic usage of gnmic in a scenario where the remote target runs an unsecured (without TLS enabled) gNMI server. The admin:admin credentials are used to connect to the gNMI server running at 10.1.0.11:57400 address. Info For the complete command usage examples, refer to the \"Command reference\" menu. Capabilities RPC # Getting the device's capabilities is done with capabilities command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure capabilities gNMI_Version: 0 .7.0 supported models: - nokia-conf, Nokia, 19 .10.R2 - nokia-state, Nokia, 19 .10.R2 - nokia-li-state, Nokia, 19 .10.R2 - nokia-li-conf, Nokia, 19 .10.R2 << SNIPPED >> supported encodings: - JS ON - BYTES Get RPC # Retrieving the data snapshot from the target device is done with get command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure \\ get --path /state/system/platform { \"source\" : \"10.1.0.11:57400\" , \"timestamp\" : 1592829586901061761 , \"time\" : \"2020-06-22T14:39:46.901061761+02:00\" , \"updates\" : [ { \"Path\" : \"state/system/platform\" , \"values\" : { \"state/system/platform\" : \"7750 SR-1s\" } } ] } Set RPC # Modifying state of the target device is done with set command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure \\ set --update-path /configure/system/name \\ --update-value gnmic_demo { \"source\" : \"0.tcp.eu.ngrok.io:12267\" , \"timestamp\" : 1592831593821038738 , \"time\" : \"2020-06-22T15:13:13.821038738+02:00\" , \"results\" : [ { \"operation\" : \"UPDATE\" , \"path\" : \"configure/system/name\" } ] } Subscribe RPC # Subscription to the gNMI telemetry data can be done with subscribe command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure \\ sub --path \"/state/port[port-id=1/1/c1/1]/statistics/in-packets\" { \"source\" : \"0.tcp.eu.ngrok.io:12267\" , \"timestamp\" : 1592832965197288856 , \"time\" : \"2020-06-22T15:36:05.197288856+02:00\" , \"prefix\" : \"state/port[port-id=1/1/c1/1]/statistics\" , \"updates\" : [ { \"Path\" : \"in-packets\" , \"values\" : { \"in-packets\" : \"12142\" } } ] } YANG path browser # gnmic can produce a list of XPATH/gNMI paths for a given YANG model with its path command. The paths in that list can be used as the --path values for the Get/Set/Subscribe commands. # nokia model gnmic path -m nokia-state --file nokia-state-combined.yang | head -10 /state/aaa/radius/statistics/coa/dropped/bad-authentication /state/aaa/radius/statistics/coa/dropped/missing-auth-policy /state/aaa/radius/statistics/coa/dropped/invalid /state/aaa/radius/statistics/coa/dropped/missing-resource /state/aaa/radius/statistics/coa/received /state/aaa/radius/statistics/coa/accepted /state/aaa/radius/statistics/coa/rejected /state/aaa/radius/statistics/disconnect-messages/dropped/bad-authentication /state/aaa/radius/statistics/disconnect-messages/dropped/missing-auth-policy /state/aaa/radius/statistics/disconnect-messages/dropped/invalid","title":"Basic usage"},{"location":"basic_usage/#capabilities-rpc","text":"Getting the device's capabilities is done with capabilities command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure capabilities gNMI_Version: 0 .7.0 supported models: - nokia-conf, Nokia, 19 .10.R2 - nokia-state, Nokia, 19 .10.R2 - nokia-li-state, Nokia, 19 .10.R2 - nokia-li-conf, Nokia, 19 .10.R2 << SNIPPED >> supported encodings: - JS ON - BYTES","title":"Capabilities RPC"},{"location":"basic_usage/#get-rpc","text":"Retrieving the data snapshot from the target device is done with get command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure \\ get --path /state/system/platform { \"source\" : \"10.1.0.11:57400\" , \"timestamp\" : 1592829586901061761 , \"time\" : \"2020-06-22T14:39:46.901061761+02:00\" , \"updates\" : [ { \"Path\" : \"state/system/platform\" , \"values\" : { \"state/system/platform\" : \"7750 SR-1s\" } } ] }","title":"Get RPC"},{"location":"basic_usage/#set-rpc","text":"Modifying state of the target device is done with set command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure \\ set --update-path /configure/system/name \\ --update-value gnmic_demo { \"source\" : \"0.tcp.eu.ngrok.io:12267\" , \"timestamp\" : 1592831593821038738 , \"time\" : \"2020-06-22T15:13:13.821038738+02:00\" , \"results\" : [ { \"operation\" : \"UPDATE\" , \"path\" : \"configure/system/name\" } ] }","title":"Set RPC"},{"location":"basic_usage/#subscribe-rpc","text":"Subscription to the gNMI telemetry data can be done with subscribe command: gnmic -a 10 .1.0.11:57400 -u admin -p admin --insecure \\ sub --path \"/state/port[port-id=1/1/c1/1]/statistics/in-packets\" { \"source\" : \"0.tcp.eu.ngrok.io:12267\" , \"timestamp\" : 1592832965197288856 , \"time\" : \"2020-06-22T15:36:05.197288856+02:00\" , \"prefix\" : \"state/port[port-id=1/1/c1/1]/statistics\" , \"updates\" : [ { \"Path\" : \"in-packets\" , \"values\" : { \"in-packets\" : \"12142\" } } ] }","title":"Subscribe RPC"},{"location":"basic_usage/#yang-path-browser","text":"gnmic can produce a list of XPATH/gNMI paths for a given YANG model with its path command. The paths in that list can be used as the --path values for the Get/Set/Subscribe commands. # nokia model gnmic path -m nokia-state --file nokia-state-combined.yang | head -10 /state/aaa/radius/statistics/coa/dropped/bad-authentication /state/aaa/radius/statistics/coa/dropped/missing-auth-policy /state/aaa/radius/statistics/coa/dropped/invalid /state/aaa/radius/statistics/coa/dropped/missing-resource /state/aaa/radius/statistics/coa/received /state/aaa/radius/statistics/coa/accepted /state/aaa/radius/statistics/coa/rejected /state/aaa/radius/statistics/disconnect-messages/dropped/bad-authentication /state/aaa/radius/statistics/disconnect-messages/dropped/missing-auth-policy /state/aaa/radius/statistics/disconnect-messages/dropped/invalid","title":"YANG path browser"},{"location":"changelog/","text":"Changelog # v 0.35.0 - January 20 th 2024 # Processors Added a plugin process type that allows users to write their own custom processors: examples gRPC metadata A new flag --metadata | -H is introduced. It allows users to add custom gRPC metadata headers to any request. Outputs: Kafka output: Added support for custom topics per target/subscription. Added support for both Async and Sync Kafka producers. Commands: Listen command: When using the listen command outputs internal metrics are properly initialized and exposed to prometheus for scraping. v 0.34.0 - November 11 th 2023 # Prometheus Write Output The number of prometheus_write writers can now be configured. Subscription Encoding A subscription encoding can now be set per target. Before, it was either a global attribute or set per subscription. With this change, it can be set globally, per target or per subscription. Processors: New event-combine processor: A convenience processor that allows combining other processors into a single one. New event-rate-limit processor: A processor that rate-limits each event with matching tags to the configured amount per-seconds. Outputs: New asciigraph output: https://asciinema.org/a/617477 Clustering: New redis locker: For leader election, service discovery and target distribution gNMIc supports both Consul and Kubernetes . It is now possible to use redis for the same purpose. v 0.33.0 - October 8 th 2023 # Rest API Added a kubernetes friendly api/v1/healthz endpoint. Set Command Added support for gNMI set union_replace operation. Outputs Allow the number of workers used by the prometheus and prometheus_write outputs to be configurable to improve performance. Go version Upgrade to Golang v1.21.1. v0.32.0 - August 31 st 2023 # TLS It is now possible to override the serverName used by gNMIc when verifying the server name present in the certificate sent by the gNMI server. PR Subscription Added support for mixing on-change and sample stream subscription in the same gRPC stream. PR Added support for attaching specific outputs to a subscription. PR REST API Added a health chek endpoint to be used by kubernetes. PR Kafka Output Added support for Kafka compression. PR Generate Path Added enum-values to the JSON output of generate path command. PR v0.31.0 - May 17 th 2023 # Prometheus output When using the Consul auto discovery feature of the Proemtheus output, it is now possible to configure different service and listen addresses. This is useful when gNMIc is running as a container of behind a NAT device Set Request file The CLI origin is now allowed in the path field of updates , replaces and deletes in a set request file. If the path field has the cli:/ origin, the value field is expected to be a string and will be set in an ascii TypedValue. v0.30.0 - April 18 th 2023 # Set Command The set command now supports the flags --replace-cli , --replace-cli-file , --update-cli and --update-cli-file , these flags can be used to send gNMI set requests with the CLI origin. Logging: Reduce log verbosity of File and HTTP target discovery mechanisms. Processors: The Drop event processor completely removes the message to be dropped instead of replacing it with an empty message. Inputs: Kafka input now supports TLS connections. Outputs: Kafka output now has a configuration attribute called insert-key , if true, the messages written will include a key built from the gNMI message source and subscription name. TCP output now has a configuration attribute called delimiter , it allows to set user defined string to be sent between each message. This allows the receiving end to properly split JSON objects. It it particularly useful with Logstash when writing gNMI events to an ELK stack. TLS: When using gNMIc 's components that expose a TLS server (gNMI server, Tunnel server, Rest API and Prometheus output) it's possible to fine tune the how the server requests and validates a client certificate. This is done using the configuration attribute client-auth under each server's TLS section, it takes 4 different values: request: The server requests a certificate from the client but does not require the client to send a certificate. If the client sends a certificate, it is not required to be valid. require: The server requires the client to send a certificate and does not fail if the client certificate is not valid. verify-if-given: The server requests a certificate, does not fail if no certificate is sent. If a certificate is sent it is required to be valid. require-verify: The server requires the client to send a valid certificate. Diff Command: The diff command has 2 new sub commands: setrequest : compares the intent between two SetRequest messages encoded in textproto format. set-to-notifs : verifies whether a set of notifications from a GetResponse or a stream of SubscribeResponse messages comply with a SetRequest messages in textproto format. The envisioned use case is to check whether a stored snapshot of device state matches that of the intended state as specified by a SetRequest . Outputs: When using the event format with certain outputs ( file , nats , jetstream , kafka , tcp or udp ) it's possible to send event message individually as opposed to sending them in an array. This is done using the attribute split-events: true under each of the outputs configuration sections. Prometheus output now supports a custom service address field under service-registration , it specifies the address to be registered in Consul for discovery. It can be a hostname, an IP address or a IP/Host:Port socket address. It it does not contain a port number, the port number from the listen field is used. Set Request file The Set request file can be used with Origin cli , gNMIc will properly format the commands as string, not as JSON value. v0.29.0 - February 20 th 2023 # Generate Path The generate path command with the flag --json shows the features the path depends on. The list of features is built recursively from the YANG attribute if-feature . Processors: New processor event-starlark allows to run a starlak script on the received messages. Loaders The HTTP loader now supports different authentication schemas as well as setting a template from a local file. v0.28.0 - December 7 th 2022 # Targets Targets static tags are now properly propagated to outputs when a cache is used. Listen Command: The system-name HTTP2 header is now used as a tag in exported metrics. Outputs: The timestamp precision under gNMIc 's InfluxDB output is now configurable. Added a new snmp output type, it allows to dynamically convert gNMI updates into SNMP traps. v0.27.0 - October 8 th 2022 # Targets Add supports for socks5 proxies per target. Logging Support for log rotation via the flags --log-max-size , log-max-backups and --log-compress v0.26.0 - June 28 th 2022 # Outputs Add Prometheus Remote Write output , this output type can be used to push metrics to various systems like Mimir , CortexMetrics , VictoriaMetrics , Thanos ... Add NATS Jetstream output , it allows to write metrics to NATS jetstream which supports persistency and filtering. gNMI historical subscriptions gNMIc now support historical subscription using the gNMI history extension v0.25.1 - June 13 th 2022 # Upgrade Go version to go1.18.1. Fix running gnmic subscribe with only Inputs and Outputs configured (no subscriptions or targets). v0.25.0 - June 11 th 2022 # Processors Strings replace processor supports replaces using regular expressions. Processors are now supported when collecting telemetry using listen command (Nokia SROS specific) New Processors Data convert Duration convert Value tag Clustering gNMIc supports kubernetes based clustering, i.e you can build gNMIc clusters on kubernetes without the need for Consul cluster. Yang path generation The command gnmic generate path supports generating paths for YANG containers. In earlier versions, the paths generation was done for YANG leaves only. Internal gNMIc Prometheus metrics gNMIc exposes additional internal metrics available to be scraped using Prometheus. Static tags from target configuration It is now possible to set static tags on events by configuring them under each target. Influxdb cache The InfluxDB output now supports gNMI based caching, allowing to apply processors on multiple event messages at once and batching the written points to InfluxDB. v0.24.0 - March 13 th 2022 # gRPC Tunnel Support Add support for gNMI RPC using a gRPC tunnel, gNMIc runs as a collector with an embedded tunnel server. v0.23.0 - February 24 th 2022 # Docker image: The published gnmic docker image is now based on alpine instead of an empty container. A from scratch image is published and can be obtained using the command: docker pull ghcr.io/karimra/gnmic:latest-scratch docker pull ghcr.io/karimra/gnmic:v0.23.0-scratch gNMIc Golang API : Add gNMI responses constructors Add gRPC tunnel proto messages constructors Target Discovery : Add the option to transform the loaded targets format using a Go text template for file and HTTP loaders Poll based target loaders (file, HTTP and docker) now support a startup delay timer v0.22.1 - February 2 nd 2022 # Fix a Prometheus output issue when using gNMI cache that causes events to be missing from the metrics. v0.22.0 - February 1 st 2022 # gNMIc Golang API : Added the github.com/karimra/gnmic/api golang package. It can be imported by other Golang programs to ease the creation of gNMI targets and gNMI Requests. v0.21.0 - January 23 rd 2022 # Generate Cmd : Add YANG module namespace to generated paths. Outputs: Outputs File , NATS and Kafka now support a msg-template field to customize the written messages using Go templates. API: Add Cluster API endpoints. Actions: Add Template action. Add Subscribe ONCE RPC to gNMI action. Allow gNMI action on multiple targets. Add Script action. Get Cmd : Implement Format event for GetResponse messages. Add the ability to execute processors with Get command flag --processor on GetResponse messages. Target Discovery : Add the ability to run actions on target discovery or deletion. Set Cmd : Add --dry-run flag which runs the set request templates and prints their output without sending the SetRequest to the targets. TLS: Add pre-master key logging for TLS connections using the flag --log-tls-secret . The key can be used to decrypt encrypted gNMI messages using wireshark. Target: Add target.Stop() method to gracefully close the target underlying gRPC connection. v0.20.0 - October 19 th 2021 # Add gomplate template functions to all templates rendered by gnmic . Path generation : gnmic generate path supports generating paths with type and description in JSON format. Set RPC template : Set RPC supports multiple template files in a single command. Clustering : gnmic clusters can be formed using secure (HTTPS) API endpoints. Configuration payload generation : Configuration keys can now be formatted as camelCase or snake_case strings v0.19.1 - October 7 th 2021 # Path search Do not enter search mode if not paths are found. Prometheus Output Change the default service name when registering with a Consul server v0.19.0 - September 16 th 2021 # Event Processors Event Convert now converts binary float notation to float Target Loaders: HTTP Loader gNMIc can now dynamically discover targets from a remote HTTP server. HTTP Loader is now properly instrumented using Prometheus metrics. File Loader Supports remote files (ftp, sftp, http(s)) in addition to local file system files. File loader is now properly instrumented using Prometheus metrics. Consul Loader Consul Loader is now properly instrumented using Prometheus metrics. Docker Loader Docker Loader is now properly instrumented using Prometheus metrics. gRPC gNMIc now adds its version as part of the user-agent HTTP header. v0.18.0 - August 17 th 2021 # gNMI Server : Add support for a global gNMI server. It supports all types of subscriptions, ran against a local cache build out the configured subscriptions. It support Get and Set RPCs as well, those are run against the configured targets. The gNMI server supports Consul based service registration. Outputs: Add support for gNMI server output type Target configuration : Support multiple IP addresses per target, all addresses are tried simultaneously. The first successful gRPC connection is used. Prometheus Output : Add the option of generating Prometheus metrics on-scrape, instead of on-reception. The gNMI notifications are stored in a local cache and used to generate metrics when a Prometheus server sends a scrape request. Event Processors: Add group-by processor, it groups events together based on a given criteria. The events can belong to different gNMI notifications or even to different subscriptions. Event Processor Convert: Add support for boolean conversion Deployment Examples : Add containerlab based deployment examples. These deployment come with a router fabric built using Nokia's SRL API server : Add Secure API server configuration options Target Loaders: Consul loader update: Add support for gNMI target discovery from Consul services. Get Request: Add printing of Target as part of Path Prefix Set Request: Add printing of Target as part of Path Prefix v0.17.0 - July 14 th 2021 # Event Trigger: Enhance event-trigger to run multiple actions sequentially when an event occurs. The output of an action can be used in the following ones. Kafka output: Add SASL_SSL and SSL security protocols to kafka output. gRPC authentication: Add support for token based gRPC authentication. v0.16.2 - July 13 th 2021 # Fix nil pointer dereference in case a subscription has suppress-redundant but no heartbeat-interval . v0.16.1 - July 12 th 2021 # Bump github.com/openconfig/goyang version to v0.2.7 v0.16.0 - June 14 th 2021 # Target Discovery: Add Docker Engine target loader, gnmic can dynamically discover gNMI targets running as docker containers. Event Trigger: gNMI action Enhance gNMI action to take external variables as input, in addition to the received gNMI update. v0.15.0 - June 7 th 2021 # Subscription: Add field set-target under subscription config, a boolean that enables setting the target name as a gNMI prefix target. Outputs: Add add-target and target-template fields under all outputs, Enables adding the target value as a tag/label based on the subscription and target metadata v0.14.3 - June 6 th 2021 # Set command: Fix ascii values encoding if used with --request-file flag. v0.14.2 - June 3 rd 2021 # Fix event-convert processor when the conversion is between integer types. Add an implicit conversion of uint to int if the influxdb output version is 1.8.x. This is a workaround for the limited support of influx APIv2 by influxDB1.8 v0.14.1 - May 31 st 2021 # Fix OverrideTS processor Add override-timestamps option under outputs, to override the message timestamps regardless of the message output format v0.14.0 - May 28 th 2021 # New Output format flat This format prints the Get and Subscribe RPCs as a list of xpath: value , where the xpath points to a leaf value. New gnmic diff command: This command prints the difference in responses between a reference target --ref and one or more targets to be compared to the reference --compare . The output is printed as flat format results. v0.13.0 - May 10 th 2021 # New gnmic generate Command: Given a set of yang models and an xpath, gnmic generate generates a JSON/YAML representation of the YANG object the given path points to. Given a set of yang models and an set of xpaths (with --update or --replace ), gnmic generate set-request generates a set request file that can be filled with the desired values and used with gnmic set --request-file The sub-command gnmic generate path is an alias to gnmic path Path Command: add flag --desc which, if present, prints the YANG leaf description together with the generated paths. add flag --config-only which, if present, only generates paths pointing to YANG leaves representing config data. add flag --state-only which, if present, only generates paths pointing to a YANG leaf representing state data. v0.12.2 - April 24 th 2021 # Fix a bug that cause gNMIc to crash if certain processors are used. v0.12.1 - April 21 st 2021 # Fix parsing of stringArray flags containing a space. v0.12.0 - April 20 th 2021 # Outputs: InfluxDB and Prometheus outputs: Convert gNMI Decimal64 values to Float64. Set Command: Add the ability to run a Set command using a single file, including replaces , updates and deletes . The request file --request-file is either a static file or a Golang Text Template rendered separately for each target. The template input is read from a file referenced by the flag --request-vars . v0.11.0 - April 15 th 2021 # Processors: Add event-allow processor, basically an allow ACL based on jq condition or regular expressions. Add event-extract-tags processor, it adds tags based on regex named groups from tag names, tag values, value names, or values. Add gnmi-action to event-trigger processor, the action runs a gNMI Set or Get if the trigger condition is met. Set Command: Improve usability by supporting reading values (--update-file and --replace-file) from standard input. v0.10.0 - April 8 th 2021 # New command: getset command: This command conditionally executes both a Get and a Set RPC, the GetResponse is used to evaluate a condition which if met triggers the execution of the Set RPC. Processors: Some processors' apply condition can be expressed using jq instead of regular expressions. v0.9.1 - March 23 rd 2021 # Processors: Add event-trigger processor: This processor is used to trigger a predefined action if a condition is met. New processor event-jq which applies a transformation on the messages expressed as a jq expression. Shell autocompletion: Shell (bash, zsh and fish) autocompletion scripts can be generated using gnmic completion [bash|zsh|fish] . gRPC gzip compression: gnmic supports gzip compression on gRPC connections. v0.9.0 - March 11 th 2021 # Clustered Prometheus output: When deployed as a cluster, it is possible to register only one of the prometheus outputs in Consul. This is handy in the case of a cluster with data replication. Proto file loading at runtime (Nokia SROS): gnmic supports loading SROS proto files at runtime to decode gNMI updates with proto encoding Kafka Output: Kafka SASL support: PLAIN, SCRAM SHA256/SHA512 OAuth mechanisms are supported. Configuration: gnmic supports configuration using environment variables. Processors: add event-merge processor. Target Loaders: gnmic supports target loaders at runtime, new targets can be added to the configuration from a file that gnmic watches or from Consul v0.8.0 - March 2 nd 2021 # Inputs: Processors can now be applied by the input plugins. Prometheus output: The Prometheus output can now register as a service in Consul, a Prometheus client can discover the output using consul service discovery. Clustering: gnmic can now run as a cluster, this requires a running Consul instance that will be used by the gnmic instance for leader election and target load sharing. Configuration file: The default configuration file placement now follows XDG recommendations CLI exit status: Failure of most commands is properly reflected in the cli exit status. Configuration: Configuration fields that are OS paths are expanded by gnmic Deployment examples: A set of deployment examples is added to the repo and the docs. v0.7.0 - January 28 th 2021 # Prometheus output metrics customization: metric-prefix and append-subscription-name can be used to change the default metric prefix and append the subscription name to the metric name. export-timestamps : enables/disables the export of timestamps together with the metric. strings-as-labels : enables/disables automatically adding paths with a value of type string as a metric label. NATS output: allow multiple NATS workers under NATS output via field num-workers . add NATS prometheus internal metrics. STAN output: allow multiple STAN workers under STAN output via field num-workers . add NATS prometheus internal metrics. File output: add File prometheus metrics. Inputs: support ingesting gNMI data from NATS, STAN or a Kafka message bus. v0.6.0 - December 14 th 2020 # Processors: Added processors to gnmic , a set of basic processors can be used to manipulate gNMI data flowing through gnmic . These processors are applied by the output plugins Upgrade command: gnmic can be upgraded using gnmic version upgrade command. v0.5.2 - December 1 st 2020 # Outputs: Improve outputs logging Add Prometheus metrics to Kafka output v0.5.1 - November 28 th 2020 # Prompt Mode: Fix subscribe RPC behavior QoS: Do not populate QoS field if not set via config file or flag. Outputs: add configurable number of workers to some outputs. v0.5.0 - November 25 th 2020 # Prompt Mode: Add prompt sub commands. XPATH parsing: Add custom xpath parsingto gnmi.Path to allow for paths including column : . TLS: Allow configurable TLS versions per target, the minimum, the maximum and the preferred TLS versions ca be configured. v0.4.3 - November 10 th 2020 # Missing path: Initialize the path field if not present in SubscribeResponse v0.4.2 - November 5 th 2020 # YANG: Prompt command flags --file and --dir support globs. Subscribe: added flags --output that allows to choose a single output for subscribe updates Prompt: Max suggestions is automatically adjusted based on the terminal height. Add suggestions for address and subscriptions. v0.4.1 - October 22 nd 2020 # Prompt: Add suggestions of xpath with origin, --suggest-with-origin . v0.4.0 - October 21 st 2020 # New Command: Add new command prompt Prompt: Add ctrl+z key bind to delete a single path element. Add YANG info to xpath suggestions. Add GoLeft, GoRight key binds. Sort xpaths and prefixes suggestions. xpaths suggestions are properly generated if a prefix is present. flag --suggest-all-flags allows adding global flags suggestion in prompt mode. Prometheus output: Add support for Prometheus output plugin. v0.3.0 - October 1 st 2020 # InfluxDB output: Add support for influxDB output plugin. v0.2.3 - September 18 th 2020 # Retry Add basic RPC retry mechanism. ONCE mode subscription: Handle targets that send an EOF error instead of a SyncResponse to signify the end of ONCE subscriptions. Docker image: Docker images added to ghcr.io as well as docker hub. v0.2.2 - September 3 rd 2020 # CLI: Properly handle paths that include quotes. Unix Socket: Allow send/rcv of gNMI data to/from a unix socket. Outputs: Add TCP output plugin. v0.2.1 - August 11 th 2020 # Releases: Add .deb. and .rpm packages to releases. Outputs: Add UDP output plugin. v0.2.0 - August 7 th 2020 # Releases: Add ARM releases. Push docker image to docker hub. v0.1.1 - July 23 rd 2020 # Set Cmd: Support json_ietf encoding when the value is specified from a file. v0.1.0 - July 16 th 2020 # Outputs: Allow NATS/STAN output subject customization. v0.0.7 - July 16 th 2020 # gNMI Target: Add support for gNMI Target field. gNMI Origin: Add support for gNMI Origin field. Prometheus internal metrics: Add support for gnmic internal metrics via a Prometheus server. Outputs: Add support for multiple output plugins (file, NATS, STAN, Kafka) Targets: Support target specific configuration. Poll Subscription: Allow selecting polled targets and subscription using a CLI select menu. gNMI Models: Support multiple Models in Get and Subscribe RPCs. v0.0.6 - June 2 nd 2020 # Nokia Dialout: Add Support for Nokia Dialout telemetry. Printing: Convert timestamps to Time. v0.0.5 - May 18 th 2020 # Formatting: Add textproto format. v0.0.4 - May 11 th 2020 # Logging: Support logging to file instead of Stderr. Set Command: support Set values from YAML file. v0.0.3 - April 23 rd 2020 # Proxy: Allow usage of ENV proxy values for gRPC connections. Installation: Add installation script. v0.0.2 - April 13 th 2020 # Terminal printing clean up. Path Command: Add search option. v0.0.1 - March 24 th 2020 # Capabilities RPC Command. Get RPC Command. Subscribe RPC Command. Set RPC Command. TLS support. Version Command. Path Commnd. initial Commit - February 20 th 2020 #","title":"Changelog"},{"location":"changelog/#changelog","text":"","title":"Changelog"},{"location":"changelog/#v-0350-january-20th-2024","text":"Processors Added a plugin process type that allows users to write their own custom processors: examples gRPC metadata A new flag --metadata | -H is introduced. It allows users to add custom gRPC metadata headers to any request. Outputs: Kafka output: Added support for custom topics per target/subscription. Added support for both Async and Sync Kafka producers. Commands: Listen command: When using the listen command outputs internal metrics are properly initialized and exposed to prometheus for scraping.","title":"v 0.35.0 - January 20th 2024"},{"location":"changelog/#v-0340-november-11th-2023","text":"Prometheus Write Output The number of prometheus_write writers can now be configured. Subscription Encoding A subscription encoding can now be set per target. Before, it was either a global attribute or set per subscription. With this change, it can be set globally, per target or per subscription. Processors: New event-combine processor: A convenience processor that allows combining other processors into a single one. New event-rate-limit processor: A processor that rate-limits each event with matching tags to the configured amount per-seconds. Outputs: New asciigraph output: https://asciinema.org/a/617477 Clustering: New redis locker: For leader election, service discovery and target distribution gNMIc supports both Consul and Kubernetes . It is now possible to use redis for the same purpose.","title":"v 0.34.0 - November 11th 2023"},{"location":"changelog/#v-0330-october-8th-2023","text":"Rest API Added a kubernetes friendly api/v1/healthz endpoint. Set Command Added support for gNMI set union_replace operation. Outputs Allow the number of workers used by the prometheus and prometheus_write outputs to be configurable to improve performance. Go version Upgrade to Golang v1.21.1.","title":"v 0.33.0 - October 8th 2023"},{"location":"changelog/#v0320-august-31st-2023","text":"TLS It is now possible to override the serverName used by gNMIc when verifying the server name present in the certificate sent by the gNMI server. PR Subscription Added support for mixing on-change and sample stream subscription in the same gRPC stream. PR Added support for attaching specific outputs to a subscription. PR REST API Added a health chek endpoint to be used by kubernetes. PR Kafka Output Added support for Kafka compression. PR Generate Path Added enum-values to the JSON output of generate path command. PR","title":"v0.32.0 - August 31st 2023"},{"location":"changelog/#v0310-may-17th-2023","text":"Prometheus output When using the Consul auto discovery feature of the Proemtheus output, it is now possible to configure different service and listen addresses. This is useful when gNMIc is running as a container of behind a NAT device Set Request file The CLI origin is now allowed in the path field of updates , replaces and deletes in a set request file. If the path field has the cli:/ origin, the value field is expected to be a string and will be set in an ascii TypedValue.","title":"v0.31.0 - May 17th 2023"},{"location":"changelog/#v0300-april-18th-2023","text":"Set Command The set command now supports the flags --replace-cli , --replace-cli-file , --update-cli and --update-cli-file , these flags can be used to send gNMI set requests with the CLI origin. Logging: Reduce log verbosity of File and HTTP target discovery mechanisms. Processors: The Drop event processor completely removes the message to be dropped instead of replacing it with an empty message. Inputs: Kafka input now supports TLS connections. Outputs: Kafka output now has a configuration attribute called insert-key , if true, the messages written will include a key built from the gNMI message source and subscription name. TCP output now has a configuration attribute called delimiter , it allows to set user defined string to be sent between each message. This allows the receiving end to properly split JSON objects. It it particularly useful with Logstash when writing gNMI events to an ELK stack. TLS: When using gNMIc 's components that expose a TLS server (gNMI server, Tunnel server, Rest API and Prometheus output) it's possible to fine tune the how the server requests and validates a client certificate. This is done using the configuration attribute client-auth under each server's TLS section, it takes 4 different values: request: The server requests a certificate from the client but does not require the client to send a certificate. If the client sends a certificate, it is not required to be valid. require: The server requires the client to send a certificate and does not fail if the client certificate is not valid. verify-if-given: The server requests a certificate, does not fail if no certificate is sent. If a certificate is sent it is required to be valid. require-verify: The server requires the client to send a valid certificate. Diff Command: The diff command has 2 new sub commands: setrequest : compares the intent between two SetRequest messages encoded in textproto format. set-to-notifs : verifies whether a set of notifications from a GetResponse or a stream of SubscribeResponse messages comply with a SetRequest messages in textproto format. The envisioned use case is to check whether a stored snapshot of device state matches that of the intended state as specified by a SetRequest . Outputs: When using the event format with certain outputs ( file , nats , jetstream , kafka , tcp or udp ) it's possible to send event message individually as opposed to sending them in an array. This is done using the attribute split-events: true under each of the outputs configuration sections. Prometheus output now supports a custom service address field under service-registration , it specifies the address to be registered in Consul for discovery. It can be a hostname, an IP address or a IP/Host:Port socket address. It it does not contain a port number, the port number from the listen field is used. Set Request file The Set request file can be used with Origin cli , gNMIc will properly format the commands as string, not as JSON value.","title":"v0.30.0 - April 18th 2023"},{"location":"changelog/#v0290-february-20th-2023","text":"Generate Path The generate path command with the flag --json shows the features the path depends on. The list of features is built recursively from the YANG attribute if-feature . Processors: New processor event-starlark allows to run a starlak script on the received messages. Loaders The HTTP loader now supports different authentication schemas as well as setting a template from a local file.","title":"v0.29.0 - February 20th 2023"},{"location":"changelog/#v0280-december-7th-2022","text":"Targets Targets static tags are now properly propagated to outputs when a cache is used. Listen Command: The system-name HTTP2 header is now used as a tag in exported metrics. Outputs: The timestamp precision under gNMIc 's InfluxDB output is now configurable. Added a new snmp output type, it allows to dynamically convert gNMI updates into SNMP traps.","title":"v0.28.0 - December 7th 2022"},{"location":"changelog/#v0270-october-8th-2022","text":"Targets Add supports for socks5 proxies per target. Logging Support for log rotation via the flags --log-max-size , log-max-backups and --log-compress","title":"v0.27.0 - October 8th 2022"},{"location":"changelog/#v0260-june-28th-2022","text":"Outputs Add Prometheus Remote Write output , this output type can be used to push metrics to various systems like Mimir , CortexMetrics , VictoriaMetrics , Thanos ... Add NATS Jetstream output , it allows to write metrics to NATS jetstream which supports persistency and filtering. gNMI historical subscriptions gNMIc now support historical subscription using the gNMI history extension","title":"v0.26.0 - June 28th 2022"},{"location":"changelog/#v0251-june-13th-2022","text":"Upgrade Go version to go1.18.1. Fix running gnmic subscribe with only Inputs and Outputs configured (no subscriptions or targets).","title":"v0.25.1 - June 13th 2022"},{"location":"changelog/#v0250-june-11th-2022","text":"Processors Strings replace processor supports replaces using regular expressions. Processors are now supported when collecting telemetry using listen command (Nokia SROS specific) New Processors Data convert Duration convert Value tag Clustering gNMIc supports kubernetes based clustering, i.e you can build gNMIc clusters on kubernetes without the need for Consul cluster. Yang path generation The command gnmic generate path supports generating paths for YANG containers. In earlier versions, the paths generation was done for YANG leaves only. Internal gNMIc Prometheus metrics gNMIc exposes additional internal metrics available to be scraped using Prometheus. Static tags from target configuration It is now possible to set static tags on events by configuring them under each target. Influxdb cache The InfluxDB output now supports gNMI based caching, allowing to apply processors on multiple event messages at once and batching the written points to InfluxDB.","title":"v0.25.0 - June 11th 2022"},{"location":"changelog/#v0240-march-13th-2022","text":"gRPC Tunnel Support Add support for gNMI RPC using a gRPC tunnel, gNMIc runs as a collector with an embedded tunnel server.","title":"v0.24.0 - March 13th 2022"},{"location":"changelog/#v0230-february-24th-2022","text":"Docker image: The published gnmic docker image is now based on alpine instead of an empty container. A from scratch image is published and can be obtained using the command: docker pull ghcr.io/karimra/gnmic:latest-scratch docker pull ghcr.io/karimra/gnmic:v0.23.0-scratch gNMIc Golang API : Add gNMI responses constructors Add gRPC tunnel proto messages constructors Target Discovery : Add the option to transform the loaded targets format using a Go text template for file and HTTP loaders Poll based target loaders (file, HTTP and docker) now support a startup delay timer","title":"v0.23.0 - February 24th 2022"},{"location":"changelog/#v0221-february-2nd-2022","text":"Fix a Prometheus output issue when using gNMI cache that causes events to be missing from the metrics.","title":"v0.22.1 - February 2nd 2022"},{"location":"changelog/#v0220-february-1st-2022","text":"gNMIc Golang API : Added the github.com/karimra/gnmic/api golang package. It can be imported by other Golang programs to ease the creation of gNMI targets and gNMI Requests.","title":"v0.22.0 - February 1st 2022"},{"location":"changelog/#v0210-january-23rd-2022","text":"Generate Cmd : Add YANG module namespace to generated paths. Outputs: Outputs File , NATS and Kafka now support a msg-template field to customize the written messages using Go templates. API: Add Cluster API endpoints. Actions: Add Template action. Add Subscribe ONCE RPC to gNMI action. Allow gNMI action on multiple targets. Add Script action. Get Cmd : Implement Format event for GetResponse messages. Add the ability to execute processors with Get command flag --processor on GetResponse messages. Target Discovery : Add the ability to run actions on target discovery or deletion. Set Cmd : Add --dry-run flag which runs the set request templates and prints their output without sending the SetRequest to the targets. TLS: Add pre-master key logging for TLS connections using the flag --log-tls-secret . The key can be used to decrypt encrypted gNMI messages using wireshark. Target: Add target.Stop() method to gracefully close the target underlying gRPC connection.","title":"v0.21.0 - January 23rd 2022"},{"location":"changelog/#v0200-october-19th-2021","text":"Add gomplate template functions to all templates rendered by gnmic . Path generation : gnmic generate path supports generating paths with type and description in JSON format. Set RPC template : Set RPC supports multiple template files in a single command. Clustering : gnmic clusters can be formed using secure (HTTPS) API endpoints. Configuration payload generation : Configuration keys can now be formatted as camelCase or snake_case strings","title":"v0.20.0 - October 19th 2021"},{"location":"changelog/#v0191-october-7th-2021","text":"Path search Do not enter search mode if not paths are found. Prometheus Output Change the default service name when registering with a Consul server","title":"v0.19.1 - October 7th 2021"},{"location":"changelog/#v0190-september-16th-2021","text":"Event Processors Event Convert now converts binary float notation to float Target Loaders: HTTP Loader gNMIc can now dynamically discover targets from a remote HTTP server. HTTP Loader is now properly instrumented using Prometheus metrics. File Loader Supports remote files (ftp, sftp, http(s)) in addition to local file system files. File loader is now properly instrumented using Prometheus metrics. Consul Loader Consul Loader is now properly instrumented using Prometheus metrics. Docker Loader Docker Loader is now properly instrumented using Prometheus metrics. gRPC gNMIc now adds its version as part of the user-agent HTTP header.","title":"v0.19.0 - September 16th 2021"},{"location":"changelog/#v0180-august-17th-2021","text":"gNMI Server : Add support for a global gNMI server. It supports all types of subscriptions, ran against a local cache build out the configured subscriptions. It support Get and Set RPCs as well, those are run against the configured targets. The gNMI server supports Consul based service registration. Outputs: Add support for gNMI server output type Target configuration : Support multiple IP addresses per target, all addresses are tried simultaneously. The first successful gRPC connection is used. Prometheus Output : Add the option of generating Prometheus metrics on-scrape, instead of on-reception. The gNMI notifications are stored in a local cache and used to generate metrics when a Prometheus server sends a scrape request. Event Processors: Add group-by processor, it groups events together based on a given criteria. The events can belong to different gNMI notifications or even to different subscriptions. Event Processor Convert: Add support for boolean conversion Deployment Examples : Add containerlab based deployment examples. These deployment come with a router fabric built using Nokia's SRL API server : Add Secure API server configuration options Target Loaders: Consul loader update: Add support for gNMI target discovery from Consul services. Get Request: Add printing of Target as part of Path Prefix Set Request: Add printing of Target as part of Path Prefix","title":"v0.18.0 - August 17th 2021"},{"location":"changelog/#v0170-july-14th-2021","text":"Event Trigger: Enhance event-trigger to run multiple actions sequentially when an event occurs. The output of an action can be used in the following ones. Kafka output: Add SASL_SSL and SSL security protocols to kafka output. gRPC authentication: Add support for token based gRPC authentication.","title":"v0.17.0 - July 14th 2021"},{"location":"changelog/#v0162-july-13th-2021","text":"Fix nil pointer dereference in case a subscription has suppress-redundant but no heartbeat-interval .","title":"v0.16.2 - July 13th 2021"},{"location":"changelog/#v0161-july-12th-2021","text":"Bump github.com/openconfig/goyang version to v0.2.7","title":"v0.16.1 - July 12th 2021"},{"location":"changelog/#v0160-june-14th-2021","text":"Target Discovery: Add Docker Engine target loader, gnmic can dynamically discover gNMI targets running as docker containers. Event Trigger: gNMI action Enhance gNMI action to take external variables as input, in addition to the received gNMI update.","title":"v0.16.0 - June 14th 2021"},{"location":"changelog/#v0150-june-7th-2021","text":"Subscription: Add field set-target under subscription config, a boolean that enables setting the target name as a gNMI prefix target. Outputs: Add add-target and target-template fields under all outputs, Enables adding the target value as a tag/label based on the subscription and target metadata","title":"v0.15.0 - June 7th 2021"},{"location":"changelog/#v0143-june-6th-2021","text":"Set command: Fix ascii values encoding if used with --request-file flag.","title":"v0.14.3 - June 6th 2021"},{"location":"changelog/#v0142-june-3rd-2021","text":"Fix event-convert processor when the conversion is between integer types. Add an implicit conversion of uint to int if the influxdb output version is 1.8.x. This is a workaround for the limited support of influx APIv2 by influxDB1.8","title":"v0.14.2 - June 3rd 2021"},{"location":"changelog/#v0141-may-31st-2021","text":"Fix OverrideTS processor Add override-timestamps option under outputs, to override the message timestamps regardless of the message output format","title":"v0.14.1 - May 31st 2021"},{"location":"changelog/#v0140-may-28th-2021","text":"New Output format flat This format prints the Get and Subscribe RPCs as a list of xpath: value , where the xpath points to a leaf value. New gnmic diff command: This command prints the difference in responses between a reference target --ref and one or more targets to be compared to the reference --compare . The output is printed as flat format results.","title":"v0.14.0 - May 28th 2021"},{"location":"changelog/#v0130-may-10th-2021","text":"New gnmic generate Command: Given a set of yang models and an xpath, gnmic generate generates a JSON/YAML representation of the YANG object the given path points to. Given a set of yang models and an set of xpaths (with --update or --replace ), gnmic generate set-request generates a set request file that can be filled with the desired values and used with gnmic set --request-file The sub-command gnmic generate path is an alias to gnmic path Path Command: add flag --desc which, if present, prints the YANG leaf description together with the generated paths. add flag --config-only which, if present, only generates paths pointing to YANG leaves representing config data. add flag --state-only which, if present, only generates paths pointing to a YANG leaf representing state data.","title":"v0.13.0 - May 10th 2021"},{"location":"changelog/#v0122-april-24th-2021","text":"Fix a bug that cause gNMIc to crash if certain processors are used.","title":"v0.12.2 - April 24th 2021"},{"location":"changelog/#v0121-april-21st-2021","text":"Fix parsing of stringArray flags containing a space.","title":"v0.12.1 - April 21st 2021"},{"location":"changelog/#v0120-april-20th-2021","text":"Outputs: InfluxDB and Prometheus outputs: Convert gNMI Decimal64 values to Float64. Set Command: Add the ability to run a Set command using a single file, including replaces , updates and deletes . The request file --request-file is either a static file or a Golang Text Template rendered separately for each target. The template input is read from a file referenced by the flag --request-vars .","title":"v0.12.0 - April 20th 2021"},{"location":"changelog/#v0110-april-15th-2021","text":"Processors: Add event-allow processor, basically an allow ACL based on jq condition or regular expressions. Add event-extract-tags processor, it adds tags based on regex named groups from tag names, tag values, value names, or values. Add gnmi-action to event-trigger processor, the action runs a gNMI Set or Get if the trigger condition is met. Set Command: Improve usability by supporting reading values (--update-file and --replace-file) from standard input.","title":"v0.11.0 - April 15th 2021"},{"location":"changelog/#v0100-april-8th-2021","text":"New command: getset command: This command conditionally executes both a Get and a Set RPC, the GetResponse is used to evaluate a condition which if met triggers the execution of the Set RPC. Processors: Some processors' apply condition can be expressed using jq instead of regular expressions.","title":"v0.10.0 - April 8th 2021"},{"location":"changelog/#v091-march-23rd-2021","text":"Processors: Add event-trigger processor: This processor is used to trigger a predefined action if a condition is met. New processor event-jq which applies a transformation on the messages expressed as a jq expression. Shell autocompletion: Shell (bash, zsh and fish) autocompletion scripts can be generated using gnmic completion [bash|zsh|fish] . gRPC gzip compression: gnmic supports gzip compression on gRPC connections.","title":"v0.9.1 - March 23rd 2021"},{"location":"changelog/#v090-march-11th-2021","text":"Clustered Prometheus output: When deployed as a cluster, it is possible to register only one of the prometheus outputs in Consul. This is handy in the case of a cluster with data replication. Proto file loading at runtime (Nokia SROS): gnmic supports loading SROS proto files at runtime to decode gNMI updates with proto encoding Kafka Output: Kafka SASL support: PLAIN, SCRAM SHA256/SHA512 OAuth mechanisms are supported. Configuration: gnmic supports configuration using environment variables. Processors: add event-merge processor. Target Loaders: gnmic supports target loaders at runtime, new targets can be added to the configuration from a file that gnmic watches or from Consul","title":"v0.9.0 - March 11th 2021"},{"location":"changelog/#v080-march-2nd-2021","text":"Inputs: Processors can now be applied by the input plugins. Prometheus output: The Prometheus output can now register as a service in Consul, a Prometheus client can discover the output using consul service discovery. Clustering: gnmic can now run as a cluster, this requires a running Consul instance that will be used by the gnmic instance for leader election and target load sharing. Configuration file: The default configuration file placement now follows XDG recommendations CLI exit status: Failure of most commands is properly reflected in the cli exit status. Configuration: Configuration fields that are OS paths are expanded by gnmic Deployment examples: A set of deployment examples is added to the repo and the docs.","title":"v0.8.0 - March 2nd 2021"},{"location":"changelog/#v070-january-28th-2021","text":"Prometheus output metrics customization: metric-prefix and append-subscription-name can be used to change the default metric prefix and append the subscription name to the metric name. export-timestamps : enables/disables the export of timestamps together with the metric. strings-as-labels : enables/disables automatically adding paths with a value of type string as a metric label. NATS output: allow multiple NATS workers under NATS output via field num-workers . add NATS prometheus internal metrics. STAN output: allow multiple STAN workers under STAN output via field num-workers . add NATS prometheus internal metrics. File output: add File prometheus metrics. Inputs: support ingesting gNMI data from NATS, STAN or a Kafka message bus.","title":"v0.7.0 - January 28th 2021"},{"location":"changelog/#v060-december-14th-2020","text":"Processors: Added processors to gnmic , a set of basic processors can be used to manipulate gNMI data flowing through gnmic . These processors are applied by the output plugins Upgrade command: gnmic can be upgraded using gnmic version upgrade command.","title":"v0.6.0 - December 14th 2020"},{"location":"changelog/#v052-december-1st-2020","text":"Outputs: Improve outputs logging Add Prometheus metrics to Kafka output","title":"v0.5.2 - December 1st 2020"},{"location":"changelog/#v051-november-28th-2020","text":"Prompt Mode: Fix subscribe RPC behavior QoS: Do not populate QoS field if not set via config file or flag. Outputs: add configurable number of workers to some outputs.","title":"v0.5.1 - November 28th 2020"},{"location":"changelog/#v050-november-25th-2020","text":"Prompt Mode: Add prompt sub commands. XPATH parsing: Add custom xpath parsingto gnmi.Path to allow for paths including column : . TLS: Allow configurable TLS versions per target, the minimum, the maximum and the preferred TLS versions ca be configured.","title":"v0.5.0 - November 25th 2020"},{"location":"changelog/#v043-november-10th-2020","text":"Missing path: Initialize the path field if not present in SubscribeResponse","title":"v0.4.3 - November 10th 2020"},{"location":"changelog/#v042-november-5th-2020","text":"YANG: Prompt command flags --file and --dir support globs. Subscribe: added flags --output that allows to choose a single output for subscribe updates Prompt: Max suggestions is automatically adjusted based on the terminal height. Add suggestions for address and subscriptions.","title":"v0.4.2 - November 5th 2020"},{"location":"changelog/#v041-october-22nd-2020","text":"Prompt: Add suggestions of xpath with origin, --suggest-with-origin .","title":"v0.4.1 - October 22nd 2020"},{"location":"changelog/#v040-october-21st-2020","text":"New Command: Add new command prompt Prompt: Add ctrl+z key bind to delete a single path element. Add YANG info to xpath suggestions. Add GoLeft, GoRight key binds. Sort xpaths and prefixes suggestions. xpaths suggestions are properly generated if a prefix is present. flag --suggest-all-flags allows adding global flags suggestion in prompt mode. Prometheus output: Add support for Prometheus output plugin.","title":"v0.4.0 - October 21st 2020"},{"location":"changelog/#v030-october-1st-2020","text":"InfluxDB output: Add support for influxDB output plugin.","title":"v0.3.0 - October 1st 2020"},{"location":"changelog/#v023-september-18th-2020","text":"Retry Add basic RPC retry mechanism. ONCE mode subscription: Handle targets that send an EOF error instead of a SyncResponse to signify the end of ONCE subscriptions. Docker image: Docker images added to ghcr.io as well as docker hub.","title":"v0.2.3 - September 18th 2020"},{"location":"changelog/#v022-september-3rd-2020","text":"CLI: Properly handle paths that include quotes. Unix Socket: Allow send/rcv of gNMI data to/from a unix socket. Outputs: Add TCP output plugin.","title":"v0.2.2 - September 3rd 2020"},{"location":"changelog/#v021-august-11th-2020","text":"Releases: Add .deb. and .rpm packages to releases. Outputs: Add UDP output plugin.","title":"v0.2.1 - August 11th 2020"},{"location":"changelog/#v020-august-7th-2020","text":"Releases: Add ARM releases. Push docker image to docker hub.","title":"v0.2.0 - August 7th 2020"},{"location":"changelog/#v011-july-23rd-2020","text":"Set Cmd: Support json_ietf encoding when the value is specified from a file.","title":"v0.1.1 - July 23rd 2020"},{"location":"changelog/#v010-july-16th-2020","text":"Outputs: Allow NATS/STAN output subject customization.","title":"v0.1.0 - July 16th 2020"},{"location":"changelog/#v007-july-16th-2020","text":"gNMI Target: Add support for gNMI Target field. gNMI Origin: Add support for gNMI Origin field. Prometheus internal metrics: Add support for gnmic internal metrics via a Prometheus server. Outputs: Add support for multiple output plugins (file, NATS, STAN, Kafka) Targets: Support target specific configuration. Poll Subscription: Allow selecting polled targets and subscription using a CLI select menu. gNMI Models: Support multiple Models in Get and Subscribe RPCs.","title":"v0.0.7 - July 16th 2020"},{"location":"changelog/#v006-june-2nd-2020","text":"Nokia Dialout: Add Support for Nokia Dialout telemetry. Printing: Convert timestamps to Time.","title":"v0.0.6 - June 2nd 2020"},{"location":"changelog/#v005-may-18th-2020","text":"Formatting: Add textproto format.","title":"v0.0.5 - May 18th 2020"},{"location":"changelog/#v004-may-11th-2020","text":"Logging: Support logging to file instead of Stderr. Set Command: support Set values from YAML file.","title":"v0.0.4 - May 11th 2020"},{"location":"changelog/#v003-april-23rd-2020","text":"Proxy: Allow usage of ENV proxy values for gRPC connections. Installation: Add installation script.","title":"v0.0.3 - April 23rd 2020"},{"location":"changelog/#v002-april-13th-2020","text":"Terminal printing clean up. Path Command: Add search option.","title":"v0.0.2 - April 13th 2020"},{"location":"changelog/#v001-march-24th-2020","text":"Capabilities RPC Command. Get RPC Command. Subscribe RPC Command. Set RPC Command. TLS support. Version Command. Path Commnd.","title":"v0.0.1 - March 24th 2020"},{"location":"changelog/#initial-commit-february-20th-2020","text":"","title":"initial Commit - February 20th 2020"},{"location":"global_flags/","text":"address # The address flag [-a | --address] is used to specify the target's gNMI server address in address:port format, for e.g: 192.168.113.11:57400 Multiple target addresses can be specified, either as comma separated values: gnmic --address 192 .168.113.11:57400,192.168.113.12:57400 or by using the --address flag multiple times: gnmic -a 192 .168.113.11:57400 --address 192 .168.113.12:57400 auth-scheme # The auth-scheme flag --auth-scheme is used to specify the authorization header type. For example, if auth-scheme is set to Basic , the gNMI requests headers will include an Authorization header with value Basic base64enc(username:password) . cluster-name # The [--cluster-name] flag is used to specify the cluster name the gnmic instance will join. The cluster name is used as part of the locked keys to share targets between multiple gnmic instances. Defaults to default-cluster config # The --config flag specifies the location of a configuration file that gnmic will read. If not specified, gnmic searches for a file named .gnmic with extensions yaml, yml, toml or json in the following locations: $PWD $HOME $XDG_CONFIG_HOME $XDG_CONFIG_HOME/gnmic debug # The debug flag [-d | --debug] enables the printing of extra information when sending/receiving an RPC dir # A path to a directory which gnmic would recursively traverse in search for the additional YANG files which may be required by YANG files specified with --file to build the YANG tree. Can also point to a single YANG file instead of a directory. Multiple --dir flags can be supplied. encoding # The encoding flag [-e | --encoding] is used to specify the gNMI encoding of the Update part of a Notification message. It is case insensitive and must be one of: JSON, BYTES, PROTO, ASCII, JSON_IETF exclude # The --exclude flag specifies the YANG module names to be excluded from the tree generation when YANG modules names clash. Multiple --exclude flags can be supplied. file # A path to a YANG file or a directory with YANG files which gnmic will use with prompt, generate and path commands. Multiple --file flags can be supplied. format # Five output formats can be configured by means of the --format flag. [proto, protojson, prototext, json, event] The default format is json . The proto format outputs the gnmi message as raw bytes, this value is not allowed when the output type is file (file system, stdout or stderr) see outputs The prototext and protojson formats are the message representation as defined in prototext and protojson The event format emits the received gNMI SubscribeResponse updates and deletes as a list of events tagged with the keys present in the subscribe path (as well as some metadata) and a timestamp Here goes an example of the same response emitted to stdout in the respective formats: protojson prototext json event { \"update\" : { \"timestamp\" : \"1595584408456503938\" , \"prefix\" : { \"elem\" : [ { \"name\" : \"state\" }, { \"name\" : \"system\" }, { \"name\" : \"version\" } ] }, \"update\" : [ { \"path\" : { \"elem\" : [ { \"name\" : \"version-string\" } ] }, \"val\" : { \"stringVal\" : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } ] } } update : { timestamp : 1595584168675434221 prefix : { elem : { name : \"state\" } elem : { name : \"system\" } elem : { name : \"version\" } } update : { path : { elem : { name : \"version-string\" } } val : { string_val : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } } { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" , \"timestamp\" : 1595584326775141151 , \"time\" : \"2020-07-24T17:52:06.775141151+08:00\" , \"prefix\" : \"state/system/version\" , \"updates\" : [ { \"Path\" : \"version-string\" , \"values\" : { \"version-string\" : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } ] } [ { \"name\" : \"default\" , \"timestamp\" : 1595584587725708234 , \"tags\" : { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/system/version/version-string\" : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } ] gzip # The [--gzip] flag enables gRPC gzip compression. insecure # The insecure flag [--insecure] is used to indicate that the client wishes to establish an non-TLS enabled gRPC connection. To disable certificate validation in a TLS-enabled connection use skip-verify flag. instance-name # The [--instance-name] flag is used to give a unique name to the running gnmic instance. This is useful when there are multiple instances of gnmic running at the same time, either for high-availability and/or scalability log # The --log flag enables log messages to appear on stderr output. By default logging is disabled. log-file # The log-file flag [--log-file ] sets the log output to a file referenced by the path. This flag supersede the --log flag log-max-size # The [--log-max-size] flag enables log rotation and sets the maximum size of the log file in megabytes before it gets rotated. log-max-backups # The [--log-max-backups] flag sets the maximum number of old log files to retain. The default is to retain all old log files. log-compress # The [--log-compress] flag determines if the rotated log files should be compressed using gzip. The default is not to perform compression. no-prefix # The no prefix flag [--no-prefix] disables prefixing the json formatted responses with [ip:port] string. Note that in case a single target is specified, the prefix is not added. password # The password flag [-p | --password] is used to specify the target password as part of the user credentials. Note that in case multiple targets are used, all should use the same credentials. proto-dir # The [--proto-dir] flag is used to specify a list of directories where gnmic will search for the proto file names specified with --proto-file . proto-file # The [--proto-file] flag is used to specify a list of proto file names that gnmic will use to decode ProtoBytes values. only Nokia SROS proto is currently supported. proxy-from-env # The proxy-from-env flag [--proxy-from-env] indicates that the gnmic should use the HTTP/HTTPS proxy addresses defined in the environment variables http_proxy and https_proxy to reach the targets specified using the --address flag. retry # The retry flag [--retry] specifies the wait time before each retry. Valid formats: 10s, 1m30s, 1h. Defaults to 10s skip-verify # The skip verify flag [--skip-verify] indicates that the target should skip the signature verification steps, in case a secure connection is used. targets-file # The [--targets-file] flag is used to configure a file target loader timeout # The timeout flag [--timeout] specifies the gRPC timeout after which the connection attempt fails. Valid formats: 10s, 1m30s, 1h. Defaults to 10s tls-ca # The TLS CA flag [--tls-ca] specifies the root certificates for verifying server certificates encoded in PEM format. tls-cert # The TLS cert flag [--tls-cert] specifies the public key for the client encoded in PEM format. tls-key # The TLS key flag [--tls-key] specifies the private key for the client encoded in PEM format. tls-max-version # The TLS max version flag [--tls-max-version] specifies the maximum supported TLS version supported by gNMIc when creating a secure gRPC connection. tls-min-version # The tls min version flag [--tls-min-version] specifies the minimum supported TLS version supported by gNMIc when creating a secure gRPC connection. tls-server-name # The TLS server name flag [--tls-server-name] sets the server name to be used when verifying the hostname on the returned certificates unless --skip-verify is set. This global flag applies to all targets. tls-version # The tls version flag [--tls-version] specifies a single supported TLS version gNMIc when creating a secure gRPC connection. This flag overwrites the previously listed flags --tls-max-version and --tls-min-version . log-tls-secret # The log TLS secret flag [--log-tls-secret] makes gnmic to log the per-session pre-master secret so that it can be used to decrypt TLS secured gNMI communications with, for example, Wireshark. The secret will be saved to a file named .tlssecret.log . token # The token flag [--token] sets a token value to be added to each RPC as an Authorization Bearer Token. Applied only in the case of a secure gRPC connection. username # The username flag [-u | --username] is used to specify the target username as part of the user credentials. calculate-latency # The --calculate-latency flag augments subscribe et get responses by calculating the delta between the message timestamp and the receive timestamp. The resulting message will include 4 extra fields: recv-timestamp :The receive timestamp in nanoseconds. recv-time : The receive time in ISO 8601 date and time representation, extended to include fractional seconds and a time zone offset.. latency-nano : The difference between the message timestamp and the receive time in nanoseconds. latency-milli : The difference between the message timestamp and the receive time in milliseconds. metadata # The [-H | --metadata] flag adds custom headers to any gRPC request. gnmic -H header1=value1 -H header2=value2","title":"Global flags"},{"location":"global_flags/#address","text":"The address flag [-a | --address] is used to specify the target's gNMI server address in address:port format, for e.g: 192.168.113.11:57400 Multiple target addresses can be specified, either as comma separated values: gnmic --address 192 .168.113.11:57400,192.168.113.12:57400 or by using the --address flag multiple times: gnmic -a 192 .168.113.11:57400 --address 192 .168.113.12:57400","title":"address"},{"location":"global_flags/#auth-scheme","text":"The auth-scheme flag --auth-scheme is used to specify the authorization header type. For example, if auth-scheme is set to Basic , the gNMI requests headers will include an Authorization header with value Basic base64enc(username:password) .","title":"auth-scheme"},{"location":"global_flags/#cluster-name","text":"The [--cluster-name] flag is used to specify the cluster name the gnmic instance will join. The cluster name is used as part of the locked keys to share targets between multiple gnmic instances. Defaults to default-cluster","title":"cluster-name"},{"location":"global_flags/#config","text":"The --config flag specifies the location of a configuration file that gnmic will read. If not specified, gnmic searches for a file named .gnmic with extensions yaml, yml, toml or json in the following locations: $PWD $HOME $XDG_CONFIG_HOME $XDG_CONFIG_HOME/gnmic","title":"config"},{"location":"global_flags/#debug","text":"The debug flag [-d | --debug] enables the printing of extra information when sending/receiving an RPC","title":"debug"},{"location":"global_flags/#dir","text":"A path to a directory which gnmic would recursively traverse in search for the additional YANG files which may be required by YANG files specified with --file to build the YANG tree. Can also point to a single YANG file instead of a directory. Multiple --dir flags can be supplied.","title":"dir"},{"location":"global_flags/#encoding","text":"The encoding flag [-e | --encoding] is used to specify the gNMI encoding of the Update part of a Notification message. It is case insensitive and must be one of: JSON, BYTES, PROTO, ASCII, JSON_IETF","title":"encoding"},{"location":"global_flags/#exclude","text":"The --exclude flag specifies the YANG module names to be excluded from the tree generation when YANG modules names clash. Multiple --exclude flags can be supplied.","title":"exclude"},{"location":"global_flags/#file","text":"A path to a YANG file or a directory with YANG files which gnmic will use with prompt, generate and path commands. Multiple --file flags can be supplied.","title":"file"},{"location":"global_flags/#format","text":"Five output formats can be configured by means of the --format flag. [proto, protojson, prototext, json, event] The default format is json . The proto format outputs the gnmi message as raw bytes, this value is not allowed when the output type is file (file system, stdout or stderr) see outputs The prototext and protojson formats are the message representation as defined in prototext and protojson The event format emits the received gNMI SubscribeResponse updates and deletes as a list of events tagged with the keys present in the subscribe path (as well as some metadata) and a timestamp Here goes an example of the same response emitted to stdout in the respective formats: protojson prototext json event { \"update\" : { \"timestamp\" : \"1595584408456503938\" , \"prefix\" : { \"elem\" : [ { \"name\" : \"state\" }, { \"name\" : \"system\" }, { \"name\" : \"version\" } ] }, \"update\" : [ { \"path\" : { \"elem\" : [ { \"name\" : \"version-string\" } ] }, \"val\" : { \"stringVal\" : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } ] } } update : { timestamp : 1595584168675434221 prefix : { elem : { name : \"state\" } elem : { name : \"system\" } elem : { name : \"version\" } } update : { path : { elem : { name : \"version-string\" } } val : { string_val : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } } { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" , \"timestamp\" : 1595584326775141151 , \"time\" : \"2020-07-24T17:52:06.775141151+08:00\" , \"prefix\" : \"state/system/version\" , \"updates\" : [ { \"Path\" : \"version-string\" , \"values\" : { \"version-string\" : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } ] } [ { \"name\" : \"default\" , \"timestamp\" : 1595584587725708234 , \"tags\" : { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/system/version/version-string\" : \"TiMOS-B-20.5.R1 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia.\\r\\nAll rights reserved. All use subject to applicable license agreements.\\r\\nBuilt on Wed May 13 14:08:50 PDT 2020 by builder in /builds/c/205B/R1/panos/main/sros\" } } ]","title":"format"},{"location":"global_flags/#gzip","text":"The [--gzip] flag enables gRPC gzip compression.","title":"gzip"},{"location":"global_flags/#insecure","text":"The insecure flag [--insecure] is used to indicate that the client wishes to establish an non-TLS enabled gRPC connection. To disable certificate validation in a TLS-enabled connection use skip-verify flag.","title":"insecure"},{"location":"global_flags/#instance-name","text":"The [--instance-name] flag is used to give a unique name to the running gnmic instance. This is useful when there are multiple instances of gnmic running at the same time, either for high-availability and/or scalability","title":"instance-name"},{"location":"global_flags/#log","text":"The --log flag enables log messages to appear on stderr output. By default logging is disabled.","title":"log"},{"location":"global_flags/#log-file","text":"The log-file flag [--log-file ] sets the log output to a file referenced by the path. This flag supersede the --log flag","title":"log-file"},{"location":"global_flags/#log-max-size","text":"The [--log-max-size] flag enables log rotation and sets the maximum size of the log file in megabytes before it gets rotated.","title":"log-max-size"},{"location":"global_flags/#log-max-backups","text":"The [--log-max-backups] flag sets the maximum number of old log files to retain. The default is to retain all old log files.","title":"log-max-backups"},{"location":"global_flags/#log-compress","text":"The [--log-compress] flag determines if the rotated log files should be compressed using gzip. The default is not to perform compression.","title":"log-compress"},{"location":"global_flags/#no-prefix","text":"The no prefix flag [--no-prefix] disables prefixing the json formatted responses with [ip:port] string. Note that in case a single target is specified, the prefix is not added.","title":"no-prefix"},{"location":"global_flags/#password","text":"The password flag [-p | --password] is used to specify the target password as part of the user credentials. Note that in case multiple targets are used, all should use the same credentials.","title":"password"},{"location":"global_flags/#proto-dir","text":"The [--proto-dir] flag is used to specify a list of directories where gnmic will search for the proto file names specified with --proto-file .","title":"proto-dir"},{"location":"global_flags/#proto-file","text":"The [--proto-file] flag is used to specify a list of proto file names that gnmic will use to decode ProtoBytes values. only Nokia SROS proto is currently supported.","title":"proto-file"},{"location":"global_flags/#proxy-from-env","text":"The proxy-from-env flag [--proxy-from-env] indicates that the gnmic should use the HTTP/HTTPS proxy addresses defined in the environment variables http_proxy and https_proxy to reach the targets specified using the --address flag.","title":"proxy-from-env"},{"location":"global_flags/#retry","text":"The retry flag [--retry] specifies the wait time before each retry. Valid formats: 10s, 1m30s, 1h. Defaults to 10s","title":"retry"},{"location":"global_flags/#skip-verify","text":"The skip verify flag [--skip-verify] indicates that the target should skip the signature verification steps, in case a secure connection is used.","title":"skip-verify"},{"location":"global_flags/#targets-file","text":"The [--targets-file] flag is used to configure a file target loader","title":"targets-file"},{"location":"global_flags/#timeout","text":"The timeout flag [--timeout] specifies the gRPC timeout after which the connection attempt fails. Valid formats: 10s, 1m30s, 1h. Defaults to 10s","title":"timeout"},{"location":"global_flags/#tls-ca","text":"The TLS CA flag [--tls-ca] specifies the root certificates for verifying server certificates encoded in PEM format.","title":"tls-ca"},{"location":"global_flags/#tls-cert","text":"The TLS cert flag [--tls-cert] specifies the public key for the client encoded in PEM format.","title":"tls-cert"},{"location":"global_flags/#tls-key","text":"The TLS key flag [--tls-key] specifies the private key for the client encoded in PEM format.","title":"tls-key"},{"location":"global_flags/#tls-max-version","text":"The TLS max version flag [--tls-max-version] specifies the maximum supported TLS version supported by gNMIc when creating a secure gRPC connection.","title":"tls-max-version"},{"location":"global_flags/#tls-min-version","text":"The tls min version flag [--tls-min-version] specifies the minimum supported TLS version supported by gNMIc when creating a secure gRPC connection.","title":"tls-min-version"},{"location":"global_flags/#tls-server-name","text":"The TLS server name flag [--tls-server-name] sets the server name to be used when verifying the hostname on the returned certificates unless --skip-verify is set. This global flag applies to all targets.","title":"tls-server-name"},{"location":"global_flags/#tls-version","text":"The tls version flag [--tls-version] specifies a single supported TLS version gNMIc when creating a secure gRPC connection. This flag overwrites the previously listed flags --tls-max-version and --tls-min-version .","title":"tls-version"},{"location":"global_flags/#log-tls-secret","text":"The log TLS secret flag [--log-tls-secret] makes gnmic to log the per-session pre-master secret so that it can be used to decrypt TLS secured gNMI communications with, for example, Wireshark. The secret will be saved to a file named .tlssecret.log .","title":"log-tls-secret"},{"location":"global_flags/#token","text":"The token flag [--token] sets a token value to be added to each RPC as an Authorization Bearer Token. Applied only in the case of a secure gRPC connection.","title":"token"},{"location":"global_flags/#username","text":"The username flag [-u | --username] is used to specify the target username as part of the user credentials.","title":"username"},{"location":"global_flags/#calculate-latency","text":"The --calculate-latency flag augments subscribe et get responses by calculating the delta between the message timestamp and the receive timestamp. The resulting message will include 4 extra fields: recv-timestamp :The receive timestamp in nanoseconds. recv-time : The receive time in ISO 8601 date and time representation, extended to include fractional seconds and a time zone offset.. latency-nano : The difference between the message timestamp and the receive time in nanoseconds. latency-milli : The difference between the message timestamp and the receive time in milliseconds.","title":"calculate-latency"},{"location":"global_flags/#metadata","text":"The [-H | --metadata] flag adds custom headers to any gRPC request. gnmic -H header1=value1 -H header2=value2","title":"metadata"},{"location":"install/","text":"gnmic is a single binary built for the Linux, Mac OS and Windows operating systems distributed via Github releases . Linux/Mac OS # To download & install the latest release the following automated installation script can be used: bash -c \" $( curl -sL https://get-gnmic.openconfig.net ) \" As a result, the latest gnmic version will be installed in the /usr/local/bin directory and the version information will be printed out. Downloading gnmic_0.0.3_Darwin_x86_64.tar.gz... Moving gnmic to /usr/local/bin version : 0.0.3 commit : f541948 date : 2020-04-23T12:06:07Z gitURL : https://github.com/openconfig/gnmic.git docs : https://gnmic.openconfig.net Installation complete! To install a specific version of gnmic , provide the version with -v flag to the installation script: bash -c \" $( curl -sL https://get-gnmic.openconfig.net ) \" -- -v 0 .5.0 Packages # Linux users running distributions with support for deb / rpm packages can install gnmic using pre-built packages: bash -c \" $( curl -sL https://get-gnmic.openconfig.net ) \" -- --use-pkg Upgrade # To upgrade gnmic to the latest version use the upgrade command: # upgrade using binary file gnmic version upgrade # upgrade using package gnmic version upgrade --use-pkg Windows # Windows users should use WSL on Windows and install the linux version of the tool. Docker # The gnmic container image can be pulled from Dockerhub or GitHub container registries. The tag of the image corresponds to the release version and latest tag points to the latest available release: # pull latest release from dockerhub docker pull gnmic/gnmic:latest # pull a specific release from dockerhub docker pull gnmic/gnmic:0.7.0 # pull latest release from github registry docker pull ghcr.io/openconfig/gnmic:latest # pull a specific release from github registry docker pull ghcr.io/openconfig/gnmic:0.5.2 Example running gnmic get command using the docker image: docker run \\ --network host \\ --rm ghcr.io/openconfig/gnmic get --log --username admin --password admin --insecure --address router1.local --path /interfaces Docker Compose # gnmic docker-compose file example: version : '2' networks : gnmic-net : driver : bridge services : gnmic-1 : image : ghcr.io/openconfig/gnmic:latest container_name : gnmic-1 networks : - gnmic-net volumes : - ./gnmic.yaml:/app/gnmic.yaml command : \"subscribe --config /app/gnmic.yaml\" See here for more deployment options","title":"Installation"},{"location":"install/#linuxmac-os","text":"To download & install the latest release the following automated installation script can be used: bash -c \" $( curl -sL https://get-gnmic.openconfig.net ) \" As a result, the latest gnmic version will be installed in the /usr/local/bin directory and the version information will be printed out. Downloading gnmic_0.0.3_Darwin_x86_64.tar.gz... Moving gnmic to /usr/local/bin version : 0.0.3 commit : f541948 date : 2020-04-23T12:06:07Z gitURL : https://github.com/openconfig/gnmic.git docs : https://gnmic.openconfig.net Installation complete! To install a specific version of gnmic , provide the version with -v flag to the installation script: bash -c \" $( curl -sL https://get-gnmic.openconfig.net ) \" -- -v 0 .5.0","title":"Linux/Mac OS"},{"location":"install/#packages","text":"Linux users running distributions with support for deb / rpm packages can install gnmic using pre-built packages: bash -c \" $( curl -sL https://get-gnmic.openconfig.net ) \" -- --use-pkg","title":"Packages"},{"location":"install/#upgrade","text":"To upgrade gnmic to the latest version use the upgrade command: # upgrade using binary file gnmic version upgrade # upgrade using package gnmic version upgrade --use-pkg","title":"Upgrade"},{"location":"install/#windows","text":"Windows users should use WSL on Windows and install the linux version of the tool.","title":"Windows"},{"location":"install/#docker","text":"The gnmic container image can be pulled from Dockerhub or GitHub container registries. The tag of the image corresponds to the release version and latest tag points to the latest available release: # pull latest release from dockerhub docker pull gnmic/gnmic:latest # pull a specific release from dockerhub docker pull gnmic/gnmic:0.7.0 # pull latest release from github registry docker pull ghcr.io/openconfig/gnmic:latest # pull a specific release from github registry docker pull ghcr.io/openconfig/gnmic:0.5.2 Example running gnmic get command using the docker image: docker run \\ --network host \\ --rm ghcr.io/openconfig/gnmic get --log --username admin --password admin --insecure --address router1.local --path /interfaces","title":"Docker"},{"location":"install/#docker-compose","text":"gnmic docker-compose file example: version : '2' networks : gnmic-net : driver : bridge services : gnmic-1 : image : ghcr.io/openconfig/gnmic:latest container_name : gnmic-1 networks : - gnmic-net volumes : - ./gnmic.yaml:/app/gnmic.yaml command : \"subscribe --config /app/gnmic.yaml\" See here for more deployment options","title":"Docker Compose"},{"location":"blog/","text":"Coming soon","title":"Index"},{"location":"cmd/capabilities/","text":"Description # The [cap | capabilities] command represents the gNMI Capabilities RPC . It is used to send a Capability Request to the specified target(s) and expects one Capability Response per target. Capabilities allows the client to retrieve the set of capabilities that is supported by the target: gNMI version available data models supported encodings gNMI extensions This allows the client to, for example, validate the service version that is implemented and retrieve the set of models that the target supports. The models can then be specified in subsequent Get/Subscribe RPCs to precisely tell the target which models to use. Usage # gnmic [global-flags] capabilities [local-flags] Examples # single host # gnmic -a --username --password \\ --insecure capabilities gNMI_Version: 0.7.0 supported models: - nokia-conf, Nokia, 19.10.R2 - nokia-state, Nokia, 19.10.R2 - nokia-li-state, Nokia, 19.10.R2 - nokia-li-conf, Nokia, 19.10.R2 << SNIPPED >> supported encodings: - JSON - BYTES multiple hosts # gnmic -a , -u -p \\ --insecure cap","title":"Capabilities"},{"location":"cmd/capabilities/#description","text":"The [cap | capabilities] command represents the gNMI Capabilities RPC . It is used to send a Capability Request to the specified target(s) and expects one Capability Response per target. Capabilities allows the client to retrieve the set of capabilities that is supported by the target: gNMI version available data models supported encodings gNMI extensions This allows the client to, for example, validate the service version that is implemented and retrieve the set of models that the target supports. The models can then be specified in subsequent Get/Subscribe RPCs to precisely tell the target which models to use.","title":"Description"},{"location":"cmd/capabilities/#usage","text":"gnmic [global-flags] capabilities [local-flags]","title":"Usage"},{"location":"cmd/capabilities/#examples","text":"","title":"Examples"},{"location":"cmd/capabilities/#single-host","text":"gnmic -a --username --password \\ --insecure capabilities gNMI_Version: 0.7.0 supported models: - nokia-conf, Nokia, 19.10.R2 - nokia-state, Nokia, 19.10.R2 - nokia-li-state, Nokia, 19.10.R2 - nokia-li-conf, Nokia, 19.10.R2 << SNIPPED >> supported encodings: - JSON - BYTES","title":"single host"},{"location":"cmd/capabilities/#multiple-hosts","text":"gnmic -a , -u -p \\ --insecure cap","title":"multiple hosts"},{"location":"cmd/generate/","text":"Description # Most gNMI targets use YANG as a modeling language for their datastores. It order to access and manipulate the stored data ( Get , Set , Subscribe ), a tool should be aware of the underlying YANG model, be able to generate paths pointing to the desired gNMI objects as well as building configuration payloads matching data instances on the targets. The generate command takes the target's YANG models as input and generates: Paths in xpath or gNMI formats. Configuration payloads that can be used as update or replace input files for the Set command. A Set request file that can be used as a template with the Set command. Aliases: gen Usage # gnmic [global-flags] generate [local-flags] or gnmic [global-flags] generate [local-flags] sub-command [sub-command-flags] Persistent Flags # output # The --output flag specifies the file to which the generated output will be written, defaults to stdout json # When used with generate command, the --json flag, if present changes the output format from YAML to JSON. When used with generate path command, it outputs the path, the leaf type , its description , its default value and if it is a state leaf or not in an array of JSON objects. Local Flags # path # The --path flag specifies the path whose payload (JSON/YAML) will be generated. Defaults to / config-only # The --config-only flag, if present instruct gnmic to generate JSON/YAML payloads from YANG nodes not marked as config false . camel-case # The --camel-case flag, if present allows to convert all the keys in the generated JSON/YAML paylod to CamelCase snake-case # The --snake-case flag, if present allows to convert all the keys in the generated JSON/YAML paylod to snake_case Sub Commands # Path # The path sub command is an alias for the gnmic path command. Set-request # The set-request sub command generates a Set request file given a list of update and/or replace paths. Examples # Openconfig # YANG repo: openconfig/public Clone the OpenConfig repository: git clone https://github.com/openconfig/public cd public gnmic --encoding json_ietf \\ generate \\ --file release/models \\ --dir third_party \\ --exclude ietf-interfaces \\ --path /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address - config : ip : \"\" prefix-length : \"\" ip : \"\" vrrp : vrrp-group : - config : accept-mode : \"false\" advertisement-interval : \"100\" preempt : \"true\" preempt-delay : \"0\" priority : \"100\" virtual-address : \"\" virtual-router-id : \"\" interface-tracking : config : priority-decrement : \"0\" track-interface : \"\" virtual-router-id : \"\"","title":"Generate"},{"location":"cmd/generate/#description","text":"Most gNMI targets use YANG as a modeling language for their datastores. It order to access and manipulate the stored data ( Get , Set , Subscribe ), a tool should be aware of the underlying YANG model, be able to generate paths pointing to the desired gNMI objects as well as building configuration payloads matching data instances on the targets. The generate command takes the target's YANG models as input and generates: Paths in xpath or gNMI formats. Configuration payloads that can be used as update or replace input files for the Set command. A Set request file that can be used as a template with the Set command. Aliases: gen","title":"Description"},{"location":"cmd/generate/#usage","text":"gnmic [global-flags] generate [local-flags] or gnmic [global-flags] generate [local-flags] sub-command [sub-command-flags]","title":"Usage"},{"location":"cmd/generate/#persistent-flags","text":"","title":"Persistent Flags"},{"location":"cmd/generate/#output","text":"The --output flag specifies the file to which the generated output will be written, defaults to stdout","title":"output"},{"location":"cmd/generate/#json","text":"When used with generate command, the --json flag, if present changes the output format from YAML to JSON. When used with generate path command, it outputs the path, the leaf type , its description , its default value and if it is a state leaf or not in an array of JSON objects.","title":"json"},{"location":"cmd/generate/#local-flags","text":"","title":"Local Flags"},{"location":"cmd/generate/#path","text":"The --path flag specifies the path whose payload (JSON/YAML) will be generated. Defaults to /","title":"path"},{"location":"cmd/generate/#config-only","text":"The --config-only flag, if present instruct gnmic to generate JSON/YAML payloads from YANG nodes not marked as config false .","title":"config-only"},{"location":"cmd/generate/#camel-case","text":"The --camel-case flag, if present allows to convert all the keys in the generated JSON/YAML paylod to CamelCase","title":"camel-case"},{"location":"cmd/generate/#snake-case","text":"The --snake-case flag, if present allows to convert all the keys in the generated JSON/YAML paylod to snake_case","title":"snake-case"},{"location":"cmd/generate/#sub-commands","text":"","title":"Sub Commands"},{"location":"cmd/generate/#path_1","text":"The path sub command is an alias for the gnmic path command.","title":"Path"},{"location":"cmd/generate/#set-request","text":"The set-request sub command generates a Set request file given a list of update and/or replace paths.","title":"Set-request"},{"location":"cmd/generate/#examples","text":"","title":"Examples"},{"location":"cmd/generate/#openconfig","text":"YANG repo: openconfig/public Clone the OpenConfig repository: git clone https://github.com/openconfig/public cd public gnmic --encoding json_ietf \\ generate \\ --file release/models \\ --dir third_party \\ --exclude ietf-interfaces \\ --path /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address - config : ip : \"\" prefix-length : \"\" ip : \"\" vrrp : vrrp-group : - config : accept-mode : \"false\" advertisement-interval : \"100\" preempt : \"true\" preempt-delay : \"0\" priority : \"100\" virtual-address : \"\" virtual-router-id : \"\" interface-tracking : config : priority-decrement : \"0\" track-interface : \"\" virtual-router-id : \"\"","title":"Openconfig"},{"location":"cmd/get/","text":"Description # The get command represents the gNMI Get RPC . It is used to send a GetRequest to the specified target(s) (using the global flag --address and expects one GetResponse per target, per path. The Get RPC is used to retrieve a snapshot of data from the target. It requests that the target snapshots a subset of the data tree as specified by the paths included in the message and serializes this to be returned to the client using the specified encoding. Usage # gnmic [global-flags] get [local-flags] Flags # prefix # As per path prefixes , the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --path flag. Defaults to \"\" . path # The mandatory path flag [--path] is used to specify the path(s) the client wants to receive a snapshot of. Multiple paths can be specified by using multiple --path flags: gnmic -a --insecure \\ get --path \"/state/ports[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" : Note The path after the origin value has to start with a / gnmic -a --insecure \\ get --path \"openconfig-interfaces:/interfaces/interface\" model # The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported. target # With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message. values-only # The flag [--values-only] allows to print only the values returned in a GetResponse. This is useful when only the value of a leaf is of interest, like check if a value was set correctly. type # The type flag [--type] is used to specify the data type requested from the server. One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to \"ALL\") processor # The [--processor] flag allow to list event processor names to be run as a result of receiving the GetReponse messages. The processors are run in the order they are specified ( --processor proc1,proc2 or --processor proc1 --processor proc2 ). Examples # # simple Get RPC gnmic -a get --path \"/state/port[port-id=*]\" # Get RPC with multiple paths gnmic -a get --path \"/state/port[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" # Get RPC with path prefix gnmic -a get --prefix \"/state\" \\ --path \"port[port-id=*]\" \\ --path \"router[router-name=*]/interface[interface-name=*]\"","title":"Get"},{"location":"cmd/get/#description","text":"The get command represents the gNMI Get RPC . It is used to send a GetRequest to the specified target(s) (using the global flag --address and expects one GetResponse per target, per path. The Get RPC is used to retrieve a snapshot of data from the target. It requests that the target snapshots a subset of the data tree as specified by the paths included in the message and serializes this to be returned to the client using the specified encoding.","title":"Description"},{"location":"cmd/get/#usage","text":"gnmic [global-flags] get [local-flags]","title":"Usage"},{"location":"cmd/get/#flags","text":"","title":"Flags"},{"location":"cmd/get/#prefix","text":"As per path prefixes , the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --path flag. Defaults to \"\" .","title":"prefix"},{"location":"cmd/get/#path","text":"The mandatory path flag [--path] is used to specify the path(s) the client wants to receive a snapshot of. Multiple paths can be specified by using multiple --path flags: gnmic -a --insecure \\ get --path \"/state/ports[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" : Note The path after the origin value has to start with a / gnmic -a --insecure \\ get --path \"openconfig-interfaces:/interfaces/interface\"","title":"path"},{"location":"cmd/get/#model","text":"The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported.","title":"model"},{"location":"cmd/get/#target","text":"With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message.","title":"target"},{"location":"cmd/get/#values-only","text":"The flag [--values-only] allows to print only the values returned in a GetResponse. This is useful when only the value of a leaf is of interest, like check if a value was set correctly.","title":"values-only"},{"location":"cmd/get/#type","text":"The type flag [--type] is used to specify the data type requested from the server. One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to \"ALL\")","title":"type"},{"location":"cmd/get/#processor","text":"The [--processor] flag allow to list event processor names to be run as a result of receiving the GetReponse messages. The processors are run in the order they are specified ( --processor proc1,proc2 or --processor proc1 --processor proc2 ).","title":"processor"},{"location":"cmd/get/#examples","text":"# simple Get RPC gnmic -a get --path \"/state/port[port-id=*]\" # Get RPC with multiple paths gnmic -a get --path \"/state/port[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" # Get RPC with path prefix gnmic -a get --prefix \"/state\" \\ --path \"port[port-id=*]\" \\ --path \"router[router-name=*]/interface[interface-name=*]\"","title":"Examples"},{"location":"cmd/getset/","text":"Description # The getset command is a combination of the gNMI Get RPC and the gNMI Set RPC . It allows to conditionally execute a Set RPC based on a condition evaluated against a GetResponse . The condition written as a jq expression , is specified using the flag --condition . The SetRPC is executed only if the condition evaluates to true Usage # gnmic [global-flags] getset [local-flags] gnmic [global-flags] gas [local-flags] gnmic [global-flags] gs [local-flags] Flags # prefix # As per path prefixes , the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --get , --update , --replace and --delete flags. Defaults to \"\" . get # The mandatory get flag [--get] is used to specify the single path used in the Get RPC. model # The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported. target # With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message. type # The type flag [--type] is used to specify the data type requested from the server. One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to \"ALL\") condition # The [--condition] is a jq expression that can be used to determine if the Set Request is executed based on the Get Response values. update # The [--update] specifies a jq expression used to build the Set Request update path. replace # The [--replace] specifies a jq expression used to build the Set Request replace path. delete # The [--delete] specifies a jq expression used to build the Set Request delete path. value # The [--value] specifies a jq expression used to build the Set Request value. Examples # The command in the below example does the following: gets the list of interface indexes to interface name mapping, checks if the interface index (ifindex) 70 exists, if it does, the set request changes the interface state to enable using the interface name. gnmic getset -a \\ --get /interface/ifindex \\ --condition '.[] | .updates[].values[\"\"][\"srl_nokia-interfaces:interface\"][] | select(.ifindex==70) | (.name != \"\" or .name !=null)' \\ --update '.[] | .updates[].values[\"\"][\"srl_nokia-interfaces:interface\"][] | select(.ifindex==70) | \"interface[name=\" + .name + \"]/admin-state\"' \\ --value enable","title":"GetSet"},{"location":"cmd/getset/#description","text":"The getset command is a combination of the gNMI Get RPC and the gNMI Set RPC . It allows to conditionally execute a Set RPC based on a condition evaluated against a GetResponse . The condition written as a jq expression , is specified using the flag --condition . The SetRPC is executed only if the condition evaluates to true","title":"Description"},{"location":"cmd/getset/#usage","text":"gnmic [global-flags] getset [local-flags] gnmic [global-flags] gas [local-flags] gnmic [global-flags] gs [local-flags]","title":"Usage"},{"location":"cmd/getset/#flags","text":"","title":"Flags"},{"location":"cmd/getset/#prefix","text":"As per path prefixes , the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --get , --update , --replace and --delete flags. Defaults to \"\" .","title":"prefix"},{"location":"cmd/getset/#get","text":"The mandatory get flag [--get] is used to specify the single path used in the Get RPC.","title":"get"},{"location":"cmd/getset/#model","text":"The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported.","title":"model"},{"location":"cmd/getset/#target","text":"With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message.","title":"target"},{"location":"cmd/getset/#type","text":"The type flag [--type] is used to specify the data type requested from the server. One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to \"ALL\")","title":"type"},{"location":"cmd/getset/#condition","text":"The [--condition] is a jq expression that can be used to determine if the Set Request is executed based on the Get Response values.","title":"condition"},{"location":"cmd/getset/#update","text":"The [--update] specifies a jq expression used to build the Set Request update path.","title":"update"},{"location":"cmd/getset/#replace","text":"The [--replace] specifies a jq expression used to build the Set Request replace path.","title":"replace"},{"location":"cmd/getset/#delete","text":"The [--delete] specifies a jq expression used to build the Set Request delete path.","title":"delete"},{"location":"cmd/getset/#value","text":"The [--value] specifies a jq expression used to build the Set Request value.","title":"value"},{"location":"cmd/getset/#examples","text":"The command in the below example does the following: gets the list of interface indexes to interface name mapping, checks if the interface index (ifindex) 70 exists, if it does, the set request changes the interface state to enable using the interface name. gnmic getset -a \\ --get /interface/ifindex \\ --condition '.[] | .updates[].values[\"\"][\"srl_nokia-interfaces:interface\"][] | select(.ifindex==70) | (.name != \"\" or .name !=null)' \\ --update '.[] | .updates[].values[\"\"][\"srl_nokia-interfaces:interface\"][] | select(.ifindex==70) | \"interface[name=\" + .name + \"]/admin-state\"' \\ --value enable","title":"Examples"},{"location":"cmd/listen/","text":"Description # gnmic can be used in a \"dial-out telemetry\" mode by means of the listen command. In the dial-out mode: a network element is configured with the telemetry paths a network element initiates a connection towards the server/collector ( gnmic acts as a server in that case) Info Currently gnmic only implements the dial-out support for Nokia 1 SR OS 20.5.r1+ routers. Usage # gnmic listen [ global flags ] [ local flags ] Flags # address # The address flag [-a | --address] tells gnmic which address to bind an internal server to in an address:port format, e.g.: 0.0.0.0:57400 . tls-cert # Path to the TLS certificate can be supplied with --tls-cert flag. tls-key # Path to the private key can be supplied with --tls-key flag. max-concurrent-streams # To limit the maximum number of concurrent HTTP2 streams use the --max-concurrent-streams flag, the default value is 256. prometheus-address # The prometheus-address flag [--prometheus-address] allows starting a prometheus server that can be scraped by a prometheus client. It exposes metrics like memory, CPU and file descriptor usage. Examples # TLS disabled server # To start gnmic as a server listening on all interfaces without TLS support is as simple as: gnmic listen -a 0 .0.0.0:57400 SR OS configuration for non TLS dialout connections /configure system telemetry destination-group \"dialout\" allow-unsecure-connection /configure system telemetry destination-group \"dialout\" destination 10.2.0.99 port 57400 router-instance \"management\" /configure system telemetry persistent-subscriptions { } /configure system telemetry persistent-subscriptions subscription \"dialout\" admin-state enable /configure system telemetry persistent-subscriptions subscription \"dialout\" sensor-group \"port_stats\" /configure system telemetry persistent-subscriptions subscription \"dialout\" mode sample /configure system telemetry persistent-subscriptions subscription \"dialout\" sample-interval 1000 /configure system telemetry persistent-subscriptions subscription \"dialout\" destination-group \"dialout\" /configure system telemetry persistent-subscriptions subscription \"dialout\" encoding bytes /configure system telemetry sensor-groups { } /configure system telemetry sensor-groups { sensor-group \"port_stats\" } /configure system telemetry sensor-groups { sensor-group \"port_stats\" path \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" } TLS enabled server # By using tls-cert and tls-key flags it is possible to run gnmic with TLS. gnmic listen -a 0 .0.0.0:57400 --tls-cert gnmic.pem --tls-key gnmic-key.pem SR OS configuration for a TLS enabled dialout connections The configuration below does not utilise router-side certificates and uses the certificate provided by the server (gnmic). The router will also not verify the certificate. /configure system telemetry destination-group \"dialout\" tls-client-profile \"client-tls\" /configure system telemetry destination-group \"dialout\" destination 10.2.0.99 port 57400 router-instance \"management\" /configure system telemetry persistent-subscriptions { } /configure system telemetry persistent-subscriptions subscription \"dialout\" admin-state enable /configure system telemetry persistent-subscriptions subscription \"dialout\" sensor-group \"port_stats\" /configure system telemetry persistent-subscriptions subscription \"dialout\" mode sample /configure system telemetry persistent-subscriptions subscription \"dialout\" sample-interval 1000 /configure system telemetry persistent-subscriptions subscription \"dialout\" destination-group \"dialout\" /configure system telemetry persistent-subscriptions subscription \"dialout\" encoding bytes /configure system telemetry sensor-groups { } /configure system telemetry sensor-groups { sensor-group \"port_stats\" } /configure system telemetry sensor-groups { sensor-group \"port_stats\" path \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" } /configure system security tls client-cipher-list \"client-ciphers\" { } /configure system security tls client-cipher-list \"client-ciphers\" cipher 1 name tls-rsa-with-aes128-cbc-sha /configure system security tls client-cipher-list \"client-ciphers\" cipher 2 name tls-rsa-with-aes128-cbc-sha256 /configure system security tls client-cipher-list \"client-ciphers\" cipher 3 name tls-rsa-with-aes256-cbc-sha /configure system security tls client-cipher-list \"client-ciphers\" cipher 4 name tls-rsa-with-aes256-cbc-sha256 /configure system security tls client-tls-profile \"client-tls\" admin-state enable /configure system security tls client-tls-profile \"client-tls\" cipher-list \"client-ciphers\" Nokia dial-out proto definition can be found in karimra/sros-dialout \u21a9","title":"Listen"},{"location":"cmd/listen/#description","text":"gnmic can be used in a \"dial-out telemetry\" mode by means of the listen command. In the dial-out mode: a network element is configured with the telemetry paths a network element initiates a connection towards the server/collector ( gnmic acts as a server in that case) Info Currently gnmic only implements the dial-out support for Nokia 1 SR OS 20.5.r1+ routers.","title":"Description"},{"location":"cmd/listen/#usage","text":"gnmic listen [ global flags ] [ local flags ]","title":"Usage"},{"location":"cmd/listen/#flags","text":"","title":"Flags"},{"location":"cmd/listen/#address","text":"The address flag [-a | --address] tells gnmic which address to bind an internal server to in an address:port format, e.g.: 0.0.0.0:57400 .","title":"address"},{"location":"cmd/listen/#tls-cert","text":"Path to the TLS certificate can be supplied with --tls-cert flag.","title":"tls-cert"},{"location":"cmd/listen/#tls-key","text":"Path to the private key can be supplied with --tls-key flag.","title":"tls-key"},{"location":"cmd/listen/#max-concurrent-streams","text":"To limit the maximum number of concurrent HTTP2 streams use the --max-concurrent-streams flag, the default value is 256.","title":"max-concurrent-streams"},{"location":"cmd/listen/#prometheus-address","text":"The prometheus-address flag [--prometheus-address] allows starting a prometheus server that can be scraped by a prometheus client. It exposes metrics like memory, CPU and file descriptor usage.","title":"prometheus-address"},{"location":"cmd/listen/#examples","text":"","title":"Examples"},{"location":"cmd/listen/#tls-disabled-server","text":"To start gnmic as a server listening on all interfaces without TLS support is as simple as: gnmic listen -a 0 .0.0.0:57400 SR OS configuration for non TLS dialout connections /configure system telemetry destination-group \"dialout\" allow-unsecure-connection /configure system telemetry destination-group \"dialout\" destination 10.2.0.99 port 57400 router-instance \"management\" /configure system telemetry persistent-subscriptions { } /configure system telemetry persistent-subscriptions subscription \"dialout\" admin-state enable /configure system telemetry persistent-subscriptions subscription \"dialout\" sensor-group \"port_stats\" /configure system telemetry persistent-subscriptions subscription \"dialout\" mode sample /configure system telemetry persistent-subscriptions subscription \"dialout\" sample-interval 1000 /configure system telemetry persistent-subscriptions subscription \"dialout\" destination-group \"dialout\" /configure system telemetry persistent-subscriptions subscription \"dialout\" encoding bytes /configure system telemetry sensor-groups { } /configure system telemetry sensor-groups { sensor-group \"port_stats\" } /configure system telemetry sensor-groups { sensor-group \"port_stats\" path \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" }","title":"TLS disabled server"},{"location":"cmd/listen/#tls-enabled-server","text":"By using tls-cert and tls-key flags it is possible to run gnmic with TLS. gnmic listen -a 0 .0.0.0:57400 --tls-cert gnmic.pem --tls-key gnmic-key.pem SR OS configuration for a TLS enabled dialout connections The configuration below does not utilise router-side certificates and uses the certificate provided by the server (gnmic). The router will also not verify the certificate. /configure system telemetry destination-group \"dialout\" tls-client-profile \"client-tls\" /configure system telemetry destination-group \"dialout\" destination 10.2.0.99 port 57400 router-instance \"management\" /configure system telemetry persistent-subscriptions { } /configure system telemetry persistent-subscriptions subscription \"dialout\" admin-state enable /configure system telemetry persistent-subscriptions subscription \"dialout\" sensor-group \"port_stats\" /configure system telemetry persistent-subscriptions subscription \"dialout\" mode sample /configure system telemetry persistent-subscriptions subscription \"dialout\" sample-interval 1000 /configure system telemetry persistent-subscriptions subscription \"dialout\" destination-group \"dialout\" /configure system telemetry persistent-subscriptions subscription \"dialout\" encoding bytes /configure system telemetry sensor-groups { } /configure system telemetry sensor-groups { sensor-group \"port_stats\" } /configure system telemetry sensor-groups { sensor-group \"port_stats\" path \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" } /configure system security tls client-cipher-list \"client-ciphers\" { } /configure system security tls client-cipher-list \"client-ciphers\" cipher 1 name tls-rsa-with-aes128-cbc-sha /configure system security tls client-cipher-list \"client-ciphers\" cipher 2 name tls-rsa-with-aes128-cbc-sha256 /configure system security tls client-cipher-list \"client-ciphers\" cipher 3 name tls-rsa-with-aes256-cbc-sha /configure system security tls client-cipher-list \"client-ciphers\" cipher 4 name tls-rsa-with-aes256-cbc-sha256 /configure system security tls client-tls-profile \"client-tls\" admin-state enable /configure system security tls client-tls-profile \"client-tls\" cipher-list \"client-ciphers\" Nokia dial-out proto definition can be found in karimra/sros-dialout \u21a9","title":"TLS enabled server"},{"location":"cmd/path/","text":"Description # With path command it is possible to generate and search through the XPATH style paths extracted from a YANG file. By extracting the XPATH styled paths from a YANG model it is made possible to utilize CLI search tools like awk , sed and alike to find the paths satisfying specific matching rules. The embedded search capability allows to perform a quick and simple search through the model's paths using simple inclusion/exclusion operators. Flags # types # When --types flag is present the extracted paths will also have a corresponding type printed out. path-type # The --path-type flag governs which style is used to display the path information. The default value is xpath which will produce the XPATH compatible paths. The other option is gnmi which will result in the paths to be formatted using the gNMI Path Conventions. XPATH gNMI /state/sfm [ sfm-slot = * ] /hardware-data/firmware-revision-status elem: { name: \"state\" } elem: { name: \"sfm\" key: { key: \"sfm-slot\" value: \"*\" }} elem: { name: \"hardware-data\" } elem: { name: \"firmware-revision-status\" } search # With the --search flag present an interactive CLI search dialog is displayed that allows to navigate through the paths list and perform a search. \u276f gnmic path --file _test/nokia-state-combined.yang --search Use the arrow keys to navigate: \u2193 \u2191 \u2192 \u2190 and : toggles search ? select path: /state/aaa/radius/statistics/coa/dropped/bad-authentication /state/aaa/radius/statistics/coa/dropped/missing-auth-policy \u25b8 /state/aaa/radius/statistics/coa/dropped/invalid /state/aaa/radius/statistics/coa/dropped/missing-resource /state/aaa/radius/statistics/coa/received /state/aaa/radius/statistics/coa/accepted /state/aaa/radius/statistics/coa/rejected /state/aaa/radius/statistics/disconnect-messages/dropped/bad-authentication /state/aaa/radius/statistics/disconnect-messages/dropped/missing-auth-policy \u2193 /state/aaa/radius/statistics/disconnect-messages/dropped/invalid descr # When the --descr flag is present, the leaf description is printed after the path, indented with a \\t . config-only # When the --config-only flag is present, paths are generated only for YANG leaves representing config data. state-only # When the --state-only flag is present, paths are generated only for YANG leaves representing state data. with-non-leaves # When the --with-non-leaves flag is present, paths are generated not only for YANG leaves. Examples # # output to stdout the XPATH styled paths # from the nokia-state module of nokia-state-combined.yang file gnmic path --file nokia-state-combined.yang # from the nokia-conf module gnmic path -m nokia-conf --file nokia-conf-combined.yang # with the gNMI styled paths gnmic path --file nokia-state-combined.yang --path-type gnmi # with path types gnmic path --file nokia-state-combined.yang --types # entering the interactive navigation prompt gnmic path --file nokia-state-combined.yang --search Nokia combined models can be found in nokia/7x50_YangModels repo. \u21a9","title":"Path"},{"location":"cmd/path/#description","text":"With path command it is possible to generate and search through the XPATH style paths extracted from a YANG file. By extracting the XPATH styled paths from a YANG model it is made possible to utilize CLI search tools like awk , sed and alike to find the paths satisfying specific matching rules. The embedded search capability allows to perform a quick and simple search through the model's paths using simple inclusion/exclusion operators.","title":"Description"},{"location":"cmd/path/#flags","text":"","title":"Flags"},{"location":"cmd/path/#types","text":"When --types flag is present the extracted paths will also have a corresponding type printed out.","title":"types"},{"location":"cmd/path/#path-type","text":"The --path-type flag governs which style is used to display the path information. The default value is xpath which will produce the XPATH compatible paths. The other option is gnmi which will result in the paths to be formatted using the gNMI Path Conventions. XPATH gNMI /state/sfm [ sfm-slot = * ] /hardware-data/firmware-revision-status elem: { name: \"state\" } elem: { name: \"sfm\" key: { key: \"sfm-slot\" value: \"*\" }} elem: { name: \"hardware-data\" } elem: { name: \"firmware-revision-status\" }","title":"path-type"},{"location":"cmd/path/#search","text":"With the --search flag present an interactive CLI search dialog is displayed that allows to navigate through the paths list and perform a search. \u276f gnmic path --file _test/nokia-state-combined.yang --search Use the arrow keys to navigate: \u2193 \u2191 \u2192 \u2190 and : toggles search ? select path: /state/aaa/radius/statistics/coa/dropped/bad-authentication /state/aaa/radius/statistics/coa/dropped/missing-auth-policy \u25b8 /state/aaa/radius/statistics/coa/dropped/invalid /state/aaa/radius/statistics/coa/dropped/missing-resource /state/aaa/radius/statistics/coa/received /state/aaa/radius/statistics/coa/accepted /state/aaa/radius/statistics/coa/rejected /state/aaa/radius/statistics/disconnect-messages/dropped/bad-authentication /state/aaa/radius/statistics/disconnect-messages/dropped/missing-auth-policy \u2193 /state/aaa/radius/statistics/disconnect-messages/dropped/invalid","title":"search"},{"location":"cmd/path/#descr","text":"When the --descr flag is present, the leaf description is printed after the path, indented with a \\t .","title":"descr"},{"location":"cmd/path/#config-only","text":"When the --config-only flag is present, paths are generated only for YANG leaves representing config data.","title":"config-only"},{"location":"cmd/path/#state-only","text":"When the --state-only flag is present, paths are generated only for YANG leaves representing state data.","title":"state-only"},{"location":"cmd/path/#with-non-leaves","text":"When the --with-non-leaves flag is present, paths are generated not only for YANG leaves.","title":"with-non-leaves"},{"location":"cmd/path/#examples","text":"# output to stdout the XPATH styled paths # from the nokia-state module of nokia-state-combined.yang file gnmic path --file nokia-state-combined.yang # from the nokia-conf module gnmic path -m nokia-conf --file nokia-conf-combined.yang # with the gNMI styled paths gnmic path --file nokia-state-combined.yang --path-type gnmi # with path types gnmic path --file nokia-state-combined.yang --types # entering the interactive navigation prompt gnmic path --file nokia-state-combined.yang --search Nokia combined models can be found in nokia/7x50_YangModels repo. \u21a9","title":"Examples"},{"location":"cmd/prompt/","text":"Description # The prompt command starts gnmic in an interactive prompt mode with the following auto-completion features: All gnmic commands names and their flags are suggested . Values for the flags that rely on YANG-defined data (like --path , --prefix , --model ,...) will be dynamically suggested, we call this feature YANG-completions . The auto-completions are generated from the YANG modules d with the --file and --dir flags. Flags with the fixed set of values ( --format , --encoding , ...) will get their values suggested . Flags that require a file path value will auto-suggest the available files as the user types. Usage # gnmic [global-flags] prompt [local-flags] Flags # description-with-prefix # When set, the description of the path elements in the suggestion box will contain module's prefix. description-with-types # When set, the description of the path elements in the suggestion box will contain element's type information. max-suggestions # The --max-suggestions flag sets the number of lines that the suggestion box will display without scrolling. Defaults to 10. Note, the terminal height might limit the number of lines in the suggestions box. suggest-all-flags # The --suggest-all-flags makes gnmic prompt suggest both global and local flags for a sub-command. The default behavior (when this flag is not set) is to suggest only local flags for any typed sub-command. suggest-with-origin # The --suggest-with-origin flag prepends the suggested path with the module name to which this path belongs. The path becomes rendered as :/ . The module name will be used as the origin of the gNMI path. suggestions-bg-color # The --suggestions-bg-color flag sets the background color of the left part of the suggestion box. Defaults to dark blue. description-bg-color # The --description-bg-color flag sets the background color of the right part of the suggestion box. Defaults to dark gray. prefix-color # The --prefix-color flag sets the gnmic prompt prefix color gnmic> . Defaults to dark blue. Examples # The detailed explanation of the prompt command the the YANG-completions is provided on the Prompt mode and auto-suggestions page.","title":"Prompt"},{"location":"cmd/prompt/#description","text":"The prompt command starts gnmic in an interactive prompt mode with the following auto-completion features: All gnmic commands names and their flags are suggested . Values for the flags that rely on YANG-defined data (like --path , --prefix , --model ,...) will be dynamically suggested, we call this feature YANG-completions . The auto-completions are generated from the YANG modules d with the --file and --dir flags. Flags with the fixed set of values ( --format , --encoding , ...) will get their values suggested . Flags that require a file path value will auto-suggest the available files as the user types.","title":"Description"},{"location":"cmd/prompt/#usage","text":"gnmic [global-flags] prompt [local-flags]","title":"Usage"},{"location":"cmd/prompt/#flags","text":"","title":"Flags"},{"location":"cmd/prompt/#description-with-prefix","text":"When set, the description of the path elements in the suggestion box will contain module's prefix.","title":"description-with-prefix"},{"location":"cmd/prompt/#description-with-types","text":"When set, the description of the path elements in the suggestion box will contain element's type information.","title":"description-with-types"},{"location":"cmd/prompt/#max-suggestions","text":"The --max-suggestions flag sets the number of lines that the suggestion box will display without scrolling. Defaults to 10. Note, the terminal height might limit the number of lines in the suggestions box.","title":"max-suggestions"},{"location":"cmd/prompt/#suggest-all-flags","text":"The --suggest-all-flags makes gnmic prompt suggest both global and local flags for a sub-command. The default behavior (when this flag is not set) is to suggest only local flags for any typed sub-command.","title":"suggest-all-flags"},{"location":"cmd/prompt/#suggest-with-origin","text":"The --suggest-with-origin flag prepends the suggested path with the module name to which this path belongs. The path becomes rendered as :/ . The module name will be used as the origin of the gNMI path.","title":"suggest-with-origin"},{"location":"cmd/prompt/#suggestions-bg-color","text":"The --suggestions-bg-color flag sets the background color of the left part of the suggestion box. Defaults to dark blue.","title":"suggestions-bg-color"},{"location":"cmd/prompt/#description-bg-color","text":"The --description-bg-color flag sets the background color of the right part of the suggestion box. Defaults to dark gray.","title":"description-bg-color"},{"location":"cmd/prompt/#prefix-color","text":"The --prefix-color flag sets the gnmic prompt prefix color gnmic> . Defaults to dark blue.","title":"prefix-color"},{"location":"cmd/prompt/#examples","text":"The detailed explanation of the prompt command the the YANG-completions is provided on the Prompt mode and auto-suggestions page.","title":"Examples"},{"location":"cmd/set/","text":"Description # The set command represents the gNMI Set RPC . It is used to send a Set Request to the specified target(s) and expects one Set Response per target. Set RPC allows the client to modify the state of data on the target. The data specified referenced by a path can be updated, replaced or deleted . Note It is possible to combine update , replace and delete in a single gnmic set command. Usage # gnmic [global-flags] set [local-flags] The Set Request can be any of (or a combination of) update, replace or/and delete operations. Flags # prefix # The --prefix flag sets a common prefix to all paths specified using the local --path flag. Defaults to \"\" . If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" : Note The path after the origin value has to start with a / gnmic set --update \"openconfig-interfaces:/interfaces/interface::::::\" target # With the optional [--target] flag it is possible to supply the path target information in the prefix field of a SetRequest message. dry-run # The --dry-run flag allow to run a Set request without sending it to the targets. This is useful while developing templated Set requests. delete # The --delete flag allows creating a SetRequest.Delete as part of teh SetRequest message. replace # The --replace flag allows creating a SetRequest.Replace as part of a SetRequest message. It is expected to be in the format $path:::$type:::$value , where $path is the gNMI path of the object to replace, $type is the type of the value and $value is the replacement value. update # The --update flag allows creating a SetRequest.Update as part of a SetRequest message. It is expected to be in the format $path:::$type:::$value , where $path is the gNMI path of the object to update, $type is the type of the value and $value is the update value. replace-path and replace-value # The --replace-path and --replace-value flags are equivalent to the --replace flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag. update-path and update-value # The --update-path and --update-value flags are equivalent to the --update flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag. replace-path and replace-file # The --replace-path and --replace-file flags are equivalent to the --replace flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag. update-path and update-file # The --update-path and --update-file flags are equivalent to the --update flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag. replace-cli # The --replace-cli flag allows setting a SetRequest.Replace as part of a SetRequest message. It expects a single CLI command which will form the value path of the Replace, the path will be set to the CLI origin cli . replace-cli-file # The --replace-cli flag allows setting a SetRequest.Replace as part of a SetRequest message. It expects a file containing one or multiple CLI commands which will form the value path of the Replace, the path will be set to the CLI origin cli . update-cli # The --update-cli flag allows setting a SetRequest.Update as part of a SetRequest message. It expects a single CLI command which will form the value path of the Replace, the path will be set to the CLI origin cli . update-cli-file # The --update-cli flag allows setting a SetRequest.Update as part of a SetRequest message. It expects a file containing one or multiple CLI commands which will form the value path of the Replace, the path will be set to the CLI origin cli . request-file and request-vars # See this section below. Update Request # There are several ways to perform an update operation with gNMI Set RPC: 1. in-line update, implicit type # Using both --update-path and --update-value flags, a user can update a value for a given path. gnmic set --update-path /configure/system/name --update-value router1 gnmic set --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --update-value enable The above 2 updates can be combined in the same CLI command: gnmic set --update-path /configure/system/name \\ --update-value router1 \\ --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --update-value enable 2. in-line update, explicit type # Using the update flag --update , one can specify the path, value type and value in a single parameter using a delimiter --delimiter . Delimiter string defaults to \":::\" . Supported types: json, json_ietf, string, int, uint, bool, decimal, float, bytes, ascii. # path:::value-type:::value gnmic set --update /configure/system/name:::json:::router1 gnmic set --update /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state:::json:::enable gnmic set --update /configure/router [ router-name = Base ] /interface [ interface-name = system ] :::json::: '{\"admin-state\":\"enable\"}' 3. update with a value from JSON or YAML file # It is also possible to specify the values from a local JSON or YAML file using --update-file flag for the value and --update-path for the path. In which case the value encoding will be determined by the global flag [ -e | --encoding ] , both JSON and JSON_IETF are supported The file's format is identified by its extension, json: .json and yaml .yaml or .yml . interface.json interface.yml { \"admin-state\" : \"enable\" , \"ipv4\" : { \"primary\" : { \"address\" : \"1.1.1.1\" , \"prefix-length\" : 32 } } } gnmic set --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] \\ --update-file interface.json \"admin-state\" : enable \"ipv4\" : \"primary\" : \"address\" : 1 .1.1.1 \"prefix-length\" : 32 gnmic set --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] \\ --update-file interface.yml Replace Request # There are 3 main ways to specify a replace operation: 1. in-line replace, implicit type # Using both --replace-path and --replace-value flags, a user can replace a value for a given path. The type of the value is implicitly set to JSON : gnmic set --replace-path /configure/system/name --replace-value router1 gnmic set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --replace-value enable The above 2 commands can be packed in the same CLI command: gnmic set --replace-path /configure/system/name \\ --replace-value router1 \\ --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --replace-value enable 2. in-line replace, explicit type # Using the replace flag --replace , you can specify the path, value type and value in a single parameter using a delimiter --delimiter . Delimiter string defaults to \":::\" . Supported types: json, json_ietf, string, int, uint, bool, decimal, float, bytes, ascii. gnmic set --replace /configure/system/name:::json:::router1 gnmic set --replace /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state:::json:::enable 3. replace with a value from JSON or YAML file # It is also possible to specify the values from a local JSON or YAML file using flag --replace-file for the value and --replace-path for the path. In which case the value encoding will be determined by the global flag [ -e | --encoding ] , both JSON and JSON_IETF are supported The file is identified by its extension, json: .json and yaml .yaml or .yml . interface.json interface.yml { \"admin-state\" : \"enable\" , \"ipv4\" : { \"primary\" : { \"address\" : \"1.1.1.1\" , \"prefix-length\" : 32 } } } \"admin-state\" : enable \"ipv4\" : \"primary\" : \"address\" : 1 .1.1.1 \"prefix-length\" : 32 Then refer to the file with --replace-file flag gnmic set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] \\ --replace-file interface.json Delete Request # A deletion operation within the Set RPC is specified using the delete flag --delete . It takes an XPATH pointing to the config node to be deleted: gnmic set --delete \"/configure/router[router-name=Base]/interface[interface-name=dummy_interface]\" Templated Set Request file # A Set Request can also be built based on one or multiple templates and (optionally) a set of variables. The variables allow to generate a Set Request file on per target basis. If no variable file is found, the execution continues and the template is assumed to be a static string. Each template specified with the flag --request-file is rendered against the variables defined in the file set with --request-vars . Each template results in a single gNMI Set Request. gnmic set --request-file --request-file --request-vars Template Format # The rendered template data can be a JSON or YAML valid string. It has 3 sections, updates , replaces and deletes . In each of the updates and replaces , a path , a value and an encoding can be configured. If not specified, path defaults to / , while encoding defaults to the value set with --encoding flag. updates and replaces result in a set of gNMI Set Updates in the Set RPC, deletes result in a set of gNMI paths to be deleted. The value can be any arbitrary data format that the target accepts, it will be encoded based on the value of \"encoding\". JSON YAML { \"updates\" : [ { \"path\" : \"/interface[name=ethernet-1/1]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine1\" }, \"encoding\" : \"json_ietf\" }, { \"path\" : \"/interface[name=ethernet-1/2]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine2\" }, \"encoding\" : \"json_ietf\" } ], \"replaces\" : [ { \"path\" : \"/interface[name=ethernet-1/3]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine3\" } }, { \"path\" : \"/interface[name=ethernet-1/4]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine4\" } } ], \"deletes\" : [ \"/interface[name=ethernet-1/5]\" , \"/interface[name=ethernet-1/6]\" ] } updates : - path : \"/interface[name=ethernet-1/1]\" value : admin-state : enable description : \"to_spine1\" encoding : \"json_ietf\" - path : \"/interface[name=ethernet-1/2]\" value : admin-state : enable description : \"to_spine2\" encoding : \"json_ietf\" replaces : - path : \"/interface[name=ethernet-1/3]\" value : admin-state : enable description : \"to_spine3\" - path : \"/interface[name=ethernet-1/4]\" value : admin-state : enable description : \"to_spine4\" deletes : - \"/interface[name=ethernet-1/5]\" - \"/interface[name=ethernet-1/6]\" Per Target Template Variables # The file --request-file can be written as a Go Text template . The parsed template is loaded with additional functions from gomplate . gnmic generates one gNMI Set request per target. The template will be rendered using variables read from the file --request-vars . Just like the template file, the variables file can either be a JSON or YAML formatted file. If the flag --request-vars is not set, gnmic looks for a file with the same path, name and extension as the request-file , appended with _vars . Within the template, the variables defined in the --request-vars file are accessible using the .Vars notation, while the target name is accessible using the .TargetName notation. Example request template: replaces : {{ $target : = index .Vars .TargetName }} {{ - range $interface : = index $target \"interfaces\" }} - path : \"/interface[name={{ index $interface \" name\" }}]\" encoding : \"json_ietf\" value : admin-state : {{ index $interface \"admin-state\" | default \"disable\" }} description : {{ index $interface \"description\" | default \"\" }} {{ - range $index , $subinterface : = index $interface \"subinterfaces\" }} subinterface : - index : {{ $index }} admin-state : {{ index $subinterface \"admin-state\" | default \"disable\" }} {{ - if has $subinterface \"ipv4-address\" }} ipv4 : address : - ip-prefix : {{ index $subinterface \"ipv4-address\" | toString }} {{ - end }} {{ - if has $subinterface \"ipv6-address\" }} ipv6 : address : - ip-prefix : {{ index $subinterface \"ipv6-address\" | toString }} {{ - end }} {{ - end }} {{ - end }} The below variables file defines the input for 3 leafs: leaf1:57400 : interfaces : - name : ethernet-1/1 admin-state : \"enable\" description : \"leaf1_to_spine1\" subinterfaces : - admin-state : enable ipv4-address : 192.168.78.1/30 - name : ethernet-1/2 admin-state : \"enable\" description : \"leaf1_to_spine2\" subinterfaces : - admin-state : enable ipv4-address : 192.168.79.1/30 leaf2:57400 : interfaces : - name : ethernet-1/1 admin-state : \"enable\" description : \"leaf2_to_spine1\" subinterfaces : - admin-state : enable ipv4-address : 192.168.88.1/30 - name : ethernet-1/2 admin-state : \"enable\" description : \"leaf2_to_spine2\" subinterfaces : - admin-state : enable ipv4-address : 192.168.89.1/30 leaf3:57400 : interfaces : - name : ethernet-1/1 admin-state : \"enable\" description : \"leaf3_to_spine1\" subinterfaces : - admin-state : enable ipv4-address : 192.168.98.1/30 - name : ethernet-1/2 admin-state : \"enable\" description : \"leaf3_to_spine2\" subinterfaces : - admin-state : enable ipv4-address : 192.168.99.1/30 Result Request file per target: leaf1 leaf2 leaf3 updates : - path : /interface[name=ethernet-1/1] encoding : \"json_ietf\" value : admin-state : enable description : leaf1_to_spine1 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.78.1/30 - path : /interface[name=ethernet-1/2] encoding : \"json_ietf\" value : admin-state : enable description : leaf1_to_spine2 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.79.1/30 updates : - path : /interface[name=ethernet-1/1] encoding : \"json_ietf\" value : admin-state : enable description : leaf2_to_spine1 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.88.1/30 - path : /interface[name=ethernet-1/2] encoding : \"json_ietf\" value : admin-state : enable description : leaf2_to_spine2 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.89.1/30 updates : - path : /interface[name=ethernet-1/1] encoding : \"json_ietf\" value : admin-state : enable description : leaf3_to_spine1 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.98.1/30 - path : /interface[name=ethernet-1/2] encoding : \"json_ietf\" value : admin-state : enable description : leaf3_to_spine2 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.99.1/30 Examples # 1. update # in-line value # gnmic -a set --update-path /configure/system/name \\ --update-value value from JSON file # cat jsonFile.json { \"name\" : \"router1\" } gnmic -a set --update-path /configure/system \\ --update-file echo '{\"name\": \"router1\"}' | gnmic -a set \\ --update-path /configure/system \\ --update-file - specify value type # gnmic -a set --update /configure/system/name:::json:::router1 gnmic -a set --update /configure/system/name@json@router1 \\ --delimiter @ 2. replace # cat interface.json { \"address\" : \"1.1.1.1\" , \"prefix-length\" : 32 } gnmic -a --insecure \\ set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = interface1 ] /ipv4/primary \\ --replace-file interface.json echo '{\"address\": \"1.1.1.1\", \"prefix-length\": 32}' | gnmic -a --insecure \\ set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = interface1 ] /ipv4/primary \\ --replace-file - 3. delete # gnmic -a --insecure set --delete /configure/router [ router-name = Base ] /interface [ interface-name = interface1 ]","title":"Set"},{"location":"cmd/set/#description","text":"The set command represents the gNMI Set RPC . It is used to send a Set Request to the specified target(s) and expects one Set Response per target. Set RPC allows the client to modify the state of data on the target. The data specified referenced by a path can be updated, replaced or deleted . Note It is possible to combine update , replace and delete in a single gnmic set command.","title":"Description"},{"location":"cmd/set/#usage","text":"gnmic [global-flags] set [local-flags] The Set Request can be any of (or a combination of) update, replace or/and delete operations.","title":"Usage"},{"location":"cmd/set/#flags","text":"","title":"Flags"},{"location":"cmd/set/#prefix","text":"The --prefix flag sets a common prefix to all paths specified using the local --path flag. Defaults to \"\" . If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" : Note The path after the origin value has to start with a / gnmic set --update \"openconfig-interfaces:/interfaces/interface::::::\"","title":"prefix"},{"location":"cmd/set/#target","text":"With the optional [--target] flag it is possible to supply the path target information in the prefix field of a SetRequest message.","title":"target"},{"location":"cmd/set/#dry-run","text":"The --dry-run flag allow to run a Set request without sending it to the targets. This is useful while developing templated Set requests.","title":"dry-run"},{"location":"cmd/set/#delete","text":"The --delete flag allows creating a SetRequest.Delete as part of teh SetRequest message.","title":"delete"},{"location":"cmd/set/#replace","text":"The --replace flag allows creating a SetRequest.Replace as part of a SetRequest message. It is expected to be in the format $path:::$type:::$value , where $path is the gNMI path of the object to replace, $type is the type of the value and $value is the replacement value.","title":"replace"},{"location":"cmd/set/#update","text":"The --update flag allows creating a SetRequest.Update as part of a SetRequest message. It is expected to be in the format $path:::$type:::$value , where $path is the gNMI path of the object to update, $type is the type of the value and $value is the update value.","title":"update"},{"location":"cmd/set/#replace-path-and-replace-value","text":"The --replace-path and --replace-value flags are equivalent to the --replace flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.","title":"replace-path and replace-value"},{"location":"cmd/set/#update-path-and-update-value","text":"The --update-path and --update-value flags are equivalent to the --update flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.","title":"update-path and update-value"},{"location":"cmd/set/#replace-path-and-replace-file","text":"The --replace-path and --replace-file flags are equivalent to the --replace flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.","title":"replace-path and replace-file"},{"location":"cmd/set/#update-path-and-update-file","text":"The --update-path and --update-file flags are equivalent to the --update flag, where the path and value are split and the type is deduced from the [-e | --encoding] global flag.","title":"update-path and update-file"},{"location":"cmd/set/#replace-cli","text":"The --replace-cli flag allows setting a SetRequest.Replace as part of a SetRequest message. It expects a single CLI command which will form the value path of the Replace, the path will be set to the CLI origin cli .","title":"replace-cli"},{"location":"cmd/set/#replace-cli-file","text":"The --replace-cli flag allows setting a SetRequest.Replace as part of a SetRequest message. It expects a file containing one or multiple CLI commands which will form the value path of the Replace, the path will be set to the CLI origin cli .","title":"replace-cli-file"},{"location":"cmd/set/#update-cli","text":"The --update-cli flag allows setting a SetRequest.Update as part of a SetRequest message. It expects a single CLI command which will form the value path of the Replace, the path will be set to the CLI origin cli .","title":"update-cli"},{"location":"cmd/set/#update-cli-file","text":"The --update-cli flag allows setting a SetRequest.Update as part of a SetRequest message. It expects a file containing one or multiple CLI commands which will form the value path of the Replace, the path will be set to the CLI origin cli .","title":"update-cli-file"},{"location":"cmd/set/#request-file-and-request-vars","text":"See this section below.","title":"request-file and request-vars"},{"location":"cmd/set/#update-request","text":"There are several ways to perform an update operation with gNMI Set RPC:","title":"Update Request"},{"location":"cmd/set/#1-in-line-update-implicit-type","text":"Using both --update-path and --update-value flags, a user can update a value for a given path. gnmic set --update-path /configure/system/name --update-value router1 gnmic set --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --update-value enable The above 2 updates can be combined in the same CLI command: gnmic set --update-path /configure/system/name \\ --update-value router1 \\ --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --update-value enable","title":"1. in-line update, implicit type"},{"location":"cmd/set/#2-in-line-update-explicit-type","text":"Using the update flag --update , one can specify the path, value type and value in a single parameter using a delimiter --delimiter . Delimiter string defaults to \":::\" . Supported types: json, json_ietf, string, int, uint, bool, decimal, float, bytes, ascii. # path:::value-type:::value gnmic set --update /configure/system/name:::json:::router1 gnmic set --update /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state:::json:::enable gnmic set --update /configure/router [ router-name = Base ] /interface [ interface-name = system ] :::json::: '{\"admin-state\":\"enable\"}'","title":"2. in-line update, explicit type"},{"location":"cmd/set/#3-update-with-a-value-from-json-or-yaml-file","text":"It is also possible to specify the values from a local JSON or YAML file using --update-file flag for the value and --update-path for the path. In which case the value encoding will be determined by the global flag [ -e | --encoding ] , both JSON and JSON_IETF are supported The file's format is identified by its extension, json: .json and yaml .yaml or .yml . interface.json interface.yml { \"admin-state\" : \"enable\" , \"ipv4\" : { \"primary\" : { \"address\" : \"1.1.1.1\" , \"prefix-length\" : 32 } } } gnmic set --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] \\ --update-file interface.json \"admin-state\" : enable \"ipv4\" : \"primary\" : \"address\" : 1 .1.1.1 \"prefix-length\" : 32 gnmic set --update-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] \\ --update-file interface.yml","title":"3. update with a value from JSON or YAML file"},{"location":"cmd/set/#replace-request","text":"There are 3 main ways to specify a replace operation:","title":"Replace Request"},{"location":"cmd/set/#1-in-line-replace-implicit-type","text":"Using both --replace-path and --replace-value flags, a user can replace a value for a given path. The type of the value is implicitly set to JSON : gnmic set --replace-path /configure/system/name --replace-value router1 gnmic set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --replace-value enable The above 2 commands can be packed in the same CLI command: gnmic set --replace-path /configure/system/name \\ --replace-value router1 \\ --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state \\ --replace-value enable","title":"1. in-line replace, implicit type"},{"location":"cmd/set/#2-in-line-replace-explicit-type","text":"Using the replace flag --replace , you can specify the path, value type and value in a single parameter using a delimiter --delimiter . Delimiter string defaults to \":::\" . Supported types: json, json_ietf, string, int, uint, bool, decimal, float, bytes, ascii. gnmic set --replace /configure/system/name:::json:::router1 gnmic set --replace /configure/router [ router-name = Base ] /interface [ interface-name = system ] /admin-state:::json:::enable","title":"2. in-line replace, explicit type"},{"location":"cmd/set/#3-replace-with-a-value-from-json-or-yaml-file","text":"It is also possible to specify the values from a local JSON or YAML file using flag --replace-file for the value and --replace-path for the path. In which case the value encoding will be determined by the global flag [ -e | --encoding ] , both JSON and JSON_IETF are supported The file is identified by its extension, json: .json and yaml .yaml or .yml . interface.json interface.yml { \"admin-state\" : \"enable\" , \"ipv4\" : { \"primary\" : { \"address\" : \"1.1.1.1\" , \"prefix-length\" : 32 } } } \"admin-state\" : enable \"ipv4\" : \"primary\" : \"address\" : 1 .1.1.1 \"prefix-length\" : 32 Then refer to the file with --replace-file flag gnmic set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = system ] \\ --replace-file interface.json","title":"3. replace with a value from JSON or YAML file"},{"location":"cmd/set/#delete-request","text":"A deletion operation within the Set RPC is specified using the delete flag --delete . It takes an XPATH pointing to the config node to be deleted: gnmic set --delete \"/configure/router[router-name=Base]/interface[interface-name=dummy_interface]\"","title":"Delete Request"},{"location":"cmd/set/#templated-set-request-file","text":"A Set Request can also be built based on one or multiple templates and (optionally) a set of variables. The variables allow to generate a Set Request file on per target basis. If no variable file is found, the execution continues and the template is assumed to be a static string. Each template specified with the flag --request-file is rendered against the variables defined in the file set with --request-vars . Each template results in a single gNMI Set Request. gnmic set --request-file --request-file --request-vars ","title":"Templated Set Request file"},{"location":"cmd/set/#template-format","text":"The rendered template data can be a JSON or YAML valid string. It has 3 sections, updates , replaces and deletes . In each of the updates and replaces , a path , a value and an encoding can be configured. If not specified, path defaults to / , while encoding defaults to the value set with --encoding flag. updates and replaces result in a set of gNMI Set Updates in the Set RPC, deletes result in a set of gNMI paths to be deleted. The value can be any arbitrary data format that the target accepts, it will be encoded based on the value of \"encoding\". JSON YAML { \"updates\" : [ { \"path\" : \"/interface[name=ethernet-1/1]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine1\" }, \"encoding\" : \"json_ietf\" }, { \"path\" : \"/interface[name=ethernet-1/2]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine2\" }, \"encoding\" : \"json_ietf\" } ], \"replaces\" : [ { \"path\" : \"/interface[name=ethernet-1/3]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine3\" } }, { \"path\" : \"/interface[name=ethernet-1/4]\" , \"value\" : { \"admin-state\" : \"enable\" , \"description\" : \"to_spine4\" } } ], \"deletes\" : [ \"/interface[name=ethernet-1/5]\" , \"/interface[name=ethernet-1/6]\" ] } updates : - path : \"/interface[name=ethernet-1/1]\" value : admin-state : enable description : \"to_spine1\" encoding : \"json_ietf\" - path : \"/interface[name=ethernet-1/2]\" value : admin-state : enable description : \"to_spine2\" encoding : \"json_ietf\" replaces : - path : \"/interface[name=ethernet-1/3]\" value : admin-state : enable description : \"to_spine3\" - path : \"/interface[name=ethernet-1/4]\" value : admin-state : enable description : \"to_spine4\" deletes : - \"/interface[name=ethernet-1/5]\" - \"/interface[name=ethernet-1/6]\"","title":"Template Format"},{"location":"cmd/set/#per-target-template-variables","text":"The file --request-file can be written as a Go Text template . The parsed template is loaded with additional functions from gomplate . gnmic generates one gNMI Set request per target. The template will be rendered using variables read from the file --request-vars . Just like the template file, the variables file can either be a JSON or YAML formatted file. If the flag --request-vars is not set, gnmic looks for a file with the same path, name and extension as the request-file , appended with _vars . Within the template, the variables defined in the --request-vars file are accessible using the .Vars notation, while the target name is accessible using the .TargetName notation. Example request template: replaces : {{ $target : = index .Vars .TargetName }} {{ - range $interface : = index $target \"interfaces\" }} - path : \"/interface[name={{ index $interface \" name\" }}]\" encoding : \"json_ietf\" value : admin-state : {{ index $interface \"admin-state\" | default \"disable\" }} description : {{ index $interface \"description\" | default \"\" }} {{ - range $index , $subinterface : = index $interface \"subinterfaces\" }} subinterface : - index : {{ $index }} admin-state : {{ index $subinterface \"admin-state\" | default \"disable\" }} {{ - if has $subinterface \"ipv4-address\" }} ipv4 : address : - ip-prefix : {{ index $subinterface \"ipv4-address\" | toString }} {{ - end }} {{ - if has $subinterface \"ipv6-address\" }} ipv6 : address : - ip-prefix : {{ index $subinterface \"ipv6-address\" | toString }} {{ - end }} {{ - end }} {{ - end }} The below variables file defines the input for 3 leafs: leaf1:57400 : interfaces : - name : ethernet-1/1 admin-state : \"enable\" description : \"leaf1_to_spine1\" subinterfaces : - admin-state : enable ipv4-address : 192.168.78.1/30 - name : ethernet-1/2 admin-state : \"enable\" description : \"leaf1_to_spine2\" subinterfaces : - admin-state : enable ipv4-address : 192.168.79.1/30 leaf2:57400 : interfaces : - name : ethernet-1/1 admin-state : \"enable\" description : \"leaf2_to_spine1\" subinterfaces : - admin-state : enable ipv4-address : 192.168.88.1/30 - name : ethernet-1/2 admin-state : \"enable\" description : \"leaf2_to_spine2\" subinterfaces : - admin-state : enable ipv4-address : 192.168.89.1/30 leaf3:57400 : interfaces : - name : ethernet-1/1 admin-state : \"enable\" description : \"leaf3_to_spine1\" subinterfaces : - admin-state : enable ipv4-address : 192.168.98.1/30 - name : ethernet-1/2 admin-state : \"enable\" description : \"leaf3_to_spine2\" subinterfaces : - admin-state : enable ipv4-address : 192.168.99.1/30 Result Request file per target: leaf1 leaf2 leaf3 updates : - path : /interface[name=ethernet-1/1] encoding : \"json_ietf\" value : admin-state : enable description : leaf1_to_spine1 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.78.1/30 - path : /interface[name=ethernet-1/2] encoding : \"json_ietf\" value : admin-state : enable description : leaf1_to_spine2 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.79.1/30 updates : - path : /interface[name=ethernet-1/1] encoding : \"json_ietf\" value : admin-state : enable description : leaf2_to_spine1 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.88.1/30 - path : /interface[name=ethernet-1/2] encoding : \"json_ietf\" value : admin-state : enable description : leaf2_to_spine2 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.89.1/30 updates : - path : /interface[name=ethernet-1/1] encoding : \"json_ietf\" value : admin-state : enable description : leaf3_to_spine1 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.98.1/30 - path : /interface[name=ethernet-1/2] encoding : \"json_ietf\" value : admin-state : enable description : leaf3_to_spine2 subinterface : - index : 0 admin-state : enable ipv4 : address : - ip-prefix : 192.168.99.1/30","title":"Per Target Template Variables"},{"location":"cmd/set/#examples","text":"","title":"Examples"},{"location":"cmd/set/#1-update","text":"","title":"1. update"},{"location":"cmd/set/#in-line-value","text":"gnmic -a set --update-path /configure/system/name \\ --update-value ","title":"in-line value"},{"location":"cmd/set/#value-from-json-file","text":"cat jsonFile.json { \"name\" : \"router1\" } gnmic -a set --update-path /configure/system \\ --update-file echo '{\"name\": \"router1\"}' | gnmic -a set \\ --update-path /configure/system \\ --update-file -","title":"value from JSON file"},{"location":"cmd/set/#specify-value-type","text":"gnmic -a set --update /configure/system/name:::json:::router1 gnmic -a set --update /configure/system/name@json@router1 \\ --delimiter @","title":"specify value type"},{"location":"cmd/set/#2-replace","text":"cat interface.json { \"address\" : \"1.1.1.1\" , \"prefix-length\" : 32 } gnmic -a --insecure \\ set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = interface1 ] /ipv4/primary \\ --replace-file interface.json echo '{\"address\": \"1.1.1.1\", \"prefix-length\": 32}' | gnmic -a --insecure \\ set --replace-path /configure/router [ router-name = Base ] /interface [ interface-name = interface1 ] /ipv4/primary \\ --replace-file -","title":"2. replace"},{"location":"cmd/set/#3-delete","text":"gnmic -a --insecure set --delete /configure/router [ router-name = Base ] /interface [ interface-name = interface1 ]","title":"3. delete"},{"location":"cmd/subscribe/","text":"Description # The [subscribe | sub] command represents the gNMI Subscribe RPC . It is used to send a Subscribe Request to the specified target(s) and expects one or multiple Subscribe Response Usage # gnmic [global-flags] subscribe [local-flags] Local Flags # The subscribe command supports the following local flags: prefix # The [--prefix] flag sets a common prefix to all paths specified using the local --path flag. Defaults to \"\" . path # The path flag [--path] is used to specify the path(s) to which the client wants to subscribe. Multiple paths can be specified by using repeated --path flags: gnmic sub --path \"/state/ports[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" : Note The path after the origin value has to start with a / gnmic sub --path \"openconfig-interfaces:/interfaces/interface\" target # With the optional [--target] flag it is possible to supply the path target information in the prefix field of the SubscriptionList message. set-target # The [--set-target] flag is used to set the SubscribeRequest Prefix target value to the configured target name stripped of the port number. model # The [--model] flag is used to specify the schema definition modules that the target should use when extracting the data to stream back. qos # The [--qos] flag specifies the packet marking that is to be used for the responses to the subscription request. Default marking is set to 20 . If qos marking is not supported by a target the marking can be disabled by setting the value to 0 . mode # The [--mode] mode flag specifies the mode of subscription to be created. This may be one of: ONCE , STREAM or POLL . It is case insensitive and defaults to STREAM . stream subscription mode # The [--stream-mode] flag is used to specify the stream subscription mode. This may be one of: ON_CHANGE, SAMPLE or TARGET_DEFINED This flag applies only if --mode is set to STREAM . It is case insensitive and defaults to SAMPLE . sample interval # The [--sample-interval] flag is used to specify the sample interval to be used by the target to send samples to the client. This flag applies only in case --mode is set to STREAM and --stream-mode is set to SAMPLE . Valid formats: 1s, 1m30s, 1h . Defaults to 0s which is the lowest interval supported by a target. heartbeat interval # The [--heartbeat-interval] flag is used to specify the server heartbeat interval. The heartbeat interval value can be specified along with ON_CHANGE or SAMPLE stream subscriptions modes. ON_CHANGE : The value of the data item(s) MUST be re-sent once per heartbeat interval regardless of whether the value has changed or not. SAMPLE : The target MUST generate one telemetry update per heartbeat interval, regardless of whether the --suppress-redundant flag is set to true. quiet # With [--quiet] flag set gnmic will not output subscription responses to stdout . The --quiet flag is useful when gnmic exports the received data to one of the export providers. suppress redundant # When the [--suppress-redundant] flag is set to true, the target SHOULD NOT generate a telemetry update message unless the value of the path being reported on has changed since the last update was generated. This flag applies only in case --mode is set to STREAM and --stream-mode is set to SAMPLE . updates only # When the [--updates-only] flag is set to true, the target MUST not transmit the current state of the paths that the client has subscribed to, but rather should send only updates to them. name # The [--name] flag is used to trigger one or multiple subscriptions already defined in the configuration file see defining subscriptions output # The [--output] flag is used to select one or multiple output already defined in the configuration file. Outputs defined under target take precedence over this flag, see defining outputs and defining targets watch-config # The [--watch-config] flag is used to enable automatic target loading from the configuration source at runtime. On each configuration change, gnmic reloads the list of targets, subscribes to new targets and/or deletes subscriptions to the deleted ones. Only addition and deletion of targets are currently supported, changes in an existing target config are not possible. backoff # The [--backoff] flag is used to specify a duration between consecutive subscription towards targets. It defaults to 0s meaning all subscription are started in parallel. If a locker is configured, the backoff timer is set to 100ms by default. lock-retry # The [--lock-retry] flag is a duration used to set the wait time between consecutive lock attempts. Defaults to 5s . history-snapshot # The [--history-snapshot] flag sets the snapshot value in the subscribe request gNMI History extension . The value can be either nanoseconds since Unix epoch or a date in RFC3339 format. history-start # The [--history-start] flag sets the start value in the subscribe request Time Range gNMI History extension . The value can be either nanoseconds since Unix epoch or a date in RFC3339 format. history-end # The [--history-end] flag sets the end value in the subscribe request Time Range gNMI History extension . Examples # 1. streaming, target-defined, 10s interval # gnmic -a sub --path /state/port [ port-id = * ] /statistics 2. streaming, sample, 30s interval # gnmic -a sub --path \"/state/port[port-id=*]/statistics\" \\ --sample-interval 30s 3. streaming, on-change, heartbeat interval 1min # gnmic -a sub --path \"/state/port[port-id=*]/statistics\" \\ --stream-mode on-change \\ --heartbeat-interval 1m 4. once subscription # gnmic -a sub --path \"/state/port[port-id=*]/statistics\" \\ --mode once","title":"Subscribe"},{"location":"cmd/subscribe/#description","text":"The [subscribe | sub] command represents the gNMI Subscribe RPC . It is used to send a Subscribe Request to the specified target(s) and expects one or multiple Subscribe Response","title":"Description"},{"location":"cmd/subscribe/#usage","text":"gnmic [global-flags] subscribe [local-flags]","title":"Usage"},{"location":"cmd/subscribe/#local-flags","text":"The subscribe command supports the following local flags:","title":"Local Flags"},{"location":"cmd/subscribe/#prefix","text":"The [--prefix] flag sets a common prefix to all paths specified using the local --path flag. Defaults to \"\" .","title":"prefix"},{"location":"cmd/subscribe/#path","text":"The path flag [--path] is used to specify the path(s) to which the client wants to subscribe. Multiple paths can be specified by using repeated --path flags: gnmic sub --path \"/state/ports[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" : Note The path after the origin value has to start with a / gnmic sub --path \"openconfig-interfaces:/interfaces/interface\"","title":"path"},{"location":"cmd/subscribe/#target","text":"With the optional [--target] flag it is possible to supply the path target information in the prefix field of the SubscriptionList message.","title":"target"},{"location":"cmd/subscribe/#set-target","text":"The [--set-target] flag is used to set the SubscribeRequest Prefix target value to the configured target name stripped of the port number.","title":"set-target"},{"location":"cmd/subscribe/#model","text":"The [--model] flag is used to specify the schema definition modules that the target should use when extracting the data to stream back.","title":"model"},{"location":"cmd/subscribe/#qos","text":"The [--qos] flag specifies the packet marking that is to be used for the responses to the subscription request. Default marking is set to 20 . If qos marking is not supported by a target the marking can be disabled by setting the value to 0 .","title":"qos"},{"location":"cmd/subscribe/#mode","text":"The [--mode] mode flag specifies the mode of subscription to be created. This may be one of: ONCE , STREAM or POLL . It is case insensitive and defaults to STREAM .","title":"mode"},{"location":"cmd/subscribe/#stream-subscription-mode","text":"The [--stream-mode] flag is used to specify the stream subscription mode. This may be one of: ON_CHANGE, SAMPLE or TARGET_DEFINED This flag applies only if --mode is set to STREAM . It is case insensitive and defaults to SAMPLE .","title":"stream subscription mode"},{"location":"cmd/subscribe/#sample-interval","text":"The [--sample-interval] flag is used to specify the sample interval to be used by the target to send samples to the client. This flag applies only in case --mode is set to STREAM and --stream-mode is set to SAMPLE . Valid formats: 1s, 1m30s, 1h . Defaults to 0s which is the lowest interval supported by a target.","title":"sample interval"},{"location":"cmd/subscribe/#heartbeat-interval","text":"The [--heartbeat-interval] flag is used to specify the server heartbeat interval. The heartbeat interval value can be specified along with ON_CHANGE or SAMPLE stream subscriptions modes. ON_CHANGE : The value of the data item(s) MUST be re-sent once per heartbeat interval regardless of whether the value has changed or not. SAMPLE : The target MUST generate one telemetry update per heartbeat interval, regardless of whether the --suppress-redundant flag is set to true.","title":"heartbeat interval"},{"location":"cmd/subscribe/#quiet","text":"With [--quiet] flag set gnmic will not output subscription responses to stdout . The --quiet flag is useful when gnmic exports the received data to one of the export providers.","title":"quiet"},{"location":"cmd/subscribe/#suppress-redundant","text":"When the [--suppress-redundant] flag is set to true, the target SHOULD NOT generate a telemetry update message unless the value of the path being reported on has changed since the last update was generated. This flag applies only in case --mode is set to STREAM and --stream-mode is set to SAMPLE .","title":"suppress redundant"},{"location":"cmd/subscribe/#updates-only","text":"When the [--updates-only] flag is set to true, the target MUST not transmit the current state of the paths that the client has subscribed to, but rather should send only updates to them.","title":"updates only"},{"location":"cmd/subscribe/#name","text":"The [--name] flag is used to trigger one or multiple subscriptions already defined in the configuration file see defining subscriptions","title":"name"},{"location":"cmd/subscribe/#output","text":"The [--output] flag is used to select one or multiple output already defined in the configuration file. Outputs defined under target take precedence over this flag, see defining outputs and defining targets","title":"output"},{"location":"cmd/subscribe/#watch-config","text":"The [--watch-config] flag is used to enable automatic target loading from the configuration source at runtime. On each configuration change, gnmic reloads the list of targets, subscribes to new targets and/or deletes subscriptions to the deleted ones. Only addition and deletion of targets are currently supported, changes in an existing target config are not possible.","title":"watch-config"},{"location":"cmd/subscribe/#backoff","text":"The [--backoff] flag is used to specify a duration between consecutive subscription towards targets. It defaults to 0s meaning all subscription are started in parallel. If a locker is configured, the backoff timer is set to 100ms by default.","title":"backoff"},{"location":"cmd/subscribe/#lock-retry","text":"The [--lock-retry] flag is a duration used to set the wait time between consecutive lock attempts. Defaults to 5s .","title":"lock-retry"},{"location":"cmd/subscribe/#history-snapshot","text":"The [--history-snapshot] flag sets the snapshot value in the subscribe request gNMI History extension . The value can be either nanoseconds since Unix epoch or a date in RFC3339 format.","title":"history-snapshot"},{"location":"cmd/subscribe/#history-start","text":"The [--history-start] flag sets the start value in the subscribe request Time Range gNMI History extension . The value can be either nanoseconds since Unix epoch or a date in RFC3339 format.","title":"history-start"},{"location":"cmd/subscribe/#history-end","text":"The [--history-end] flag sets the end value in the subscribe request Time Range gNMI History extension .","title":"history-end"},{"location":"cmd/subscribe/#examples","text":"","title":"Examples"},{"location":"cmd/subscribe/#1-streaming-target-defined-10s-interval","text":"gnmic -a sub --path /state/port [ port-id = * ] /statistics","title":"1. streaming, target-defined, 10s interval"},{"location":"cmd/subscribe/#2-streaming-sample-30s-interval","text":"gnmic -a sub --path \"/state/port[port-id=*]/statistics\" \\ --sample-interval 30s","title":"2. streaming, sample, 30s interval"},{"location":"cmd/subscribe/#3-streaming-on-change-heartbeat-interval-1min","text":"gnmic -a sub --path \"/state/port[port-id=*]/statistics\" \\ --stream-mode on-change \\ --heartbeat-interval 1m","title":"3. streaming, on-change, heartbeat interval 1min"},{"location":"cmd/subscribe/#4-once-subscription","text":"gnmic -a sub --path \"/state/port[port-id=*]/statistics\" \\ --mode once","title":"4. once subscription"},{"location":"cmd/diff/diff/","text":"Description # The diff command is similar to a get or subscribe (mode ONCE) commands ran against at least 2 targets, a reference and one or more compared targets. The command will compare the returned responses from the compared targets to the ones returned from the reference target and only print the difference between them. The output is printed as a list \"flattened\" gNMI updates, each line containing an XPath pointing to a leaf followed by its value. Each line is preceded with either signs + or - : + means the leaf and its value are present in the compared target but not in the reference target. - means the leaf and its value are present in the reference target but not in the compared target. e.g: + network-instance[name=default]/interface[name=ethernet-1/36.0]: {} - network-instance[name=default]/protocols/bgp/autonomous-system: 101 The output above indicates: The compared target has interface ethernet-1/36.0 added to network instance default while the reference doesn't. The compared target is missing the autonomous-system 101 configuration under network-instance default protocols/bgp compared to the reference. The data to be compared is specified with the flag --path , which can be set multiple times to compare multiple data sets. By default, the data it is retrieved using a Get RPC , if the flag --sub is present, a Subscribe RPC with mode ONCE is used instead. Each of the get and subscribe methods has pros and cons, with the get method you can choose to compare CONFIG or STATE only, via the flag --type . The subscribe method allows to stream the response(s) in case a larger data set needs to be compared. In addition to that, some routers support more encoding options when using the subscribe RPC Multiple targets can be compared to the reference at once, the printed output of each difference will start with the line \"$reference\" vs \"$compared\" Aliases: compare Usage # gnmic [global-flags] diff [local-flags] Flags # ref # The --ref flag is a mandatory flag that specifies the target to used as reference to compare other targets to. compare # The --compare flag is a mandatory flag that specifies the targets to compare to the reference target. prefix # As per path prefixes , the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --path flag. Defaults to \"\" . path # The mandatory path flag [--path] is used to specify the path(s) the client wants to receive a snapshot of. Multiple paths can be specified by using multiple --path flags: gnmic --insecure \\ --ref router1 --compare router2,router3 diff --path \"/state/ports[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" : model # The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported. target # With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message. type # The type flag [--type] is used to specify the data type requested from the server. One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to \"ALL\") sub # When the flag --sub is present, gnmic will use a Subscribe RPC with mode ONCE, instead of a Get RPC to retrieve the data to be compared. Examples # gnmic diff -t config --skip-verify -e ascii \\ --ref clab-te-leaf1 \\ --compare clab-te-leaf2 \\ --path /network-instance \"clab-te-leaf1:57400\" vs \"clab-te-leaf2:57400\" + network-instance [ name = default ] /interface [ name = ethernet-1/36.0 ] : {} - network-instance [ name = default ] /protocols/bgp/autonomous-system : 101 + network-instance [ name = default ] /protocols/bgp/autonomous-system : 102 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] : {} - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] /admin-state: enable - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] /peer-as : 201 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] /peer-group : eBGPv6 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] : {} - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] /admin-state: enable - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] /peer-as : 202 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] /peer-group : eBGPv6 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] : {} + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] /admin-state: enable + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] /peer-as : 201 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] /peer-group : eBGPv6 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] : {} + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] /admin-state: enable + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] /peer-as : 202 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] /peer-group : eBGPv6 + network-instance [ name = default ] /protocols/bgp/router-id : 10 .0.1.2 - network-instance [ name = default ] /protocols/bgp/router-id : 10 .0.1.1 - network-instance [ name = myins ] : {} - network-instance [ name = myins ] /admin-state : enable - network-instance [ name = myins ] /description : desc1 - network-instance [ name = myins ] /interface [ name = ethernet-1/36.0 ] : {} - network-instance [ name = myins ] /type : ip-vrf","title":"Diff"},{"location":"cmd/diff/diff/#description","text":"The diff command is similar to a get or subscribe (mode ONCE) commands ran against at least 2 targets, a reference and one or more compared targets. The command will compare the returned responses from the compared targets to the ones returned from the reference target and only print the difference between them. The output is printed as a list \"flattened\" gNMI updates, each line containing an XPath pointing to a leaf followed by its value. Each line is preceded with either signs + or - : + means the leaf and its value are present in the compared target but not in the reference target. - means the leaf and its value are present in the reference target but not in the compared target. e.g: + network-instance[name=default]/interface[name=ethernet-1/36.0]: {} - network-instance[name=default]/protocols/bgp/autonomous-system: 101 The output above indicates: The compared target has interface ethernet-1/36.0 added to network instance default while the reference doesn't. The compared target is missing the autonomous-system 101 configuration under network-instance default protocols/bgp compared to the reference. The data to be compared is specified with the flag --path , which can be set multiple times to compare multiple data sets. By default, the data it is retrieved using a Get RPC , if the flag --sub is present, a Subscribe RPC with mode ONCE is used instead. Each of the get and subscribe methods has pros and cons, with the get method you can choose to compare CONFIG or STATE only, via the flag --type . The subscribe method allows to stream the response(s) in case a larger data set needs to be compared. In addition to that, some routers support more encoding options when using the subscribe RPC Multiple targets can be compared to the reference at once, the printed output of each difference will start with the line \"$reference\" vs \"$compared\" Aliases: compare","title":"Description"},{"location":"cmd/diff/diff/#usage","text":"gnmic [global-flags] diff [local-flags]","title":"Usage"},{"location":"cmd/diff/diff/#flags","text":"","title":"Flags"},{"location":"cmd/diff/diff/#ref","text":"The --ref flag is a mandatory flag that specifies the target to used as reference to compare other targets to.","title":"ref"},{"location":"cmd/diff/diff/#compare","text":"The --compare flag is a mandatory flag that specifies the targets to compare to the reference target.","title":"compare"},{"location":"cmd/diff/diff/#prefix","text":"As per path prefixes , the prefix [--prefix] flag represents a common prefix that is applied to all paths specified using the local --path flag. Defaults to \"\" .","title":"prefix"},{"location":"cmd/diff/diff/#path","text":"The mandatory path flag [--path] is used to specify the path(s) the client wants to receive a snapshot of. Multiple paths can be specified by using multiple --path flags: gnmic --insecure \\ --ref router1 --compare router2,router3 diff --path \"/state/ports[port-id=*]\" \\ --path \"/state/router[router-name=*]/interface[interface-name=*]\" If a user needs to provide origin information to the Path message, the following pattern should be used for the path string: \"origin:path\" :","title":"path"},{"location":"cmd/diff/diff/#model","text":"The optional model flag [--model] is used to specify the schema definition modules that the target should use when returning a GetResponse. The model name should match the names returned in Capabilities RPC. Currently only single model name is supported.","title":"model"},{"location":"cmd/diff/diff/#target","text":"With the optional [--target] flag it is possible to supply the path target information in the prefix field of the GetRequest message.","title":"target"},{"location":"cmd/diff/diff/#type","text":"The type flag [--type] is used to specify the data type requested from the server. One of: ALL, CONFIG, STATE, OPERATIONAL (defaults to \"ALL\")","title":"type"},{"location":"cmd/diff/diff/#sub","text":"When the flag --sub is present, gnmic will use a Subscribe RPC with mode ONCE, instead of a Get RPC to retrieve the data to be compared.","title":"sub"},{"location":"cmd/diff/diff/#examples","text":"gnmic diff -t config --skip-verify -e ascii \\ --ref clab-te-leaf1 \\ --compare clab-te-leaf2 \\ --path /network-instance \"clab-te-leaf1:57400\" vs \"clab-te-leaf2:57400\" + network-instance [ name = default ] /interface [ name = ethernet-1/36.0 ] : {} - network-instance [ name = default ] /protocols/bgp/autonomous-system : 101 + network-instance [ name = default ] /protocols/bgp/autonomous-system : 102 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] : {} - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] /admin-state: enable - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] /peer-as : 201 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:11:1 ] /peer-group : eBGPv6 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] : {} - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] /admin-state: enable - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] /peer-as : 202 - network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:12:1 ] /peer-group : eBGPv6 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] : {} + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] /admin-state: enable + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] /peer-as : 201 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:21:1 ] /peer-group : eBGPv6 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] : {} + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] /admin-state: enable + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] /peer-as : 202 + network-instance [ name = default ] /protocols/bgp/neighbor [ peer-address = 2002 ::192:168:22:1 ] /peer-group : eBGPv6 + network-instance [ name = default ] /protocols/bgp/router-id : 10 .0.1.2 - network-instance [ name = default ] /protocols/bgp/router-id : 10 .0.1.1 - network-instance [ name = myins ] : {} - network-instance [ name = myins ] /admin-state : enable - network-instance [ name = myins ] /description : desc1 - network-instance [ name = myins ] /interface [ name = ethernet-1/36.0 ] : {} - network-instance [ name = myins ] /type : ip-vrf","title":"Examples"},{"location":"cmd/diff/diff_set_to_notifs/","text":"Description # The diff set-to-notifs command is used to verify whether a set of notifications from a GetResponse or a stream of SubscribeResponse messages comply with a SetRequest messages in textproto format. The envisioned use case is to check whether a stored snapshot of device state matches that of the intended state as specified by a SetRequest . The output is printed as a list of \"flattened\" gNMI updates, each line containing an XPath pointing to a leaf followed by its value. Each line is preceded with either signs + or - : + means the leaf and its value are present in the new SetRequest but not in the reference SetRequest. - means the leaf and its value are present in the reference SetRequest but not in the new SetRequest. e.g: SetToNotifsDiff(-want/SetRequest, +got/Notifications): - /lacp/interfaces/interface[name=Port-Channel9]/config/interval: \"FAST\" - /lacp/interfaces/interface[name=Port-Channel9]/config/name: \"Port-Channel9\" - /lacp/interfaces/interface[name=Port-Channel9]/name: \"Port-Channel9\" - /network-instances/network-instance[name=VrfBlue]/config/name: \"VrfBlue\" - /network-instances/network-instance[name=VrfBlue]/config/type: \"openconfig-network-instance-types:L3VRF\" - /network-instances/network-instance[name=VrfBlue]/name: \"VrfBlue\" m /system/config/hostname: - \"violetsareblue\" + \"rosesarered\" The output above indicates: The set of paths starting with /lacp/interfaces/interface[name=Port-Channel9]/config/interval: \"FAST\" are present in the SetRequest but missing in the response from the device. The value at path /system/config/hostname does not match that of the SetRequest. When --full is specified, values common between the SetRequest and the response messages are also shown. How to obtain a GetResponse or SubscribeResponse # To obtain GetRespnse/SubscribeResponse in textproto format, simply run gnmic 's subscribe or get functions and pass in the flag --format prototext . Responses retrieved from either GetRequest or SubscribeRequest are supported by this command's --response flag. Usage # gnmic [global-flags] diff set-to-notifs [local-flags] Flags # setrequest # The --setrequest flag is a mandatory flag that specifies the reference gNMI SetRequest textproto file for comparing against the new SetRequest. response # The --response flag is a mandatory flag that specifies the gNMI Notifications textproto file (can contain a GetResponse or SubscribeResponse stream) for comparing against the reference SetRequest. Examples # $ gnmic diff set-to-notifs --setrequest cmd/demo/setrequest.textproto --response cmd/demo/subscriberesponses.textproto","title":"Diff Set-To-Notifs"},{"location":"cmd/diff/diff_set_to_notifs/#description","text":"The diff set-to-notifs command is used to verify whether a set of notifications from a GetResponse or a stream of SubscribeResponse messages comply with a SetRequest messages in textproto format. The envisioned use case is to check whether a stored snapshot of device state matches that of the intended state as specified by a SetRequest . The output is printed as a list of \"flattened\" gNMI updates, each line containing an XPath pointing to a leaf followed by its value. Each line is preceded with either signs + or - : + means the leaf and its value are present in the new SetRequest but not in the reference SetRequest. - means the leaf and its value are present in the reference SetRequest but not in the new SetRequest. e.g: SetToNotifsDiff(-want/SetRequest, +got/Notifications): - /lacp/interfaces/interface[name=Port-Channel9]/config/interval: \"FAST\" - /lacp/interfaces/interface[name=Port-Channel9]/config/name: \"Port-Channel9\" - /lacp/interfaces/interface[name=Port-Channel9]/name: \"Port-Channel9\" - /network-instances/network-instance[name=VrfBlue]/config/name: \"VrfBlue\" - /network-instances/network-instance[name=VrfBlue]/config/type: \"openconfig-network-instance-types:L3VRF\" - /network-instances/network-instance[name=VrfBlue]/name: \"VrfBlue\" m /system/config/hostname: - \"violetsareblue\" + \"rosesarered\" The output above indicates: The set of paths starting with /lacp/interfaces/interface[name=Port-Channel9]/config/interval: \"FAST\" are present in the SetRequest but missing in the response from the device. The value at path /system/config/hostname does not match that of the SetRequest. When --full is specified, values common between the SetRequest and the response messages are also shown.","title":"Description"},{"location":"cmd/diff/diff_set_to_notifs/#how-to-obtain-a-getresponse-or-subscriberesponse","text":"To obtain GetRespnse/SubscribeResponse in textproto format, simply run gnmic 's subscribe or get functions and pass in the flag --format prototext . Responses retrieved from either GetRequest or SubscribeRequest are supported by this command's --response flag.","title":"How to obtain a GetResponse or SubscribeResponse"},{"location":"cmd/diff/diff_set_to_notifs/#usage","text":"gnmic [global-flags] diff set-to-notifs [local-flags]","title":"Usage"},{"location":"cmd/diff/diff_set_to_notifs/#flags","text":"","title":"Flags"},{"location":"cmd/diff/diff_set_to_notifs/#setrequest","text":"The --setrequest flag is a mandatory flag that specifies the reference gNMI SetRequest textproto file for comparing against the new SetRequest.","title":"setrequest"},{"location":"cmd/diff/diff_set_to_notifs/#response","text":"The --response flag is a mandatory flag that specifies the gNMI Notifications textproto file (can contain a GetResponse or SubscribeResponse stream) for comparing against the reference SetRequest.","title":"response"},{"location":"cmd/diff/diff_set_to_notifs/#examples","text":"$ gnmic diff set-to-notifs --setrequest cmd/demo/setrequest.textproto --response cmd/demo/subscriberesponses.textproto","title":"Examples"},{"location":"cmd/diff/diff_setrequest/","text":"Description # The diff setrequest command is used to compare the intent between two SetRequest messages encoded in textproto format. The output is printed as a list of \"flattened\" gNMI updates, each line containing an XPath pointing to a leaf followed by its value. Each line is preceded with either signs + or - : + means the leaf and its value are present in the new SetRequest but not in the reference SetRequest. - means the leaf and its value are present in the reference SetRequest but not in the new SetRequest. e.g: SetRequestIntentDiff(-A, +B): -------- deletes/replaces -------- + /network-instances/network-instance[name=VrfBlue]: deleted or replaced only in B -------- updates -------- m /system/config/hostname: - \"violetsareblue\" + \"rosesarered\" The output above indicates: The new target deletes or replaces the path /network-instances/network-instance[name=VrfBlue] while the reference doesn't. The new target changes the value of /system/config/hostname compared to the reference from \"violetsareblue\" to \"rosesarered\" . When --full is specified, values common between the two SetRequest are also shown. SetRequest Intent # It is possible for two SetRequests to be different but which are semantically equivalent -- i.e. they both modify the same leafs in the same ways. In other words, their overall effects are the same. For example, a replace on the leaf /system/config/hostname with the value \"foo\" is the same as an update on the same leaf with the same value. A replace on the container /system/ with the value { config: { hostname: \"foo\" } } is the same as a delete on that container followed by a replace to the leaf. Overwrites are also possible, although this is currently unsupported. In order to compare equivalent SetRequests correctly, this tool breaks down a SetRequest into its \"minimal intent\" (deletes followed by updates) prior to the diff computation. This is why the output groups deletes/replaces into the same section. Usage # gnmic [global-flags] diff setrequest [local-flags] Flags # ref # The --ref flag is a mandatory flag that specifies the reference gNMI SetRequest textproto file for comparing against the new SetRequest. new # The --new flag is a mandatory flag that specifies the new gNMI SetRequest textproto file for comparing against the reference SetRequest. Examples # $ gnmic diff setrequest --ref cmd/demo/setrequest.textproto --new cmd/demo/setrequest2.textproto","title":"Diff Setrequest"},{"location":"cmd/diff/diff_setrequest/#description","text":"The diff setrequest command is used to compare the intent between two SetRequest messages encoded in textproto format. The output is printed as a list of \"flattened\" gNMI updates, each line containing an XPath pointing to a leaf followed by its value. Each line is preceded with either signs + or - : + means the leaf and its value are present in the new SetRequest but not in the reference SetRequest. - means the leaf and its value are present in the reference SetRequest but not in the new SetRequest. e.g: SetRequestIntentDiff(-A, +B): -------- deletes/replaces -------- + /network-instances/network-instance[name=VrfBlue]: deleted or replaced only in B -------- updates -------- m /system/config/hostname: - \"violetsareblue\" + \"rosesarered\" The output above indicates: The new target deletes or replaces the path /network-instances/network-instance[name=VrfBlue] while the reference doesn't. The new target changes the value of /system/config/hostname compared to the reference from \"violetsareblue\" to \"rosesarered\" . When --full is specified, values common between the two SetRequest are also shown.","title":"Description"},{"location":"cmd/diff/diff_setrequest/#setrequest-intent","text":"It is possible for two SetRequests to be different but which are semantically equivalent -- i.e. they both modify the same leafs in the same ways. In other words, their overall effects are the same. For example, a replace on the leaf /system/config/hostname with the value \"foo\" is the same as an update on the same leaf with the same value. A replace on the container /system/ with the value { config: { hostname: \"foo\" } } is the same as a delete on that container followed by a replace to the leaf. Overwrites are also possible, although this is currently unsupported. In order to compare equivalent SetRequests correctly, this tool breaks down a SetRequest into its \"minimal intent\" (deletes followed by updates) prior to the diff computation. This is why the output groups deletes/replaces into the same section.","title":"SetRequest Intent"},{"location":"cmd/diff/diff_setrequest/#usage","text":"gnmic [global-flags] diff setrequest [local-flags]","title":"Usage"},{"location":"cmd/diff/diff_setrequest/#flags","text":"","title":"Flags"},{"location":"cmd/diff/diff_setrequest/#ref","text":"The --ref flag is a mandatory flag that specifies the reference gNMI SetRequest textproto file for comparing against the new SetRequest.","title":"ref"},{"location":"cmd/diff/diff_setrequest/#new","text":"The --new flag is a mandatory flag that specifies the new gNMI SetRequest textproto file for comparing against the reference SetRequest.","title":"new"},{"location":"cmd/diff/diff_setrequest/#examples","text":"$ gnmic diff setrequest --ref cmd/demo/setrequest.textproto --new cmd/demo/setrequest2.textproto","title":"Examples"},{"location":"cmd/generate/generate_path/","text":"Description # The path sub command is an alias for the gnmic path command.","title":"Generate Path"},{"location":"cmd/generate/generate_path/#description","text":"The path sub command is an alias for the gnmic path command.","title":"Description"},{"location":"cmd/generate/generate_set_request/","text":"Description # The set-request sub command generates a Set request file given a list of update and/or replace paths. If no paths are supplied, a root ( / ) replace path is used as a default. The generated file can be manually edited and used with gnmic set command: gnmic set --request-file Aliases: sreq , srq , sr Usage # gnmic [global-flags] generate [generate-flags] set-request [sub-command-flags] Flags # update # The --update flag specifies a valid xpath, used to generate an updates section of the set request file . Multiple --update flags can be supplied. replace # The --replace flag specifies a valid xpath, used to generate a replaces section of the set request file . Multiple --replace flags can be supplied. Examples # Openconfig # YANG repo: openconfig/public Clone the OpenConfig repository: git clone https://github.com/openconfig/public cd public gnmic --encoding json_ietf \\ generate \\ --file release/models \\ --dir third_party \\ --exclude ietf-interfaces \\ set-request \\ --replace /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address value : - config : ip : \"\" prefix-length : \"\" ip : \"\" vrrp : vrrp-group : - config : accept-mode : \"false\" advertisement-interval : \"100\" preempt : \"true\" preempt-delay : \"0\" priority : \"100\" virtual-address : \"\" virtual-router-id : \"\" interface-tracking : config : priority-decrement : \"0\" track-interface : \"\" virtual-router-id : \"\" encoding : JSON_IETF The value section can be filled with the desired configuration variables. Nokia SR OS # git clone https://github.com/nokia/7x50_YangModels cd 7x50_YangModels git checkout sros_21.2.r2 gnmic generate \\ --file YANG/nokia-combined \\ --dir YANG \\ set-request \\ --replace /configure/service/vprn/bgp/family The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /configure/service/vprn/bgp/family value : flow-ipv4 : \"false\" flow-ipv6 : \"false\" ipv4 : \"true\" ipv6 : \"false\" label-ipv4 : \"false\" mcast-ipv4 : \"false\" mcast-ipv6 : \"false\" Cisco # YANG repo: YangModels/yang Clone the YangModels/yang repo and change into the main directory of the repo: git clone https://github.com/YangModels/yang cd yang/vendor gnmic --encoding json_ietf \\ generate \\ --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \\ --file vendor/cisco/xr/721/Cisco-IOS-XR-ipv4-bgp-oper.yang \\ --dir standard/ietf \\ set-request \\ --path /active-nodes The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /active-nodes value : active-node : - node-name : \"\" selective-vrf-download : role : address-family : ipv4 : unicast : \"\" ipv6 : unicast : \"\" vrf-groups : vrf-group : - vrf-group-name : \"\" encoding : JSON_IETF Juniper # YANG repo: Juniper/yang Clone the Juniper YANG repository and change into the release directory: git clone https://github.com/Juniper/yang cd yang/20.3/20.3R1 gnmic --encoding json_ietf \\ generate --file junos/conf \\ --dir common set-request \\ --replace /configuration/interfaces/interface/unit/family/inet/address The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /configuration/interfaces/interface/unit/family/inet/address value : - apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" arp : - case_1 : \"\" case_2 : \"\" l2-interface : \"\" name : \"\" publish : \"\" broadcast : \"\" destination : \"\" destination-profile : \"\" master-only : \"\" multipoint-destination : - apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" case_1 : \"\" case_2 : \"\" epd-threshold : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" epd-threshold-plp0 : \"\" plp1 : \"\" inverse-arp : \"\" name : \"\" oam-liveness : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" down-count : \"\" up-count : \"\" oam-period : disable : {} oam_period : \"\" shaping : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" cbr : cbr-value : \"\" cdvt : \"\" queue-length : \"\" rtvbr : burst : \"\" cdvt : \"\" peak : \"\" sustained : \"\" vbr : burst : \"\" cdvt : \"\" peak : \"\" sustained : \"\" transmit-weight : \"\" name : \"\" preferred : \"\" primary : \"\" virtual-gateway-address : \"\" vrrp-group : - advertisements-threshold : \"\" apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" authentication-key : \"\" authentication-type : \"\" case_1 : \"\" case_2 : \"\" case_3 : \"\" name : \"\" preferred : \"\" priority : \"\" track : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" interface : - apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" bandwidth-threshold : - name : \"\" priority-cost : \"\" name : \"\" priority-cost : \"\" priority-hold-time : \"\" route : - priority-cost : \"\" route_address : \"\" routing-instance : \"\" virtual-link-local-address : \"\" vrrp-inherit-from : active-group : \"\" active-interface : \"\" apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" web-authentication : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" http : \"\" https : \"\" redirect-to-https : \"\" encoding : JSON_IETF Arista # YANG repo: aristanetworks/yang Arista uses a subset of OpenConfig modules and does not provide IETF modules inside their repo. So make sure you have IETF models available so you can reference it, a openconfig/public is a good candidate. Clone the Arista YANG repo: git clone https://github.com/aristanetworks/yang cd yang The above command generates the below YAML output (JSON if --json flag is supplied) gnmic --encoding json_ietf \\ generate --file EOS-4.23.2F/openconfig/public/release/models \\ --dir ../openconfig/public/third_party/ietf \\ --exclude ietf-interfaces \\ set-request \\ --replace bgp/neighbors/neighbor/config replaces : - path : bgp/neighbors/neighbor/config value : auth-password : \"\" description : \"\" enabled : \"true\" local-as : \"\" neighbor-address : \"\" peer-as : \"\" peer-group : \"\" peer-type : \"\" remove-private-as : \"\" route-flap-damping : \"false\" send-community : NONE","title":"Generate Set-Request"},{"location":"cmd/generate/generate_set_request/#description","text":"The set-request sub command generates a Set request file given a list of update and/or replace paths. If no paths are supplied, a root ( / ) replace path is used as a default. The generated file can be manually edited and used with gnmic set command: gnmic set --request-file Aliases: sreq , srq , sr","title":"Description"},{"location":"cmd/generate/generate_set_request/#usage","text":"gnmic [global-flags] generate [generate-flags] set-request [sub-command-flags]","title":"Usage"},{"location":"cmd/generate/generate_set_request/#flags","text":"","title":"Flags"},{"location":"cmd/generate/generate_set_request/#update","text":"The --update flag specifies a valid xpath, used to generate an updates section of the set request file . Multiple --update flags can be supplied.","title":"update"},{"location":"cmd/generate/generate_set_request/#replace","text":"The --replace flag specifies a valid xpath, used to generate a replaces section of the set request file . Multiple --replace flags can be supplied.","title":"replace"},{"location":"cmd/generate/generate_set_request/#examples","text":"","title":"Examples"},{"location":"cmd/generate/generate_set_request/#openconfig","text":"YANG repo: openconfig/public Clone the OpenConfig repository: git clone https://github.com/openconfig/public cd public gnmic --encoding json_ietf \\ generate \\ --file release/models \\ --dir third_party \\ --exclude ietf-interfaces \\ set-request \\ --replace /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address value : - config : ip : \"\" prefix-length : \"\" ip : \"\" vrrp : vrrp-group : - config : accept-mode : \"false\" advertisement-interval : \"100\" preempt : \"true\" preempt-delay : \"0\" priority : \"100\" virtual-address : \"\" virtual-router-id : \"\" interface-tracking : config : priority-decrement : \"0\" track-interface : \"\" virtual-router-id : \"\" encoding : JSON_IETF The value section can be filled with the desired configuration variables.","title":"Openconfig"},{"location":"cmd/generate/generate_set_request/#nokia-sr-os","text":"git clone https://github.com/nokia/7x50_YangModels cd 7x50_YangModels git checkout sros_21.2.r2 gnmic generate \\ --file YANG/nokia-combined \\ --dir YANG \\ set-request \\ --replace /configure/service/vprn/bgp/family The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /configure/service/vprn/bgp/family value : flow-ipv4 : \"false\" flow-ipv6 : \"false\" ipv4 : \"true\" ipv6 : \"false\" label-ipv4 : \"false\" mcast-ipv4 : \"false\" mcast-ipv6 : \"false\"","title":"Nokia SR OS"},{"location":"cmd/generate/generate_set_request/#cisco","text":"YANG repo: YangModels/yang Clone the YangModels/yang repo and change into the main directory of the repo: git clone https://github.com/YangModels/yang cd yang/vendor gnmic --encoding json_ietf \\ generate \\ --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \\ --file vendor/cisco/xr/721/Cisco-IOS-XR-ipv4-bgp-oper.yang \\ --dir standard/ietf \\ set-request \\ --path /active-nodes The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /active-nodes value : active-node : - node-name : \"\" selective-vrf-download : role : address-family : ipv4 : unicast : \"\" ipv6 : unicast : \"\" vrf-groups : vrf-group : - vrf-group-name : \"\" encoding : JSON_IETF","title":"Cisco"},{"location":"cmd/generate/generate_set_request/#juniper","text":"YANG repo: Juniper/yang Clone the Juniper YANG repository and change into the release directory: git clone https://github.com/Juniper/yang cd yang/20.3/20.3R1 gnmic --encoding json_ietf \\ generate --file junos/conf \\ --dir common set-request \\ --replace /configuration/interfaces/interface/unit/family/inet/address The above command generates the below YAML output (JSON if --json flag is supplied) replaces : - path : /configuration/interfaces/interface/unit/family/inet/address value : - apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" arp : - case_1 : \"\" case_2 : \"\" l2-interface : \"\" name : \"\" publish : \"\" broadcast : \"\" destination : \"\" destination-profile : \"\" master-only : \"\" multipoint-destination : - apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" case_1 : \"\" case_2 : \"\" epd-threshold : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" epd-threshold-plp0 : \"\" plp1 : \"\" inverse-arp : \"\" name : \"\" oam-liveness : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" down-count : \"\" up-count : \"\" oam-period : disable : {} oam_period : \"\" shaping : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" cbr : cbr-value : \"\" cdvt : \"\" queue-length : \"\" rtvbr : burst : \"\" cdvt : \"\" peak : \"\" sustained : \"\" vbr : burst : \"\" cdvt : \"\" peak : \"\" sustained : \"\" transmit-weight : \"\" name : \"\" preferred : \"\" primary : \"\" virtual-gateway-address : \"\" vrrp-group : - advertisements-threshold : \"\" apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" authentication-key : \"\" authentication-type : \"\" case_1 : \"\" case_2 : \"\" case_3 : \"\" name : \"\" preferred : \"\" priority : \"\" track : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" interface : - apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" bandwidth-threshold : - name : \"\" priority-cost : \"\" name : \"\" priority-cost : \"\" priority-hold-time : \"\" route : - priority-cost : \"\" route_address : \"\" routing-instance : \"\" virtual-link-local-address : \"\" vrrp-inherit-from : active-group : \"\" active-interface : \"\" apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" web-authentication : apply-groups : \"\" apply-groups-except : \"\" apply-macro : - data : - name : \"\" value : \"\" name : \"\" http : \"\" https : \"\" redirect-to-https : \"\" encoding : JSON_IETF","title":"Juniper"},{"location":"cmd/generate/generate_set_request/#arista","text":"YANG repo: aristanetworks/yang Arista uses a subset of OpenConfig modules and does not provide IETF modules inside their repo. So make sure you have IETF models available so you can reference it, a openconfig/public is a good candidate. Clone the Arista YANG repo: git clone https://github.com/aristanetworks/yang cd yang The above command generates the below YAML output (JSON if --json flag is supplied) gnmic --encoding json_ietf \\ generate --file EOS-4.23.2F/openconfig/public/release/models \\ --dir ../openconfig/public/third_party/ietf \\ --exclude ietf-interfaces \\ set-request \\ --replace bgp/neighbors/neighbor/config replaces : - path : bgp/neighbors/neighbor/config value : auth-password : \"\" description : \"\" enabled : \"true\" local-as : \"\" neighbor-address : \"\" peer-as : \"\" peer-group : \"\" peer-type : \"\" remove-private-as : \"\" route-flap-damping : \"false\" send-community : NONE","title":"Arista"},{"location":"deployments/deployments_intro/","text":"There are numerous ways gnmic can be deployed, each fulfilling a specific use case. Whether it is gNMI telemetry collection and export to a single output, or clustered data pipelines with high availability and redundancy, the below examples should cover the most common use cases. In this section you will find multiple deployment examples, using docker-compose or containerlab . Each deployment comes with: a docker-compose or clab file one or multiple gnmic configuration file(s) extra configuration files if required by the use case (e.g: prometheus, grafana,...) The containerlab examples come with a fabric deployed using Nokia's SR Linux If you don't find an example that fits your needs, feel free to open an issue on github Single Instance # These examples showcase single gnmic instance deployments with the most commonly used outputs NATS output: clab , docker-compose Kafka output: clab , docker-compose InfluxDB output: clab , docker-compose Prometheus output: clab , docker-compose Multiple outputs: clab , docker-compose Clusters # gnmic can also be deployed in clustered mode to either load share the targets connections between multiple instances and offer connection resiliency, and/or replicate the collected data among all the cluster members InfluxDB output: clab , docker-compose Prometheus output: clab , docker-compose Prometheus output with data replication: clab , docker-compose Pipelines # Building data pipelines using gnmic is achieved using the outputs and inputs plugins. You will be able to process the data in a serial fashion, split it for parallel processing or mirror it to create a forked pipeline. NATS to Prometheus: docker-compose NATS to InfluxDB: docker-compose Clustered pipeline: docker-compose Forked pipeline: docker-compose","title":"Deployments"},{"location":"deployments/deployments_intro/#single-instance","text":"These examples showcase single gnmic instance deployments with the most commonly used outputs NATS output: clab , docker-compose Kafka output: clab , docker-compose InfluxDB output: clab , docker-compose Prometheus output: clab , docker-compose Multiple outputs: clab , docker-compose","title":"Single Instance"},{"location":"deployments/deployments_intro/#clusters","text":"gnmic can also be deployed in clustered mode to either load share the targets connections between multiple instances and offer connection resiliency, and/or replicate the collected data among all the cluster members InfluxDB output: clab , docker-compose Prometheus output: clab , docker-compose Prometheus output with data replication: clab , docker-compose","title":"Clusters"},{"location":"deployments/deployments_intro/#pipelines","text":"Building data pipelines using gnmic is achieved using the outputs and inputs plugins. You will be able to process the data in a serial fashion, split it for parallel processing or mirror it to create a forked pipeline. NATS to Prometheus: docker-compose NATS to InfluxDB: docker-compose Clustered pipeline: docker-compose Forked pipeline: docker-compose","title":"Pipelines"},{"location":"deployments/clusters/containerlab/cluster_with_gnmi_server_and_prometheus_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability and data aggregation via clustering. This deployment example includes: A 3 instances gNMIc cluster , A standalone gNMIc instance. A Prometheus Server A Grafana Server A Consul Server The leader election and target distribution is done with the help of a Consul server All members of the cluster expose a gNMI Server that the single gNMIc instance will use to aggregate the collected data. The aggregation gNMIc instance exposes a Prometheus output that is registered in Consul and is discoverable by the Prometheus server. The whole lab is pretty much self organising: The gNMIc cluster instances discover the targets dynamically using a Docker Loader The gNMIc standalone instance, discovers the cluster instance using a Consul Loader The Prometheus server discovers gNMIc's Prometheus output using Consul Service Discovery Deployment files: containerlab gNMIc cluster config gNMIc aggregator config Prometheus config Grafana datasource Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/2.clusters/4.gnmi-server/containerlab sudo clab deploy -t gnmi-server.clab.yaml +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | 1 | clab-lab24-agg-gnmic | 2e9cc2821b07 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 2 | clab-lab24-consul-agent | c17d31d5f41b | consul:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 3 | clab-lab24-gnmic1 | 3d56e09955f2 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 4 | clab-lab24-gnmic2 | eba24dacea36 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 5 | clab-lab24-gnmic3 | caf473f500f6 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 6 | clab-lab24-grafana | eaa224e62243 | grafana/grafana:latest | linux | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 7 | clab-lab24-leaf1 | 6771dc8d3786 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 8 | clab-lab24-leaf2 | 5cfb1cf68958 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.14/24 | 2001:172:20:20::e/64 | | 9 | clab-lab24-leaf3 | c438f734e44d | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.19/24 | 2001:172:20:20::13/64 | | 10 | clab-lab24-leaf4 | ae4321825a03 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.17/24 | 2001:172:20:20::11/64 | | 11 | clab-lab24-leaf5 | ee7a520fd844 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.18/24 | 2001:172:20:20::12/64 | | 12 | clab-lab24-leaf6 | 59c3c515ef35 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 13 | clab-lab24-leaf7 | 111f858b19fd | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.22/24 | 2001:172:20:20::16/64 | | 14 | clab-lab24-leaf8 | 0ecc69891eb4 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.20/24 | 2001:172:20:20::14/64 | | 15 | clab-lab24-prometheus | 357821ec726e | prom/prometheus:latest | linux | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 16 | clab-lab24-spine1 | 0f5f6f6dc5fa | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.13/24 | 2001:172:20:20::d/64 | | 17 | clab-lab24-spine2 | b718503d3b3f | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.15/24 | 2001:172:20:20::f/64 | | 18 | clab-lab24-spine3 | e02f18d0e3ff | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.11/24 | 2001:172:20:20::b/64 | | 19 | clab-lab24-spine4 | 3347cba3f277 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.12/24 | 2001:172:20:20::c/64 | | 20 | clab-lab24-super-spine1 | 4abc7bcaf43c | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.16/24 | 2001:172:20:20::10/64 | | 21 | clab-lab24-super-spine2 | 5b2f5f153d43 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.21/24 | 2001:172:20:20::15/64 | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ Check the Prometheus Output and gNMI Server documentation pages for more configuration options","title":"Containerlab"},{"location":"deployments/clusters/containerlab/cluster_with_influxdb_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability via clustering. This deployment example includes: A 3 instances gNMIc cluster , A InfluxDB Server A Grafana Server A Consul Server The leader election and target distribution is done with the help of a Consul server Deployment files: containerlab gNMIc config Grafana datasource Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/2.clusters/1.influxdb-output/containerlab sudo clab deploy -t lab21.clab.yaml +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | 1 | clab-lab21-consul-agent | a6f6eb70965f | consul:latest | linux | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 2 | clab-lab21-gnmic1 | 9758b0761431 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 3 | clab-lab21-gnmic2 | 6d6ae91c64bf | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 4 | clab-lab21-gnmic3 | 5df100a9fa73 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 5 | clab-lab21-grafana | fe51bda1830c | grafana/grafana:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 6 | clab-lab21-influxdb | 20712484d835 | influxdb:latest | linux | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 7 | clab-lab21-leaf1 | ce084f636942 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.14/24 | 2001:172:20:20::e/64 | | 8 | clab-lab21-leaf2 | 5cbaba4bc9ff | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.11/24 | 2001:172:20:20::b/64 | | 9 | clab-lab21-leaf3 | a5e92ca08c7e | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 10 | clab-lab21-leaf4 | 1ccfe0082b15 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.12/24 | 2001:172:20:20::c/64 | | 11 | clab-lab21-leaf5 | 7fd4144277a0 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 12 | clab-lab21-leaf6 | cb4df0d609db | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.13/24 | 2001:172:20:20::d/64 | | 13 | clab-lab21-leaf7 | 8f09b622365f | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.19/24 | 2001:172:20:20::13/64 | | 14 | clab-lab21-leaf8 | 0ab91010b4a7 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.18/24 | 2001:172:20:20::12/64 | | 15 | clab-lab21-spine1 | 86d00f11b944 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.15/24 | 2001:172:20:20::f/64 | | 16 | clab-lab21-spine2 | 90cf49595ad2 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.20/24 | 2001:172:20:20::14/64 | | 17 | clab-lab21-spine3 | 1c694820eb88 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.16/24 | 2001:172:20:20::10/64 | | 18 | clab-lab21-spine4 | 1e3eac3de55f | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 19 | clab-lab21-super-spine1 | aafc478de31d | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.21/24 | 2001:172:20:20::15/64 | | 20 | clab-lab21-super-spine2 | bb27b743c97f | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.17/24 | 2001:172:20:20::11/64 | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ Check the InfluxDB Output documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/clusters/containerlab/cluster_with_nats_input_and_prometheus_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability as well as data replication . The redundancy and high-availability are guaranteed by deploying a gnmic cluster. The data replication is achieved using a NATS server acting as both a gnmic input and output. This deployment example includes a: 3 instances gNMIc cluster , A NATS Server acting as both input and output A Prometheus Server A Grafana Server A Consul Server The leader election and target distribution is done with the help of a Consul server Each gnmic instance outputs the streamed gNMI data to NATS, and reads back all the data from the same NATS server (including its own), This effectively guarantees that each instance holds the data streamed by the whole cluster. Like in the previous examples, each gnmic instance will also register its Prometheus output service in Consul . But before doing so, it will attempt to acquire a key lock gnmic/$CLUSTER_NAME/prometheus-output , ( use-lock: true ) prom-output : type : prometheus listen : \":9804\" service-registration : address : consul-agent:8500 use-lock : true # <=== Since only one instance can hold a lock, only one prometheus output is registered, so only one output is scraped by Prometheus. Deployment files: containerlab gNMIc config prometheus config Grafana datasource Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/2.clusters/3.nats-input-prometheus-output/containerlab sudo clab deploy -t lab23.clab.yaml +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | 1 | clab-lab23-consul-agent | cfdaf19e9435 | consul:latest | linux | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 2 | clab-lab23-gnmic1 | 7e2a4060a1ae | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 3 | clab-lab23-gnmic2 | 9e27e4620104 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 4 | clab-lab23-gnmic3 | bb7471eb5f49 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 5 | clab-lab23-grafana | 3fbf7755c49e | grafana/grafana:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 6 | clab-lab23-leaf1 | a61624d5312b | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.21/24 | 2001:172:20:20::15/64 | | 7 | clab-lab23-leaf2 | ef86f701b379 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.14/24 | 2001:172:20:20::e/64 | | 8 | clab-lab23-leaf3 | 352433a2ab3b | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.22/24 | 2001:172:20:20::16/64 | | 9 | clab-lab23-leaf4 | 5ddba813d36f | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.19/24 | 2001:172:20:20::13/64 | | 10 | clab-lab23-leaf5 | aad20f4b9969 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.11/24 | 2001:172:20:20::b/64 | | 11 | clab-lab23-leaf6 | 757c76527a75 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.15/24 | 2001:172:20:20::f/64 | | 12 | clab-lab23-leaf7 | d85e94aaa0dd | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 13 | clab-lab23-leaf8 | ef6210c0e5aa | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.20/24 | 2001:172:20:20::14/64 | | 14 | clab-lab23-nats | f1a1f351bbf8 | nats:latest | linux | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 15 | clab-lab23-prometheus | f7f194a934c5 | prom/prometheus:latest | linux | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 16 | clab-lab23-spine1 | ddbf4e804097 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.16/24 | 2001:172:20:20::10/64 | | 17 | clab-lab23-spine2 | f48323a4de88 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.17/24 | 2001:172:20:20::11/64 | | 18 | clab-lab23-spine3 | 2a65eed26a7e | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.18/24 | 2001:172:20:20::12/64 | | 19 | clab-lab23-spine4 | ea59d0e5d9ed | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.12/24 | 2001:172:20:20::c/64 | | 20 | clab-lab23-super-spine1 | 37af6cd04dd8 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 21 | clab-lab23-super-spine2 | 3408891a0718 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.13/24 | 2001:172:20:20::d/64 | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ Check the NATS Output , NATS Input and Prometheus Output documentation pages for more configuration options.","title":"Containerlab"},{"location":"deployments/clusters/containerlab/cluster_with_prometheus_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability via clustering. This deployment example includes: A 3 instances gNMIc cluster , A Prometheus Server A Grafana Server A Consul Server The leader election and target distribution is done with the help of a Consul server gnmic will also register its Prometheus output service in Consul so that Prometheus can discover which Prometheus servers are available to be scraped. Deployment files: containerlab gNMIc config Prometheus config Grafana datasource Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/2.clusters/2.prometheus-output/containerlab sudo clab deploy -t lab22.clab.yaml +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ | 1 | clab-lab22-consul-agent | 542169159f8b | consul:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 2 | clab-lab22-gnmic1 | c04b2b597e7a | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 3 | clab-lab22-gnmic2 | 49604280d82d | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 4 | clab-lab22-gnmic3 | 49e910460cad | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 5 | clab-lab22-grafana | c0a37b012d29 | grafana/grafana:latest | linux | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 6 | clab-lab22-leaf1 | c6429b499c11 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.19/24 | 2001:172:20:20::13/64 | | 7 | clab-lab22-leaf2 | 62f235b39a62 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.17/24 | 2001:172:20:20::11/64 | | 8 | clab-lab22-leaf3 | 78d3b4e62a6b | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.11/24 | 2001:172:20:20::b/64 | | 9 | clab-lab22-leaf4 | 8c5d80b4d916 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.13/24 | 2001:172:20:20::d/64 | | 10 | clab-lab22-leaf5 | 508d4d2389b4 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.16/24 | 2001:172:20:20::10/64 | | 11 | clab-lab22-leaf6 | 14ce19a8c5da | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 12 | clab-lab22-leaf7 | c4f6e586baa3 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.20/24 | 2001:172:20:20::14/64 | | 13 | clab-lab22-leaf8 | 1e00e6346bf1 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.12/24 | 2001:172:20:20::c/64 | | 14 | clab-lab22-prometheus | 5ed38ce63113 | prom/prometheus:latest | linux | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 15 | clab-lab22-spine1 | 38247b0f81e7 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 16 | clab-lab22-spine2 | 76bf66748acd | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.21/24 | 2001:172:20:20::15/64 | | 17 | clab-lab22-spine3 | 5c8776e2fc77 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.15/24 | 2001:172:20:20::f/64 | | 18 | clab-lab22-spine4 | de67e5b92f36 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.14/24 | 2001:172:20:20::e/64 | | 19 | clab-lab22-super-spine1 | 00f0aee0265a | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.18/24 | 2001:172:20:20::12/64 | | 20 | clab-lab22-super-spine2 | 418888eb7325 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+-----------------------+ Check the Prometheus Output documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/clusters/docker-compose/cluster_with_influxdb_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability via clustering. This deployment example includes: A 3 instances gnmic cluster , A single InfluxDB output The leader election and target distribution is done with the help of a Consul server Deployment files: Docker Compose gNMIc config Download the files, update the gnmic config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the InfluxDB Output documentation page for more configuration options.","title":"Docker Compose"},{"location":"deployments/clusters/docker-compose/cluster_with_nats_input_and_prometheus_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability as well as data replication . The redundancy and high-availability are guaranteed by deploying a gnmic cluster. The data replication is achieved using a NATS server acting as both a gnmic input and output. This deployment example includes a: 3 instances gnmic cluster , A NATS input and output A Prometheus output The leader election and target distribution is done with the help of a Consul server Each gnmic instance outputs the streamed gNMI data to NATS, and reads back all the data from the same NATS server (including its own), This effectively guarantees that each instance holds the data streamed by the whole cluster. Like in the previous examples, each gnmic instance will also register its Prometheus output service in Consul . But before doing so, it will attempt to acquire a key lock gnmic/$CLUSTER_NAME/prometheus-output , ( use-lock: true ) prom-output : type : prometheus listen : \":9804\" service-registration : address : consul-agent:8500 use-lock : true # <=== Since only one instance can hold a lock, only one prometheus output is registered, so only one output is scraped by Prometheus. Deployment files: Docker Compose gNMIc config Prometheus config Download the files, update the gnmic config files with the desired subscriptions and targets. Note The targets outputs list should include the nats output name Deploy it with: sudo docker-compose up -d Check the NATS Output , NATS Input and Prometheus Output documentation pages for more configuration options.","title":"Docker Compose"},{"location":"deployments/clusters/docker-compose/cluster_with_prometheus_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability via clustering. This deployment example includes: A 3 instances gnmic cluster , A single Prometheus output The leader election and target distribution is done with the help of a Consul server gnmic will also register its Prometheus output service in Consul so that Prometheus can discover which Prometheus servers are available to be scraped Deployment files: Docker Compose gNMIc config Prometheus config Download the files, update the gnmic config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus Output documentation page for more configuration options.","title":"Docker Compose"},{"location":"deployments/clusters/kubernetes/cluster_with_prometheus_output/","text":"The purpose of this deployment is to achieve redundancy , high-availability using Kubernetes and gnmic 's internal clustering mechanism. This deployment example includes: A 3 instances gnmic cluster , A single Prometheus output The leader election and target distribution is done with the help of a Consul server gnmic can be discovered by Prometheus using Kubernetes service discovery. Kubernetes uses a headless service with a StatefulSet to disable the internal load balancing across multiple pods of the same StatefulSet and allow Prometheus to discover all instances of gnmic . Prometheus Operator must be installed prior to gnmic deployment. (Can also be installed via kube-prometheus-stack helm chart or kube-prometheus ) Deployment files: gnmic consul prometheus servicemonitor Download the files, update the gnmic ConfigMap with the desired subscriptions and targets and make sure that prometheus servicemonitor is in a namespace or has a label that Prometheus operator is watching. Deploy it with: kubectl create ns gnmic kubectl apply -n gnmic -f kubernetes/consul kubectl apply -n gnmic -f kubernetes/gnmic-app # Before deploying the Prometheus ServiceMonitor # Install Prometheus operator or kube-prometheus or kube-prometheus-stack helm chart # Otherwise the command will fail kubectl apply -f kubernetes/prometheus Check the Prometheus Output documentation page for more configuration options.","title":"Kubernetes"},{"location":"deployments/pipelines/docker-compose/forked_pipeline/","text":"The purpose of this deployment is to create a forked data pipeline using NATS , Influxdb and Prometheus The example includes 3 gnmic instances. The first, called collector , is responsible for streaming the gNMI data from the targets and output it to a NATS server. The second and third, called relay1 and relay2 , reads the data from NATS and writes it to either InfluxDB or Prometheus This deployment enables a few use cases: Apply different processors by the collector and relay. Scale the collector and relay separately, see this example for a scaled-out version. Fork the data into a separate pipeline for a different use case. Deployment files: docker compose gnmic collector config gnmic relay1 config gnmic relay2 config prometheus config Download the files, update the gnmic collector config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus Output and NATS Input documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/pipelines/docker-compose/gnmic_cluster_nats_prometheus/","text":"The purpose of this deployment is to create a clustered data pipeline using NATS and Prometheus . Achieving redundancy , high-availability and data replication , all in clustered data pipeline. The example is divided in 2 parts: Clustered collectors and single relay Clustered collectors and clustered relays These 2 examples are essentially scaled-out versions of this example Clustered collectors and single relay # Deployment files: docker compose gnmic collector config gnmic relay config prometheus config Download the files, update the gnmic collectors config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus Output and NATS Input documentation page for more configuration options Clustered collectors and clustered relays # Deployment files: docker compose gnmic collector config gnmic relay config prometheus config Download the files, update the gnmic collectors config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus Output and NATS Input documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/pipelines/docker-compose/gnmic_cluster_nats_prometheus/#clustered-collectors-and-single-relay","text":"Deployment files: docker compose gnmic collector config gnmic relay config prometheus config Download the files, update the gnmic collectors config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus Output and NATS Input documentation page for more configuration options","title":"Clustered collectors and single relay"},{"location":"deployments/pipelines/docker-compose/gnmic_cluster_nats_prometheus/#clustered-collectors-and-clustered-relays","text":"Deployment files: docker compose gnmic collector config gnmic relay config prometheus config Download the files, update the gnmic collectors config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus Output and NATS Input documentation page for more configuration options","title":"Clustered collectors and clustered relays"},{"location":"deployments/pipelines/docker-compose/nats_influxdb/","text":"The purpose of this deployment is to create data pipeline using NATS and InfluxDB The example includes 2 gnmic instances. The first, called collector , is responsible for streaming the gNMI data from the targets and output it to a NATS server. The second, called relay , reads the data from NATS and writes it to InfluxDB This deployment enables a few use cases: Apply different processors by the collector and relay. Scale the collector and relay separately, see this example for a scaled-out version. Fork the data into a separate pipeline for a different use case. Deployment files: docker compose gnmic collector config gnmic relay config Download the files, update the gnmic collector config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the InfluxDB Output and NATS Input documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/pipelines/docker-compose/nats_prometheus/","text":"The purpose of this deployment is to create data pipeline using NATS and Prometheus The example includes 2 gnmic instances. The first, called collector , is responsible for streaming the gNMI data from the targets and output it to a NATS server. The second, called relay , reads the data from NATS and writes it to Prometheus This deployment enables a few use cases: Apply different processors by the collector and relay. Scale the collector and relay separately, see this example for a scaled-out version. Fork the data into a separate pipeline for a different use case. Deployment files: docker compose gnmic collector config gnmic relay config Download the files, update the gnmic collector config files with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus Output and NATS Input documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/single-instance/containerlab/influxdb-output/","text":"The purpose of this deployment is to collect gNMI data and write it to an InfluxDB instance. This deployment example includes a single gnmic instance, a single InfluxDB server acting as an InfluxDB output and a Grafana server Deployment files: containerlab gNMIc config Grafana datasource The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed. Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/1.single-instance/3.influxdb-output/containerlab sudo clab deploy -t influxdb.clab.yaml +---+---------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +---+---------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | 1 | clab-lab13-gnmic | 1ee4c75ff443 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 2 | clab-lab13-grafana | a932207780bb | grafana/grafana:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 3 | clab-lab13-influxdb | 0768ba6ca10b | influxdb:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 4 | clab-lab13-leaf1 | e0e2045fca7f | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 5 | clab-lab13-leaf2 | 75b8978e734c | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 6 | clab-lab13-leaf3 | 7b03eed78f5d | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 7 | clab-lab13-leaf4 | 19007ce81e04 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 8 | clab-lab13-spine1 | c044fc51196d | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 9 | clab-lab13-spine2 | bcfa52ad2772 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | +---+---------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ Check the InfluxDB Output documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/single-instance/containerlab/kafka-output/","text":"The purpose of this deployment is to collect gNMI data and write it to a Kafka broker. Multiple 3 rd Party systems (acting as a Kafka consumers) can then read the data from the Kafka broker for further processing. This deployment example includes a single gnmic instance and a single Kafka output Deployment files: containerlab gNMIc config The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed. Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/1.single-instance/2.kafka-output/containerlab sudo clab deploy -t kafka.clab.yaml +---+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +---+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | 1 | clab-lab12-gnmic | e79d31f92a7a | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 2 | clab-lab12-kafka-server | 004a338cdb3d | bitnami/kafka:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 3 | clab-lab12-leaf1 | b9269bac3adf | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 4 | clab-lab12-leaf2 | baaeea0ad1a6 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 5 | clab-lab12-leaf3 | 08127014b3cd | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 6 | clab-lab12-leaf4 | da037997c5ff | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 7 | clab-lab12-spine1 | c3bcfe40fcc7 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 8 | clab-lab12-spine2 | 842b259d01b0 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 9 | clab-lab12-zookeeper-server | 5c89e48fdff1 | bitnami/zookeeper:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | +---+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ Check the Kafka Output documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/single-instance/containerlab/multiple-outputs/","text":"The purpose of this deployment is to collect gNMI data and write it to multiple outputs. This deployment example includes: A single gnmic instance A Prometheus Server An InfluxDB Server A NATS Server A Kafka Server A File output A Consul Agent A Grafana Server Deployment files: containerlab gNMIc config Prometheus config Grafana datasource Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/1.single-instance/5.multiple-outputs/containerlab sudo clab deploy -t multiple-outputs.clab.yaml +----+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +----+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | 1 | clab-lab15-consul-agent | 14f864fb1da9 | consul:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 2 | clab-lab15-gnmic | cfb8bfca7547 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 3 | clab-lab15-grafana | 56c19565e27c | grafana/grafana:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 4 | clab-lab15-influxdb | f2d0b2186e10 | influxdb:latest | linux | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 5 | clab-lab15-kafka-server | efe445dbf0f0 | bitnami/kafka:latest | linux | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 6 | clab-lab15-leaf1 | 42d57c79385e | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 7 | clab-lab15-leaf2 | e4b041046779 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.11/24 | 2001:172:20:20::b/64 | | 8 | clab-lab15-leaf3 | ba87204f2678 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.13/24 | 2001:172:20:20::d/64 | | 9 | clab-lab15-leaf4 | 327461ee913e | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.15/24 | 2001:172:20:20::f/64 | | 10 | clab-lab15-nats | 0363dae05edf | nats:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 11 | clab-lab15-prometheus | 44611ebe4a03 | prom/prometheus:latest | linux | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 12 | clab-lab15-spine1 | 8b2b430eea87 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.12/24 | 2001:172:20:20::c/64 | | 13 | clab-lab15-spine2 | 425bea3a243e | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.14/24 | 2001:172:20:20::e/64 | | 14 | clab-lab15-zookeeper-server | 91b546eb7bf9 | bitnami/zookeeper:latest | linux | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | +----+-----------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ Check the gnmic outputs documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/single-instance/containerlab/nats-output/","text":"The purpose of this deployment is to collect gNMI data and write it to a NATS server. Multiple 3 rd Party systems (acting as a NATS clients) can then read the data from the NATS server for further processing. This deployment example includes a single gnmic instance and a single NATS output Deployment files: containerlab gNMIc config The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed. Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/1.single-instance/1.nats-output/containerlab sudo clab deploy -t nats.clab.yaml +---+-------------------+--------------+------------------------------+-------+-------+---------+----------------+----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +---+-------------------+--------------+------------------------------+-------+-------+---------+----------------+----------------------+ | 1 | clab-lab11-gnmic | 955eaa35b730 | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 2 | clab-lab11-leaf1 | f0f61a79124e | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 3 | clab-lab11-leaf2 | de714ee79856 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 4 | clab-lab11-leaf3 | c674b7bbb898 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 5 | clab-lab11-leaf4 | c37033f30e99 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 6 | clab-lab11-nats | ebbd346d2aee | nats:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 7 | clab-lab11-spine1 | 0fe91271bdfe | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 8 | clab-lab11-spine2 | 6b05f4e42cc4 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | +---+-------------------+--------------+------------------------------+-------+-------+---------+----------------+----------------------+ Check the NATS Output documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/single-instance/containerlab/prometheus-output/","text":"The purpose of this deployment is to collect gNMI data and make it available for scraping by a Prometheus client. This deployment example includes a single gnmic instance, a Prometheus Server , a Consul agent used by Prometheus to discover gNMIc's Prometheus output and a Grafana server. Deployment files: containerlab gNMIc config Prometheus config Grafana datasource The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed. Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/1.single-instance/4.prometheus-output/containerlab sudo clab deploy -t prometheus.clab.yaml +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ | 1 | clab-lab14-consul-agent | e402b0516753 | consul:latest | linux | | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | | 2 | clab-lab14-gnmic | 53943cdb8cde | ghcr.io/openconfig/gnmic:latest | linux | | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 3 | clab-lab14-grafana | 1a57efb74f37 | grafana/grafana:latest | linux | | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 4 | clab-lab14-leaf1 | 8343848fbd7a | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 5 | clab-lab14-leaf2 | 9986ff987048 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 6 | clab-lab14-leaf3 | 25a212fcb7a1 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.11/24 | 2001:172:20:20::b/64 | | 7 | clab-lab14-leaf4 | 025373e9f192 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.10/24 | 2001:172:20:20::a/64 | | 8 | clab-lab14-prometheus | ae9b47c49c8d | prom/prometheus:latest | linux | | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 9 | clab-lab14-spine1 | fb9abd5b4c5c | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 10 | clab-lab14-spine2 | f32906f19d55 | ghcr.io/nokia/srlinux | srl | | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | +----+-------------------------+--------------+------------------------------+-------+-------+---------+-----------------+----------------------+ Check the Prometheus output documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/single-instance/containerlab/prometheus-remote-write-output/","text":"The purpose of this deployment is to collect gNMI data and use Prometheus remote write API to push it to different monitoring systems like Prometheus , Mimir , CortexMetrics , VictoriaMetrics , Thanos ... This deployment example includes a single gnmic instance, a Prometheus Server , and a Grafana server. Deployment files: containerlab gNMIc config Prometheus config Grafana datasource The deployed SR Linux nodes are discovered using Docker API and are loaded as gNMI targets. Edit the subscriptions section if needed. Deploy it with: git clone https://github.com/openconfig/gnmic.git cd gnmic/examples/deployments/1.single-instance/6.prometheus-write-output/containerlab sudo clab deploy -t prometheus.clab.yaml +----+-------------------------+--------------+------------------------------+-------+---------+-----------------+--------------+ | # | Name | Container ID | Image | Kind | State | IPv4 Address | IPv6 Address | +----+-------------------------+--------------+------------------------------+-------+---------+-----------------+--------------+ | 1 | clab-lab16-consul-agent | 10054b55e722 | consul:latest | linux | running | 172.19.19.3/24 | N/A | | 2 | clab-lab16-gnmic | 1eeab0771731 | ghcr.io/openconfig/gnmic:latest | linux | running | 172.19.19.5/24 | N/A | | 3 | clab-lab16-grafana | fd09146937ef | grafana/grafana:latest | linux | running | 172.19.19.2/24 | N/A | | 4 | clab-lab16-leaf1 | 0c8f5bf7bafb | ghcr.io/nokia/srlinux | srl | running | 172.19.19.11/24 | N/A | | 5 | clab-lab16-leaf2 | a33868bef0a3 | ghcr.io/nokia/srlinux | srl | running | 172.19.19.9/24 | N/A | | 6 | clab-lab16-leaf3 | 3fb3b459cd48 | ghcr.io/nokia/srlinux | srl | running | 172.19.19.10/24 | N/A | | 7 | clab-lab16-leaf4 | bb2cbc064b05 | ghcr.io/nokia/srlinux | srl | running | 172.19.19.6/24 | N/A | | 8 | clab-lab16-prometheus | 63b6fb1551de | prom/prometheus:latest | linux | running | 172.19.19.4/24 | N/A | | 9 | clab-lab16-spine1 | 76853ab9c4a8 | ghcr.io/nokia/srlinux | srl | running | 172.19.19.8/24 | N/A | | 10 | clab-lab16-spine2 | fdf42ca0fec1 | ghcr.io/nokia/srlinux | srl | running | 172.19.19.7/24 | N/A | +----+-------------------------+--------------+------------------------------+-------+---------+-----------------+--------------+ Check the Prometheus Remote Write output documentation page for more configuration options.","title":"Containerlab"},{"location":"deployments/single-instance/docker-compose/influxdb-output/","text":"The purpose of this deployment is to collect gNMI data and write it to an InfluxDB instance. This deployment example includes a single gnmic instance and a single InfluxDB output Deployment files: Docker Compose gNMIc config Download both files, update the gnmic config file with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the InfluxDB Output documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/single-instance/docker-compose/kafka-output/","text":"The purpose of this deployment is to collect gNMI data and write it to a Kafka broker. Multiple 3 rd Party systems (acting as a Kafka consumers) can then read the data from the Kafka broker for further processing. This deployment example includes a single gnmic instance and a single Kafka output Deployment files: Docker Compose gNMIc config Download both files, update the gnmic config file with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Kafka Output documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/single-instance/docker-compose/multiple-outputs/","text":"The purpose of this deployment is to collect gNMI data and write it to multiple outputs. This deployment example includes: A single gnmic instance A Prometheus output An InfluxDB output A NATS output A Kafka output A File output Deployment files: Docker Compose gNMIc config Prometheus config Download both files, update the gnmic config file with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the gnmic outputs documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/single-instance/docker-compose/nats-output/","text":"The purpose of this deployment is to collect gNMI data and write it to a NATS server. Multiple 3 rd Party systems (acting as a NATS clients) can then read the data from the NATS server for further processing. This deployment example includes a single gnmic instance and a single NATS output Deployment files: Docker Compose gNMIc config Download both files, update the gnmic config file with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the NATS Output documentation page for more configuration options","title":"Docker Compose"},{"location":"deployments/single-instance/docker-compose/prometheus-output/","text":"The purpose of this deployment is to collect gNMI data and make it available for scraping by a Prometheus client. This deployment example includes a single gnmic instance and a single Prometheus output Deployment files: Docker Compose gNMIc config Prometheus config Download both files, update the gnmic config file with the desired subscriptions and targets. Deploy it with: sudo docker-compose up -d Check the Prometheus output documentation page for more configuration options","title":"Docker Compose"},{"location":"user_guide/HA/","text":"Multiple instances of gnmic can be run in clustered mode in order to load share the targets connections and protect against failures. The cluster mode allows gnmic to scale and be highly available at the same time To join the cluster, the instances rely on a service discovery system and distributed KV store such as Consul , Clustering process # At startup, all instances belonging to a cluster: Enter an election process in order to become the cluster leader. Register their API service gnmic-api in a configured service discovery system. Upon becoming the leader: The gnmic instance starts watching the registered gnmic-api services, and maintains a local cache of the active ones. These are essentially the instances restAPI addresses. The leader then waits for clustering/leader-wait-timer to allow the other instances to register their API services as well. This is useful in case an instance is slow to boot, which leaves it out of the initial load sharing process. The leader then enters a \"target watch loop\" ( clustering/targets-watch-timer ), at each iteration the leader tries to determine if all configured targets are handled by an instance of the cluster, this is done by checking if there is a lock maintained for each configured target. The instances which failed to become the leader, continue to try to acquire the leader lock. Target distribution process # If the leader detects that a target does not have a lock, it triggers the target distribution process: Query all the targets keys from the KV store and calculate each instance load (number of maintained gNMI targets). If the target configuration includes tags , the leader selects the instance with the most matching tags (in order). If multiple instances have the same matching tags, the one with the lowest load is selected. If the target doesn't have configured tags, the leader simply select the least loaded instance to handle the target's subscriptions. Retrieve the selected instance API address from the local services cache. Send both the target configuration as well as a target activation action to the selected instance. When a cluster instance gets assigned a target (target activation): Acquire a key lock for that specific target. Once the lock is acquired, create the configured gNMI subscriptions. Maintain the target lock for the duration of the gNMI subscription. The whole target distribution process is repeated for each target missing a lock. Configuration # The cluster configuration is as simple as: # rest api address, format \"address:port\" api : \"\" # clustering related configuration fields clustering : # the cluster name, tells with instances belong to the same cluster # it is used as part of the leader key lock, and the targets key locks # if no value is configured, the value from flag --cluster-name is used. # if the flag has the empty string as value, \"default-cluster\" is used. cluster-name : default-cluster # unique instance name within the cluster, # used as the value in the target locks, # used as the value in the leader lock. # if no value is configured, the value from flag --instance-name is used. # if the flag has the empty string as value, a value is generated in # the format `gnmic-$UUID` instance-name : \"\" # service address to be registered in the locker(Consul) # if not defined, it defaults to the address part of the API address:port service-address : \"\" # gnmic instances API service watch timer # this is a long timer used by the cluster leader # in a consul long-blocking query: # https://www.consul.io/api-docs/features/blocking#implementation-details services-watch-timer : 60s # targets-watch-timer, targets watch timer, duration the leader waits # between consecutive targets distributions targets-watch-timer : 20s # target-assignment-timeout, max time a leader waits for an instance to # lock an assigned target. # if the timeout is reached the leader unassigns the target and reselects # a different instance. target-assignment-timeout : 10s # leader wait timer, allows to configure a wait time after an instance # acquires the leader key. # this wait time goal is to give more chances to other instances to register # their API services before the target distribution starts leader-wait-timer : 5s # ordered list of strings to be added as tags during api service # registration in addition to `cluster-name=${cluster-name}` and # `instance-name=${instance-name}` tags : [] # locker is used to configure the KV store used for # service registration, service discovery, leader election and targets locks locker : # type of locker, only consul is supported currently type : consul # address of the locker server address : localhost:8500 # Consul Data center, defaults to dc1 datacenter : # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token which overrides # the agent's default token token : # session-ttl, session time-to-live after which a session is considered # invalid if not renewed # upon session invalidation, all services and locks created using this session # are considered invalid. session-ttl : 10s # delay, a time duration (0s to 60s), in the event of a session invalidation # consul will prevent the lock from being acquired for this duration. # The purpose is to allow a gnmic instance to stop active subscriptions before # another one takes over. delay : 5s # retry-timer, wait period between retries to acquire a lock # in the event of client failure, key is already locked or lock lost. retry-timer : 2s # renew-period, session renew period, must be lower that session-ttl. # if the value is greater or equal than session-ttl, is will be set to half # of session-ttl. renew-period : 5s # debug, enable extra logging messages debug : false A gnmic instance creates gNMI subscriptions only towards targets for which it acquired locks. It is also responsible for maintaining that lock for the duration of the subscription. Instance affinity # The target distribution process can be influenced using tags added to the target configuration. By default, gnmic instances register their API service with 2 tags; cluster-name=${clustering/cluster-name} instance-name=${clustering/instance-name} By adding the same tags to a target router1 configuration (below YAML), the cluster leader will \"assign\" router1 to instance gnmic1 in cluster my-cluster regardless of the instance load. targets : router1 : tags : - cluster-name=my-cluster - instance-name=gnmic1 Custom tags can be added to an instance API service registration in order to customize the instance affinity logic. clustering : tags : - my-custom-tag=value1 Instance failure # In the event of an instance failure, its maintained targets locks expire, which on the next clustering/targets-watch-timer interval will be detected by the cluster leader. The leader then performs the same target distribution process for those targets without a lock. Leader reelection # If a cluster leader fails, one of the other instances in the cluster eventually acquires the leader lock and becomes the cluster leader. It then, proceeds with the targets distribution process to assign the unhandled targets to an instance in the cluster. Scalability # Using the same above-mentioned clustering mechanism, gnmic can horizontally scale the number of supported gNMI connections distributed across multiple gnmic instances. The collected gNMI data can then be aggregated and made available through any of the running gnmic instances, regardless of whether that instance collected the data from the target or not. The data aggregation is done by chaining gnmic outputs and inputs to build a gNMI data pipeline. In the diagram below, the gnmic instances on the left and right side of NATS server can be identical.","title":"Clustering"},{"location":"user_guide/HA/#clustering-process","text":"At startup, all instances belonging to a cluster: Enter an election process in order to become the cluster leader. Register their API service gnmic-api in a configured service discovery system. Upon becoming the leader: The gnmic instance starts watching the registered gnmic-api services, and maintains a local cache of the active ones. These are essentially the instances restAPI addresses. The leader then waits for clustering/leader-wait-timer to allow the other instances to register their API services as well. This is useful in case an instance is slow to boot, which leaves it out of the initial load sharing process. The leader then enters a \"target watch loop\" ( clustering/targets-watch-timer ), at each iteration the leader tries to determine if all configured targets are handled by an instance of the cluster, this is done by checking if there is a lock maintained for each configured target. The instances which failed to become the leader, continue to try to acquire the leader lock.","title":"Clustering process"},{"location":"user_guide/HA/#target-distribution-process","text":"If the leader detects that a target does not have a lock, it triggers the target distribution process: Query all the targets keys from the KV store and calculate each instance load (number of maintained gNMI targets). If the target configuration includes tags , the leader selects the instance with the most matching tags (in order). If multiple instances have the same matching tags, the one with the lowest load is selected. If the target doesn't have configured tags, the leader simply select the least loaded instance to handle the target's subscriptions. Retrieve the selected instance API address from the local services cache. Send both the target configuration as well as a target activation action to the selected instance. When a cluster instance gets assigned a target (target activation): Acquire a key lock for that specific target. Once the lock is acquired, create the configured gNMI subscriptions. Maintain the target lock for the duration of the gNMI subscription. The whole target distribution process is repeated for each target missing a lock.","title":"Target distribution process"},{"location":"user_guide/HA/#configuration","text":"The cluster configuration is as simple as: # rest api address, format \"address:port\" api : \"\" # clustering related configuration fields clustering : # the cluster name, tells with instances belong to the same cluster # it is used as part of the leader key lock, and the targets key locks # if no value is configured, the value from flag --cluster-name is used. # if the flag has the empty string as value, \"default-cluster\" is used. cluster-name : default-cluster # unique instance name within the cluster, # used as the value in the target locks, # used as the value in the leader lock. # if no value is configured, the value from flag --instance-name is used. # if the flag has the empty string as value, a value is generated in # the format `gnmic-$UUID` instance-name : \"\" # service address to be registered in the locker(Consul) # if not defined, it defaults to the address part of the API address:port service-address : \"\" # gnmic instances API service watch timer # this is a long timer used by the cluster leader # in a consul long-blocking query: # https://www.consul.io/api-docs/features/blocking#implementation-details services-watch-timer : 60s # targets-watch-timer, targets watch timer, duration the leader waits # between consecutive targets distributions targets-watch-timer : 20s # target-assignment-timeout, max time a leader waits for an instance to # lock an assigned target. # if the timeout is reached the leader unassigns the target and reselects # a different instance. target-assignment-timeout : 10s # leader wait timer, allows to configure a wait time after an instance # acquires the leader key. # this wait time goal is to give more chances to other instances to register # their API services before the target distribution starts leader-wait-timer : 5s # ordered list of strings to be added as tags during api service # registration in addition to `cluster-name=${cluster-name}` and # `instance-name=${instance-name}` tags : [] # locker is used to configure the KV store used for # service registration, service discovery, leader election and targets locks locker : # type of locker, only consul is supported currently type : consul # address of the locker server address : localhost:8500 # Consul Data center, defaults to dc1 datacenter : # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token which overrides # the agent's default token token : # session-ttl, session time-to-live after which a session is considered # invalid if not renewed # upon session invalidation, all services and locks created using this session # are considered invalid. session-ttl : 10s # delay, a time duration (0s to 60s), in the event of a session invalidation # consul will prevent the lock from being acquired for this duration. # The purpose is to allow a gnmic instance to stop active subscriptions before # another one takes over. delay : 5s # retry-timer, wait period between retries to acquire a lock # in the event of client failure, key is already locked or lock lost. retry-timer : 2s # renew-period, session renew period, must be lower that session-ttl. # if the value is greater or equal than session-ttl, is will be set to half # of session-ttl. renew-period : 5s # debug, enable extra logging messages debug : false A gnmic instance creates gNMI subscriptions only towards targets for which it acquired locks. It is also responsible for maintaining that lock for the duration of the subscription.","title":"Configuration"},{"location":"user_guide/HA/#instance-affinity","text":"The target distribution process can be influenced using tags added to the target configuration. By default, gnmic instances register their API service with 2 tags; cluster-name=${clustering/cluster-name} instance-name=${clustering/instance-name} By adding the same tags to a target router1 configuration (below YAML), the cluster leader will \"assign\" router1 to instance gnmic1 in cluster my-cluster regardless of the instance load. targets : router1 : tags : - cluster-name=my-cluster - instance-name=gnmic1 Custom tags can be added to an instance API service registration in order to customize the instance affinity logic. clustering : tags : - my-custom-tag=value1","title":"Instance affinity"},{"location":"user_guide/HA/#instance-failure","text":"In the event of an instance failure, its maintained targets locks expire, which on the next clustering/targets-watch-timer interval will be detected by the cluster leader. The leader then performs the same target distribution process for those targets without a lock.","title":"Instance failure"},{"location":"user_guide/HA/#leader-reelection","text":"If a cluster leader fails, one of the other instances in the cluster eventually acquires the leader lock and becomes the cluster leader. It then, proceeds with the targets distribution process to assign the unhandled targets to an instance in the cluster.","title":"Leader reelection"},{"location":"user_guide/HA/#scalability","text":"Using the same above-mentioned clustering mechanism, gnmic can horizontally scale the number of supported gNMI connections distributed across multiple gnmic instances. The collected gNMI data can then be aggregated and made available through any of the running gnmic instances, regardless of whether that instance collected the data from the target or not. The data aggregation is done by chaining gnmic outputs and inputs to build a gNMI data pipeline. In the diagram below, the gnmic instances on the left and right side of NATS server can be identical.","title":"Scalability"},{"location":"user_guide/caching/","text":"Caching refers to the process of storing the collected gNMI updates before sending them out to the intended output(s). By default, gNMIc outputs send out the received gNMI updates as they arrive (i.e without storing them). A cache is used to store the received updates when the gnmi-server functionality is enabled and (optionally) when influxdb and prometheus outputs are enabled to allow for advanced data pipeline processing. Caching messages before writing them to a remote location allows implementing a few use cases like rate limiting , batch processing , data replication , etc. Caching support for other outputs is planned. How does it work? # When caching is enabled for a certain output, the received gNMI updates are not written directly to the output remote server (for e.g: InfluxDB server), but rather cached locally until the cache-flush-timer is reached (in the case of an influxdb output) or when the output receives a Prometheus scrape request (in the case of a prometheus output). The below diagram shows how an InfluxDB output works with and without cache enabled: The cached gNMI updates are periodically retrieved from the cache in batch then converted to events . If processors are defined under the output config section, they are applied to the whole list of events at once. This allows for augmentation of messages with values from other messages even if they where received in separate updates or collected from a different target/subscription. Enable caching # gnmi-server # The gNMI server has caching enabled by default. The cache type and its behavior can be tweaked, see here gnmi-server : # # other gnmi-server related attributes # cache : {} outputs # Caching can be enabled per output by populating the cache attribute under the desired output: outputs : output1 : type : prometheus # # other output related attributes # cache : {} This enables output1 to use a cache of type oc . Each output has its own cache. Using a single global cache will be implemented in a future release. Distributed caches # When running multiple instances of gNMIc it's possible to synchronize the collected data between all the instances using a distributed cache. Each output that is configured with a remote cache will write the collected gNMI updates to the remote cache first, then syncs back all the cached data to its local cache then eventually write it to the output. (1) The received gNMI updates are written to the remote cache. (2) The output syncs the remote cache data to its local cache. (3) The locally cached data is written to the remote output periodically or on scape request. This is useful when different instances collect data from different targets and/or subscriptions. A single instance can be responsible for writing all the collected data to the output or each instance would be writing to a different output. Cache types # gNMIc supports 4 cache types. There is 1 local cache and 3 distributed caches \"flavors\". The choice of cache to use depends on the use case you are trying to implement. A local cache is local to the gNMIc instance i.e not exposed externally, while a distributed cache is external to the gNMIc instance, potentially shared by multiple gNMIc instances and is always combined with a local cache to sync updates between gNMIc instances. gNMI cache (local) # Is an in-memory gNMI cache based on the Openconfig gNMI cache published here This type of cache is ideal when running a single gNMIc instance. It is also the default cache type for the gNMI server and for an output when caching is enabled. Configuration: outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : oc # duration, default: 60s. # updates older than the expiration value will not be read from the cache. expiration : 60s # enable extra logging debug : false NATS cache (distributed) # Is a cache type that relies on a NATS server to distribute the collected updates between gNMIc instances. This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths. Configuration: outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : nats # string, address of the remote NATS server, # if left empty an in memory NATS server will be created an used. address : # string, the NATS server username. username : # string, the NATS server password. password : # string, expiration period of received messages. expiration : 60s # enable extra logging debug : false JetStream cache (distributed) # Is a cache type that relies on a JetStream server to distribute the collected updates between gNMIc instances. This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths. It is planned to add gNMI historical subscriptions support using the jetstream cache type. Configuration: outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : jetstream # string, address of the remote NATS JetStream server, # if left empty an in memory NATS JetStream server will be created an used. address : # string, the JetStream server username. username : # string, the JetStream server password. password : # duration, default: 60s. # Expiration period of received messages. expiration : 60s # int64, default: 1073741824 (1 GiB). # Max number of bytes stored in the cache per subscription. max-bytes : # int64, default: 1048576. # Max number of messages stored per subscription. max-msgs-per-subscription : # int, default 100. # Batch size used by the JetStream pull subscriber. fetch-batch-size : # duration, default 100ms. # Wait time used by the JetStream pull subscriber. fetch-wait-time : # enable extra logging debug : false Redis cache (distributed) # Is a cache type that relies on a Redis PUBSUB server to distribute the collected updates between gNMIc instances. This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths. outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : redis # string, redis server address address : # string, the Redis server username. username : # string, the Redis server password. password : # duration, default: 60s. # Expiration period of received messages. expiration : 60s # enable extra logging debug : false","title":"Caching"},{"location":"user_guide/caching/#how-does-it-work","text":"When caching is enabled for a certain output, the received gNMI updates are not written directly to the output remote server (for e.g: InfluxDB server), but rather cached locally until the cache-flush-timer is reached (in the case of an influxdb output) or when the output receives a Prometheus scrape request (in the case of a prometheus output). The below diagram shows how an InfluxDB output works with and without cache enabled: The cached gNMI updates are periodically retrieved from the cache in batch then converted to events . If processors are defined under the output config section, they are applied to the whole list of events at once. This allows for augmentation of messages with values from other messages even if they where received in separate updates or collected from a different target/subscription.","title":"How does it work?"},{"location":"user_guide/caching/#enable-caching","text":"","title":"Enable caching"},{"location":"user_guide/caching/#gnmi-server","text":"The gNMI server has caching enabled by default. The cache type and its behavior can be tweaked, see here gnmi-server : # # other gnmi-server related attributes # cache : {}","title":"gnmi-server"},{"location":"user_guide/caching/#outputs","text":"Caching can be enabled per output by populating the cache attribute under the desired output: outputs : output1 : type : prometheus # # other output related attributes # cache : {} This enables output1 to use a cache of type oc . Each output has its own cache. Using a single global cache will be implemented in a future release.","title":"outputs"},{"location":"user_guide/caching/#distributed-caches","text":"When running multiple instances of gNMIc it's possible to synchronize the collected data between all the instances using a distributed cache. Each output that is configured with a remote cache will write the collected gNMI updates to the remote cache first, then syncs back all the cached data to its local cache then eventually write it to the output. (1) The received gNMI updates are written to the remote cache. (2) The output syncs the remote cache data to its local cache. (3) The locally cached data is written to the remote output periodically or on scape request. This is useful when different instances collect data from different targets and/or subscriptions. A single instance can be responsible for writing all the collected data to the output or each instance would be writing to a different output.","title":"Distributed caches"},{"location":"user_guide/caching/#cache-types","text":"gNMIc supports 4 cache types. There is 1 local cache and 3 distributed caches \"flavors\". The choice of cache to use depends on the use case you are trying to implement. A local cache is local to the gNMIc instance i.e not exposed externally, while a distributed cache is external to the gNMIc instance, potentially shared by multiple gNMIc instances and is always combined with a local cache to sync updates between gNMIc instances.","title":"Cache types"},{"location":"user_guide/caching/#gnmi-cache-local","text":"Is an in-memory gNMI cache based on the Openconfig gNMI cache published here This type of cache is ideal when running a single gNMIc instance. It is also the default cache type for the gNMI server and for an output when caching is enabled. Configuration: outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : oc # duration, default: 60s. # updates older than the expiration value will not be read from the cache. expiration : 60s # enable extra logging debug : false","title":"gNMI cache (local)"},{"location":"user_guide/caching/#nats-cache-distributed","text":"Is a cache type that relies on a NATS server to distribute the collected updates between gNMIc instances. This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths. Configuration: outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : nats # string, address of the remote NATS server, # if left empty an in memory NATS server will be created an used. address : # string, the NATS server username. username : # string, the NATS server password. password : # string, expiration period of received messages. expiration : 60s # enable extra logging debug : false","title":"NATS cache (distributed)"},{"location":"user_guide/caching/#jetstream-cache-distributed","text":"Is a cache type that relies on a JetStream server to distribute the collected updates between gNMIc instances. This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths. It is planned to add gNMI historical subscriptions support using the jetstream cache type. Configuration: outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : jetstream # string, address of the remote NATS JetStream server, # if left empty an in memory NATS JetStream server will be created an used. address : # string, the JetStream server username. username : # string, the JetStream server password. password : # duration, default: 60s. # Expiration period of received messages. expiration : 60s # int64, default: 1073741824 (1 GiB). # Max number of bytes stored in the cache per subscription. max-bytes : # int64, default: 1048576. # Max number of messages stored per subscription. max-msgs-per-subscription : # int, default 100. # Batch size used by the JetStream pull subscriber. fetch-batch-size : # duration, default 100ms. # Wait time used by the JetStream pull subscriber. fetch-wait-time : # enable extra logging debug : false","title":"JetStream cache (distributed)"},{"location":"user_guide/caching/#redis-cache-distributed","text":"Is a cache type that relies on a Redis PUBSUB server to distribute the collected updates between gNMIc instances. This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths. outputs : output1 : type : prometheus # or influxdb # # other output related fields # cache : type : redis # string, redis server address address : # string, the Redis server username. username : # string, the Redis server password. password : # duration, default: 60s. # Expiration period of received messages. expiration : 60s # enable extra logging debug : false","title":"Redis cache (distributed)"},{"location":"user_guide/configuration_env/","text":"gnmic can be configured using environment variables, it will read the environment variables starting with GNMIC_ . The Env variable names are inline with the flag names as well as the configuration hierarchy. For e.g to set the gNMI username, the env variable GNMIC_USERNAME should be set. Constructing environment variables names # Flags to environment variables mapping # Global flags to env variable name mapping: Flag name ENV variable name --address GNMIC_ADDRESS --encoding GNMIC_ENCODING --format GNMIC_FORMAT --insecure GNMIC_INSECURE --log GNMIC_LOG --log-file GNMIC_LOG_FILE --no-prefix GNMIC_NO_PREFIX --password GNMIC_PASSWORD --prometheus-address GNMIC_PROMETHEUS_ADDRESS --proxy-from-env GNMIC_PROXY_FROM_ENV --retry GNMIC_RETRY --skip-verify GNMIC_SKIP_VERIFY --timeout GNMIC_TIMEOUT --tls-ca GNMIC_TLS_CA --tls-cert GNMIC_TLS_CERT --tls-key GNMIC_TLS_KEY --tls-max-version GNMIC_TLS_MAX_VERSION --tls-min-version GNMIC_TLS_MIN_VERSION --tls-version GNMIC_TLS_VERSION --log-tls-secret GNMIC_LOG_TLS_SECRET --username GNMIC_USERNAME --cluster-name GNMIC_CLUSTER_NAME --instance-name GNMIC_INSTANCE_NAME --proto-file GNMIC_PROTO_FILE --proto-dir GNMIC_PROTO_DIR --token GNMIC_TOKEN Configuration file to environment variables mapping # For configuration items that do not have a corresponding flag, the env variable will be constructed from the path elements to the variable name joined with a _ . For e.g to set the clustering locker address, as in the yaml blob below: clustering : locker : address : the env variable GNMIC_CLUSTERING_LOCKER_ADDRESS should be set Note Configuration items of type list cannot be set using env vars. Intermediate configuration keys should not contain _ or - . Example: outputs : output1 : # <-- should not contain `_` or `-` type : prometheus listen : :9804 Is equivalent to: GNMIC_OUTPUTS_OUTPUT1_TYPE=prometheus GNMIC_OUTPUTS_OUTPUT1_LISTEN=:9804","title":"Environment variables"},{"location":"user_guide/configuration_env/#constructing-environment-variables-names","text":"","title":"Constructing environment variables names"},{"location":"user_guide/configuration_env/#flags-to-environment-variables-mapping","text":"Global flags to env variable name mapping: Flag name ENV variable name --address GNMIC_ADDRESS --encoding GNMIC_ENCODING --format GNMIC_FORMAT --insecure GNMIC_INSECURE --log GNMIC_LOG --log-file GNMIC_LOG_FILE --no-prefix GNMIC_NO_PREFIX --password GNMIC_PASSWORD --prometheus-address GNMIC_PROMETHEUS_ADDRESS --proxy-from-env GNMIC_PROXY_FROM_ENV --retry GNMIC_RETRY --skip-verify GNMIC_SKIP_VERIFY --timeout GNMIC_TIMEOUT --tls-ca GNMIC_TLS_CA --tls-cert GNMIC_TLS_CERT --tls-key GNMIC_TLS_KEY --tls-max-version GNMIC_TLS_MAX_VERSION --tls-min-version GNMIC_TLS_MIN_VERSION --tls-version GNMIC_TLS_VERSION --log-tls-secret GNMIC_LOG_TLS_SECRET --username GNMIC_USERNAME --cluster-name GNMIC_CLUSTER_NAME --instance-name GNMIC_INSTANCE_NAME --proto-file GNMIC_PROTO_FILE --proto-dir GNMIC_PROTO_DIR --token GNMIC_TOKEN","title":"Flags to environment variables mapping"},{"location":"user_guide/configuration_env/#configuration-file-to-environment-variables-mapping","text":"For configuration items that do not have a corresponding flag, the env variable will be constructed from the path elements to the variable name joined with a _ . For e.g to set the clustering locker address, as in the yaml blob below: clustering : locker : address : the env variable GNMIC_CLUSTERING_LOCKER_ADDRESS should be set Note Configuration items of type list cannot be set using env vars. Intermediate configuration keys should not contain _ or - . Example: outputs : output1 : # <-- should not contain `_` or `-` type : prometheus listen : :9804 Is equivalent to: GNMIC_OUTPUTS_OUTPUT1_TYPE=prometheus GNMIC_OUTPUTS_OUTPUT1_LISTEN=:9804","title":"Configuration file to environment variables mapping"},{"location":"user_guide/configuration_file/","text":"gnmic configuration by means of the command line flags is both consistent and reliable. But sometimes its not the best way forward. With lots of configuration options that gnmic supports it might get tedious to pass them all via CLI flags. In cases like that the file-based configuration comes handy. With a configuration file a user can specify all the command line flags by means of a single file. gnmic will read this file and retrieve the configuration options from it. What options can be in a file? # Configuration file allows a user to specify everything that can be supplied over the CLI and more. Global flags # All of the global flags can be put in a conf file. Consider the following example of a typical configuration file in YAML format: # gNMI target address; CLI flag `--address` address : \"10.0.0.1:57400\" # gNMI target user name; CLI flag `--username` username : admin # gNMI target user password; CLI flag `--password` password : NokiaSrl1! # connection mode; CLI flag `--insecure` insecure : true # log file location; CLI flag `--log-file` log-file : /tmp/gnmic.log With such a file located at a default path the gNMI requests can be made in a very short and concise form: # configuration file is read by its default path gnmi capabilities # cfg file has all the global options set, so only the local flags are needed gnmi get --path /configure/system/name Local flags # Local flags have the scope of the command where they have been defined. Local flags can be put in a configuration file as well. To avoid flags names overlap between the different commands a command name should prepend the flag name - - . So, for example, we can provide the path flag of a get command in the file by adding the get- prefix to the local flag name: address : \"router.lab:57400\" username : admin password : NokiaSrl1! insecure : true get-path : /configure/system/name # `get` command local flag Another example: the update-path flag of a set will be set-update-path in the configuration file. Targets # It is possible to specify multiple targets with different configurations (credentials, timeout,...). This is described in Multiple targets documentation article. Subscriptions # It is possible to specify multiple subscriptions and associate them with different targets in a flexible way. This configuration option is described in Multiple subscriptions documentation article. Outputs # The other mode gnmic supports (in contrast to CLI) is running as a daemon and exporting the data received from gNMI subscriptions to multiple outputs like stan/nats, kafka, file, prometheus, influxdb, etc... Inputs # gnmic supports reading gNMI data from a set of inputs and export the data to any of the configured outputs. This is used when building data pipelines with gnmic Repeated flags # If a flag can appear more than once on the CLI, it can be represented as a list in the file. For example one can set multiple paths for get/set/subscribe operations. In the following example we define multiple paths for the get command to operate on: address : \"router.lab:57400\" username : admin password : NokiaSrl1! insecure : true get-path : - /configure/system/name - /state/system/version Options preference # Configuration passed via CLI flags and Env variables take precedence over the file config. Environment variables in file # Environment variables can be used in the configuration file and will be expanded at the time the configuration is read. outputs : output1 : type : nats address : ${NATS_IP}:4222","title":"File configuration"},{"location":"user_guide/configuration_file/#what-options-can-be-in-a-file","text":"Configuration file allows a user to specify everything that can be supplied over the CLI and more.","title":"What options can be in a file?"},{"location":"user_guide/configuration_file/#global-flags","text":"All of the global flags can be put in a conf file. Consider the following example of a typical configuration file in YAML format: # gNMI target address; CLI flag `--address` address : \"10.0.0.1:57400\" # gNMI target user name; CLI flag `--username` username : admin # gNMI target user password; CLI flag `--password` password : NokiaSrl1! # connection mode; CLI flag `--insecure` insecure : true # log file location; CLI flag `--log-file` log-file : /tmp/gnmic.log With such a file located at a default path the gNMI requests can be made in a very short and concise form: # configuration file is read by its default path gnmi capabilities # cfg file has all the global options set, so only the local flags are needed gnmi get --path /configure/system/name","title":"Global flags"},{"location":"user_guide/configuration_file/#local-flags","text":"Local flags have the scope of the command where they have been defined. Local flags can be put in a configuration file as well. To avoid flags names overlap between the different commands a command name should prepend the flag name - - . So, for example, we can provide the path flag of a get command in the file by adding the get- prefix to the local flag name: address : \"router.lab:57400\" username : admin password : NokiaSrl1! insecure : true get-path : /configure/system/name # `get` command local flag Another example: the update-path flag of a set will be set-update-path in the configuration file.","title":"Local flags"},{"location":"user_guide/configuration_file/#targets","text":"It is possible to specify multiple targets with different configurations (credentials, timeout,...). This is described in Multiple targets documentation article.","title":"Targets"},{"location":"user_guide/configuration_file/#subscriptions","text":"It is possible to specify multiple subscriptions and associate them with different targets in a flexible way. This configuration option is described in Multiple subscriptions documentation article.","title":"Subscriptions"},{"location":"user_guide/configuration_file/#outputs","text":"The other mode gnmic supports (in contrast to CLI) is running as a daemon and exporting the data received from gNMI subscriptions to multiple outputs like stan/nats, kafka, file, prometheus, influxdb, etc...","title":"Outputs"},{"location":"user_guide/configuration_file/#inputs","text":"gnmic supports reading gNMI data from a set of inputs and export the data to any of the configured outputs. This is used when building data pipelines with gnmic","title":"Inputs"},{"location":"user_guide/configuration_file/#repeated-flags","text":"If a flag can appear more than once on the CLI, it can be represented as a list in the file. For example one can set multiple paths for get/set/subscribe operations. In the following example we define multiple paths for the get command to operate on: address : \"router.lab:57400\" username : admin password : NokiaSrl1! insecure : true get-path : - /configure/system/name - /state/system/version","title":"Repeated flags"},{"location":"user_guide/configuration_file/#options-preference","text":"Configuration passed via CLI flags and Env variables take precedence over the file config.","title":"Options preference"},{"location":"user_guide/configuration_file/#environment-variables-in-file","text":"Environment variables can be used in the configuration file and will be expanded at the time the configuration is read. outputs : output1 : type : nats address : ${NATS_IP}:4222","title":"Environment variables in file"},{"location":"user_guide/configuration_flags/","text":"gnmic supports a set of global flags, applicable to all sub commands, as well as local flags which are specific to each sub command. Global flags Local flags: Capabilities Get Set Subscribe Prompt Path Listen","title":"Configuration flags"},{"location":"user_guide/configuration_intro/","text":"gnmic reads configuration from three different sources, Global and local flags , environment variables and local system file . The different sources follow a precedence order where a configuration variable from a source take precedence over the next one in the below list: global and local flags Environment variables configuration file Flags # See here for a complete list of the supported global and local flags. Environment variables # gnmic can also be configured using environment variables, it will read the environment variables starting with GNMIC_ . The Env variable names are inline with the flag names as well as the configuration hierarchy. See here for more details on environment variables. File configuration # Configuration file that gnmic reads must be in one of the following formats: JSON, YAML, TOML, HCL or Properties. By default, gnmic will search for a file named .gnmic.[yml/yaml, toml, json] in the following locations and will use the first file that exists: $PWD $HOME $XDG_CONFIG_HOME $XDG_CONFIG_HOME/gnmic The default path can be overridden with --config flag. # config file default path is : # $PWD/.gnmic.[yml, toml, json], or # $HOME/.gnmic.[yml, toml, json], or # $XDG_CONFIG_HOME/.gnmic.[yml, toml, json], or # $XDG_CONFIG_HOME/gnmic/.gnmic.[yml, toml, json] gnmic capabilities # read `cfg.yml` file located in the current directory gnmic --config ./cfg.yml capabilities If the file referenced by --config flag is not present, the default path won't be tried. Example of the gnmic config files are provided in the following formats: YAML , JSON , TOML .","title":"Introduction"},{"location":"user_guide/configuration_intro/#flags","text":"See here for a complete list of the supported global and local flags.","title":"Flags"},{"location":"user_guide/configuration_intro/#environment-variables","text":"gnmic can also be configured using environment variables, it will read the environment variables starting with GNMIC_ . The Env variable names are inline with the flag names as well as the configuration hierarchy. See here for more details on environment variables.","title":"Environment variables"},{"location":"user_guide/configuration_intro/#file-configuration","text":"Configuration file that gnmic reads must be in one of the following formats: JSON, YAML, TOML, HCL or Properties. By default, gnmic will search for a file named .gnmic.[yml/yaml, toml, json] in the following locations and will use the first file that exists: $PWD $HOME $XDG_CONFIG_HOME $XDG_CONFIG_HOME/gnmic The default path can be overridden with --config flag. # config file default path is : # $PWD/.gnmic.[yml, toml, json], or # $HOME/.gnmic.[yml, toml, json], or # $XDG_CONFIG_HOME/.gnmic.[yml, toml, json], or # $XDG_CONFIG_HOME/gnmic/.gnmic.[yml, toml, json] gnmic capabilities # read `cfg.yml` file located in the current directory gnmic --config ./cfg.yml capabilities If the file referenced by --config flag is not present, the default path won't be tried. Example of the gnmic config files are provided in the following formats: YAML , JSON , TOML .","title":"File configuration"},{"location":"user_guide/gnmi_server/","text":"gNMI Server # Introduction # On top of acting as gNMI client gNMIc can run a gNMI server that supports Get , Set and Subscribe RPCs. The goal is to act as a caching point for the collected gNMI notifications and make them available to other collectors via the Subscribe RPC. Using this gNMI server feature it is possible to build gNMI based clusters and pipelines with gNMIc . The server keeps a cache of the gNMI notifications received from the defined subscriptions and utilizes it to build the Subscribe RPC responses. The unary RPCs, Get and Set, are relayed to known targets based on the Prefix.Target field. Supported features # Supports gNMI RPCs, Get, Set, Subscribe Acts as a gNMI gateway for Get and Set RPCs. Supports Service registration with Consul server. Supports all types of gNMI subscriptions, once , poll , stream . Supports all types of stream subscriptions, on-change , target-defined and sample . Supports updates-only with stream and once subscriptions. Supports suppress-redundant . Supports heartbeat-interval with on-change and sample stream subscriptions. Get RPC # The server supports the gNMI Get RPC, it allows a client to retrieve gNMI notifications from multiple targets into a single GetResponse . It relies on the GetRequest Prefix.Target field to select the target(s) against which it will run the Get RPC. If Prefix.Target is left empty or is equal to * , the Get RPC is performed against all known targets. The received GetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Get RPC to. gnmic -a gnmic-server:57400 get --path /interfaces \\ --target router1,router2,router3 Once all GetResponses are received back successfully, the notifications contained in each GetResponse are combined into a single GetResponse with each notification's Prefix.Target populated, if empty. The resulting GetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client. If the GetRequest Path has the Origin field set to gnmic , the request is performed against the internal gNMIc server configuration. Currently only the paths targets and subscriptions are supported. gnmic -a gnmic-server:57400 get --path gnmic:/targets gnmic -a gnmic-server:57400 get --path gnmic:/subscriptions Set RPC # This gNMI server supports the gNMI Set RPC, it allows a client to run a single Set RPC against multiple targets. Just like in the case of Get RPC, the server relies on the Prefix.Target field to select the target(s) against which it will run the Set RPC. If Prefix.Target is left empty or is equal to * , a Set RPC is performed against all known targets. The received SetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Set RPC to. gnmic -a gnmic-server:57400 set \\ --update /system/ssh-server/admin-state:::json:::disable \\ --target router1,router2,router3 Once all SetResponses are received back successfully, the UpdateResult s from each response are merged into a single SetResponse, with the addition of the target name set in Path.Target . Note Adding a target value to a non prefix path is not compliant with the gNMI specification which stipulates that the Target field should only be present in Prefix Paths The resulting SetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client. Subscribe RPC # The gNMIc server keeps a cache of gNMI notifications synched with the configured targets based on the configured subscriptions. The Subscribe requests received from a client are run against the afore mentioned cache, this means that a client cannot get updates about a leaf that gNMIc did not subscribe to as a client. Clients can subscribe to specific target using the gNMI Prefix.Target field, while leaving the Prefix.Target field empty or setting it to * is equivalent to subscribing to all known targets. Subscription Mode # gNMIc gNMI Server supports the 3 gNMI specified subscription modes: Once , Poll and Stream . It also supports some subscription behavior modifiers: updates-only with stream and once subscriptions. suppress-redundant . heartbeat-interval with on-change and sample stream subscriptions. Once # A subscription operating in the ONCE mode acts as a single request/response channel. The target creates the relevant update messages, transmits them, and subsequently closes the RPC. In this subscription mode, gNMIc server supports the updates-only knob. Poll # Polling subscriptions are used for on-demand retrieval of data items via long-lived RPCs. A poll subscription relates to a certain set of subscribed paths, and is initiated by sending a SubscribeRequest message with encapsulated SubscriptionList. Subscription messages contained within the SubscriptionList indicate the set of paths that are of interest to the polling client. Stream # Stream subscriptions are long-lived subscriptions which continue to transmit updates relating to the set of paths that are covered within the subscription indefinitely. In this subscription mode, gNMIc server supports the updates-only knob. On Change # When a subscription is defined to be on-change , data updates are only sent to the client when the value of the data item changes. In the case of gNMIc gNMI server, on-change subscriptions depend on the subscription writing data to the local cache, if it is a sample subscription, each update from a target will trigger an on-change update to the server client. gNMIc gNMI server supports on-change subscriptions with heartbeat-interval . If the heartbeat-interval value is set to a non zero value, the value of the data item(s) MUST be re-sent once per heartbeat interval regardless of whether the value has changed or not. Note The minimum heartbeat-interval is configurable using the field min-heartbeat-interval . It defaults to 1s If the received heartbeat-interval value is greater than zero but lower than min-heartbeat-interval , the min-heartbeat-interval value is used instead. Target Defined # When a client creates a subscription specifying the target defined mode, the target MUST determine the best type of subscription to be created on a per-leaf basis. In the case of gNMIc gNMI server, a target-defined stream subscription, is treated as an on-change subscription. Note that this does not mean that gNMIc will filter out the unchanged values received from a sample subscription to the actual targets. Sample # A sample subscription is one where data items are sent to the client once per sample-interval . The minimum supported sample-interval is configurable using the field min-sample-interval , defaults to 1ms . If within a SubscribeRequest the received sample-interval is zero, the default-sample-interval is used, defaults to 1s . Configuration # gnmi-server : # the address the gNMI server will listen to address : :57400 # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" max-subscriptions : 64 # maximum number of active Get/Set RPCs max-unary-rpc : 64 # defines the minimum allowed sample interval, this value is used when the received sample-interval # is greater than zero but lower than this minimum value. min-sample-interval : 1ms # defines the default sample interval, # this value is used when the received sample-interval is zero within a stream/sample subscription. default-sample-interval : 1s # defines the minimum heartbeat-interval # this value is used when the received heartbeat-interval is greater than zero but # lower than this minimum value min-heartbeat-interval : 1s # enables the collection of Prometheus gRPC server metrics enable-metrics : false # enable additional debug logs debug : false # Enables Consul service registration service-registration : # Consul server address, default to localhost:8500 address : # Consul Data center, defaults to dc1 datacenter : # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token # which overrides the agent's default token token : # gnmi server service check interval, only TTL Consul check is enabled # defaults to 5s check-interval : # Maximum number of failed checks before the service is deleted by Consul # defaults to 3 max-fail : # Consul service name name : # List of tags to be added to the service registration, # if available, the instance-name and cluster-name will be added as tags, # in the format: gnmic-instance=$instance-name and gnmic-cluster=$cluster-name tags : # cache configuration cache : # cache type, defaults to `oc` type : oc # string, address of the remote cache server, # irrelevant if type is `oc` address : # string, the remote server username. username : # string, the remote server password. password : # string, expiration period of received messages. expiration : 60s # enable extra logging debug : false # int64, default: 1073741824 (1 GiB). # Max number of bytes stored in the cache per subscription. max-bytes : # int64, default: 1048576. # Max number of messages stored per subscription. max-msgs-per-subscription : # int, default 100. # Batch size used by the JetStream pull subscriber. fetch-batch-size : # duration, default 100ms. # Wait time used by the JetStream pull subscriber. fetch-wait-time : Secure vs Insecure Server # Insecure Mode # By default, the server runs in insecure mode, as long as skip-verify is false and none of ca-file , cert-file and key-file are set. Secure Mode # To run this gNMI server in secure mode, there are a few options: Using self signed certificates, without client certificate verification: gnmi-server : skip-verify : true Using self signed certificates, with client certificate verification: gnmi-server : # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile Using CA provided certificates, without client certificate verification: gnmi-server : skip-verify : true # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key Using CA provided certificates, with client certificate verification: gnmi-server : # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key Fields # address # Defines the address the gNMI server will listen to. This can be a tcp socket in the format or a unix socket starting with unix:/// skip-verify # If true, the server will not verify the client's certificates. ca-file # Defines the path to the CA certificate file to be used, irrelevant if skip-verify is true cert-file # Defines the path to the server certificate file to be used. key-file # Defines the path to the server key file to be used. max-subscriptions # Defines the maximum number of allowed subscriptions. Defaults to 64 . max-unary-rpc # Defines the maximum number of active Get/Set RPCs. Defaults to 64 . min-sample-interval # Defines the minimum allowed sample interval, this value is used when the received sample-interval is greater than zero but lower than this minimum value. Defaults to 1ms default-sample-interval # Defines the default sample interval, this value is used when the received sample-interval is zero within a stream/sample subscription. Defaults to 1s min-heartbeat-interval # Defines the minimum heartbeat-interval, this value is used when the received heartbeat-interval is greater than zero but lower than this minimum value. Defaults to 1s enable-metrics # Enables the collection of Prometheus gRPC server metrics. debug # Enables additional debug logging. Caching # By default, the gNMI server uses Openconfig's gNMI cache as a backend. Distributed caching is supported using any of the other cache types specified here . When a distributed cache is used together with the gNMI server feature, a gNMI client can subscribe to any of the gNMI servers to get gNMI updates collected from all the targets. On the other hand, if the gNMI client sends a unary RPC (Get, Set), it will have be directed to the gNMI server directly connected to the target. gnmi-server : # # other gnmi-server attributes # cache : # cache type, defaults to `oc` type : oc # redis, nats or jetstream # string, address of the remote cache server, # irrelevant if type is `oc` address : # string, the remote server username. username : # string, the remote server password. password : # string, expiration period of received messages. expiration : 60s # enable extra logging. debug : false # int64, default: 1073741824 (1 GiB). # Max number of bytes stored in the cache per subscription. max-bytes : # int64, default: 1048576. # Max number of messages stored per subscription. max-msgs-per-subscription : # int, default 100. # Batch size used by the JetStream pull subscriber. fetch-batch-size : # duration, default 100ms. # Wait time used by the JetStream pull subscriber. fetch-wait-time :","title":"gNMI Server"},{"location":"user_guide/gnmi_server/#gnmi-server","text":"","title":"gNMI Server"},{"location":"user_guide/gnmi_server/#introduction","text":"On top of acting as gNMI client gNMIc can run a gNMI server that supports Get , Set and Subscribe RPCs. The goal is to act as a caching point for the collected gNMI notifications and make them available to other collectors via the Subscribe RPC. Using this gNMI server feature it is possible to build gNMI based clusters and pipelines with gNMIc . The server keeps a cache of the gNMI notifications received from the defined subscriptions and utilizes it to build the Subscribe RPC responses. The unary RPCs, Get and Set, are relayed to known targets based on the Prefix.Target field.","title":"Introduction"},{"location":"user_guide/gnmi_server/#supported-features","text":"Supports gNMI RPCs, Get, Set, Subscribe Acts as a gNMI gateway for Get and Set RPCs. Supports Service registration with Consul server. Supports all types of gNMI subscriptions, once , poll , stream . Supports all types of stream subscriptions, on-change , target-defined and sample . Supports updates-only with stream and once subscriptions. Supports suppress-redundant . Supports heartbeat-interval with on-change and sample stream subscriptions.","title":"Supported features"},{"location":"user_guide/gnmi_server/#get-rpc","text":"The server supports the gNMI Get RPC, it allows a client to retrieve gNMI notifications from multiple targets into a single GetResponse . It relies on the GetRequest Prefix.Target field to select the target(s) against which it will run the Get RPC. If Prefix.Target is left empty or is equal to * , the Get RPC is performed against all known targets. The received GetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Get RPC to. gnmic -a gnmic-server:57400 get --path /interfaces \\ --target router1,router2,router3 Once all GetResponses are received back successfully, the notifications contained in each GetResponse are combined into a single GetResponse with each notification's Prefix.Target populated, if empty. The resulting GetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client. If the GetRequest Path has the Origin field set to gnmic , the request is performed against the internal gNMIc server configuration. Currently only the paths targets and subscriptions are supported. gnmic -a gnmic-server:57400 get --path gnmic:/targets gnmic -a gnmic-server:57400 get --path gnmic:/subscriptions","title":"Get RPC"},{"location":"user_guide/gnmi_server/#set-rpc","text":"This gNMI server supports the gNMI Set RPC, it allows a client to run a single Set RPC against multiple targets. Just like in the case of Get RPC, the server relies on the Prefix.Target field to select the target(s) against which it will run the Set RPC. If Prefix.Target is left empty or is equal to * , a Set RPC is performed against all known targets. The received SetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Set RPC to. gnmic -a gnmic-server:57400 set \\ --update /system/ssh-server/admin-state:::json:::disable \\ --target router1,router2,router3 Once all SetResponses are received back successfully, the UpdateResult s from each response are merged into a single SetResponse, with the addition of the target name set in Path.Target . Note Adding a target value to a non prefix path is not compliant with the gNMI specification which stipulates that the Target field should only be present in Prefix Paths The resulting SetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client.","title":"Set RPC"},{"location":"user_guide/gnmi_server/#subscribe-rpc","text":"The gNMIc server keeps a cache of gNMI notifications synched with the configured targets based on the configured subscriptions. The Subscribe requests received from a client are run against the afore mentioned cache, this means that a client cannot get updates about a leaf that gNMIc did not subscribe to as a client. Clients can subscribe to specific target using the gNMI Prefix.Target field, while leaving the Prefix.Target field empty or setting it to * is equivalent to subscribing to all known targets.","title":"Subscribe RPC"},{"location":"user_guide/gnmi_server/#subscription-mode","text":"gNMIc gNMI Server supports the 3 gNMI specified subscription modes: Once , Poll and Stream . It also supports some subscription behavior modifiers: updates-only with stream and once subscriptions. suppress-redundant . heartbeat-interval with on-change and sample stream subscriptions.","title":"Subscription Mode"},{"location":"user_guide/gnmi_server/#once","text":"A subscription operating in the ONCE mode acts as a single request/response channel. The target creates the relevant update messages, transmits them, and subsequently closes the RPC. In this subscription mode, gNMIc server supports the updates-only knob.","title":"Once"},{"location":"user_guide/gnmi_server/#poll","text":"Polling subscriptions are used for on-demand retrieval of data items via long-lived RPCs. A poll subscription relates to a certain set of subscribed paths, and is initiated by sending a SubscribeRequest message with encapsulated SubscriptionList. Subscription messages contained within the SubscriptionList indicate the set of paths that are of interest to the polling client.","title":"Poll"},{"location":"user_guide/gnmi_server/#stream","text":"Stream subscriptions are long-lived subscriptions which continue to transmit updates relating to the set of paths that are covered within the subscription indefinitely. In this subscription mode, gNMIc server supports the updates-only knob.","title":"Stream"},{"location":"user_guide/gnmi_server/#on-change","text":"When a subscription is defined to be on-change , data updates are only sent to the client when the value of the data item changes. In the case of gNMIc gNMI server, on-change subscriptions depend on the subscription writing data to the local cache, if it is a sample subscription, each update from a target will trigger an on-change update to the server client. gNMIc gNMI server supports on-change subscriptions with heartbeat-interval . If the heartbeat-interval value is set to a non zero value, the value of the data item(s) MUST be re-sent once per heartbeat interval regardless of whether the value has changed or not. Note The minimum heartbeat-interval is configurable using the field min-heartbeat-interval . It defaults to 1s If the received heartbeat-interval value is greater than zero but lower than min-heartbeat-interval , the min-heartbeat-interval value is used instead.","title":"On Change"},{"location":"user_guide/gnmi_server/#target-defined","text":"When a client creates a subscription specifying the target defined mode, the target MUST determine the best type of subscription to be created on a per-leaf basis. In the case of gNMIc gNMI server, a target-defined stream subscription, is treated as an on-change subscription. Note that this does not mean that gNMIc will filter out the unchanged values received from a sample subscription to the actual targets.","title":"Target Defined"},{"location":"user_guide/gnmi_server/#sample","text":"A sample subscription is one where data items are sent to the client once per sample-interval . The minimum supported sample-interval is configurable using the field min-sample-interval , defaults to 1ms . If within a SubscribeRequest the received sample-interval is zero, the default-sample-interval is used, defaults to 1s .","title":"Sample"},{"location":"user_guide/gnmi_server/#configuration","text":"gnmi-server : # the address the gNMI server will listen to address : :57400 # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" max-subscriptions : 64 # maximum number of active Get/Set RPCs max-unary-rpc : 64 # defines the minimum allowed sample interval, this value is used when the received sample-interval # is greater than zero but lower than this minimum value. min-sample-interval : 1ms # defines the default sample interval, # this value is used when the received sample-interval is zero within a stream/sample subscription. default-sample-interval : 1s # defines the minimum heartbeat-interval # this value is used when the received heartbeat-interval is greater than zero but # lower than this minimum value min-heartbeat-interval : 1s # enables the collection of Prometheus gRPC server metrics enable-metrics : false # enable additional debug logs debug : false # Enables Consul service registration service-registration : # Consul server address, default to localhost:8500 address : # Consul Data center, defaults to dc1 datacenter : # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token # which overrides the agent's default token token : # gnmi server service check interval, only TTL Consul check is enabled # defaults to 5s check-interval : # Maximum number of failed checks before the service is deleted by Consul # defaults to 3 max-fail : # Consul service name name : # List of tags to be added to the service registration, # if available, the instance-name and cluster-name will be added as tags, # in the format: gnmic-instance=$instance-name and gnmic-cluster=$cluster-name tags : # cache configuration cache : # cache type, defaults to `oc` type : oc # string, address of the remote cache server, # irrelevant if type is `oc` address : # string, the remote server username. username : # string, the remote server password. password : # string, expiration period of received messages. expiration : 60s # enable extra logging debug : false # int64, default: 1073741824 (1 GiB). # Max number of bytes stored in the cache per subscription. max-bytes : # int64, default: 1048576. # Max number of messages stored per subscription. max-msgs-per-subscription : # int, default 100. # Batch size used by the JetStream pull subscriber. fetch-batch-size : # duration, default 100ms. # Wait time used by the JetStream pull subscriber. fetch-wait-time :","title":"Configuration"},{"location":"user_guide/gnmi_server/#secure-vs-insecure-server","text":"","title":"Secure vs Insecure Server"},{"location":"user_guide/gnmi_server/#insecure-mode","text":"By default, the server runs in insecure mode, as long as skip-verify is false and none of ca-file , cert-file and key-file are set.","title":"Insecure Mode"},{"location":"user_guide/gnmi_server/#secure-mode","text":"To run this gNMI server in secure mode, there are a few options: Using self signed certificates, without client certificate verification: gnmi-server : skip-verify : true Using self signed certificates, with client certificate verification: gnmi-server : # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile Using CA provided certificates, without client certificate verification: gnmi-server : skip-verify : true # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key Using CA provided certificates, with client certificate verification: gnmi-server : # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key","title":"Secure Mode"},{"location":"user_guide/gnmi_server/#fields","text":"","title":"Fields"},{"location":"user_guide/gnmi_server/#address","text":"Defines the address the gNMI server will listen to. This can be a tcp socket in the format or a unix socket starting with unix:///","title":"address"},{"location":"user_guide/gnmi_server/#skip-verify","text":"If true, the server will not verify the client's certificates.","title":"skip-verify"},{"location":"user_guide/gnmi_server/#ca-file","text":"Defines the path to the CA certificate file to be used, irrelevant if skip-verify is true","title":"ca-file"},{"location":"user_guide/gnmi_server/#cert-file","text":"Defines the path to the server certificate file to be used.","title":"cert-file"},{"location":"user_guide/gnmi_server/#key-file","text":"Defines the path to the server key file to be used.","title":"key-file"},{"location":"user_guide/gnmi_server/#max-subscriptions","text":"Defines the maximum number of allowed subscriptions. Defaults to 64 .","title":"max-subscriptions"},{"location":"user_guide/gnmi_server/#max-unary-rpc","text":"Defines the maximum number of active Get/Set RPCs. Defaults to 64 .","title":"max-unary-rpc"},{"location":"user_guide/gnmi_server/#min-sample-interval","text":"Defines the minimum allowed sample interval, this value is used when the received sample-interval is greater than zero but lower than this minimum value. Defaults to 1ms","title":"min-sample-interval"},{"location":"user_guide/gnmi_server/#default-sample-interval","text":"Defines the default sample interval, this value is used when the received sample-interval is zero within a stream/sample subscription. Defaults to 1s","title":"default-sample-interval"},{"location":"user_guide/gnmi_server/#min-heartbeat-interval","text":"Defines the minimum heartbeat-interval, this value is used when the received heartbeat-interval is greater than zero but lower than this minimum value. Defaults to 1s","title":"min-heartbeat-interval"},{"location":"user_guide/gnmi_server/#enable-metrics","text":"Enables the collection of Prometheus gRPC server metrics.","title":"enable-metrics"},{"location":"user_guide/gnmi_server/#debug","text":"Enables additional debug logging.","title":"debug"},{"location":"user_guide/gnmi_server/#caching","text":"By default, the gNMI server uses Openconfig's gNMI cache as a backend. Distributed caching is supported using any of the other cache types specified here . When a distributed cache is used together with the gNMI server feature, a gNMI client can subscribe to any of the gNMI servers to get gNMI updates collected from all the targets. On the other hand, if the gNMI client sends a unary RPC (Get, Set), it will have be directed to the gNMI server directly connected to the target. gnmi-server : # # other gnmi-server attributes # cache : # cache type, defaults to `oc` type : oc # redis, nats or jetstream # string, address of the remote cache server, # irrelevant if type is `oc` address : # string, the remote server username. username : # string, the remote server password. password : # string, expiration period of received messages. expiration : 60s # enable extra logging. debug : false # int64, default: 1073741824 (1 GiB). # Max number of bytes stored in the cache per subscription. max-bytes : # int64, default: 1048576. # Max number of messages stored per subscription. max-msgs-per-subscription : # int, default 100. # Batch size used by the JetStream pull subscriber. fetch-batch-size : # duration, default 100ms. # Wait time used by the JetStream pull subscriber. fetch-wait-time :","title":"Caching"},{"location":"user_guide/prompt_suggestions/","text":"Starting with gnmic v0.4.0 release the users can enjoy the interactive prompt mode which can be enabled with the prompt command. The prompt mode delivers two major features: simplifies gnmic commands and flags navigation, as every option is suggested and auto-completed provides interactive YANG path auto-suggestions for get , set , subscribe commands effectively making the terminal your YANG browser Using the prompt interface # Depending on the cursor position in the prompt line, a so-called suggestion box pops up with contextual auto-completions. The user can enter the suggestion box by pressing the TAB key. The \u2191 and \u2193 keys can be used to navigate the suggestion list. Select the suggested menu item with SPACE key or directly commit your command with ENTER , its that easy! The following most-common key bindings will work in the prompt mode: Key combination Description Option/Control + \u2192/\u2190 move cursor a word right/left Control + W delete a word to the left Control + Z delete a path element in the xpath string ( example ) Control + A move cursor to the beginning of a line Control + E move cursor to the end of a line Control + C discard the current line Control + D exit prompt Control + K delete the line after the cursor to the clipboard Control + U delete the line before the cursor to the clipboard Control + L clear screen Commands and flags suggestions # To make gnmic configurable and flexible we introduced a considerable amount of flags and sub-commands. To help the users navigate the sheer selection of gnmic configuration options, the prompt mode will auto-suggest the global flags, sub-commands and local flags of those sub-commands. When the prompt mode is launched, the suggestions will be shown for the top-level commands and all the global flags. Once the sub-command is typed into the terminal, the auto-suggestions will be provided for the commands nested under this command and its local flags. In the following demo we show how the command and flag suggestions work. As the prompt starts, the suggestion box immediately hints what commands and global flags are available for input as well as their description. The user starts with adding the global flags --address, --insecure, --username and then selects the capabilities command and commits it. This results in gNMI Capability RPC execution against a specified target. Mixed mode # Its perfectly fine to specify some global flags outside of the prompt command and add more within the prompt mode. For example, the following is a valid invocation: gnmic --insecure --username admin --password admin --address 10.1.0.11 prompt Here the prompt will start with with the insecure, username, password, address flags set. YANG-completions # One of the most challenging problems in the network automation field is to process the YANG models and traverse YANG trees to construct the requests used against the network elements. Be it gNMI, NETCONF or RESTCONF a users still needs to have a path pointing to specific YANG-defined node which is targeted by a request. In gNMI paths can be represented in a human readable XPATH-like form - /a/b/c[key=val]/d - and these paths are based on the underlying YANG models. The problem at hand was how to get these paths interactively, or even better - walk the YANG tree from within the CLI and dynamically build the path used in a gNMI RPC? With YANG-completions feature embedded in gnmic what used to be a dream is now a reality \ud83c\udf89 Let us explain what just happened there. In the demonstration above, we called the gnmic with the well-known flags defining the gNMI target ( address , username , password ). But this time we also added a few YANG specific flags ( --file and --dir ) that load the full set of Nokia SR OS YANG models and the 3 rd party models SR OS rely on. gnmic --address 10.1.0.11 --insecure --username admin --password admin \\ --file ~/7x50_YangModels/YANG/nokia-combined \\ --dir ~/7x50_YangModels/YANG \\ prompt In the background gnmic processed these YANG models to build the entire schema tree of the Nokia SR OS state and configuration datastores. With that in-mem stored information, gnmic was able to auto-suggest all the possible YANG paths when the user entered the --path flag which accepts gNMI paths. By using the auto-suggestion hints, a user navigated the /state tree of a router and drilled down to the version-number leaf that, in the end, was retrieved with the gNMI Get RPC. YANG-driven path suggestions gnmic is now capable of reading and processing YANG modules to enable live path auto-suggestions YANG processing # For the YANG-completion feature to work its absolutely imperative for gnmic to successfully parse and compile the YANG models. The prompt command leverages the --file and --dir flags to select the YANG models for processing. With the --file flag a user specifies a file path to a YANG file or a directory of them that gnmic will read and process. If it points to a directory it will be visited recursively reading in all *.yang files it finds. The --dir flag also points to a YANG file or a directory and indicates which additional YANG files might be required. For example, if the YANG modules that a user specified with the --file flag import or include modules that were not part of the path specified with --file , they need to be added with the --dir flag. The Examples section provide some good practical examples on how these two flags can be used together to process the YANG models from different vendors. Understanding path suggestions # When gnmic provides a user with the path suggestions it does it in a smart and intuitive way. First, it understands in what part of the tree a user currently is and suggests only the next possible elements. Additionally, the suggested next path elements will be augmented with the information extracted from the YANG model, such as: element description, as given in the YANG description statement for the element element configuration state ( rw / ro ), as defined in section 4.2.3 of RFC 7950 . node type: The containers and lists will be denoted with the [+] marker, which means that a user can type / char after them to receive suggestions for the nested elements. the [\u22ef] character belongs to a leaf-list element. an empty space will indicate the leaf element. Examples # The examples in this section will show how to use the --file and --dir flags of the prompt command with the YANG collections from different vendors and standard bodies. Nokia SR OS # YANG repo: nokia/7x50_YangModels Clone the repository with Nokia YANG models and checkout the release of interest: git clone https://github.com/nokia/7x50_YangModels cd 7x50_YangModels git checkout sros_20.7.r2 Start gnmic in prompt mode and read in the nokia-combined YANG modules: gnmic --file YANG/nokia-combined \\ --dir YANG \\ prompt This will enable path auto-suggestions for the entire tree of the Nokia SR OS YANG models. The full command with the gNMI target specified could look like this: gnmic --address 10.1.0.11 --insecure --username admin --password admin \\ prompt \\ --file ~/7x50_YangModels/YANG/nokia-combined \\ --dir ~/7x50_YangModels/YANG Openconfig # YANG repo: openconfig/public Clone the OpenConfig repository: git clone https://github.com/openconfig/public cd public Start gnmic in prompt mode and read in all the modules: gnmic --file release/models \\ --dir third_party \\ --exclude ietf-interfaces \\ prompt Note With OpenConfig models we have to use --exclude flag to exclude ietf-interfaces module from being clashed with OpenConfig interfaces module. Cisco # YANG repo: YangModels/yang Clone the YangModels/yang repo and change into the main directory of the repo: git clone https://github.com/YangModels/yang cd yang/vendor IOS-XR # The IOS-XR native YANG models are disaggregated and spread all over the place. Although its technically possible to load them all in one go, this approach will produce a lot of top-level modules making the navigation quite hard. An easier and cleaner approach would be to find the relevant module(s) and load them separately or in small batches. For example here we load BGP config and operational models together: gnmic --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \\ --file vendor/cisco/xr/721/Cisco-IOS-XR-ipv4-bgp-oper.yang \\ --dir standard/ietf \\ prompt Note We needed to include the ietf/ directory by means of the --dir flag, since the Cisco's native modules rely on the IETF modules and these modules are not in the same directory as the BGP modules. The full command that you can against the real Cisco IOS-XR node must have a target defined, the encoding set and origin suggestions enabled. Here is what it can look like: gnmic -a 10.10.30.5:57500 --insecure -e json_ietf -u admin -p Cisco123 \\ prompt \\ --file yang/vendor/cisco/xr/662/Cisco-IOS-XR-ipv4-bgp-cfg.yang \\ --file yang/vendor/cisco/xr/662/Cisco-IOS-XR-ipv4-bgp-oper.yang \\ --dir yang/standard/ietf \\ --suggest-with-origin NX-OS # Cisco NX-OS native modules, on the other hand, are aggregated in a single file, here is how you can generate the suggestions from it: gnmic --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \\ --dir standard/ietf \\ prompt Juniper # YANG repo: Juniper/yang Clone the Juniper YANG repository and change into the release directory: git clone https://github.com/Juniper/yang cd yang/20.3/20.3R1 Start gnmic and generate path suggestions for the whole configuration tree of Juniper MX: gnmic --file junos/conf --dir common prompt Note Juniper models are constructed in a way that a top-level container appears to be /configuration , that will not work with your gNMI Subscribe RPC. Instead, you should omit this top level container. So, for example, the suggested path /configuration/interfaces/interface/state should become /interfaces/interface/state . Juniper vMX doesn't support gNMI Get RPC, if you plan to test it, use gNMI Subscribe RPC With gNMI Subscribe, specify -e proto flag to enable protobuf encoding. Arista # YANG repo: aristanetworks/yang Arista uses a subset of OpenConfig modules and does not provide IETF modules inside their repo. So make sure you have IETF models available so you can reference it, a openconfig/public is a good candidate. Clone the Arista YANG repo: git clone https://github.com/aristanetworks/yang cd yang Generate path suggestions for all Arista OpenConfig modules: gnmic --file EOS-4.23.2F/openconfig/public/release/models \\ --dir ~/public/third_party/ietf \\ --exclude ietf-interfaces \\ prompt Enumeration suggestions # gnmic flags that can take pre-defined values (enumerations) will get suggestions as well. For example, no need to keep in mind which subscription modes are available, the prompt will hint you: File-path completions # Whenever a user needs to provide a file path in a prompt mode, the filepath suggestions will make the process interactive:","title":"Prompt mode"},{"location":"user_guide/prompt_suggestions/#using-the-prompt-interface","text":"Depending on the cursor position in the prompt line, a so-called suggestion box pops up with contextual auto-completions. The user can enter the suggestion box by pressing the TAB key. The \u2191 and \u2193 keys can be used to navigate the suggestion list. Select the suggested menu item with SPACE key or directly commit your command with ENTER , its that easy! The following most-common key bindings will work in the prompt mode: Key combination Description Option/Control + \u2192/\u2190 move cursor a word right/left Control + W delete a word to the left Control + Z delete a path element in the xpath string ( example ) Control + A move cursor to the beginning of a line Control + E move cursor to the end of a line Control + C discard the current line Control + D exit prompt Control + K delete the line after the cursor to the clipboard Control + U delete the line before the cursor to the clipboard Control + L clear screen","title":"Using the prompt interface"},{"location":"user_guide/prompt_suggestions/#commands-and-flags-suggestions","text":"To make gnmic configurable and flexible we introduced a considerable amount of flags and sub-commands. To help the users navigate the sheer selection of gnmic configuration options, the prompt mode will auto-suggest the global flags, sub-commands and local flags of those sub-commands. When the prompt mode is launched, the suggestions will be shown for the top-level commands and all the global flags. Once the sub-command is typed into the terminal, the auto-suggestions will be provided for the commands nested under this command and its local flags. In the following demo we show how the command and flag suggestions work. As the prompt starts, the suggestion box immediately hints what commands and global flags are available for input as well as their description. The user starts with adding the global flags --address, --insecure, --username and then selects the capabilities command and commits it. This results in gNMI Capability RPC execution against a specified target.","title":"Commands and flags suggestions"},{"location":"user_guide/prompt_suggestions/#mixed-mode","text":"Its perfectly fine to specify some global flags outside of the prompt command and add more within the prompt mode. For example, the following is a valid invocation: gnmic --insecure --username admin --password admin --address 10.1.0.11 prompt Here the prompt will start with with the insecure, username, password, address flags set.","title":"Mixed mode"},{"location":"user_guide/prompt_suggestions/#yang-completions","text":"One of the most challenging problems in the network automation field is to process the YANG models and traverse YANG trees to construct the requests used against the network elements. Be it gNMI, NETCONF or RESTCONF a users still needs to have a path pointing to specific YANG-defined node which is targeted by a request. In gNMI paths can be represented in a human readable XPATH-like form - /a/b/c[key=val]/d - and these paths are based on the underlying YANG models. The problem at hand was how to get these paths interactively, or even better - walk the YANG tree from within the CLI and dynamically build the path used in a gNMI RPC? With YANG-completions feature embedded in gnmic what used to be a dream is now a reality \ud83c\udf89 Let us explain what just happened there. In the demonstration above, we called the gnmic with the well-known flags defining the gNMI target ( address , username , password ). But this time we also added a few YANG specific flags ( --file and --dir ) that load the full set of Nokia SR OS YANG models and the 3 rd party models SR OS rely on. gnmic --address 10.1.0.11 --insecure --username admin --password admin \\ --file ~/7x50_YangModels/YANG/nokia-combined \\ --dir ~/7x50_YangModels/YANG \\ prompt In the background gnmic processed these YANG models to build the entire schema tree of the Nokia SR OS state and configuration datastores. With that in-mem stored information, gnmic was able to auto-suggest all the possible YANG paths when the user entered the --path flag which accepts gNMI paths. By using the auto-suggestion hints, a user navigated the /state tree of a router and drilled down to the version-number leaf that, in the end, was retrieved with the gNMI Get RPC. YANG-driven path suggestions gnmic is now capable of reading and processing YANG modules to enable live path auto-suggestions","title":"YANG-completions"},{"location":"user_guide/prompt_suggestions/#yang-processing","text":"For the YANG-completion feature to work its absolutely imperative for gnmic to successfully parse and compile the YANG models. The prompt command leverages the --file and --dir flags to select the YANG models for processing. With the --file flag a user specifies a file path to a YANG file or a directory of them that gnmic will read and process. If it points to a directory it will be visited recursively reading in all *.yang files it finds. The --dir flag also points to a YANG file or a directory and indicates which additional YANG files might be required. For example, if the YANG modules that a user specified with the --file flag import or include modules that were not part of the path specified with --file , they need to be added with the --dir flag. The Examples section provide some good practical examples on how these two flags can be used together to process the YANG models from different vendors.","title":"YANG processing"},{"location":"user_guide/prompt_suggestions/#understanding-path-suggestions","text":"When gnmic provides a user with the path suggestions it does it in a smart and intuitive way. First, it understands in what part of the tree a user currently is and suggests only the next possible elements. Additionally, the suggested next path elements will be augmented with the information extracted from the YANG model, such as: element description, as given in the YANG description statement for the element element configuration state ( rw / ro ), as defined in section 4.2.3 of RFC 7950 . node type: The containers and lists will be denoted with the [+] marker, which means that a user can type / char after them to receive suggestions for the nested elements. the [\u22ef] character belongs to a leaf-list element. an empty space will indicate the leaf element.","title":"Understanding path suggestions"},{"location":"user_guide/prompt_suggestions/#examples","text":"The examples in this section will show how to use the --file and --dir flags of the prompt command with the YANG collections from different vendors and standard bodies.","title":"Examples"},{"location":"user_guide/prompt_suggestions/#nokia-sr-os","text":"YANG repo: nokia/7x50_YangModels Clone the repository with Nokia YANG models and checkout the release of interest: git clone https://github.com/nokia/7x50_YangModels cd 7x50_YangModels git checkout sros_20.7.r2 Start gnmic in prompt mode and read in the nokia-combined YANG modules: gnmic --file YANG/nokia-combined \\ --dir YANG \\ prompt This will enable path auto-suggestions for the entire tree of the Nokia SR OS YANG models. The full command with the gNMI target specified could look like this: gnmic --address 10.1.0.11 --insecure --username admin --password admin \\ prompt \\ --file ~/7x50_YangModels/YANG/nokia-combined \\ --dir ~/7x50_YangModels/YANG","title":"Nokia SR OS"},{"location":"user_guide/prompt_suggestions/#openconfig","text":"YANG repo: openconfig/public Clone the OpenConfig repository: git clone https://github.com/openconfig/public cd public Start gnmic in prompt mode and read in all the modules: gnmic --file release/models \\ --dir third_party \\ --exclude ietf-interfaces \\ prompt Note With OpenConfig models we have to use --exclude flag to exclude ietf-interfaces module from being clashed with OpenConfig interfaces module.","title":"Openconfig"},{"location":"user_guide/prompt_suggestions/#cisco","text":"YANG repo: YangModels/yang Clone the YangModels/yang repo and change into the main directory of the repo: git clone https://github.com/YangModels/yang cd yang/vendor","title":"Cisco"},{"location":"user_guide/prompt_suggestions/#ios-xr","text":"The IOS-XR native YANG models are disaggregated and spread all over the place. Although its technically possible to load them all in one go, this approach will produce a lot of top-level modules making the navigation quite hard. An easier and cleaner approach would be to find the relevant module(s) and load them separately or in small batches. For example here we load BGP config and operational models together: gnmic --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \\ --file vendor/cisco/xr/721/Cisco-IOS-XR-ipv4-bgp-oper.yang \\ --dir standard/ietf \\ prompt Note We needed to include the ietf/ directory by means of the --dir flag, since the Cisco's native modules rely on the IETF modules and these modules are not in the same directory as the BGP modules. The full command that you can against the real Cisco IOS-XR node must have a target defined, the encoding set and origin suggestions enabled. Here is what it can look like: gnmic -a 10.10.30.5:57500 --insecure -e json_ietf -u admin -p Cisco123 \\ prompt \\ --file yang/vendor/cisco/xr/662/Cisco-IOS-XR-ipv4-bgp-cfg.yang \\ --file yang/vendor/cisco/xr/662/Cisco-IOS-XR-ipv4-bgp-oper.yang \\ --dir yang/standard/ietf \\ --suggest-with-origin","title":"IOS-XR"},{"location":"user_guide/prompt_suggestions/#nx-os","text":"Cisco NX-OS native modules, on the other hand, are aggregated in a single file, here is how you can generate the suggestions from it: gnmic --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \\ --dir standard/ietf \\ prompt","title":"NX-OS"},{"location":"user_guide/prompt_suggestions/#juniper","text":"YANG repo: Juniper/yang Clone the Juniper YANG repository and change into the release directory: git clone https://github.com/Juniper/yang cd yang/20.3/20.3R1 Start gnmic and generate path suggestions for the whole configuration tree of Juniper MX: gnmic --file junos/conf --dir common prompt Note Juniper models are constructed in a way that a top-level container appears to be /configuration , that will not work with your gNMI Subscribe RPC. Instead, you should omit this top level container. So, for example, the suggested path /configuration/interfaces/interface/state should become /interfaces/interface/state . Juniper vMX doesn't support gNMI Get RPC, if you plan to test it, use gNMI Subscribe RPC With gNMI Subscribe, specify -e proto flag to enable protobuf encoding.","title":"Juniper"},{"location":"user_guide/prompt_suggestions/#arista","text":"YANG repo: aristanetworks/yang Arista uses a subset of OpenConfig modules and does not provide IETF modules inside their repo. So make sure you have IETF models available so you can reference it, a openconfig/public is a good candidate. Clone the Arista YANG repo: git clone https://github.com/aristanetworks/yang cd yang Generate path suggestions for all Arista OpenConfig modules: gnmic --file EOS-4.23.2F/openconfig/public/release/models \\ --dir ~/public/third_party/ietf \\ --exclude ietf-interfaces \\ prompt","title":"Arista"},{"location":"user_guide/prompt_suggestions/#enumeration-suggestions","text":"gnmic flags that can take pre-defined values (enumerations) will get suggestions as well. For example, no need to keep in mind which subscription modes are available, the prompt will hint you:","title":"Enumeration suggestions"},{"location":"user_guide/prompt_suggestions/#file-path-completions","text":"Whenever a user needs to provide a file path in a prompt mode, the filepath suggestions will make the process interactive:","title":"File-path completions"},{"location":"user_guide/subscriptions/","text":"Defining subscriptions with subscribe command's CLI flags is a quick&easy way to work with gNMI subscriptions. A downside of that approach is that commands can get lengthy when defining multiple subscriptions and not all possible flavors and combinations of subscription can be defined. With the multiple subscriptions defined in the configuration file we make a complex task of managing multiple subscriptions for multiple targets easy. The idea behind the multiple subscriptions is to define the subscriptions separately and then bind them to the targets. Defining subscriptions # CLI-based subscription # A subscription is configured through a series of command-line interface (CLI) flags. These include, but are not limited to : --path : This flag is used to set the paths for the subscription. --mode [once | poll | stream] : Defines the subscription mode. It can be set to once, poll, or stream. --stream-mode [target-defined | sample | on-change] : Sets the stream subscription mode. The options are target-defined, sample, or on-change. --sample-interval : Determines the sample interval for a stream/sample subscription. A command executed with these flags will generate a single SubscribeRequest that is sent to the target. Every path configured with the --path flag leads to a Subscription added to the subscriptionList message. There are no constraints when defining a ONCE or POLL subscribe request. However, when a STREAM subscribe request is defined using flags, all subscriptions (paths) will adopt the same mode ( target-defined , on-change , or sample ) and stream subscription attributes such as sample-interval and heartbeat-interval . File-based subscription config # To define a subscription a user needs to create the subscriptions container in the configuration file: subscriptions : # a configurable subscription name subscription-name : # string, path to be set as the Subscribe Request Prefix prefix : # string, value to set as the SubscribeRequest Prefix Target target : # boolean, if true, the SubscribeRequest Prefix Target will be set to # the configured target name under section `targets`. # does not apply if the previous field `target` is set. set-target : # true | false # list of strings, list of subscription paths for the named subscription paths : [] # list of strings, schema definition modules models : [] # string, case insensitive, one of ONCE, STREAM, POLL mode : STREAM # string, case insensitive, if `mode` is set to STREAM, this defines the type # of streamed subscription, # one of SAMPLE, TARGET_DEFINED, ON_CHANGE stream-mode : TARGET_DEFINED # string, case insensitive, defines the gNMI encoding to be used for the subscription encoding : JSON # integer, specifies the packet marking that is to be used for the subscribe responses qos : # duration, Golang duration format, e.g: 1s, 1m30s, 1h. # specifies the sample interval for a STREAM/SAMPLE subscription sample-interval : # duration, Golang duration format, e.g: 1s, 1m30s, 1h. # The heartbeat interval value can be specified along with `ON_CHANGE` or `SAMPLE` # stream subscriptions modes and has the following meanings in each case: # - `ON_CHANGE`: The value of the data item(s) MUST be re-sent once per heartbeat # interval regardless of whether the value has changed or not. # - `SAMPLE`: The target MUST generate one telemetry update per heartbeat interval, # regardless of whether the `--suppress-redundant` flag is set to true. heartbeat-interval : # boolean, if set to true, the target SHOULD NOT generate a telemetry update message unless # the value of the path being reported on has changed since the last suppress-redundant : # boolean, if set to true, the target MUST not transmit the current state of the paths # that the client has subscribed to, but rather should send only updates to them. updates-only : # list of strings, the list of outputs to send updates to. If blank, defaults to all outputs outputs : - output1 - output2 # list of subscription definition, this field is used to define multiple stream subscriptions (target-defined, sample or on-change) # that will be created using a single SubscribeRequest (i.e: share the same gRPC stream). # This field cannot be defined if `paths`, `stream-mode`, `sample-interval`, `heartbeat-interval` or`suppress-redundant` are set. # Only fields applicable to STREAM subscriptions can be set in this list of subscriptions: # `paths`, `stream-mode`, `sample-interval`, `heartbeat-interval` or`suppress-redundant` stream-subscriptions : - paths : [] stream-mode : sample-interval : heartbeat-interval : suppress-redundant : - paths : [] stream-mode : sample-interval : heartbeat-interval : suppress-redundant : # historical subscription config: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-history.md#1-purpose history : # string, nanoseconds since Unix epoch or RFC3339 format. # if set, the history extension type will be a Snapshot request snapshot : # string, nanoseconds since Unix epoch or RFC3339 format. # if set, the history extension type will be a Range request start : # string, nanoseconds since Unix epoch or RFC3339 format. # if set, the history extension type will be a Range request end : Subscription config to gNMI SubscribeRequest # Each subscription (under subscriptions: ) results in a single SubscribeRequest being sent to the target. If paths is set, each path results in a separate Subscription message being added to the subscriptionList message. If instead of paths, a list of stream-subscriptions is defined: subscriptions : sub1 : stream-subscriptions : - paths : Each path under each stream-subscriptions will result in a separate Subscription message being added to the subscriptionList message. Examples # A single stream/sample subscription # YAML CLI PROTOTEXT subscriptions : port_stats : paths : - \"/state/port[port-id=*]/statistics\" stream-mode : sample sample-interval : 5s encoding : bytes gnmic sub --path /state/port/statistics \\ --stream-mode sample \\ --sample-interval 5s \\ --encoding bytes subscribe: { subscription: { path: { elem: { name: \"state\" } elem: { name: \"port\" } elem: { name: \"statistics\" } } mode: SAMPLE sample_interval: 5000000000 } encoding: BYTES } A single stream/on-change subscription # YAML CLI PROTOTEXT subscriptions : port_stats : paths : - \"/state/port/oper-state\" stream-mode : on-change encoding : bytes gnmic sub --path /state/port/oper-state \\ --stream-mode on-change \\ --encoding bytes subscribe: { subscription: { path: { elem: { name: \"state\" } elem: { name: \"port\" } elem: { name: \"oper-state\" } } mode: ON_CHANGE } encoding: BYTES } A ONCE subscription # YAML CLI PROTOTEXT subscriptions : system_facts : paths : - /configure/system/name - /state/system/version mode : once encoding : bytes gnmic sub --path /configure/system/name \\ --path /state/system/version \\ --mode once \\ --encoding bytes subscribe: { subscription: { path: { elem: { name: \"configure\" } elem: { name: \"port\" } elem: { name: \"name\" } } } subscription: { path: { elem: { name: \"state\" } elem: { name: \"system\" } elem: { name: \"version\" } } } mode: ONCE encoding: BYTES } Combining multiple stream subscriptions in the same gRPC stream # YAML CLI PROTOTEXT subscriptions : sub1 : stream-subscriptions : - paths : - /configure/system/name stream-mode : on-change - paths : - /state/port/statistics stream-mode : sample sample-interval : 10s encoding : bytes NA subscribe: { subscription: { path: { elem: { name: \"configure\" } elem: { name: \"system\" } elem: { name: \"name\" } } mode: ON_CHANGE } subscription: { path: { elem: { name: \"state\" } elem: { name: \"port\" } elem: { name: \"statistics\" } } mode: SAMPLE sample_interval: 10000000000 } encoding: BYTES } Configure multiple subscriptions # # part of ~/gnmic.yml config file subscriptions : # container for subscriptions port_stats : # a named subscription, a key is a name paths : # list of subscription paths for that named subscription - \"/state/port[port-id=1/1/c1/1]/statistics/out-octets\" - \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" stream-mode : sample # one of [on-change target-defined sample] sample-interval : 5s encoding : bytes service_state : paths : - \"/state/service/vpls[service-name=*]/oper-state\" - \"/state/service/vprn[service-name=*]/oper-state\" stream-mode : on-change system_facts : paths : - \"/configure/system/name\" - \"/state/system/version\" mode : once Inside that subscriptions container a user defines individual named subscriptions; in the example above two named subscriptions port_stats and service_state were defined. These subscriptions can be used on the cli via the [ --name ] flag of subscribe command: gnmic subscribe --name service_state --name port_stats Or by binding them to different targets, (see next section) Binding subscriptions # Once the subscriptions are defined, they can be flexibly associated with the targets. # part of ~/gnmic.yml config file targets : router1.lab.com : username : admin password : secret subscriptions : - port_stats - service_state router2.lab.com : username : gnmi password : telemetry subscriptions : - service_state The named subscriptions are put under the subscriptions section of a target container. As shown in the example above, it is allowed to add multiple named subscriptions under a single target; in that case each named subscription will result in a separate Subscription Request towards a target. Note If a target is not explicitly associated with any subscription, the client will subscribe to all defined subscriptions in the file. The full configuration with the subscriptions defined and associated with targets will look like this: username : admin password : nokiasr0s insecure : true targets : router1.lab.com : subscriptions : - port_stats - service_state - system_facts router2.lab.com : subscriptions : - service_state - system_facts subscriptions : port_stats : paths : - \"/state/port[port-id=1/1/c1/1]/statistics/out-octets\" - \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" stream-mode : sample sample-interval : 5s encoding : bytes service_state : paths : - \"/state/service/vpls[service-name=*]/oper-state\" - \"/state/service/vprn[service-name=*]/oper-state\" stream-mode : on-change system_facts : paths : - \"/configure/system/name\" - \"/state/system/version\" mode : once As a result of such configuration the gnmic will set up three gNMI subscriptions to router1 and two other gNMI subscriptions to router2: $ gnmic subscribe gnmic 2020 /07/06 22 :03:35.579942 target 'router2.lab.com' initialized gnmic 2020 /07/06 22 :03:35.593082 target 'router1.lab.com' initialized { \"source\" : \"router2.lab.com\" , \"subscription-name\" : \"service_state\" , \"timestamp\" : 1594065869313065895 , \"time\" : \"2020-07-06T22:04:29.313065895+02:00\" , \"prefix\" : \"state/service/vpls[service-name=testvpls]\" , \"updates\" : [ { \"Path\" : \"oper-state\" , \"values\" : { \"oper-state\" : \"down\" } } ] } { \"source\" : \"router1.lab.com\" , \"subscription-name\" : \"service_state\" , \"timestamp\" : 1594065868850351364 , \"time\" : \"2020-07-06T22:04:28.850351364+02:00\" , \"prefix\" : \"state/service/vpls[service-name=test]\" , \"updates\" : [ { \"Path\" : \"oper-state\" , \"values\" : { \"oper-state\" : \"down\" } } ] } { \"source\" : \"router1.lab.com\" , \"subscription-name\" : \"port_stats\" , \"timestamp\" : 1594065873938155916 , \"time\" : \"2020-07-06T22:04:33.938155916+02:00\" , \"prefix\" : \"state/port[port-id=1/1/c1/1]/statistics\" , \"updates\" : [ { \"Path\" : \"in-octets\" , \"values\" : { \"in-octets\" : \"671552\" } } ] } { \"source\" : \"router1.lab.com\" , \"subscription-name\" : \"port_stats\" , \"timestamp\" : 1594065873938043848 , \"time\" : \"2020-07-06T22:04:33.938043848+02:00\" , \"prefix\" : \"state/port[port-id=1/1/c1/1]/statistics\" , \"updates\" : [ { \"Path\" : \"out-octets\" , \"values\" : { \"out-octets\" : \"370930\" } } ] } ^C received sig nal 'i nterru p t '. ter mi nat i n g...","title":"Subscriptions"},{"location":"user_guide/subscriptions/#defining-subscriptions","text":"","title":"Defining subscriptions"},{"location":"user_guide/subscriptions/#cli-based-subscription","text":"A subscription is configured through a series of command-line interface (CLI) flags. These include, but are not limited to : --path : This flag is used to set the paths for the subscription. --mode [once | poll | stream] : Defines the subscription mode. It can be set to once, poll, or stream. --stream-mode [target-defined | sample | on-change] : Sets the stream subscription mode. The options are target-defined, sample, or on-change. --sample-interval : Determines the sample interval for a stream/sample subscription. A command executed with these flags will generate a single SubscribeRequest that is sent to the target. Every path configured with the --path flag leads to a Subscription added to the subscriptionList message. There are no constraints when defining a ONCE or POLL subscribe request. However, when a STREAM subscribe request is defined using flags, all subscriptions (paths) will adopt the same mode ( target-defined , on-change , or sample ) and stream subscription attributes such as sample-interval and heartbeat-interval .","title":"CLI-based subscription"},{"location":"user_guide/subscriptions/#file-based-subscription-config","text":"To define a subscription a user needs to create the subscriptions container in the configuration file: subscriptions : # a configurable subscription name subscription-name : # string, path to be set as the Subscribe Request Prefix prefix : # string, value to set as the SubscribeRequest Prefix Target target : # boolean, if true, the SubscribeRequest Prefix Target will be set to # the configured target name under section `targets`. # does not apply if the previous field `target` is set. set-target : # true | false # list of strings, list of subscription paths for the named subscription paths : [] # list of strings, schema definition modules models : [] # string, case insensitive, one of ONCE, STREAM, POLL mode : STREAM # string, case insensitive, if `mode` is set to STREAM, this defines the type # of streamed subscription, # one of SAMPLE, TARGET_DEFINED, ON_CHANGE stream-mode : TARGET_DEFINED # string, case insensitive, defines the gNMI encoding to be used for the subscription encoding : JSON # integer, specifies the packet marking that is to be used for the subscribe responses qos : # duration, Golang duration format, e.g: 1s, 1m30s, 1h. # specifies the sample interval for a STREAM/SAMPLE subscription sample-interval : # duration, Golang duration format, e.g: 1s, 1m30s, 1h. # The heartbeat interval value can be specified along with `ON_CHANGE` or `SAMPLE` # stream subscriptions modes and has the following meanings in each case: # - `ON_CHANGE`: The value of the data item(s) MUST be re-sent once per heartbeat # interval regardless of whether the value has changed or not. # - `SAMPLE`: The target MUST generate one telemetry update per heartbeat interval, # regardless of whether the `--suppress-redundant` flag is set to true. heartbeat-interval : # boolean, if set to true, the target SHOULD NOT generate a telemetry update message unless # the value of the path being reported on has changed since the last suppress-redundant : # boolean, if set to true, the target MUST not transmit the current state of the paths # that the client has subscribed to, but rather should send only updates to them. updates-only : # list of strings, the list of outputs to send updates to. If blank, defaults to all outputs outputs : - output1 - output2 # list of subscription definition, this field is used to define multiple stream subscriptions (target-defined, sample or on-change) # that will be created using a single SubscribeRequest (i.e: share the same gRPC stream). # This field cannot be defined if `paths`, `stream-mode`, `sample-interval`, `heartbeat-interval` or`suppress-redundant` are set. # Only fields applicable to STREAM subscriptions can be set in this list of subscriptions: # `paths`, `stream-mode`, `sample-interval`, `heartbeat-interval` or`suppress-redundant` stream-subscriptions : - paths : [] stream-mode : sample-interval : heartbeat-interval : suppress-redundant : - paths : [] stream-mode : sample-interval : heartbeat-interval : suppress-redundant : # historical subscription config: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-history.md#1-purpose history : # string, nanoseconds since Unix epoch or RFC3339 format. # if set, the history extension type will be a Snapshot request snapshot : # string, nanoseconds since Unix epoch or RFC3339 format. # if set, the history extension type will be a Range request start : # string, nanoseconds since Unix epoch or RFC3339 format. # if set, the history extension type will be a Range request end :","title":"File-based subscription config"},{"location":"user_guide/subscriptions/#subscription-config-to-gnmi-subscriberequest","text":"Each subscription (under subscriptions: ) results in a single SubscribeRequest being sent to the target. If paths is set, each path results in a separate Subscription message being added to the subscriptionList message. If instead of paths, a list of stream-subscriptions is defined: subscriptions : sub1 : stream-subscriptions : - paths : Each path under each stream-subscriptions will result in a separate Subscription message being added to the subscriptionList message.","title":"Subscription config to gNMI SubscribeRequest"},{"location":"user_guide/subscriptions/#examples","text":"","title":"Examples"},{"location":"user_guide/subscriptions/#a-single-streamsample-subscription","text":"YAML CLI PROTOTEXT subscriptions : port_stats : paths : - \"/state/port[port-id=*]/statistics\" stream-mode : sample sample-interval : 5s encoding : bytes gnmic sub --path /state/port/statistics \\ --stream-mode sample \\ --sample-interval 5s \\ --encoding bytes subscribe: { subscription: { path: { elem: { name: \"state\" } elem: { name: \"port\" } elem: { name: \"statistics\" } } mode: SAMPLE sample_interval: 5000000000 } encoding: BYTES }","title":"A single stream/sample subscription"},{"location":"user_guide/subscriptions/#a-single-streamon-change-subscription","text":"YAML CLI PROTOTEXT subscriptions : port_stats : paths : - \"/state/port/oper-state\" stream-mode : on-change encoding : bytes gnmic sub --path /state/port/oper-state \\ --stream-mode on-change \\ --encoding bytes subscribe: { subscription: { path: { elem: { name: \"state\" } elem: { name: \"port\" } elem: { name: \"oper-state\" } } mode: ON_CHANGE } encoding: BYTES }","title":"A single stream/on-change subscription"},{"location":"user_guide/subscriptions/#a-once-subscription","text":"YAML CLI PROTOTEXT subscriptions : system_facts : paths : - /configure/system/name - /state/system/version mode : once encoding : bytes gnmic sub --path /configure/system/name \\ --path /state/system/version \\ --mode once \\ --encoding bytes subscribe: { subscription: { path: { elem: { name: \"configure\" } elem: { name: \"port\" } elem: { name: \"name\" } } } subscription: { path: { elem: { name: \"state\" } elem: { name: \"system\" } elem: { name: \"version\" } } } mode: ONCE encoding: BYTES }","title":"A ONCE subscription"},{"location":"user_guide/subscriptions/#combining-multiple-stream-subscriptions-in-the-same-grpc-stream","text":"YAML CLI PROTOTEXT subscriptions : sub1 : stream-subscriptions : - paths : - /configure/system/name stream-mode : on-change - paths : - /state/port/statistics stream-mode : sample sample-interval : 10s encoding : bytes NA subscribe: { subscription: { path: { elem: { name: \"configure\" } elem: { name: \"system\" } elem: { name: \"name\" } } mode: ON_CHANGE } subscription: { path: { elem: { name: \"state\" } elem: { name: \"port\" } elem: { name: \"statistics\" } } mode: SAMPLE sample_interval: 10000000000 } encoding: BYTES }","title":"Combining multiple stream subscriptions in the same gRPC stream"},{"location":"user_guide/subscriptions/#configure-multiple-subscriptions","text":"# part of ~/gnmic.yml config file subscriptions : # container for subscriptions port_stats : # a named subscription, a key is a name paths : # list of subscription paths for that named subscription - \"/state/port[port-id=1/1/c1/1]/statistics/out-octets\" - \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" stream-mode : sample # one of [on-change target-defined sample] sample-interval : 5s encoding : bytes service_state : paths : - \"/state/service/vpls[service-name=*]/oper-state\" - \"/state/service/vprn[service-name=*]/oper-state\" stream-mode : on-change system_facts : paths : - \"/configure/system/name\" - \"/state/system/version\" mode : once Inside that subscriptions container a user defines individual named subscriptions; in the example above two named subscriptions port_stats and service_state were defined. These subscriptions can be used on the cli via the [ --name ] flag of subscribe command: gnmic subscribe --name service_state --name port_stats Or by binding them to different targets, (see next section)","title":"Configure multiple subscriptions"},{"location":"user_guide/subscriptions/#binding-subscriptions","text":"Once the subscriptions are defined, they can be flexibly associated with the targets. # part of ~/gnmic.yml config file targets : router1.lab.com : username : admin password : secret subscriptions : - port_stats - service_state router2.lab.com : username : gnmi password : telemetry subscriptions : - service_state The named subscriptions are put under the subscriptions section of a target container. As shown in the example above, it is allowed to add multiple named subscriptions under a single target; in that case each named subscription will result in a separate Subscription Request towards a target. Note If a target is not explicitly associated with any subscription, the client will subscribe to all defined subscriptions in the file. The full configuration with the subscriptions defined and associated with targets will look like this: username : admin password : nokiasr0s insecure : true targets : router1.lab.com : subscriptions : - port_stats - service_state - system_facts router2.lab.com : subscriptions : - service_state - system_facts subscriptions : port_stats : paths : - \"/state/port[port-id=1/1/c1/1]/statistics/out-octets\" - \"/state/port[port-id=1/1/c1/1]/statistics/in-octets\" stream-mode : sample sample-interval : 5s encoding : bytes service_state : paths : - \"/state/service/vpls[service-name=*]/oper-state\" - \"/state/service/vprn[service-name=*]/oper-state\" stream-mode : on-change system_facts : paths : - \"/configure/system/name\" - \"/state/system/version\" mode : once As a result of such configuration the gnmic will set up three gNMI subscriptions to router1 and two other gNMI subscriptions to router2: $ gnmic subscribe gnmic 2020 /07/06 22 :03:35.579942 target 'router2.lab.com' initialized gnmic 2020 /07/06 22 :03:35.593082 target 'router1.lab.com' initialized { \"source\" : \"router2.lab.com\" , \"subscription-name\" : \"service_state\" , \"timestamp\" : 1594065869313065895 , \"time\" : \"2020-07-06T22:04:29.313065895+02:00\" , \"prefix\" : \"state/service/vpls[service-name=testvpls]\" , \"updates\" : [ { \"Path\" : \"oper-state\" , \"values\" : { \"oper-state\" : \"down\" } } ] } { \"source\" : \"router1.lab.com\" , \"subscription-name\" : \"service_state\" , \"timestamp\" : 1594065868850351364 , \"time\" : \"2020-07-06T22:04:28.850351364+02:00\" , \"prefix\" : \"state/service/vpls[service-name=test]\" , \"updates\" : [ { \"Path\" : \"oper-state\" , \"values\" : { \"oper-state\" : \"down\" } } ] } { \"source\" : \"router1.lab.com\" , \"subscription-name\" : \"port_stats\" , \"timestamp\" : 1594065873938155916 , \"time\" : \"2020-07-06T22:04:33.938155916+02:00\" , \"prefix\" : \"state/port[port-id=1/1/c1/1]/statistics\" , \"updates\" : [ { \"Path\" : \"in-octets\" , \"values\" : { \"in-octets\" : \"671552\" } } ] } { \"source\" : \"router1.lab.com\" , \"subscription-name\" : \"port_stats\" , \"timestamp\" : 1594065873938043848 , \"time\" : \"2020-07-06T22:04:33.938043848+02:00\" , \"prefix\" : \"state/port[port-id=1/1/c1/1]/statistics\" , \"updates\" : [ { \"Path\" : \"out-octets\" , \"values\" : { \"out-octets\" : \"370930\" } } ] } ^C received sig nal 'i nterru p t '. ter mi nat i n g...","title":"Binding subscriptions"},{"location":"user_guide/tunnel_server/","text":"Tunnel Server # Introduction # gNMIc supports gNMI Dial-out as defined by openconfig/grpctunnel . gNMIc embeds a tunnel server to which the gNMI targets register. Once registered, gNMIc triggers the request gNMI RPC towards the target via the established tunnel. This use case is described here Server operation # When running a Subscribe RPC using gNMIc with the flag --use-tunnel-server , gNMIc starts by running the Tunnel server as defined under tunnel-server . The next steps depend on the type of RPC (Unary/Stream) and/or Subscribe Mode (poll/once/stream) Unary RPCs # gNMIc waits for tunnel-server.target-wait-time for targets to register with the tunnel server, after which it requests a new session from the server for the specified target(s) and runs the RPC through the newly established tunnel. Note that if no target is specified, the RPC runs for all registered targets. $ cat tunnel_server_config.yaml insecure: true log: true username: admin password: NokiaSrl1! tunnel-server: address: \":57401\" $ gnmic --config tunnel_server_config.yaml \\ --use-tunnel-server \\ get \\ --path /configure/system/name 2022 /03/09 10 :12:34.729037 [ gnmic ] version = dev, commit = none, date = unknown, gitURL = , docs = https://gnmic.openconfig.net 2022 /03/09 10 :12:34.729063 [ gnmic ] using config file \"tunnel_server_config.yaml\" 2022 /03/09 10 :12:34.730472 [ gnmic ] waiting for targets to register with the tunnel server... 2022 /03/09 10 :12:36.435521 [ gnmic ] tunnel server discovered target { ID:sr1 Type:GNMI_GNOI } 2022 /03/09 10 :12:36.436332 [ gnmic ] tunnel server discovered target { ID:sr2 Type:GNMI_GNOI } 2022 /03/09 10 :12:36.731125 [ gnmic ] adding target { \"name\" : \"sr1\" , \"address\" : \"sr1\" , \"username\" : \"admin\" , \"password\" : \"NokiaSrl1!\" , \"timeout\" :10000000000, \"insecure\" :true, \"skip-verify\" :false, \"subscriptions\" : [ \"sub1\" ] , \"retry-timer\" :10000000000, \"log-tls-secret\" :false, \"gzip\" :false, \"token\" : \"\" } 2022 /03/09 10 :12:36.731158 [ gnmic ] adding target { \"name\" : \"sr2\" , \"address\" : \"sr2\" , \"username\" : \"admin\" , \"password\" : \"NokiaSrl1!\" , \"timeout\" :10000000000, \"insecure\" :true, \"skip-verify\" :false, \"subscriptions\" : [ \"sub1\" ] , \"retry-timer\" :10000000000, \"log-tls-secret\" :false, \"gzip\" :false, \"token\" : \"\" } 2022 /03/09 10 :12:36.731651 [ gnmic ] sending gNMI GetRequest: prefix = '' , path = '[elem:{name:\"configure\"} elem:{name:\"system\"} elem:{name:\"name\"}]' , type = 'ALL' , encoding = 'JSON' , models = '[]' , extension = '[]' to sr1 2022 /03/09 10 :12:36.731742 [ gnmic ] sending gNMI GetRequest: prefix = '' , path = '[elem:{name:\"configure\"} elem:{name:\"system\"} elem:{name:\"name\"}]' , type = 'ALL' , encoding = 'JSON' , models = '[]' , extension = '[]' to sr2 2022 /03/09 10 :12:36.732337 [ gnmic ] dialing tunnel connection for tunnel target \"sr2\" 2022 /03/09 10 :12:36.732572 [ gnmic ] dialing tunnel connection for tunnel target \"sr1\" [ sr1 ] [ [ sr1 ] { [ sr1 ] \"source\" : \"sr1\" , [ sr1 ] \"timestamp\" : 1646849561604621769 , [ sr1 ] \"time\" : \"2022-03-09T10:12:41.604621769-08:00\" , [ sr1 ] \"updates\" : [ [ sr1 ] { [ sr1 ] \"Path\" : \"configure/system/name\" , [ sr1 ] \"values\" : { [ sr1 ] \"configure/system/name\" : \"sr1\" [ sr1 ] } [ sr1 ] } [ sr1 ] ] [ sr1 ] } [ sr1 ] ] [ sr2 ] [ [ sr2 ] { [ sr2 ] \"source\" : \"sr2\" , [ sr2 ] \"timestamp\" : 1646849562004804732 , [ sr2 ] \"time\" : \"2022-03-09T10:12:42.004804732-08:00\" , [ sr2 ] \"updates\" : [ [ sr2 ] { [ sr2 ] \"Path\" : \"configure/system/name\" , [ sr2 ] \"values\" : { [ sr2 ] \"configure/system/name\" : \"sr2\" [ sr2 ] } [ sr2 ] } [ sr2 ] ] [ sr2 ] } [ sr2 ] ] Subscribe RPC # Poll and Once subscription # When a Poll or Once subscription are requested, gNMIc behaves the same way as for a unary RPC, i.e waits for targets to register then runs the RPC. Stream subscription # In the case of a stream subscription, gNMIc triggers the Subscribe RPC as soon as a target registers. Similarly, a stream subscription will be stopped when a target deregisters from the tunnel server. Configuration # tunnel-server : # the address the tunnel server will listen to address : # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # the wait time before triggering unary RPCs or subscribe poll/once target-wait-time : 2s # enables the collection of Prometheus gRPC server metrics enable-metrics : false # enable additional debug logs debug : false Combining Tunnel server with a gNMI server # It is possible to start gNMIc with both a gnmi-server and tunnel-server enabled. This mode allows to run gNMI RPCs against gNMIc 's gNMI server, they will routed to the relevant targets ( --target flag) or to all known target (i.e registered targets) The configuration file would look like: insecure : true username : admin password : NokiaSrl1! subscriptions : sub1 : paths : - /state/port sample-interface : 10s gnmi-server : address : :57400 tunnel-server : address : :57401 targets : - id : .* type : GNMI_GNOI config : subscriptions : - sub1 Running a Get RPC towards all registered targets $ gnmic -a localhost:57400 --insecure get \\ --path /configure/system/name [ { \"source\" : \"localhost\" , \"timestamp\" : 1646850987401608313 , \"time\" : \"2022-03-09T10:36:27.401608313-08:00\" , \"target\" : \"sr2\" , \"updates\" : [ { \"Path\" : \"configure/system/name\" , \"values\" : { \"configure/system/name\" : \"sr2\" } } ] } , { \"source\" : \"localhost\" , \"timestamp\" : 1646850987205206394 , \"time\" : \"2022-03-09T10:36:27.205206394-08:00\" , \"target\" : \"sr1\" , \"updates\" : [ { \"Path\" : \"configure/system/name\" , \"values\" : { \"configure/system/name\" : \"sr1\" } } ] } ] Running a Get RPC towards a single target $ gnmic -a localhost:57400 --insecure \\ --target sr1 \\ get --path /configure/system/name [ { \"source\" : \"localhost\" , \"timestamp\" : 1646851044004381267 , \"time\" : \"2022-03-09T10:37:24.004381267-08:00\" , \"target\" : \"sr1\" , \"updates\" : [ { \"Path\" : \"configure/system/name\" , \"values\" : { \"configure/system/name\" : \"sr1\" } } ] } ] For detailed configuration of the gnmi-server check this page","title":"Tunnel Server"},{"location":"user_guide/tunnel_server/#tunnel-server","text":"","title":"Tunnel Server"},{"location":"user_guide/tunnel_server/#introduction","text":"gNMIc supports gNMI Dial-out as defined by openconfig/grpctunnel . gNMIc embeds a tunnel server to which the gNMI targets register. Once registered, gNMIc triggers the request gNMI RPC towards the target via the established tunnel. This use case is described here","title":"Introduction"},{"location":"user_guide/tunnel_server/#server-operation","text":"When running a Subscribe RPC using gNMIc with the flag --use-tunnel-server , gNMIc starts by running the Tunnel server as defined under tunnel-server . The next steps depend on the type of RPC (Unary/Stream) and/or Subscribe Mode (poll/once/stream)","title":"Server operation"},{"location":"user_guide/tunnel_server/#unary-rpcs","text":"gNMIc waits for tunnel-server.target-wait-time for targets to register with the tunnel server, after which it requests a new session from the server for the specified target(s) and runs the RPC through the newly established tunnel. Note that if no target is specified, the RPC runs for all registered targets. $ cat tunnel_server_config.yaml insecure: true log: true username: admin password: NokiaSrl1! tunnel-server: address: \":57401\" $ gnmic --config tunnel_server_config.yaml \\ --use-tunnel-server \\ get \\ --path /configure/system/name 2022 /03/09 10 :12:34.729037 [ gnmic ] version = dev, commit = none, date = unknown, gitURL = , docs = https://gnmic.openconfig.net 2022 /03/09 10 :12:34.729063 [ gnmic ] using config file \"tunnel_server_config.yaml\" 2022 /03/09 10 :12:34.730472 [ gnmic ] waiting for targets to register with the tunnel server... 2022 /03/09 10 :12:36.435521 [ gnmic ] tunnel server discovered target { ID:sr1 Type:GNMI_GNOI } 2022 /03/09 10 :12:36.436332 [ gnmic ] tunnel server discovered target { ID:sr2 Type:GNMI_GNOI } 2022 /03/09 10 :12:36.731125 [ gnmic ] adding target { \"name\" : \"sr1\" , \"address\" : \"sr1\" , \"username\" : \"admin\" , \"password\" : \"NokiaSrl1!\" , \"timeout\" :10000000000, \"insecure\" :true, \"skip-verify\" :false, \"subscriptions\" : [ \"sub1\" ] , \"retry-timer\" :10000000000, \"log-tls-secret\" :false, \"gzip\" :false, \"token\" : \"\" } 2022 /03/09 10 :12:36.731158 [ gnmic ] adding target { \"name\" : \"sr2\" , \"address\" : \"sr2\" , \"username\" : \"admin\" , \"password\" : \"NokiaSrl1!\" , \"timeout\" :10000000000, \"insecure\" :true, \"skip-verify\" :false, \"subscriptions\" : [ \"sub1\" ] , \"retry-timer\" :10000000000, \"log-tls-secret\" :false, \"gzip\" :false, \"token\" : \"\" } 2022 /03/09 10 :12:36.731651 [ gnmic ] sending gNMI GetRequest: prefix = '' , path = '[elem:{name:\"configure\"} elem:{name:\"system\"} elem:{name:\"name\"}]' , type = 'ALL' , encoding = 'JSON' , models = '[]' , extension = '[]' to sr1 2022 /03/09 10 :12:36.731742 [ gnmic ] sending gNMI GetRequest: prefix = '' , path = '[elem:{name:\"configure\"} elem:{name:\"system\"} elem:{name:\"name\"}]' , type = 'ALL' , encoding = 'JSON' , models = '[]' , extension = '[]' to sr2 2022 /03/09 10 :12:36.732337 [ gnmic ] dialing tunnel connection for tunnel target \"sr2\" 2022 /03/09 10 :12:36.732572 [ gnmic ] dialing tunnel connection for tunnel target \"sr1\" [ sr1 ] [ [ sr1 ] { [ sr1 ] \"source\" : \"sr1\" , [ sr1 ] \"timestamp\" : 1646849561604621769 , [ sr1 ] \"time\" : \"2022-03-09T10:12:41.604621769-08:00\" , [ sr1 ] \"updates\" : [ [ sr1 ] { [ sr1 ] \"Path\" : \"configure/system/name\" , [ sr1 ] \"values\" : { [ sr1 ] \"configure/system/name\" : \"sr1\" [ sr1 ] } [ sr1 ] } [ sr1 ] ] [ sr1 ] } [ sr1 ] ] [ sr2 ] [ [ sr2 ] { [ sr2 ] \"source\" : \"sr2\" , [ sr2 ] \"timestamp\" : 1646849562004804732 , [ sr2 ] \"time\" : \"2022-03-09T10:12:42.004804732-08:00\" , [ sr2 ] \"updates\" : [ [ sr2 ] { [ sr2 ] \"Path\" : \"configure/system/name\" , [ sr2 ] \"values\" : { [ sr2 ] \"configure/system/name\" : \"sr2\" [ sr2 ] } [ sr2 ] } [ sr2 ] ] [ sr2 ] } [ sr2 ] ]","title":"Unary RPCs"},{"location":"user_guide/tunnel_server/#subscribe-rpc","text":"","title":"Subscribe RPC"},{"location":"user_guide/tunnel_server/#poll-and-once-subscription","text":"When a Poll or Once subscription are requested, gNMIc behaves the same way as for a unary RPC, i.e waits for targets to register then runs the RPC.","title":"Poll and Once subscription"},{"location":"user_guide/tunnel_server/#stream-subscription","text":"In the case of a stream subscription, gNMIc triggers the Subscribe RPC as soon as a target registers. Similarly, a stream subscription will be stopped when a target deregisters from the tunnel server.","title":"Stream subscription"},{"location":"user_guide/tunnel_server/#configuration","text":"tunnel-server : # the address the tunnel server will listen to address : # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # the wait time before triggering unary RPCs or subscribe poll/once target-wait-time : 2s # enables the collection of Prometheus gRPC server metrics enable-metrics : false # enable additional debug logs debug : false","title":"Configuration"},{"location":"user_guide/tunnel_server/#combining-tunnel-server-with-a-gnmi-server","text":"It is possible to start gNMIc with both a gnmi-server and tunnel-server enabled. This mode allows to run gNMI RPCs against gNMIc 's gNMI server, they will routed to the relevant targets ( --target flag) or to all known target (i.e registered targets) The configuration file would look like: insecure : true username : admin password : NokiaSrl1! subscriptions : sub1 : paths : - /state/port sample-interface : 10s gnmi-server : address : :57400 tunnel-server : address : :57401 targets : - id : .* type : GNMI_GNOI config : subscriptions : - sub1 Running a Get RPC towards all registered targets $ gnmic -a localhost:57400 --insecure get \\ --path /configure/system/name [ { \"source\" : \"localhost\" , \"timestamp\" : 1646850987401608313 , \"time\" : \"2022-03-09T10:36:27.401608313-08:00\" , \"target\" : \"sr2\" , \"updates\" : [ { \"Path\" : \"configure/system/name\" , \"values\" : { \"configure/system/name\" : \"sr2\" } } ] } , { \"source\" : \"localhost\" , \"timestamp\" : 1646850987205206394 , \"time\" : \"2022-03-09T10:36:27.205206394-08:00\" , \"target\" : \"sr1\" , \"updates\" : [ { \"Path\" : \"configure/system/name\" , \"values\" : { \"configure/system/name\" : \"sr1\" } } ] } ] Running a Get RPC towards a single target $ gnmic -a localhost:57400 --insecure \\ --target sr1 \\ get --path /configure/system/name [ { \"source\" : \"localhost\" , \"timestamp\" : 1646851044004381267 , \"time\" : \"2022-03-09T10:37:24.004381267-08:00\" , \"target\" : \"sr1\" , \"updates\" : [ { \"Path\" : \"configure/system/name\" , \"values\" : { \"configure/system/name\" : \"sr1\" } } ] } ] For detailed configuration of the gnmi-server check this page","title":"Combining Tunnel server with a gNMI server"},{"location":"user_guide/actions/actions/","text":"Actions # gNMIc supports running actions as result of an event, possible triggering events are: A gNMI SubscribeResponse or GetReponse message is received and matches certain criteria. A target is discovered or deleted by a target loader. There are 4 types of actions: http : build and send an HTTP request gNMI : run a Get, Set or Subscribe ONCE gNMI RPC as a gNMI client template : execute a Go template against the received input script : run arbitrary shell scripts/commands. The actions are executed in sequence. An action can use the result of any previous action as one of it inputs using the Go Template syntax {{ .Env.$action_name }} or {{ index .Env \"$action_name\"}} HTTP Action # Using the HTTP action you can send an HTTP request to a server. The request body can be customized using Go Templates that take the event message or the discovered target as input. actions : counter1_alert : # action type type : http # HTTP method method : POST # target url, can be a go template url : http://remote-server:8080/ # http headers to add to the request headers : content-type : application/text # http request timeout timeout : 5s # go template used to build the request body. # if left empty the whole event message is added as a json object to the request's body body : '\"counter1\" crossed threshold, value={{ index .Values \"counter1\" }}' # enable extra logging debug : false gNMI Action # Using the gNMI action you can trigger a gNMI Get, Set or Subscribe ONCE RPC. Just like the HTTP action the RPC fields can be customized using Go Templates actions : my_gnmi_action : # action type type : gnmi # gNMI rpc, defaults to `get`, # if `set` is used it will default to a set update. # to trigger a set replace, use `set-replace`. # `subscribe` is always a subscribe with mode=ONCE # possible values: `get`, `set`, `set-update`, `set-replace`, `set-delete`, `sub`, `subscribe` rpc : set # the target router, it defaults to the value in tag \"source\" # the value `all` means all known targets target : '{{ index .Event.Tags \"source\" }}' # paths templates to build xpaths paths : - | {{ if eq ( index .Event.Tags \"interface_name\" ) \"ethernet-1/1\"}} {{$interfaceName := \"ethernet-1/2\"}} {{else}} {{$interfaceName := \"ethernet-1/1\"}} {{end}} /interfaces/interface[name={{$interfaceName}}]/admin-state # values templates to build the values in case of set-update or set-replace values : - \"enable\" # data-type in case of get RPC, one of: ALL, CONFIG, STATE, OPERATIONAL data-type : ALL # gNMI encoding, defaults to json encoding : json # debug, enable extra logging debug : false Template Action # The Template action allows to combine different data sources and produce custom payloads to be writen to a remote server or simply to a file. The template is a Go Template that is executed against the Input message that triggered the action, any variable defined by the trigger processor as well as the results of any previous action. Data Template syntax Input Messge {{ .Input }} Trigger Variables {{ .Vars }} Previous actions results {{ .Env.$action_name }} or {{ index .Env \"$action_name\"}} actions : awesome_template : # action type type : template # template string, if not present template-file applies. template : '{{ . }}' # path to a file, or a glob. # applies only if `.template `is not set. # if not template and template-file are not set, # the default template `{{ . }}` is used. template-file : # string, either `stdout` or a path to a file # the result of executing to template will be written to the file # specified by .output output : # debug, enable extra logging debug : false Script Action # The Script action allows to run arbitrary scripts as a result of an event trigger. The commands to be executed can be specified using the field command , e.g: actions : weather : type : script shell : /bin/bash command : | curl wttr.in curl cheat.sh Or using the field file , e.g: actions : exec : type : script file : ./my_executable_script.sh When using command , the shell interpreter can be set using shell field. Otherwise it defaults to /bin/bash . Examples # Add basic configuration to targets upon discovery # Referencing Actions under a target loader allows to run then in sequence when a target is discovered. This allows to add some basic configuration to a target upon discovery before starting the gNMI subscriptions In the below example, a docker loader is defined. It discovers Docker containers with label clab-node-kind=srl and adds them as gNMI targets. Before the targets are added to the target's list for subscriptions, a list of actions are executed: config_interfaces , config_subinterfaces and config_network_instances username : admin password : NokiaSrl1! skip-verify : true encoding : ascii log : true subscriptions : sub1 : paths : - /interface/statistics - /network-instance/statistics loader : type : docker filters : - containers : - label : clab-node-kind=srl on-add : - config_interfaces - config_sub_interfaces - config_netins outputs : out : type : file format : event filename : /path/to/file actions : config_interfaces : name : config_interfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/admin-state - /interface[name=ethernet-1/2]/admin-state values : - enable - enable config_subinterfaces : name : config_subinterfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/subinterface[index=0]/admin-state - /interface[name=ethernet-1/2]/subinterface[index=0]/admin-state values : - enable - enable config_network_instances : name : config_network_instances type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /network-instance[name=default]/admin-state - /network-instance[name=default]/interface - /network-instance[name=default]/interface values : - enable - '{\"name\": \"ethernet-1/1.0\"}' - '{\"name\": \"ethernet-1/2.0\"}' Clone a network topology and deploy it using containerlab # Using lldp neighbor information it's possible to build a containerlab topology using gnmic actions. In the below confoguration file, an event processor called clone-topology is defined. When triggered it will run a series of actions to gather information (chassis type, lldp neighbors, configuration,...) from the defined targets. It then builds a containerlab topology from a defined template and the gathered info, writes it to a file and runs a clab deploy command. username : admin password : NokiaSrl1! skip-verify : true encoding : json_ietf # log: true targets : srl1 : srl2 : srl3 : processors : clone-topology : event-trigger : # debug: true actions : - chassis - lldp - read_config - write_config - clab_topo - deploy_topo actions : chassis : name : chassis type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /platform/chassis/type lldp : name : lldp type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /system/lldp/interface[name=ethernet-*] read_config : name : read_config type : gnmi target : all rpc : get data-type : config encoding : json_ietf #debug: true paths : - / write_config : name : write_config type : template template : | {{- range $n, $m := .Env.read_config }} {{- $filename := print $n \".json\"}} {{ file.Write $filename (index $m 0 \"updates\" 0 \"values\" \"\" | data.ToJSONPretty \" \" ) }} {{- end }} #debug: true clab_topo : name : clab_topo type : template #debug: true output : gnmic.clab.yaml template : | name: gNMIc-action-generated topology: defaults: kind: srl kinds: srl: image: ghcr.io/nokia/srlinux:latest nodes: {{- range $n, $m := .Env.lldp }} {{- $type := index $.Env.chassis $n 0 0 \"values\" \"/srl_nokia-platform:platform/srl_nokia-platform-chassis:chassis/type\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D1\" \"ixrd1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D2\" \"ixrd2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D3\" \"ixrd3\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-6\" \"ixr6\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-10\" \"ixr10\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H1\" \"ixrh1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H2\" \"ixrh2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H3\" \"ixrh3\" }} {{ $n | strings.TrimPrefix \"clab-test1-\" }}: type: {{ $type }} startup-config: {{ print $n \".json\"}} {{- end }} links: {{- range $n, $m := .Env.lldp }} {{- range $rsp := $m }} {{- range $ev := $rsp }} {{- if index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $node1 := $ev.tags.source | strings.TrimPrefix \"clab-test1-\" }} {{- $iface1 := $ev.tags.interface_name | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- $node2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $iface2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/port-id\" | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- if lt $node1 $node2 }} - endpoints: [\"{{ $node1 }}:{{ $iface1 }}\", \"{{ $node2 }}:{{ $iface2 }}\"] {{- end }} {{- end }} {{- end }} {{- end }} {{- end }} deploy_topo : name : deploy_topo type : script command : sudo clab dep -t gnmic.clab.yaml --reconfigure debug : true The above described processor can be triggered with the below command: gnmic --config clone.yaml get --path /system/name --processor clone-topology","title":"Actions"},{"location":"user_guide/actions/actions/#actions","text":"gNMIc supports running actions as result of an event, possible triggering events are: A gNMI SubscribeResponse or GetReponse message is received and matches certain criteria. A target is discovered or deleted by a target loader. There are 4 types of actions: http : build and send an HTTP request gNMI : run a Get, Set or Subscribe ONCE gNMI RPC as a gNMI client template : execute a Go template against the received input script : run arbitrary shell scripts/commands. The actions are executed in sequence. An action can use the result of any previous action as one of it inputs using the Go Template syntax {{ .Env.$action_name }} or {{ index .Env \"$action_name\"}}","title":"Actions"},{"location":"user_guide/actions/actions/#http-action","text":"Using the HTTP action you can send an HTTP request to a server. The request body can be customized using Go Templates that take the event message or the discovered target as input. actions : counter1_alert : # action type type : http # HTTP method method : POST # target url, can be a go template url : http://remote-server:8080/ # http headers to add to the request headers : content-type : application/text # http request timeout timeout : 5s # go template used to build the request body. # if left empty the whole event message is added as a json object to the request's body body : '\"counter1\" crossed threshold, value={{ index .Values \"counter1\" }}' # enable extra logging debug : false","title":"HTTP Action"},{"location":"user_guide/actions/actions/#gnmi-action","text":"Using the gNMI action you can trigger a gNMI Get, Set or Subscribe ONCE RPC. Just like the HTTP action the RPC fields can be customized using Go Templates actions : my_gnmi_action : # action type type : gnmi # gNMI rpc, defaults to `get`, # if `set` is used it will default to a set update. # to trigger a set replace, use `set-replace`. # `subscribe` is always a subscribe with mode=ONCE # possible values: `get`, `set`, `set-update`, `set-replace`, `set-delete`, `sub`, `subscribe` rpc : set # the target router, it defaults to the value in tag \"source\" # the value `all` means all known targets target : '{{ index .Event.Tags \"source\" }}' # paths templates to build xpaths paths : - | {{ if eq ( index .Event.Tags \"interface_name\" ) \"ethernet-1/1\"}} {{$interfaceName := \"ethernet-1/2\"}} {{else}} {{$interfaceName := \"ethernet-1/1\"}} {{end}} /interfaces/interface[name={{$interfaceName}}]/admin-state # values templates to build the values in case of set-update or set-replace values : - \"enable\" # data-type in case of get RPC, one of: ALL, CONFIG, STATE, OPERATIONAL data-type : ALL # gNMI encoding, defaults to json encoding : json # debug, enable extra logging debug : false","title":"gNMI Action"},{"location":"user_guide/actions/actions/#template-action","text":"The Template action allows to combine different data sources and produce custom payloads to be writen to a remote server or simply to a file. The template is a Go Template that is executed against the Input message that triggered the action, any variable defined by the trigger processor as well as the results of any previous action. Data Template syntax Input Messge {{ .Input }} Trigger Variables {{ .Vars }} Previous actions results {{ .Env.$action_name }} or {{ index .Env \"$action_name\"}} actions : awesome_template : # action type type : template # template string, if not present template-file applies. template : '{{ . }}' # path to a file, or a glob. # applies only if `.template `is not set. # if not template and template-file are not set, # the default template `{{ . }}` is used. template-file : # string, either `stdout` or a path to a file # the result of executing to template will be written to the file # specified by .output output : # debug, enable extra logging debug : false","title":"Template Action"},{"location":"user_guide/actions/actions/#script-action","text":"The Script action allows to run arbitrary scripts as a result of an event trigger. The commands to be executed can be specified using the field command , e.g: actions : weather : type : script shell : /bin/bash command : | curl wttr.in curl cheat.sh Or using the field file , e.g: actions : exec : type : script file : ./my_executable_script.sh When using command , the shell interpreter can be set using shell field. Otherwise it defaults to /bin/bash .","title":"Script Action"},{"location":"user_guide/actions/actions/#examples","text":"","title":"Examples"},{"location":"user_guide/actions/actions/#add-basic-configuration-to-targets-upon-discovery","text":"Referencing Actions under a target loader allows to run then in sequence when a target is discovered. This allows to add some basic configuration to a target upon discovery before starting the gNMI subscriptions In the below example, a docker loader is defined. It discovers Docker containers with label clab-node-kind=srl and adds them as gNMI targets. Before the targets are added to the target's list for subscriptions, a list of actions are executed: config_interfaces , config_subinterfaces and config_network_instances username : admin password : NokiaSrl1! skip-verify : true encoding : ascii log : true subscriptions : sub1 : paths : - /interface/statistics - /network-instance/statistics loader : type : docker filters : - containers : - label : clab-node-kind=srl on-add : - config_interfaces - config_sub_interfaces - config_netins outputs : out : type : file format : event filename : /path/to/file actions : config_interfaces : name : config_interfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/admin-state - /interface[name=ethernet-1/2]/admin-state values : - enable - enable config_subinterfaces : name : config_subinterfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/subinterface[index=0]/admin-state - /interface[name=ethernet-1/2]/subinterface[index=0]/admin-state values : - enable - enable config_network_instances : name : config_network_instances type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /network-instance[name=default]/admin-state - /network-instance[name=default]/interface - /network-instance[name=default]/interface values : - enable - '{\"name\": \"ethernet-1/1.0\"}' - '{\"name\": \"ethernet-1/2.0\"}'","title":"Add basic configuration to targets upon discovery"},{"location":"user_guide/actions/actions/#clone-a-network-topology-and-deploy-it-using-containerlab","text":"Using lldp neighbor information it's possible to build a containerlab topology using gnmic actions. In the below confoguration file, an event processor called clone-topology is defined. When triggered it will run a series of actions to gather information (chassis type, lldp neighbors, configuration,...) from the defined targets. It then builds a containerlab topology from a defined template and the gathered info, writes it to a file and runs a clab deploy command. username : admin password : NokiaSrl1! skip-verify : true encoding : json_ietf # log: true targets : srl1 : srl2 : srl3 : processors : clone-topology : event-trigger : # debug: true actions : - chassis - lldp - read_config - write_config - clab_topo - deploy_topo actions : chassis : name : chassis type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /platform/chassis/type lldp : name : lldp type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /system/lldp/interface[name=ethernet-*] read_config : name : read_config type : gnmi target : all rpc : get data-type : config encoding : json_ietf #debug: true paths : - / write_config : name : write_config type : template template : | {{- range $n, $m := .Env.read_config }} {{- $filename := print $n \".json\"}} {{ file.Write $filename (index $m 0 \"updates\" 0 \"values\" \"\" | data.ToJSONPretty \" \" ) }} {{- end }} #debug: true clab_topo : name : clab_topo type : template #debug: true output : gnmic.clab.yaml template : | name: gNMIc-action-generated topology: defaults: kind: srl kinds: srl: image: ghcr.io/nokia/srlinux:latest nodes: {{- range $n, $m := .Env.lldp }} {{- $type := index $.Env.chassis $n 0 0 \"values\" \"/srl_nokia-platform:platform/srl_nokia-platform-chassis:chassis/type\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D1\" \"ixrd1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D2\" \"ixrd2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D3\" \"ixrd3\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-6\" \"ixr6\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-10\" \"ixr10\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H1\" \"ixrh1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H2\" \"ixrh2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H3\" \"ixrh3\" }} {{ $n | strings.TrimPrefix \"clab-test1-\" }}: type: {{ $type }} startup-config: {{ print $n \".json\"}} {{- end }} links: {{- range $n, $m := .Env.lldp }} {{- range $rsp := $m }} {{- range $ev := $rsp }} {{- if index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $node1 := $ev.tags.source | strings.TrimPrefix \"clab-test1-\" }} {{- $iface1 := $ev.tags.interface_name | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- $node2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $iface2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/port-id\" | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- if lt $node1 $node2 }} - endpoints: [\"{{ $node1 }}:{{ $iface1 }}\", \"{{ $node2 }}:{{ $iface2 }}\"] {{- end }} {{- end }} {{- end }} {{- end }} {{- end }} deploy_topo : name : deploy_topo type : script command : sudo clab dep -t gnmic.clab.yaml --reconfigure debug : true The above described processor can be triggered with the below command: gnmic --config clone.yaml get --path /system/name --processor clone-topology","title":"Clone a network topology and deploy it using containerlab"},{"location":"user_guide/api/api_intro/","text":"A limited set of REST endpoints are supported, these are mainly used to allow for a clustered deployment for multiple gnmic instances. The API can be used to automate (to a certain extent) the targets configuration loading and starting/stopping subscriptions. Configuration # Enabling the API server can be done via a command line flag: gnmic --config gnmic.yaml subscribe --api \":7890\" via ENV variable: GNMIC_API=':7890' Or via file configuration, by adding the below line to the config file: api : \":7890\" More advanced API configuration options (like a secure API Server) can be achieved by setting the fields under api-server . api-server : # string, in the form IP:port, the IP part can be omitted. # if not set, it defaults to the value of `api` in the file main level. # if `api` is not set, the default is `:7890` address : :7890 # duration, the server timeout. # The set value is equally split between read and write timeouts timeout : 10s # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # boolean, if true, the server will also handle the path /metrics and serve # gNMIc's enabled prometheus metrics. enable-metrics : false # boolean, enables extra debug log printing debug : false API Endpoints # Configuration Targets Cluster Other","title":"Introduction"},{"location":"user_guide/api/api_intro/#configuration","text":"Enabling the API server can be done via a command line flag: gnmic --config gnmic.yaml subscribe --api \":7890\" via ENV variable: GNMIC_API=':7890' Or via file configuration, by adding the below line to the config file: api : \":7890\" More advanced API configuration options (like a secure API Server) can be achieved by setting the fields under api-server . api-server : # string, in the form IP:port, the IP part can be omitted. # if not set, it defaults to the value of `api` in the file main level. # if `api` is not set, the default is `:7890` address : :7890 # duration, the server timeout. # The set value is equally split between read and write timeouts timeout : 10s # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # boolean, if true, the server will also handle the path /metrics and serve # gNMIc's enabled prometheus metrics. enable-metrics : false # boolean, enables extra debug log printing debug : false","title":"Configuration"},{"location":"user_guide/api/api_intro/#api-endpoints","text":"Configuration Targets Cluster Other","title":"API Endpoints"},{"location":"user_guide/api/cluster/","text":"GET /api/v1/cluster # Request gNMIc cluster state and details Returns gNMIc cluster state and details Request 200 OK 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/cluster { \"name\" : \"collectors\" , \"number-of-locked-targets\" : 70 , \"leader\" : \"clab-telemetry-gnmic1\" , \"members\" : [ { \"name\" : \"clab-telemetry-gnmic1\" , \"api-endpoint\" : \"clab-telemetry-gnmic1:7890\" , \"is-leader\" : true , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab2-leaf6\" , \"clab-lab5-spine2\" , \"clab-lab4-leaf4\" , \"clab-lab2-leaf8\" , \"clab-lab3-leaf2\" , \"clab-lab5-spine1\" , \"clab-lab1-spine1\" , \"clab-lab2-super-spine2\" , \"clab-lab3-super-spine1\" , \"clab-lab4-spine3\" , \"clab-lab2-spine3\" , \"clab-lab3-leaf7\" , \"clab-lab5-leaf7\" , \"clab-lab5-leaf8\" , \"clab-lab1-spine2\" , \"clab-lab4-leaf8\" , \"clab-lab4-leaf1\" , \"clab-lab4-spine1\" , \"clab-lab2-spine2\" , \"clab-lab3-spine2\" , \"clab-lab1-leaf8\" , \"clab-lab3-leaf8\" , \"clab-lab4-leaf2\" ] }, { \"name\" : \"clab-telemetry-gnmic2\" , \"api-endpoint\" : \"clab-telemetry-gnmic2:7891\" , \"number-of-locked-nodes\" : 24 , \"locked-targets\" : [ \"clab-lab3-leaf6\" , \"clab-lab1-leaf7\" , \"clab-lab2-leaf3\" , \"clab-lab5-leaf5\" , \"clab-lab1-super-spine1\" , \"clab-lab3-leaf5\" , \"clab-lab4-super-spine1\" , \"clab-lab5-leaf6\" , \"clab-lab2-spine1\" , \"clab-lab3-leaf3\" , \"clab-lab4-leaf3\" , \"clab-lab2-leaf4\" , \"clab-lab4-super-spine2\" , \"clab-lab1-spine3\" , \"clab-lab3-leaf4\" , \"clab-lab5-spine4\" , \"clab-lab1-leaf4\" , \"clab-lab2-leaf2\" , \"clab-lab2-super-spine1\" , \"clab-lab4-spine4\" , \"clab-lab5-leaf2\" , \"clab-lab5-leaf4\" , \"clab-lab4-leaf7\" , \"clab-lab1-spine4\" ] }, { \"name\" : \"clab-telemetry-gnmic3\" , \"api-endpoint\" : \"clab-telemetry-gnmic3:7892\" , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab1-leaf5\" , \"clab-lab3-spine3\" , \"clab-lab1-leaf1\" , \"clab-lab2-spine4\" , \"clab-lab1-super-spine2\" , \"clab-lab5-leaf3\" , \"clab-lab4-spine2\" , \"clab-lab1-leaf3\" , \"clab-lab5-spine3\" , \"clab-lab3-super-spine2\" , \"clab-lab2-leaf5\" , \"clab-lab1-leaf2\" , \"clab-lab1-leaf6\" , \"clab-lab4-leaf5\" , \"clab-lab2-leaf7\" , \"clab-lab3-leaf1\" , \"clab-lab2-leaf1\" , \"clab-lab3-spine1\" , \"clab-lab5-leaf1\" , \"clab-lab5-super-spine2\" , \"clab-lab4-leaf6\" , \"clab-lab3-spine4\" , \"clab-lab5-super-spine1\" ] } ] } { \"errors\" : [ \"Error Text\" ] } GET /api/v1/cluster/members # Query gNMIc cluster members Returns a list of gNMIc cluster members with details Request 200 OK 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/cluster/members [ { \"name\" : \"clab-telemetry-gnmic1\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic1:7890\" , \"is-leader\" : true , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab2-spine3\" , \"clab-lab5-spine1\" , \"clab-lab2-super-spine2\" , \"clab-lab4-leaf2\" , \"clab-lab4-leaf4\" , \"clab-lab5-spine2\" , \"clab-lab1-leaf8\" , \"clab-lab4-spine1\" , \"clab-lab5-leaf7\" , \"clab-lab2-spine2\" , \"clab-lab3-super-spine1\" , \"clab-lab1-spine1\" , \"clab-lab3-leaf2\" , \"clab-lab3-spine2\" , \"clab-lab2-leaf6\" , \"clab-lab4-leaf1\" , \"clab-lab4-spine3\" , \"clab-lab1-spine2\" , \"clab-lab2-leaf8\" , \"clab-lab3-leaf8\" , \"clab-lab5-leaf8\" , \"clab-lab3-leaf7\" , \"clab-lab4-leaf8\" ] }, { \"name\" : \"clab-telemetry-gnmic2\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic2:7891\" , \"number-of-locked-nodes\" : 24 , \"locked-targets\" : [ \"clab-lab1-spine4\" , \"clab-lab2-leaf2\" , \"clab-lab3-leaf3\" , \"clab-lab4-super-spine1\" , \"clab-lab5-leaf4\" , \"clab-lab1-spine3\" , \"clab-lab1-leaf4\" , \"clab-lab3-leaf6\" , \"clab-lab5-leaf2\" , \"clab-lab2-leaf4\" , \"clab-lab3-leaf4\" , \"clab-lab4-leaf3\" , \"clab-lab5-spine4\" , \"clab-lab3-leaf5\" , \"clab-lab4-super-spine2\" , \"clab-lab1-leaf7\" , \"clab-lab2-leaf3\" , \"clab-lab2-super-spine1\" , \"clab-lab5-leaf6\" , \"clab-lab2-spine1\" , \"clab-lab1-super-spine1\" , \"clab-lab4-leaf7\" , \"clab-lab4-spine4\" , \"clab-lab5-leaf5\" ] }, { \"name\" : \"clab-telemetry-gnmic3\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic3:7892\" , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab1-leaf3\" , \"clab-lab1-leaf5\" , \"clab-lab3-spine4\" , \"clab-lab3-spine3\" , \"clab-lab1-leaf1\" , \"clab-lab1-leaf6\" , \"clab-lab2-leaf5\" , \"clab-lab4-leaf6\" , \"clab-lab5-leaf1\" , \"clab-lab5-leaf3\" , \"clab-lab5-super-spine2\" , \"clab-lab2-spine4\" , \"clab-lab5-super-spine1\" , \"clab-lab4-spine2\" , \"clab-lab3-spine1\" , \"clab-lab4-leaf5\" , \"clab-lab5-spine3\" , \"clab-lab1-super-spine2\" , \"clab-lab2-leaf1\" , \"clab-lab3-super-spine2\" , \"clab-lab3-leaf1\" , \"clab-lab1-leaf2\" , \"clab-lab2-leaf7\" ] } ] { \"errors\" : [ \"Error Text\" ] } GET /api/v1/cluster/leader # Queries the cluster leader deatils Returns details of the gNMIc cluster leader. Request 200 OK 500 Internal Server Error curl --request POST gnmic-api-address:port/api/v1/cluster/leader [ { \"name\" : \"clab-telemetry-gnmic1\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic1:7890\" , \"is-leader\" : true , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab4-leaf8\" , \"clab-lab5-leaf8\" , \"clab-lab1-spine2\" , \"clab-lab3-leaf7\" , \"clab-lab4-leaf4\" , \"clab-lab2-leaf8\" , \"clab-lab2-spine3\" , \"clab-lab4-leaf1\" , \"clab-lab4-leaf2\" , \"clab-lab4-spine3\" , \"clab-lab5-spine2\" , \"clab-lab1-spine1\" , \"clab-lab2-leaf6\" , \"clab-lab5-leaf7\" , \"clab-lab1-leaf8\" , \"clab-lab3-leaf8\" , \"clab-lab3-spine2\" , \"clab-lab3-super-spine1\" , \"clab-lab5-spine1\" , \"clab-lab2-super-spine2\" , \"clab-lab3-leaf2\" , \"clab-lab2-spine2\" , \"clab-lab4-spine1\" ] } ] { \"errors\" : [ \"Error Text\" ] }","title":"Cluster"},{"location":"user_guide/api/cluster/#get-apiv1cluster","text":"Request gNMIc cluster state and details Returns gNMIc cluster state and details Request 200 OK 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/cluster { \"name\" : \"collectors\" , \"number-of-locked-targets\" : 70 , \"leader\" : \"clab-telemetry-gnmic1\" , \"members\" : [ { \"name\" : \"clab-telemetry-gnmic1\" , \"api-endpoint\" : \"clab-telemetry-gnmic1:7890\" , \"is-leader\" : true , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab2-leaf6\" , \"clab-lab5-spine2\" , \"clab-lab4-leaf4\" , \"clab-lab2-leaf8\" , \"clab-lab3-leaf2\" , \"clab-lab5-spine1\" , \"clab-lab1-spine1\" , \"clab-lab2-super-spine2\" , \"clab-lab3-super-spine1\" , \"clab-lab4-spine3\" , \"clab-lab2-spine3\" , \"clab-lab3-leaf7\" , \"clab-lab5-leaf7\" , \"clab-lab5-leaf8\" , \"clab-lab1-spine2\" , \"clab-lab4-leaf8\" , \"clab-lab4-leaf1\" , \"clab-lab4-spine1\" , \"clab-lab2-spine2\" , \"clab-lab3-spine2\" , \"clab-lab1-leaf8\" , \"clab-lab3-leaf8\" , \"clab-lab4-leaf2\" ] }, { \"name\" : \"clab-telemetry-gnmic2\" , \"api-endpoint\" : \"clab-telemetry-gnmic2:7891\" , \"number-of-locked-nodes\" : 24 , \"locked-targets\" : [ \"clab-lab3-leaf6\" , \"clab-lab1-leaf7\" , \"clab-lab2-leaf3\" , \"clab-lab5-leaf5\" , \"clab-lab1-super-spine1\" , \"clab-lab3-leaf5\" , \"clab-lab4-super-spine1\" , \"clab-lab5-leaf6\" , \"clab-lab2-spine1\" , \"clab-lab3-leaf3\" , \"clab-lab4-leaf3\" , \"clab-lab2-leaf4\" , \"clab-lab4-super-spine2\" , \"clab-lab1-spine3\" , \"clab-lab3-leaf4\" , \"clab-lab5-spine4\" , \"clab-lab1-leaf4\" , \"clab-lab2-leaf2\" , \"clab-lab2-super-spine1\" , \"clab-lab4-spine4\" , \"clab-lab5-leaf2\" , \"clab-lab5-leaf4\" , \"clab-lab4-leaf7\" , \"clab-lab1-spine4\" ] }, { \"name\" : \"clab-telemetry-gnmic3\" , \"api-endpoint\" : \"clab-telemetry-gnmic3:7892\" , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab1-leaf5\" , \"clab-lab3-spine3\" , \"clab-lab1-leaf1\" , \"clab-lab2-spine4\" , \"clab-lab1-super-spine2\" , \"clab-lab5-leaf3\" , \"clab-lab4-spine2\" , \"clab-lab1-leaf3\" , \"clab-lab5-spine3\" , \"clab-lab3-super-spine2\" , \"clab-lab2-leaf5\" , \"clab-lab1-leaf2\" , \"clab-lab1-leaf6\" , \"clab-lab4-leaf5\" , \"clab-lab2-leaf7\" , \"clab-lab3-leaf1\" , \"clab-lab2-leaf1\" , \"clab-lab3-spine1\" , \"clab-lab5-leaf1\" , \"clab-lab5-super-spine2\" , \"clab-lab4-leaf6\" , \"clab-lab3-spine4\" , \"clab-lab5-super-spine1\" ] } ] } { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/cluster"},{"location":"user_guide/api/cluster/#get-apiv1clustermembers","text":"Query gNMIc cluster members Returns a list of gNMIc cluster members with details Request 200 OK 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/cluster/members [ { \"name\" : \"clab-telemetry-gnmic1\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic1:7890\" , \"is-leader\" : true , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab2-spine3\" , \"clab-lab5-spine1\" , \"clab-lab2-super-spine2\" , \"clab-lab4-leaf2\" , \"clab-lab4-leaf4\" , \"clab-lab5-spine2\" , \"clab-lab1-leaf8\" , \"clab-lab4-spine1\" , \"clab-lab5-leaf7\" , \"clab-lab2-spine2\" , \"clab-lab3-super-spine1\" , \"clab-lab1-spine1\" , \"clab-lab3-leaf2\" , \"clab-lab3-spine2\" , \"clab-lab2-leaf6\" , \"clab-lab4-leaf1\" , \"clab-lab4-spine3\" , \"clab-lab1-spine2\" , \"clab-lab2-leaf8\" , \"clab-lab3-leaf8\" , \"clab-lab5-leaf8\" , \"clab-lab3-leaf7\" , \"clab-lab4-leaf8\" ] }, { \"name\" : \"clab-telemetry-gnmic2\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic2:7891\" , \"number-of-locked-nodes\" : 24 , \"locked-targets\" : [ \"clab-lab1-spine4\" , \"clab-lab2-leaf2\" , \"clab-lab3-leaf3\" , \"clab-lab4-super-spine1\" , \"clab-lab5-leaf4\" , \"clab-lab1-spine3\" , \"clab-lab1-leaf4\" , \"clab-lab3-leaf6\" , \"clab-lab5-leaf2\" , \"clab-lab2-leaf4\" , \"clab-lab3-leaf4\" , \"clab-lab4-leaf3\" , \"clab-lab5-spine4\" , \"clab-lab3-leaf5\" , \"clab-lab4-super-spine2\" , \"clab-lab1-leaf7\" , \"clab-lab2-leaf3\" , \"clab-lab2-super-spine1\" , \"clab-lab5-leaf6\" , \"clab-lab2-spine1\" , \"clab-lab1-super-spine1\" , \"clab-lab4-leaf7\" , \"clab-lab4-spine4\" , \"clab-lab5-leaf5\" ] }, { \"name\" : \"clab-telemetry-gnmic3\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic3:7892\" , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab1-leaf3\" , \"clab-lab1-leaf5\" , \"clab-lab3-spine4\" , \"clab-lab3-spine3\" , \"clab-lab1-leaf1\" , \"clab-lab1-leaf6\" , \"clab-lab2-leaf5\" , \"clab-lab4-leaf6\" , \"clab-lab5-leaf1\" , \"clab-lab5-leaf3\" , \"clab-lab5-super-spine2\" , \"clab-lab2-spine4\" , \"clab-lab5-super-spine1\" , \"clab-lab4-spine2\" , \"clab-lab3-spine1\" , \"clab-lab4-leaf5\" , \"clab-lab5-spine3\" , \"clab-lab1-super-spine2\" , \"clab-lab2-leaf1\" , \"clab-lab3-super-spine2\" , \"clab-lab3-leaf1\" , \"clab-lab1-leaf2\" , \"clab-lab2-leaf7\" ] } ] { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/cluster/members"},{"location":"user_guide/api/cluster/#get-apiv1clusterleader","text":"Queries the cluster leader deatils Returns details of the gNMIc cluster leader. Request 200 OK 500 Internal Server Error curl --request POST gnmic-api-address:port/api/v1/cluster/leader [ { \"name\" : \"clab-telemetry-gnmic1\" , \"api-endpoint\" : \"http://clab-telemetry-gnmic1:7890\" , \"is-leader\" : true , \"number-of-locked-nodes\" : 23 , \"locked-targets\" : [ \"clab-lab4-leaf8\" , \"clab-lab5-leaf8\" , \"clab-lab1-spine2\" , \"clab-lab3-leaf7\" , \"clab-lab4-leaf4\" , \"clab-lab2-leaf8\" , \"clab-lab2-spine3\" , \"clab-lab4-leaf1\" , \"clab-lab4-leaf2\" , \"clab-lab4-spine3\" , \"clab-lab5-spine2\" , \"clab-lab1-spine1\" , \"clab-lab2-leaf6\" , \"clab-lab5-leaf7\" , \"clab-lab1-leaf8\" , \"clab-lab3-leaf8\" , \"clab-lab3-spine2\" , \"clab-lab3-super-spine1\" , \"clab-lab5-spine1\" , \"clab-lab2-super-spine2\" , \"clab-lab3-leaf2\" , \"clab-lab2-spine2\" , \"clab-lab4-spine1\" ] } ] { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/cluster/leader"},{"location":"user_guide/api/configuration/","text":"Configuration # /api/v1/config # GET /api/v1/config # Request all gnmic configuration Returns the whole configuration as json Request 200 OK 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/config { \"username\" : \"admin\" , \"password\" : \"admin\" , \"port\" : \"57400\" , \"encoding\" : \"json_ietf\" , \"insecure\" : true , \"timeout\" : 10000000000 , \"log\" : true , \"max-msg-size\" : 536870912 , \"prometheus-address\" : \":8989\" , \"retry\" : 10000000000 , \"api\" : \":7890\" , \"get-type\" : \"ALL\" , \"set-delimiter\" : \":::\" , \"subscribe-mode\" : \"stream\" , \"subscribe-stream-mode\" : \"target-defined\" , \"subscribe-cluster-name\" : \"default-cluster\" , \"subscribe-lock-retry\" : 5000000000 , \"path-path-type\" : \"xpath\" , \"prompt-max-suggestions\" : 10 , \"prompt-prefix-color\" : \"dark_blue\" , \"prompt-suggestions-bg-color\" : \"dark_blue\" , \"prompt-description-bg-color\" : \"dark_gray\" , \"targets\" : { \"192.168.1.131:57400\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"192.168.1.132:57400\" : { \"name\" : \"192.168.1.132:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 } }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } }, \"Outputs\" : { \"output2\" : { \"address\" : \"192.168.1.131:4222\" , \"format\" : \"event\" , \"subject\" : \"telemetry\" , \"type\" : \"nats\" , \"write-timeout\" : \"10s\" } }, \"inputs\" : {}, \"processors\" : {}, \"clustering\" : { \"cluster-name\" : \"cluster1\" , \"instance-name\" : \"gnmic1\" , \"service-address\" : \"gnmic1\" , \"services-watch-timer\" : 60000000000 , \"targets-watch-timer\" : 5000000000 , \"leader-wait-timer\" : 5000000000 , \"locker\" : { \"address\" : \"consul-agent:8500\" , \"type\" : \"consul\" } } } { \"errors\" : [ \"Error Text\" ] } /api/v1/config/targets # GET /api/v1/config/targets # Request all targets configuration returns the targets configuration as json Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/config/targets { \"192.168.1.131:57400\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"192.168.1.132:57400\" : { \"name\" : \"192.168.1.132:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 } } { \"errors\" : [ \"no targets found\" , ] } { \"errors\" : [ \"Error Text\" ] } GET /api/v1/config/targets/{id} # Request a single target configuration Returns a single target configuration as json, where {id} is the target ID Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/config/targets/192.168.1.131:57400 { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 } { \"errors\" : [ \"target $target not found\" , ] } { \"errors\" : [ \"Error Text\" ] } POST /api/v1/config/targets # Add a new target to gnmic configuration Expected request body is a single target config as json Returns an empty body if successful. Request 200 OK 400 Bad Request 500 Internal Server Error curl --request POST -H \"Content-Type: application/json\" \\ -d '{\"address\": \"10.10.10.10:57400\", \"username\": \"admin\", \"password\": \"admin\", \"insecure\": true}' \\ gnmic-api-address:port/api/v1/config/targets { \"errors\" : [ \"Error Text\" ] } DELETE /api/v1/config/targets/{id} # Deletes a target {id} configuration, all active subscriptions are terminated. Returns an empty body Request 200 OK curl --request DELETE gnmic-api-address:port/api/v1/config/targets/192.168.1.131:57400 /api/v1/config/subscriptions # GET /api/v1/config/subscriptions # Request all the configured subscriptions. Returns the subscriptions configuration as json /api/v1/config/outputs # GET /api/v1/config/outputs # Request all the configured outputs. Returns the outputs configuration as json /api/v1/config/inputs # GET /api/v1/config/inputs # Request all the configured inputs. Returns the outputs configuration as json /api/v1/config/processors # GET /api/v1/config/processors # Request all the configured processors. Returns the processors configuration as json /api/v1/config/clustering # GET /api/v1/config/clustering # Request the clustering configuration. Returns the clustering configuration as json","title":"Configuration"},{"location":"user_guide/api/configuration/#configuration","text":"","title":"Configuration"},{"location":"user_guide/api/configuration/#apiv1config","text":"","title":"/api/v1/config"},{"location":"user_guide/api/configuration/#get-apiv1config","text":"Request all gnmic configuration Returns the whole configuration as json Request 200 OK 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/config { \"username\" : \"admin\" , \"password\" : \"admin\" , \"port\" : \"57400\" , \"encoding\" : \"json_ietf\" , \"insecure\" : true , \"timeout\" : 10000000000 , \"log\" : true , \"max-msg-size\" : 536870912 , \"prometheus-address\" : \":8989\" , \"retry\" : 10000000000 , \"api\" : \":7890\" , \"get-type\" : \"ALL\" , \"set-delimiter\" : \":::\" , \"subscribe-mode\" : \"stream\" , \"subscribe-stream-mode\" : \"target-defined\" , \"subscribe-cluster-name\" : \"default-cluster\" , \"subscribe-lock-retry\" : 5000000000 , \"path-path-type\" : \"xpath\" , \"prompt-max-suggestions\" : 10 , \"prompt-prefix-color\" : \"dark_blue\" , \"prompt-suggestions-bg-color\" : \"dark_blue\" , \"prompt-description-bg-color\" : \"dark_gray\" , \"targets\" : { \"192.168.1.131:57400\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"192.168.1.132:57400\" : { \"name\" : \"192.168.1.132:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 } }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } }, \"Outputs\" : { \"output2\" : { \"address\" : \"192.168.1.131:4222\" , \"format\" : \"event\" , \"subject\" : \"telemetry\" , \"type\" : \"nats\" , \"write-timeout\" : \"10s\" } }, \"inputs\" : {}, \"processors\" : {}, \"clustering\" : { \"cluster-name\" : \"cluster1\" , \"instance-name\" : \"gnmic1\" , \"service-address\" : \"gnmic1\" , \"services-watch-timer\" : 60000000000 , \"targets-watch-timer\" : 5000000000 , \"leader-wait-timer\" : 5000000000 , \"locker\" : { \"address\" : \"consul-agent:8500\" , \"type\" : \"consul\" } } } { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/config"},{"location":"user_guide/api/configuration/#apiv1configtargets","text":"","title":"/api/v1/config/targets"},{"location":"user_guide/api/configuration/#get-apiv1configtargets","text":"Request all targets configuration returns the targets configuration as json Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/config/targets { \"192.168.1.131:57400\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"192.168.1.132:57400\" : { \"name\" : \"192.168.1.132:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 } } { \"errors\" : [ \"no targets found\" , ] } { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/config/targets"},{"location":"user_guide/api/configuration/#get-apiv1configtargetsid","text":"Request a single target configuration Returns a single target configuration as json, where {id} is the target ID Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/config/targets/192.168.1.131:57400 { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 } { \"errors\" : [ \"target $target not found\" , ] } { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/config/targets/{id}"},{"location":"user_guide/api/configuration/#post-apiv1configtargets","text":"Add a new target to gnmic configuration Expected request body is a single target config as json Returns an empty body if successful. Request 200 OK 400 Bad Request 500 Internal Server Error curl --request POST -H \"Content-Type: application/json\" \\ -d '{\"address\": \"10.10.10.10:57400\", \"username\": \"admin\", \"password\": \"admin\", \"insecure\": true}' \\ gnmic-api-address:port/api/v1/config/targets { \"errors\" : [ \"Error Text\" ] }","title":"POST /api/v1/config/targets"},{"location":"user_guide/api/configuration/#delete-apiv1configtargetsid","text":"Deletes a target {id} configuration, all active subscriptions are terminated. Returns an empty body Request 200 OK curl --request DELETE gnmic-api-address:port/api/v1/config/targets/192.168.1.131:57400","title":"DELETE /api/v1/config/targets/{id}"},{"location":"user_guide/api/configuration/#apiv1configsubscriptions","text":"","title":"/api/v1/config/subscriptions"},{"location":"user_guide/api/configuration/#get-apiv1configsubscriptions","text":"Request all the configured subscriptions. Returns the subscriptions configuration as json","title":"GET /api/v1/config/subscriptions"},{"location":"user_guide/api/configuration/#apiv1configoutputs","text":"","title":"/api/v1/config/outputs"},{"location":"user_guide/api/configuration/#get-apiv1configoutputs","text":"Request all the configured outputs. Returns the outputs configuration as json","title":"GET /api/v1/config/outputs"},{"location":"user_guide/api/configuration/#apiv1configinputs","text":"","title":"/api/v1/config/inputs"},{"location":"user_guide/api/configuration/#get-apiv1configinputs","text":"Request all the configured inputs. Returns the outputs configuration as json","title":"GET /api/v1/config/inputs"},{"location":"user_guide/api/configuration/#apiv1configprocessors","text":"","title":"/api/v1/config/processors"},{"location":"user_guide/api/configuration/#get-apiv1configprocessors","text":"Request all the configured processors. Returns the processors configuration as json","title":"GET /api/v1/config/processors"},{"location":"user_guide/api/configuration/#apiv1configclustering","text":"","title":"/api/v1/config/clustering"},{"location":"user_guide/api/configuration/#get-apiv1configclustering","text":"Request the clustering configuration. Returns the clustering configuration as json","title":"GET /api/v1/config/clustering"},{"location":"user_guide/api/other/","text":"Other # /api/v1/healthz # GET /api/v1/healthz # Health check endpoint for Kubernetes or similar Request 200 OK curl --request GET gnmic-api-address:port/api/v1/healthz { \"status\" : \"healthy\" }","title":"Other"},{"location":"user_guide/api/other/#other","text":"","title":"Other"},{"location":"user_guide/api/other/#apiv1healthz","text":"","title":"/api/v1/healthz"},{"location":"user_guide/api/other/#get-apiv1healthz","text":"Health check endpoint for Kubernetes or similar Request 200 OK curl --request GET gnmic-api-address:port/api/v1/healthz { \"status\" : \"healthy\" }","title":"GET /api/v1/healthz"},{"location":"user_guide/api/targets/","text":"GET /api/v1/targets # Request all active targets details. Returns all active targets as json Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/targets { \"192.168.1.131:57400\" : { \"config\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } } }, \"192.168.1.131:57401\" : { \"config\" : { \"name\" : \"192.168.1.131:57401\" , \"address\" : \"192.168.1.131:57401\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } } } } { \"errors\" : [ \"no targets found\" ] } { \"errors\" : [ \"Error Text\" ] } GET /api/v1/targets/{id} # Query a single target details, if active. Returns a single target if active as json, where {id} is the target ID Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/targets/192.168.1.131:57400 { \"config\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } } } { \"errors\" : [ \"no targets found\" ] } { \"errors\" : [ \"Error Text\" ] } POST /api/v1/targets/{id} # Starts a single target subscriptions, where {id} is the target ID Returns an empty body if successful. Request 200 OK 404 Not found 500 Internal Server Error curl --request POST gnmic-api-address:port/api/v1/targets/192.168.1.131:57400 { \"errors\" : [ \"target $target not found\" ] } { \"errors\" : [ \"Error Text\" ] } DELETE /api/v1/targets/{id} # Stops a single target active subscriptions, where {id} is the target ID Returns an empty body if successful. Request 200 OK 404 Not found 500 Internal Server Error curl --request DELETE gnmic-api-address:port/api/v1/targets/192.168.1.131:57400 { \"errors\" : [ \"target $target not found\" ] } { \"errors\" : [ \"Error Text\" ] }","title":"Targets"},{"location":"user_guide/api/targets/#get-apiv1targets","text":"Request all active targets details. Returns all active targets as json Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/api/v1/targets { \"192.168.1.131:57400\" : { \"config\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } } }, \"192.168.1.131:57401\" : { \"config\" : { \"name\" : \"192.168.1.131:57401\" , \"address\" : \"192.168.1.131:57401\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } } } } { \"errors\" : [ \"no targets found\" ] } { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/targets"},{"location":"user_guide/api/targets/#get-apiv1targetsid","text":"Query a single target details, if active. Returns a single target if active as json, where {id} is the target ID Request 200 OK 404 Not found 500 Internal Server Error curl --request GET gnmic-api-address:port/targets/192.168.1.131:57400 { \"config\" : { \"name\" : \"192.168.1.131:57400\" , \"address\" : \"192.168.1.131:57400\" , \"username\" : \"admin\" , \"password\" : \"admin\" , \"timeout\" : 10000000000 , \"insecure\" : true , \"skip-verify\" : false , \"buffer-size\" : 1000 , \"retry-timer\" : 10000000000 }, \"subscriptions\" : { \"sub1\" : { \"name\" : \"sub1\" , \"paths\" : [ \"/interface/statistics\" ], \"mode\" : \"stream\" , \"stream-mode\" : \"sample\" , \"encoding\" : \"json_ietf\" , \"sample-interval\" : 1000000000 } } } { \"errors\" : [ \"no targets found\" ] } { \"errors\" : [ \"Error Text\" ] }","title":"GET /api/v1/targets/{id}"},{"location":"user_guide/api/targets/#post-apiv1targetsid","text":"Starts a single target subscriptions, where {id} is the target ID Returns an empty body if successful. Request 200 OK 404 Not found 500 Internal Server Error curl --request POST gnmic-api-address:port/api/v1/targets/192.168.1.131:57400 { \"errors\" : [ \"target $target not found\" ] } { \"errors\" : [ \"Error Text\" ] }","title":"POST /api/v1/targets/{id}"},{"location":"user_guide/api/targets/#delete-apiv1targetsid","text":"Stops a single target active subscriptions, where {id} is the target ID Returns an empty body if successful. Request 200 OK 404 Not found 500 Internal Server Error curl --request DELETE gnmic-api-address:port/api/v1/targets/192.168.1.131:57400 { \"errors\" : [ \"target $target not found\" ] } { \"errors\" : [ \"Error Text\" ] }","title":"DELETE /api/v1/targets/{id}"},{"location":"user_guide/event_processors/event_add_tag/","text":"The event-add-tag processor adds a set of tags to an event message if one of the configured regular expressions in the values, value names, tags or tag names sections matches. It is possible to overwrite a tag if it's name already exists. processors : # processor name sample-processor : # processor type event-add-tag : # jq expression, if evaluated to true, the tags are added condition : # list of regular expressions to be matched against the tags names, if matched, the tags are added tag-names : # list of regular expressions to be matched against the tags values, if matched, the tags are added tags : # list of regular expressions to be matched against the values names, if matched, the tags are added value-names : # list of regular expressions to be matched against the values, if matched, the tags are added values : # list of regular expressions to be matched against the deleted paths, if matched, the tags are added deletes : # boolean, if true tags are over-written with the added ones if they already exist. overwrite : # map of tags to be added add : tag_name : tag_value Examples # processors : # processor name sample-processor : # processor type event-add-tag : value-names : - \".\" add : tag_name : tag_value Event format before Event format after { \"name\" : \"sub1\" , \"timestamp\" : 1607678293684962443 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.20.20.5:57400\" }, \"values\" : { \"Carrier_Transitions\" : 1 , \"In_Broadcast_Packets\" : 448 , \"In_Error_Packets\" : 0 , \"In_Fcs_Error_Packets\" : 0 , \"In_Multicast_Packets\" : 47578 , \"In_Octets\" : 15557349 , \"In_Unicast_Packets\" : 6482 , \"Out_Broadcast_Packets\" : 110 , \"Out_Error_Packets\" : 0 , \"Out_Multicast_Packets\" : 10 , \"Out_Octets\" : 464766 } } { \"name\" : \"sub1\" , \"timestamp\" : 1607678293684962443 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.20.20.5:57400\" , \"tag_name\" : \"tag_value\" }, \"values\" : { \"Carrier_Transitions\" : 1 , \"In_Broadcast_Packets\" : 448 , \"In_Error_Packets\" : 0 , \"In_Fcs_Error_Packets\" : 0 , \"In_Multicast_Packets\" : 47578 , \"In_Octets\" : 15557349 , \"In_Unicast_Packets\" : 6482 , \"Out_Broadcast_Packets\" : 110 , \"Out_Error_Packets\" : 0 , \"Out_Multicast_Packets\" : 10 , \"Out_Octets\" : 464766 } }","title":"Add Tag"},{"location":"user_guide/event_processors/event_add_tag/#examples","text":"processors : # processor name sample-processor : # processor type event-add-tag : value-names : - \".\" add : tag_name : tag_value Event format before Event format after { \"name\" : \"sub1\" , \"timestamp\" : 1607678293684962443 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.20.20.5:57400\" }, \"values\" : { \"Carrier_Transitions\" : 1 , \"In_Broadcast_Packets\" : 448 , \"In_Error_Packets\" : 0 , \"In_Fcs_Error_Packets\" : 0 , \"In_Multicast_Packets\" : 47578 , \"In_Octets\" : 15557349 , \"In_Unicast_Packets\" : 6482 , \"Out_Broadcast_Packets\" : 110 , \"Out_Error_Packets\" : 0 , \"Out_Multicast_Packets\" : 10 , \"Out_Octets\" : 464766 } } { \"name\" : \"sub1\" , \"timestamp\" : 1607678293684962443 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.20.20.5:57400\" , \"tag_name\" : \"tag_value\" }, \"values\" : { \"Carrier_Transitions\" : 1 , \"In_Broadcast_Packets\" : 448 , \"In_Error_Packets\" : 0 , \"In_Fcs_Error_Packets\" : 0 , \"In_Multicast_Packets\" : 47578 , \"In_Octets\" : 15557349 , \"In_Unicast_Packets\" : 6482 , \"Out_Broadcast_Packets\" : 110 , \"Out_Error_Packets\" : 0 , \"Out_Multicast_Packets\" : 10 , \"Out_Octets\" : 464766 } }","title":"Examples"},{"location":"user_guide/event_processors/event_allow/","text":"The event-allow processor allows only messages matching the configured condition or one of the regular expressions under tags , tag-names , values or value-names . Non matching messages are dropped. processors : # processor name sample-processor : # processor type event-allow : # jq expression, if evaluated to true, the message is allowed condition : # list of regular expressions to be matched against the tags names, # if matched, the message is allowed tag-names : # list of regular expressions to be matched against the tags values, # if matched, the message is allowed tags : # list of regular expressions to be matched against the values names, # if matched, the message is allowed value-names : # list of regular expressions to be matched against the values, # if matched, the message is allowed values : Examples # processors : # processor name allow-processor : # processor type event-allow : condition : \".tags.interface_name == 1/1/1\" Event format before Event format after [ { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } }, { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"1/1/1\" , \"source\" : \"172.23.23.3:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } ] [ { }, { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"1/1/1\" , \"source\" : \"172.23.23.3:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } ]","title":"Allow"},{"location":"user_guide/event_processors/event_allow/#examples","text":"processors : # processor name allow-processor : # processor type event-allow : condition : \".tags.interface_name == 1/1/1\" Event format before Event format after [ { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } }, { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"1/1/1\" , \"source\" : \"172.23.23.3:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } ] [ { }, { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"1/1/1\" , \"source\" : \"172.23.23.3:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } ]","title":"Examples"},{"location":"user_guide/event_processors/event_combine/","text":"The event-combine processor combines multiple processors together. This allows to declare processors once and reuse them to build more complex processors. Configuration # processors : # processor name pipeline1 : # processor type event-combine : # list of regex to be matched with the values names processors : # The \"sub\" processor execution condition. A jq expression. - condition : # the processor name, should be declared in the # `processors` section. name : # enable extra logging debug : false Conditional Execution of Subprocessors # The workflow for processing event messages can include multiple subprocessors, each potentially governed by its own condition. These conditions are defined using the jq query language, enabling dynamic and precise control over when each subprocessor should be executed. Defining Conditions for Subprocessors # When configuring your subprocessors, you have the option to attach a jq-based condition to each one. The specified condition acts as a gatekeeper, determining whether the corresponding subprocessor should be activated for a particular event message. Condition Evaluation Process # For a subprocessor to run, the following criteria must be met: Condition Presence: If a condition is specified for the subprocessor, it must be evaluated. Condition Outcome: The result of the jq condition evaluation must be true. Combined Conditions: In scenarios where both the main processor and the subprocessor have associated conditions, both conditions must independently evaluate to true for the subprocessor to be triggered. Only when all relevant conditions are met will the subprocessor execute its designated operations on the event message. It is important to note that the absence of a condition is equivalent to a condition that always evaluates to true. Thus, if no condition is provided for a subprocessor, it will execute as long as the main processor's condition (if any) is met. By using conditional execution, you can build sophisticated and efficient event message processing workflows that react dynamically to the content of the messages. Examples # In the below example, we define 3 regular processors and 2 event-combine processors. proc1 : Allows event message that have tag \"interface_name = ethernet-1/1 proc2 : Renames values names to their path base. e.g: interface/statistics/out-octets \u2192 out-octets proc3 : Converts any values with a name ending with octets to int . pipeline1 : Combines proc1 , proc2 and proc3 , applying proc2 only to subscription sub1 pipeline2 : Combines proc2 and proc3 , applying proc2 only to subscription sub2 The 2 combine processors can be linked with different outputs. processors : proc1 : event-allow : condition : '.tags.interface_name == \"ethernet-1/1\"' proc2 : event-strings : value-names : - \".*\" transforms : - path-base : apply-on : \"name\" proc3 : event-convert : value-names : - \".*octets$\" type : int pipeline1 : event-combine : processors : - name : proc1 - condition : '.tags[\"subscription-name\"] == \"sub1\"' name : proc2 - name : proc3 pipeline2 : event-combine : processors : - condition : '.tags[\"subscription-name\"] == \"sub2\"' name : proc2 - name : proc3","title":"Combine"},{"location":"user_guide/event_processors/event_combine/#configuration","text":"processors : # processor name pipeline1 : # processor type event-combine : # list of regex to be matched with the values names processors : # The \"sub\" processor execution condition. A jq expression. - condition : # the processor name, should be declared in the # `processors` section. name : # enable extra logging debug : false","title":"Configuration"},{"location":"user_guide/event_processors/event_combine/#conditional-execution-of-subprocessors","text":"The workflow for processing event messages can include multiple subprocessors, each potentially governed by its own condition. These conditions are defined using the jq query language, enabling dynamic and precise control over when each subprocessor should be executed.","title":"Conditional Execution of Subprocessors"},{"location":"user_guide/event_processors/event_combine/#defining-conditions-for-subprocessors","text":"When configuring your subprocessors, you have the option to attach a jq-based condition to each one. The specified condition acts as a gatekeeper, determining whether the corresponding subprocessor should be activated for a particular event message.","title":"Defining Conditions for Subprocessors"},{"location":"user_guide/event_processors/event_combine/#condition-evaluation-process","text":"For a subprocessor to run, the following criteria must be met: Condition Presence: If a condition is specified for the subprocessor, it must be evaluated. Condition Outcome: The result of the jq condition evaluation must be true. Combined Conditions: In scenarios where both the main processor and the subprocessor have associated conditions, both conditions must independently evaluate to true for the subprocessor to be triggered. Only when all relevant conditions are met will the subprocessor execute its designated operations on the event message. It is important to note that the absence of a condition is equivalent to a condition that always evaluates to true. Thus, if no condition is provided for a subprocessor, it will execute as long as the main processor's condition (if any) is met. By using conditional execution, you can build sophisticated and efficient event message processing workflows that react dynamically to the content of the messages.","title":"Condition Evaluation Process"},{"location":"user_guide/event_processors/event_combine/#examples","text":"In the below example, we define 3 regular processors and 2 event-combine processors. proc1 : Allows event message that have tag \"interface_name = ethernet-1/1 proc2 : Renames values names to their path base. e.g: interface/statistics/out-octets \u2192 out-octets proc3 : Converts any values with a name ending with octets to int . pipeline1 : Combines proc1 , proc2 and proc3 , applying proc2 only to subscription sub1 pipeline2 : Combines proc2 and proc3 , applying proc2 only to subscription sub2 The 2 combine processors can be linked with different outputs. processors : proc1 : event-allow : condition : '.tags.interface_name == \"ethernet-1/1\"' proc2 : event-strings : value-names : - \".*\" transforms : - path-base : apply-on : \"name\" proc3 : event-convert : value-names : - \".*octets$\" type : int pipeline1 : event-combine : processors : - name : proc1 - condition : '.tags[\"subscription-name\"] == \"sub1\"' name : proc2 - name : proc3 pipeline2 : event-combine : processors : - condition : '.tags[\"subscription-name\"] == \"sub2\"' name : proc2 - name : proc3","title":"Examples"},{"location":"user_guide/event_processors/event_convert/","text":"The event-convert processor converts the values matching one of the regular expressions to a specific type: uint , int , string , float or bool Examples # processors : # processor name convert-int-processor : # processor type event-convert : # list of regex to be matched with the values names value-names : - \".*octets$\" # the desired value type, one of: int, uint, string, float, bool type : int Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"7753940\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : 7753940 } }","title":"Convert"},{"location":"user_guide/event_processors/event_convert/#examples","text":"processors : # processor name convert-int-processor : # processor type event-convert : # list of regex to be matched with the values names value-names : - \".*octets$\" # the desired value type, one of: int, uint, string, float, bool type : int Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"7753940\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : 7753940 } }","title":"Examples"},{"location":"user_guide/event_processors/event_data_convert/","text":"The event-data-convert processor converts data values matching one of the regular expressions from/to a specific data unit: Symbol Unit Symbol Unit Symbol Unit b Bit B Byte KiB KibiByte kb kiloBit KB KiloByte MiB MebiByte mb MegaBit MB MegaByte GiB GibiByte gb GigaBit GB GigaByte TiB TebiByte tb TeraBit TB TeraByte EiB ExbiByte eb ExaBit EB ExaByte ZiB ZebiByte ZB ZetaByte YiB YobiByte YB YottaByte The source values can be of any numeric type including a string with or without a unit, e.g: 2.3 , 1KB or 1.1 TB . The unit of the original value can be derived as Byte from its name if it ends with -bytes , -octets , _bytes or _octets . Examples # simple conversion # The below processor will convert any value with a name ending in -octets from Byte to KiloByte . processors : # processor name convert-data-unit : # processor type event-data-convert : # list of regex to be matched with the values names value-names : - \".*-octets$\" # the source value unit, defaults to B (Byte) from : B # the desired value unit, defaults to B (Byte) to : KB # keep the original value, # a new value name will be added with the converted value, # the new value name will be the original name with _$to as suffix # if no regex renaming is defined using `old` and `new` keep : false # old, a regex to be used to rename the converted value old : # new, the replacement string new : # debug, enables this processor logging debug : false Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"2048\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : 2 } } conversion with renaming # The below data convert processor converts any value with a name ending in -octets from Byte to Kilobyte. It will retain the original value while renaming the new value name by replacing -octets with -kilobytes . processors : # processor name convert-data-unit : # processor type event-data-convert : # list of regex to be matched with the values names value-names : - \".*-octets$\" # the source value unit, defaults to B (Byte) from : B # the desired value unit, defaults to B (Byte) to : KB # keep the original value, # a new value name will be added with the converted value, # the new value name will be the original name with _$to as suffix # if no regex renaming is defined using `old` and `new` keep : true # old, a regex to be used to rename the converted value old : ^(\\S+)-octets$ # new, the replacement string new : ${1}-kilobytes # debug, enables this processor logging debug : false Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"2048\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"2048\" \"/state/port/ethernet/statistics/in-kilobytes\" : 2 } }","title":"Data Convert"},{"location":"user_guide/event_processors/event_data_convert/#examples","text":"","title":"Examples"},{"location":"user_guide/event_processors/event_data_convert/#simple-conversion","text":"The below processor will convert any value with a name ending in -octets from Byte to KiloByte . processors : # processor name convert-data-unit : # processor type event-data-convert : # list of regex to be matched with the values names value-names : - \".*-octets$\" # the source value unit, defaults to B (Byte) from : B # the desired value unit, defaults to B (Byte) to : KB # keep the original value, # a new value name will be added with the converted value, # the new value name will be the original name with _$to as suffix # if no regex renaming is defined using `old` and `new` keep : false # old, a regex to be used to rename the converted value old : # new, the replacement string new : # debug, enables this processor logging debug : false Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"2048\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : 2 } }","title":"simple conversion"},{"location":"user_guide/event_processors/event_data_convert/#conversion-with-renaming","text":"The below data convert processor converts any value with a name ending in -octets from Byte to Kilobyte. It will retain the original value while renaming the new value name by replacing -octets with -kilobytes . processors : # processor name convert-data-unit : # processor type event-data-convert : # list of regex to be matched with the values names value-names : - \".*-octets$\" # the source value unit, defaults to B (Byte) from : B # the desired value unit, defaults to B (Byte) to : KB # keep the original value, # a new value name will be added with the converted value, # the new value name will be the original name with _$to as suffix # if no regex renaming is defined using `old` and `new` keep : true # old, a regex to be used to rename the converted value old : ^(\\S+)-octets$ # new, the replacement string new : ${1}-kilobytes # debug, enables this processor logging debug : false Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"2048\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/state/port/ethernet/statistics/in-octets\" : \"2048\" \"/state/port/ethernet/statistics/in-kilobytes\" : 2 } }","title":"conversion with renaming"},{"location":"user_guide/event_processors/event_date_string/","text":"The event-date-string processor converts a specific timestamp value (under tags or values) to a string representation. The format and location can be configured. Examples # processors : # processor name convert-timestamp-processor : # processor type event-date-string : # list of regex to be matched with the values names value-names : - \"timestamp\" # received timestamp unit precision : ms # desired date string format, defaults to RFC3339 format : \"2006-01-02T15:04:05Z07:00\" # timezone, defaults to the local timezone location : Asia/Taipei","title":"Date string"},{"location":"user_guide/event_processors/event_date_string/#examples","text":"processors : # processor name convert-timestamp-processor : # processor type event-date-string : # list of regex to be matched with the values names value-names : - \"timestamp\" # received timestamp unit precision : ms # desired date string format, defaults to RFC3339 format : \"2006-01-02T15:04:05Z07:00\" # timezone, defaults to the local timezone location : Asia/Taipei","title":"Examples"},{"location":"user_guide/event_processors/event_delete/","text":"The event-delete processor deletes all tags or values matching a set of regular expressions from the event message. Examples # processors : # processor name delete-processor : # processor type event-delete : value-names : - \".*multicast.*\" - \".*broadcast.*\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } }","title":"Delete"},{"location":"user_guide/event_processors/event_delete/#examples","text":"processors : # processor name delete-processor : # processor type event-delete : value-names : - \".*multicast.*\" - \".*broadcast.*\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } }","title":"Examples"},{"location":"user_guide/event_processors/event_drop/","text":"The event-drop processor drops the whole message if it matches the configured condition or one of the regexes under tags , tag-names , values or value-names . processors : # processor name sample-processor : # processor type event-drop : # jq expression, if evaluated to true, the message is dropped condition : # list of regular expressions to be matched against the tags names, if matched, the message is dropped tag-names : # list of regular expressions to be matched against the tags values, if matched, the message is dropped tags : # list of regular expressions to be matched against the values names, if matched, the message is dropped value-names : # list of regular expressions to be matched against the values, if matched, the message is dropped values : Examples # processors : # processor name drop-processor : # processor type event-drop : tags : - \"172.23.23.2*\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { }","title":"Drop"},{"location":"user_guide/event_processors/event_drop/#examples","text":"processors : # processor name drop-processor : # processor type event-drop : tags : - \"172.23.23.2*\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { }","title":"Examples"},{"location":"user_guide/event_processors/event_duration_convert/","text":"The event-duration-convert processor converts duration written as string to a integer with second precision. The string format supported is a series of digits and a single letter indicating the unit, e.g 1w3d (1 week 3 days) The highest unit is w for week and the lowest is s for second. Any of the units may or may not be present. Examples # simple conversion # processors : # processor name convert-uptime : # processor type event-duration-convert : # list of regex to be matched with the values names value-names : - \".*_uptime$\" # keep the original value, # a new value name will be added with the converted value, # the new value name will be the original name with _seconds as suffix keep : false # debug, enables this processor logging debug : false Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"connection_uptime\" : \"1w5s\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"connection_uptime\" : 604805 } }","title":"Duration Convert"},{"location":"user_guide/event_processors/event_duration_convert/#examples","text":"","title":"Examples"},{"location":"user_guide/event_processors/event_duration_convert/#simple-conversion","text":"processors : # processor name convert-uptime : # processor type event-duration-convert : # list of regex to be matched with the values names value-names : - \".*_uptime$\" # keep the original value, # a new value name will be added with the converted value, # the new value name will be the original name with _seconds as suffix keep : false # debug, enables this processor logging debug : false Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"connection_uptime\" : \"1w5s\" } } { \"name\" : \"default\" , \"timestamp\" : 1607290633806716620 , \"tags\" : { \"port_port-id\" : \"A/1\" , \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"connection_uptime\" : 604805 } }","title":"simple conversion"},{"location":"user_guide/event_processors/event_extract_tags/","text":"The event-extract-tags processor extracts tags from a value, a value name, a tag name or a tag value using regex named groups. It is possible to overwrite a tag if its name already exists. processors : # processor name sample-processor : # processor type event-extract-tags : # list of regular expressions to be used to extract strings to be added as a tag. tag-names : # list of regular expressions to be used to extract strings to be added as a tag. tags : # list of regular expressions to be used to extract strings to be added as a tag. value-names : # list of regular expressions to be used to extract strings to be added as a tag. values : # boolean, if true tags are over-written with the added ones if they already exist. overwrite : # boolean, enable extra logging debug : Examples # processors : # processor name sample-processor : # processor type event-extract-tags : value-names : - /([a-zA-Z0-9-_:]+)/(?P[a-zA-Z0-9-_:]+)/([a-zA-Z0-9-_:]+) Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"group\" : \"statistics\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } }","title":"Extract Tags"},{"location":"user_guide/event_processors/event_extract_tags/#examples","text":"processors : # processor name sample-processor : # processor type event-extract-tags : value-names : - /([a-zA-Z0-9-_:]+)/(?P[a-zA-Z0-9-_:]+)/([a-zA-Z0-9-_:]+) Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"group\" : \"statistics\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } }","title":"Examples"},{"location":"user_guide/event_processors/event_group_by/","text":"The event-group-by processor groups values under the same event message based on a list of tag names. This processor is intended to be used together with an output with cached gNMI notifications, like prometheus output with cache: {} . Configuration # processors : # processor name sample-processor : # processor type event-group-by : # list of strings defining the tags to group by the values under # a single event tags : [] # a boolean, if true only the values from events of the same name # are grouped together according to the list of tags by-name : # boolean debug : false Examples # group by a single tag # processors : group-by-source : event-group-by : tags : - source Event format before Event format after [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" } }, { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_received_messages_malformed_updates\" : \"0\" , \"bgp_neighbor_received_messages_queue_depth\" : 0 , \"bgp_neighbor_received_messages_total_messages\" : \"424\" , \"bgp_neighbor_received_messages_total_non_updates\" : \"418\" , \"bgp_neighbor_received_messages_total_updates\" : \"6\" } } ] [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" , \"bgp_neighbor_received_messages_malformed_updates\" : \"0\" , \"bgp_neighbor_received_messages_queue_depth\" : 0 , \"bgp_neighbor_received_messages_total_messages\" : \"424\" , \"bgp_neighbor_received_messages_total_non_updates\" : \"418\" , \"bgp_neighbor_received_messages_total_updates\" : \"6\" } } ] group by multiple tags # processors : group-by-queue-id : event-group-by : tags : - source - interface_name - multicast-queue_queue-id Event Format Before Event Format After [ { \"name\" : \"sub1\" , \"timestamp\" : 1627997491187771616 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"multicast-queue_queue-id\" : \"5\" , \"source\" : \"clab-ndk-srl1:57400\" , \"subscription-name\" : \"sub1\" , }, \"values\" : { \"/interface/qos/output/multicast-queue/queue-depth/maximum-burst-size\" : \"0\" } }, { \"name\" : \"sub1\" , \"timestamp\" : 1627997491187771616 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"multicast-queue_queue-id\" : \"5\" , \"source\" : \"clab-ndk-srl1:57400\" , \"subscription-name\" : \"sub1\" , }, \"values\" : { \"/interface/qos/output/multicast-queue/scheduling/peak-rate-bps\" : \"0\" } } ] [ { \"name\" : \"sub1\" , \"timestamp\" : 1627997491187771616 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"multicast-queue_queue-id\" : \"5\" , \"source\" : \"clab-ndk-srl1:57400\" , \"subscription-name\" : \"sub1\" , }, \"values\" : { \"/interface/qos/output/multicast-queue/queue-depth/maximum-burst-size\" : \"0\" , \"/interface/qos/output/multicast-queue/scheduling/peak-rate-bps\" : \"0\" } } ]","title":"Group by"},{"location":"user_guide/event_processors/event_group_by/#configuration","text":"processors : # processor name sample-processor : # processor type event-group-by : # list of strings defining the tags to group by the values under # a single event tags : [] # a boolean, if true only the values from events of the same name # are grouped together according to the list of tags by-name : # boolean debug : false","title":"Configuration"},{"location":"user_guide/event_processors/event_group_by/#examples","text":"","title":"Examples"},{"location":"user_guide/event_processors/event_group_by/#group-by-a-single-tag","text":"processors : group-by-source : event-group-by : tags : - source Event format before Event format after [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" } }, { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_received_messages_malformed_updates\" : \"0\" , \"bgp_neighbor_received_messages_queue_depth\" : 0 , \"bgp_neighbor_received_messages_total_messages\" : \"424\" , \"bgp_neighbor_received_messages_total_non_updates\" : \"418\" , \"bgp_neighbor_received_messages_total_updates\" : \"6\" } } ] [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" , \"bgp_neighbor_received_messages_malformed_updates\" : \"0\" , \"bgp_neighbor_received_messages_queue_depth\" : 0 , \"bgp_neighbor_received_messages_total_messages\" : \"424\" , \"bgp_neighbor_received_messages_total_non_updates\" : \"418\" , \"bgp_neighbor_received_messages_total_updates\" : \"6\" } } ]","title":"group by a single tag"},{"location":"user_guide/event_processors/event_group_by/#group-by-multiple-tags","text":"processors : group-by-queue-id : event-group-by : tags : - source - interface_name - multicast-queue_queue-id Event Format Before Event Format After [ { \"name\" : \"sub1\" , \"timestamp\" : 1627997491187771616 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"multicast-queue_queue-id\" : \"5\" , \"source\" : \"clab-ndk-srl1:57400\" , \"subscription-name\" : \"sub1\" , }, \"values\" : { \"/interface/qos/output/multicast-queue/queue-depth/maximum-burst-size\" : \"0\" } }, { \"name\" : \"sub1\" , \"timestamp\" : 1627997491187771616 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"multicast-queue_queue-id\" : \"5\" , \"source\" : \"clab-ndk-srl1:57400\" , \"subscription-name\" : \"sub1\" , }, \"values\" : { \"/interface/qos/output/multicast-queue/scheduling/peak-rate-bps\" : \"0\" } } ] [ { \"name\" : \"sub1\" , \"timestamp\" : 1627997491187771616 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"multicast-queue_queue-id\" : \"5\" , \"source\" : \"clab-ndk-srl1:57400\" , \"subscription-name\" : \"sub1\" , }, \"values\" : { \"/interface/qos/output/multicast-queue/queue-depth/maximum-burst-size\" : \"0\" , \"/interface/qos/output/multicast-queue/scheduling/peak-rate-bps\" : \"0\" } } ]","title":"group by multiple tags"},{"location":"user_guide/event_processors/event_jq/","text":"The event-jq processor applies a jq expression on the received event messages. jq expressions are a powerful tool that can be used to slice, filter, map, transform JSON object. The event-jq processor uses two configuration fields, condition and expression , both support jq expressions. condition (that needs to return a boolean value) determines if the processor is to be applied on the event message. if false the message is returned as is. expression is used to transform, filter and/or enrich the messages. It needs to return a JSON object that can be mapped to an array of event messages. The event messages resulting from a single gNMI Notification are passed to the jq expression as a JSON array. Some jq expression examples: Select messages with name \"sub1\" that include a value called \"counter1\" with value higher than 90 expression : .[] | select(.name==\"sub1\" and .values.counter1 > 90) Delete values with name \"counter1\" expression : .[] | del(.values.counter1) Delete values with names \"counter1\" or \"counter2\" expression : .[] | del(.values.[\"counter1\", \"counter2\"]) Delete tags with names \"tag1\" or \"tag2\" expression : .[] | del(.tags.[\"tag1\", \"tag2\"]) Add a tag called \"my_new_tag\" with value \"tag1\" expression : .[] |= (.tags.my_new_tag = \"tag1\") Move a value to tag under a custom key expression : .[] |= (.tags.my_new_tag_name = .values.value_name) Configuration # processors : # processor name sample-processor : # processor type event-jq : # condition of application of the processor condition : # jq expression to transform/filter/enrich the message expression : # boolean enabling extra logging debug :","title":"JQ"},{"location":"user_guide/event_processors/event_jq/#configuration","text":"processors : # processor name sample-processor : # processor type event-jq : # condition of application of the processor condition : # jq expression to transform/filter/enrich the message expression : # boolean enabling extra logging debug :","title":"Configuration"},{"location":"user_guide/event_processors/event_merge/","text":"The event-merge processor merges multiple event messages together based on some criteria. Each gNMI subscribe Response Update in a gNMI subscribe Response Notification is transformed into an Event Message The event-merge processor is used to merge the updates into one event message if it's needed. The default merge strategy is based on the timestamp, the updates with the same timestamp will be merged into the same event message. processors : # processor name sample-processor : # processor type event-merge : # if always is set to true, # the updates are merged regardless of the timestamp values always : false debug : false Event format before Event format after [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" } }, { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_received_messages_malformed_updates\" : \"0\" , \"bgp_neighbor_received_messages_queue_depth\" : 0 , \"bgp_neighbor_received_messages_total_messages\" : \"424\" , \"bgp_neighbor_received_messages_total_non_updates\" : \"418\" , \"bgp_neighbor_received_messages_total_updates\" : \"6\" } } ] [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" , \"bgp_neighbor_received_messages_malformed_updates\" : \"0\" , \"bgp_neighbor_received_messages_queue_depth\" : 0 , \"bgp_neighbor_received_messages_total_messages\" : \"424\" , \"bgp_neighbor_received_messages_total_non_updates\" : \"418\" , \"bgp_neighbor_received_messages_total_updates\" : \"6\" } } ]","title":"Merge"},{"location":"user_guide/event_processors/event_override_ts/","text":"The event-override-ts processor overrides the message timestamp with time.Now() . The precision s , ms , us or ns (default) can be configured. Examples # processors : # processor name set-timestamp-processor : # processor type event-override-ts : # timestamp precision, s, ms, us, ns (default) precision : ms","title":"Override TS"},{"location":"user_guide/event_processors/event_override_ts/#examples","text":"processors : # processor name set-timestamp-processor : # processor type event-override-ts : # timestamp precision, s, ms, us, ns (default) precision : ms","title":"Examples"},{"location":"user_guide/event_processors/event_plugin/","text":"The event-plugin processor initializes a processor that gNMIc loaded from the configured path under the plugins: section. plugins : # path to load plugin binaries from. path : /path/to/plugin/bin # glob to match binaries against. glob : \"*\" # sets a start timeout for plugins. start-timeout : 0s The specific configuration of an event-plugin processor varies from one plugin to another. But they are configured just like any other processor i.e under the processors: section of the config file and linked to outputs by name reference. The below configuration snippet initializes the event-add-hostname processor (a binary stored under plugins.path ) and links to output out1 . processors : proc1 : event-add-hostname : debug : true # the tag name to add with the host hostname as a tag value. hostname-tag-name : \"collector-host\" # read-interval controls how often the plugin runs the hostname cmd to get the host hostanme # by default it's at most every 1 minute read-interval : 1m outputs : out1 : type : file format : event event-processors : - proc1 Examples # See here . Writing a plugin processor # Currently plugin processor can only be written in Golang. It relies on Hashicorp's go-plugin package for discovery and communication with gNMIc's main process. To write your own processor you can use the below skeleton code as a starting point. Can be found here as well. Choose a name for your processor Add struct fields to decode the processor's config into. Implement your processor's logic under the Apply method Optionally, store the targets , actions and processors config maps given to the processor under your processor's struct ( myProcessor ) if they are relevant to your processor's logic. package main import ( \"log\" \"os\" \"github.com/hashicorp/go-hclog\" \"github.com/hashicorp/go-plugin\" \"github.com/openconfig/gnmic/pkg/formatters\" \"github.com/openconfig/gnmic/pkg/formatters/event_plugin\" \"github.com/openconfig/gnmic/pkg/types\" ) const ( // TODO: Choose a name for your processor processorType = \"event-my-processor\" ) type myProcessor struct { // TODO: Add your config struct fields here } func ( p * myProcessor ) Init ( cfg interface {}, opts ... formatters . Option ) error { // decode the plugin config err := formatters . DecodeConfig ( cfg , p ) if err != nil { return err } // apply options for _ , o := range opts { o ( p ) } // TODO: Other initialization steps... return nil } func ( p * myProcessor ) Apply ( event ...* formatters . EventMsg ) [] * formatters . EventMsg { // TODO: The processor's logic is applied here return event } func ( p * myProcessor ) WithActions ( act map [ string ] map [ string ] interface {}) { } func ( p * myProcessor ) WithTargets ( tcs map [ string ] * types . TargetConfig ) { } func ( p * myProcessor ) WithProcessors ( procs map [ string ] map [ string ] any ) { } func ( p * myProcessor ) WithLogger ( l * log . Logger ) { } func main () { logger := hclog . New ( & hclog . LoggerOptions { Output : os . Stderr , DisableTime : true , }) logger . Info ( \"starting plugin processor\" , \"name\" , processorType ) // TODO: Create and initialize your processor's struct plug := & myProcessor {} // start it plugin . Serve ( & plugin . ServeConfig { HandshakeConfig : plugin . HandshakeConfig { ProtocolVersion : 1 , MagicCookieKey : \"GNMIC_PLUGIN\" , MagicCookieValue : \"gnmic\" , }, Plugins : map [ string ] plugin . Plugin { processorType : & event_plugin . EventProcessorPlugin { Impl : plug }, }, Logger : logger , }) }","title":"Event plugin"},{"location":"user_guide/event_processors/event_plugin/#examples","text":"See here .","title":"Examples"},{"location":"user_guide/event_processors/event_plugin/#writing-a-plugin-processor","text":"Currently plugin processor can only be written in Golang. It relies on Hashicorp's go-plugin package for discovery and communication with gNMIc's main process. To write your own processor you can use the below skeleton code as a starting point. Can be found here as well. Choose a name for your processor Add struct fields to decode the processor's config into. Implement your processor's logic under the Apply method Optionally, store the targets , actions and processors config maps given to the processor under your processor's struct ( myProcessor ) if they are relevant to your processor's logic. package main import ( \"log\" \"os\" \"github.com/hashicorp/go-hclog\" \"github.com/hashicorp/go-plugin\" \"github.com/openconfig/gnmic/pkg/formatters\" \"github.com/openconfig/gnmic/pkg/formatters/event_plugin\" \"github.com/openconfig/gnmic/pkg/types\" ) const ( // TODO: Choose a name for your processor processorType = \"event-my-processor\" ) type myProcessor struct { // TODO: Add your config struct fields here } func ( p * myProcessor ) Init ( cfg interface {}, opts ... formatters . Option ) error { // decode the plugin config err := formatters . DecodeConfig ( cfg , p ) if err != nil { return err } // apply options for _ , o := range opts { o ( p ) } // TODO: Other initialization steps... return nil } func ( p * myProcessor ) Apply ( event ...* formatters . EventMsg ) [] * formatters . EventMsg { // TODO: The processor's logic is applied here return event } func ( p * myProcessor ) WithActions ( act map [ string ] map [ string ] interface {}) { } func ( p * myProcessor ) WithTargets ( tcs map [ string ] * types . TargetConfig ) { } func ( p * myProcessor ) WithProcessors ( procs map [ string ] map [ string ] any ) { } func ( p * myProcessor ) WithLogger ( l * log . Logger ) { } func main () { logger := hclog . New ( & hclog . LoggerOptions { Output : os . Stderr , DisableTime : true , }) logger . Info ( \"starting plugin processor\" , \"name\" , processorType ) // TODO: Create and initialize your processor's struct plug := & myProcessor {} // start it plugin . Serve ( & plugin . ServeConfig { HandshakeConfig : plugin . HandshakeConfig { ProtocolVersion : 1 , MagicCookieKey : \"GNMIC_PLUGIN\" , MagicCookieValue : \"gnmic\" , }, Plugins : map [ string ] plugin . Plugin { processorType : & event_plugin . EventProcessorPlugin { Impl : plug }, }, Logger : logger , }) }","title":"Writing a plugin processor"},{"location":"user_guide/event_processors/event_rate_limit/","text":"The event-rate-limit processor rate-limits each event with matching tags to the configured amount per-seconds. All the tags for each event is hashed, and if the hash matches a previously seen event, then the timestamp of the event itself is compared to assess if the configured limit has been exceeded. If it has, then this new event is dropped from the pipeline. The cache for comparing timestamp is an LRU cache, with a default size of 1000 that can be increased for bigger deployments. To account for cases where the device will artificially split the event into multiple chunks (with the same timestamp), the rate-limiter will ignore events with exactly the same timestamp. Examples # processors : # processor name rate-limit-100pps : # processor type event-rate-limit : # rate of filtering, in events per seconds per-second : 100 # set the cache size for doing the rate-limiting comparison # default value is 1000 cache-size : 10000 # debug for additionnal logging of dropped events debug : true","title":"Rate Limit"},{"location":"user_guide/event_processors/event_rate_limit/#examples","text":"processors : # processor name rate-limit-100pps : # processor type event-rate-limit : # rate of filtering, in events per seconds per-second : 100 # set the cache size for doing the rate-limiting comparison # default value is 1000 cache-size : 10000 # debug for additionnal logging of dropped events debug : true","title":"Examples"},{"location":"user_guide/event_processors/event_starlark/","text":"Intro # The event-starlark processor applies a Starlark function on a list of event messages before returning them to the processors pipeline and then to the output. starlark is a dialect of Python, developed initially for the Bazel build tool but found multiple uses as a configuration language embedded in a larger application. There are a few differences between Python and Starlark, programs written in Starlark are supposed to be short-lived and have no external side effects, their main result is structured data or side effects on the host application. As a result, Starlark has no need for classes, exceptions, reflection, concurrency, and other such features of Python. gNMIc uses the Go implementation of Starlark. A Starlark program running as a gNMIc processor should define an apply function that takes an arbitrary number of arguments of type Event and returns zero or more Event s. An Event is the transformed gNMI update message as gNMIc processes it. def apply ( * events ) # events transformed/augmented/filtered here return events Configuration # processors : # processor name sample-processor : # processor type event-starlark : # the source of the starlark program. source : | def apply(*events): # processor logic here return events # path to a file containing the starlark program to run. # Mutually exclusive with `source` parameter. script : # boolean enabling extra logging debug : false Writing a Starlark processor # To write a starlark processor all that is needed is writing a function called apply that will read/modify/delete a list of Event messages. Starlark specification defines multiple builtin types and functions. gNMIc provides additional builtin functions like Event(name) which creates a new Event message and copy_event(Event) which duplicates a given Event message. The Event message comprises a few fields: name : string timestamp : int64 tags : dictionary of string to string values : dictionary of string to any deletes : list of strings Starlark allows for the dynamic loading of other modules . In the context of gNMIc, the following two modules are available for loading within a starlark program: time : load(\"time.star\", \"time\") loads the time library which provides the following functions to work with the Event message timestamp field: time.from_timestamp(sec, nsec) : Converts the given Unix time corresponding to the number of seconds and (optionally) nanoseconds since January 1, 1970 UTC into an object of type Time. For more details, refer to https://pkg.go.dev/time#Unix . time.is_valid_timezone(loc) : Reports whether loc is a valid time zone name. time.now() : Returns the current local time. time.parse_duration(d) : Parses the given duration string. For more details, refer to https://pkg.go.dev/time#ParseDuration . time.parse_time(x, format, location) : Parses the given time string using a specific time format and location. The expected arguments are a time string (mandatory), a time format (optional, set to RFC3339 by default, e.g. \"2021-03-22T23:20:50.52Z\") and a name of location (optional, set to UTC by default). For more details, refer to https://pkg.go.dev/time#Parse and https://pkg.go.dev/time#ParseInLocation . time.time(year, month, day, hour, minute, second, nanosecond, location) : Returns the Time corresponding to yyyy-mm-dd hh:mm:ss + nsec nanoseconds in the appropriate zone for that time in the given location. All the parameters are optional. math : load(\"math.star\", \"math\") loads the math library which provides a set of constants and math-related functions: ceil(x) : Returns the ceiling of x, the smallest integer greater than or equal to x. copysign(x, y) : Returns a value with the magnitude of x and the sign of y. fabs(x) : Returns the absolute value of x as float. floor(x) : Returns the floor of x, the largest integer less than or equal to x. mod(x, y) : Returns the floating-point remainder of x/y. The magnitude of the result is less than y and its sign agrees with that of x. pow(x, y) : Returns x**y, the base-x exponential of y. remainder(x, y) : Returns the IEEE 754 floating-point remainder of x/y. round(x) : Returns the nearest integer, rounding half away from zero. exp(x) : Returns e raised to the power x, where e = 2.718281\u2026 is the base of natural logarithms. sqrt(x) : Returns the square root of x. acos(x) : Returns the arc cosine of x, in radians. asin(x) : Returns the arc sine of x, in radians. atan(x) : Returns the arc tangent of x, in radians. atan2(y, x) : Returns atan(y / x), in radians. The result is between -pi and pi. The vector in the plane from the origin to point (x, y) makes this angle with the positive X axis. The point of atan2() is that the signs of both inputs are known to it, so it can compute the correct quadrant for the angle. For example, atan(1) and atan2(1, 1) are both pi/4, but atan2(-1, -1) is -3*pi/4. cos(x) : Returns the cosine of x, in radians. hypot(x, y) : Returns the Euclidean norm, sqrt(x*x + y*y). This is the length of the vector from the origin to point (x, y). sin(x) : Returns the sine of x, in radians. tan(x) : Returns the tangent of x, in radians. degrees(x) : Converts angle x from radians to degrees. radians(x) : Converts angle x from degrees to radians. acosh(x) : Returns the inverse hyperbolic cosine of x. asinh(x) : Returns the inverse hyperbolic sine of x. atanh(x) : Returns the inverse hyperbolic tangent of x. cosh(x) : Returns the hyperbolic cosine of x. sinh(x) : Returns the hyperbolic sine of x. tanh(x) : Returns the hyperbolic tangent of x. log(x, base) : Returns the logarithm of x in the given base, or natural logarithm by default. gamma(x) : Returns the Gamma function of x. Examples # Move a value to a tag # def apply ( * events ): dels = [] for e in events : for k , v in e . values . items (): if k == \"val1\" : e . tags [ k ] = str ( v ) dels . append ( k ) for d in dels : e . values . pop ( d ) return events Rename values # val_map = { \"val1\" : \"new_val\" , } def apply ( * events ): for e in events : for k , v in e . values . items (): if k in val_map : e . values [ val_map [ k ]] = v e . values . pop ( k ) return events Convert strings to integers # def apply ( * events ): for e in events : for k , v in e . values . items (): if v . isdigit (): e . values [ k ] = int ( v ) return events Set an interface description as a tag # This script stores each interface description per target/interface in a cache and adds it to other values as a tag. cache = {} def apply ( * events ): evs = [] # check if on the event messages contains an interface description # and store in th cache dict for e in events : if e . values . get ( \"/interface/description\" ): target_if = e . tags [ \"source\" ] + \"_\" + e . tags [ \"interface_name\" ] cache [ target_if ] = e . values [ \"/interface/description\" ] # for each event get the 'source' and 'interface_name', check # if a corresponding cache entry exists and set it as a # 'description' tag for e in events : if e . tags . get ( \"source\" ) and e . tags . get ( \"interface_name\" ): target_if = e . tags [ \"source\" ] + \"_\" + e . tags [ \"interface_name\" ] if cache . get ( target_if ): e . tags [ \"description\" ] = cache [ target_if ] evs . append ( e ) return evs Calculate new values based on the received ones # The below script calculates the avg, min, max of a list of values over their last N=10 values cache = {} values_names = [ '/interface/statistics/out-octets' , '/interface/statistics/in-octets' ] N = 10 def apply ( * events ): for e in events : for value_name in values_names : v = e . values . get ( value_name ) # check if v is not None and is a digit to proceed if not v . isdigit (): continue # update cache with the latest value val_key = \"_\" . join ([ e . tags [ \"source\" ], e . tags [ \"interface_name\" ], value_name ]) if not cache . get ( val_key ): # initialize the cache entry if empty cache . update ({ val_key : []}) if len ( cache [ val_key ]) >= N : # remove the oldest entry if the number of entries reached N cache [ val_key ] = cache [ val_key ][ 1 :] # update cache entry cache [ val_key ] . append ( int ( v )) # get the list of values val_list = cache [ val_key ] # calculate min, max and avg e . values [ value_name + \"_min\" ] = min ( val_list ) e . values [ value_name + \"_max\" ] = max ( val_list ) e . values [ value_name + \"_avg\" ] = avg ( val_list ) return events def avg ( vals ): sum = 0 for v in vals : sum = sum + v return sum / len ( vals ) The below script builds on top of the previous one by adding the rate calculation to the added values. Now the cache contains a timestamp as well as the value. cache = {} values_names = [ '/interface/statistics/out-octets' , '/interface/statistics/in-octets' ] N = 10 def apply ( * events ): for e in events : for value_name in values_names : v = e . values . get ( value_name ) # check if v is not None and is a digit to proceed if not v . isdigit (): continue # update cache with the latest value val_key = \"_\" . join ([ e . tags [ \"source\" ], e . tags [ \"interface_name\" ], value_name ]) if not cache . get ( val_key ): # initialize the cache entry if empty cache . update ({ val_key : []}) if len ( cache [ val_key ]) >= N : # remove the oldest entry if the number of entries reached N cache [ val_key ] = cache [ val_key ][ 1 :] # update cache entry cache [ val_key ] . append (( e . timestamp , int ( v ))) # get the list of values val_list = cache [ val_key ] # calculate min, max and avg vals = [ x [ 1 ] for x in val_list ] e . values [ value_name + \"_min\" ] = min ( vals ) e . values [ value_name + \"_max\" ] = max ( vals ) e . values [ value_name + \"_avg\" ] = avg ( vals ) if len ( val_list ) > 1 : e . values [ value_name + \"_rate\" ] = rate ( val_list [ - 2 :]) return events def avg ( vals ): sum = 0 for v in vals : sum = sum + v return sum / len ( vals ) def rate ( vals ): period = ( vals [ 1 ][ 0 ] - vals [ 0 ][ 0 ]) / 1000000000 change = vals [ 1 ][ 1 ] - vals [ 0 ][ 1 ] return change / period Ungroup values # The below script ungroups values part of the same event message producing an event message per value. def apply ( * events ): ungrouped_events = [] for e in events : for k , v in e . values . items (): # create a new event without any value new_event = Event ( e . name , e . timestamp , e . tags ) # add a single value to the new event new_event . values [ k ] = v # add the new event to the array ungrouped_events . append ( new_event ) return ungrouped_events","title":"Starlark"},{"location":"user_guide/event_processors/event_starlark/#intro","text":"The event-starlark processor applies a Starlark function on a list of event messages before returning them to the processors pipeline and then to the output. starlark is a dialect of Python, developed initially for the Bazel build tool but found multiple uses as a configuration language embedded in a larger application. There are a few differences between Python and Starlark, programs written in Starlark are supposed to be short-lived and have no external side effects, their main result is structured data or side effects on the host application. As a result, Starlark has no need for classes, exceptions, reflection, concurrency, and other such features of Python. gNMIc uses the Go implementation of Starlark. A Starlark program running as a gNMIc processor should define an apply function that takes an arbitrary number of arguments of type Event and returns zero or more Event s. An Event is the transformed gNMI update message as gNMIc processes it. def apply ( * events ) # events transformed/augmented/filtered here return events","title":"Intro"},{"location":"user_guide/event_processors/event_starlark/#configuration","text":"processors : # processor name sample-processor : # processor type event-starlark : # the source of the starlark program. source : | def apply(*events): # processor logic here return events # path to a file containing the starlark program to run. # Mutually exclusive with `source` parameter. script : # boolean enabling extra logging debug : false","title":"Configuration"},{"location":"user_guide/event_processors/event_starlark/#writing-a-starlark-processor","text":"To write a starlark processor all that is needed is writing a function called apply that will read/modify/delete a list of Event messages. Starlark specification defines multiple builtin types and functions. gNMIc provides additional builtin functions like Event(name) which creates a new Event message and copy_event(Event) which duplicates a given Event message. The Event message comprises a few fields: name : string timestamp : int64 tags : dictionary of string to string values : dictionary of string to any deletes : list of strings Starlark allows for the dynamic loading of other modules . In the context of gNMIc, the following two modules are available for loading within a starlark program: time : load(\"time.star\", \"time\") loads the time library which provides the following functions to work with the Event message timestamp field: time.from_timestamp(sec, nsec) : Converts the given Unix time corresponding to the number of seconds and (optionally) nanoseconds since January 1, 1970 UTC into an object of type Time. For more details, refer to https://pkg.go.dev/time#Unix . time.is_valid_timezone(loc) : Reports whether loc is a valid time zone name. time.now() : Returns the current local time. time.parse_duration(d) : Parses the given duration string. For more details, refer to https://pkg.go.dev/time#ParseDuration . time.parse_time(x, format, location) : Parses the given time string using a specific time format and location. The expected arguments are a time string (mandatory), a time format (optional, set to RFC3339 by default, e.g. \"2021-03-22T23:20:50.52Z\") and a name of location (optional, set to UTC by default). For more details, refer to https://pkg.go.dev/time#Parse and https://pkg.go.dev/time#ParseInLocation . time.time(year, month, day, hour, minute, second, nanosecond, location) : Returns the Time corresponding to yyyy-mm-dd hh:mm:ss + nsec nanoseconds in the appropriate zone for that time in the given location. All the parameters are optional. math : load(\"math.star\", \"math\") loads the math library which provides a set of constants and math-related functions: ceil(x) : Returns the ceiling of x, the smallest integer greater than or equal to x. copysign(x, y) : Returns a value with the magnitude of x and the sign of y. fabs(x) : Returns the absolute value of x as float. floor(x) : Returns the floor of x, the largest integer less than or equal to x. mod(x, y) : Returns the floating-point remainder of x/y. The magnitude of the result is less than y and its sign agrees with that of x. pow(x, y) : Returns x**y, the base-x exponential of y. remainder(x, y) : Returns the IEEE 754 floating-point remainder of x/y. round(x) : Returns the nearest integer, rounding half away from zero. exp(x) : Returns e raised to the power x, where e = 2.718281\u2026 is the base of natural logarithms. sqrt(x) : Returns the square root of x. acos(x) : Returns the arc cosine of x, in radians. asin(x) : Returns the arc sine of x, in radians. atan(x) : Returns the arc tangent of x, in radians. atan2(y, x) : Returns atan(y / x), in radians. The result is between -pi and pi. The vector in the plane from the origin to point (x, y) makes this angle with the positive X axis. The point of atan2() is that the signs of both inputs are known to it, so it can compute the correct quadrant for the angle. For example, atan(1) and atan2(1, 1) are both pi/4, but atan2(-1, -1) is -3*pi/4. cos(x) : Returns the cosine of x, in radians. hypot(x, y) : Returns the Euclidean norm, sqrt(x*x + y*y). This is the length of the vector from the origin to point (x, y). sin(x) : Returns the sine of x, in radians. tan(x) : Returns the tangent of x, in radians. degrees(x) : Converts angle x from radians to degrees. radians(x) : Converts angle x from degrees to radians. acosh(x) : Returns the inverse hyperbolic cosine of x. asinh(x) : Returns the inverse hyperbolic sine of x. atanh(x) : Returns the inverse hyperbolic tangent of x. cosh(x) : Returns the hyperbolic cosine of x. sinh(x) : Returns the hyperbolic sine of x. tanh(x) : Returns the hyperbolic tangent of x. log(x, base) : Returns the logarithm of x in the given base, or natural logarithm by default. gamma(x) : Returns the Gamma function of x.","title":"Writing a Starlark processor"},{"location":"user_guide/event_processors/event_starlark/#examples","text":"","title":"Examples"},{"location":"user_guide/event_processors/event_starlark/#move-a-value-to-a-tag","text":"def apply ( * events ): dels = [] for e in events : for k , v in e . values . items (): if k == \"val1\" : e . tags [ k ] = str ( v ) dels . append ( k ) for d in dels : e . values . pop ( d ) return events","title":"Move a value to a tag"},{"location":"user_guide/event_processors/event_starlark/#rename-values","text":"val_map = { \"val1\" : \"new_val\" , } def apply ( * events ): for e in events : for k , v in e . values . items (): if k in val_map : e . values [ val_map [ k ]] = v e . values . pop ( k ) return events","title":"Rename values"},{"location":"user_guide/event_processors/event_starlark/#convert-strings-to-integers","text":"def apply ( * events ): for e in events : for k , v in e . values . items (): if v . isdigit (): e . values [ k ] = int ( v ) return events","title":"Convert strings to integers"},{"location":"user_guide/event_processors/event_starlark/#set-an-interface-description-as-a-tag","text":"This script stores each interface description per target/interface in a cache and adds it to other values as a tag. cache = {} def apply ( * events ): evs = [] # check if on the event messages contains an interface description # and store in th cache dict for e in events : if e . values . get ( \"/interface/description\" ): target_if = e . tags [ \"source\" ] + \"_\" + e . tags [ \"interface_name\" ] cache [ target_if ] = e . values [ \"/interface/description\" ] # for each event get the 'source' and 'interface_name', check # if a corresponding cache entry exists and set it as a # 'description' tag for e in events : if e . tags . get ( \"source\" ) and e . tags . get ( \"interface_name\" ): target_if = e . tags [ \"source\" ] + \"_\" + e . tags [ \"interface_name\" ] if cache . get ( target_if ): e . tags [ \"description\" ] = cache [ target_if ] evs . append ( e ) return evs","title":"Set an interface description as a tag"},{"location":"user_guide/event_processors/event_starlark/#calculate-new-values-based-on-the-received-ones","text":"The below script calculates the avg, min, max of a list of values over their last N=10 values cache = {} values_names = [ '/interface/statistics/out-octets' , '/interface/statistics/in-octets' ] N = 10 def apply ( * events ): for e in events : for value_name in values_names : v = e . values . get ( value_name ) # check if v is not None and is a digit to proceed if not v . isdigit (): continue # update cache with the latest value val_key = \"_\" . join ([ e . tags [ \"source\" ], e . tags [ \"interface_name\" ], value_name ]) if not cache . get ( val_key ): # initialize the cache entry if empty cache . update ({ val_key : []}) if len ( cache [ val_key ]) >= N : # remove the oldest entry if the number of entries reached N cache [ val_key ] = cache [ val_key ][ 1 :] # update cache entry cache [ val_key ] . append ( int ( v )) # get the list of values val_list = cache [ val_key ] # calculate min, max and avg e . values [ value_name + \"_min\" ] = min ( val_list ) e . values [ value_name + \"_max\" ] = max ( val_list ) e . values [ value_name + \"_avg\" ] = avg ( val_list ) return events def avg ( vals ): sum = 0 for v in vals : sum = sum + v return sum / len ( vals ) The below script builds on top of the previous one by adding the rate calculation to the added values. Now the cache contains a timestamp as well as the value. cache = {} values_names = [ '/interface/statistics/out-octets' , '/interface/statistics/in-octets' ] N = 10 def apply ( * events ): for e in events : for value_name in values_names : v = e . values . get ( value_name ) # check if v is not None and is a digit to proceed if not v . isdigit (): continue # update cache with the latest value val_key = \"_\" . join ([ e . tags [ \"source\" ], e . tags [ \"interface_name\" ], value_name ]) if not cache . get ( val_key ): # initialize the cache entry if empty cache . update ({ val_key : []}) if len ( cache [ val_key ]) >= N : # remove the oldest entry if the number of entries reached N cache [ val_key ] = cache [ val_key ][ 1 :] # update cache entry cache [ val_key ] . append (( e . timestamp , int ( v ))) # get the list of values val_list = cache [ val_key ] # calculate min, max and avg vals = [ x [ 1 ] for x in val_list ] e . values [ value_name + \"_min\" ] = min ( vals ) e . values [ value_name + \"_max\" ] = max ( vals ) e . values [ value_name + \"_avg\" ] = avg ( vals ) if len ( val_list ) > 1 : e . values [ value_name + \"_rate\" ] = rate ( val_list [ - 2 :]) return events def avg ( vals ): sum = 0 for v in vals : sum = sum + v return sum / len ( vals ) def rate ( vals ): period = ( vals [ 1 ][ 0 ] - vals [ 0 ][ 0 ]) / 1000000000 change = vals [ 1 ][ 1 ] - vals [ 0 ][ 1 ] return change / period","title":"Calculate new values based on the received ones"},{"location":"user_guide/event_processors/event_starlark/#ungroup-values","text":"The below script ungroups values part of the same event message producing an event message per value. def apply ( * events ): ungrouped_events = [] for e in events : for k , v in e . values . items (): # create a new event without any value new_event = Event ( e . name , e . timestamp , e . tags ) # add a single value to the new event new_event . values [ k ] = v # add the new event to the array ungrouped_events . append ( new_event ) return ungrouped_events","title":"Ungroup values"},{"location":"user_guide/event_processors/event_strings/","text":"The event-strings processor exposes a few of Golang strings transformation functions, there functions can be applied to tags, tag names, values or value names. Supported functions: strings.Replace strings.TrimPrefix strings.TrimSuffix strings.Title strings.ToLower strings.ToUpper strings.Split filepath.Base processors : # processor name sample-processor : # processor type event-strings : value-names : [] tag-names : [] values : [] tags : [] transforms : # strings function name - replace : apply-on : # apply the transformation on name or value keep : # keep the old value or not if the name changed old : # string to be replaced new : #replacement string of old - trim-prefix : apply-on : # apply the transformation on name or value prefix : # prefix to be trimmed - trim_suffix : apply-on : # apply the transformation on name or value suffix : # suffix to be trimmed - title : apply-on : # apply the transformation on name or value - to-upper : apply-on : # apply the transformation on name or value - to-lower : apply-on : # apply the transformation on name or value - split : apply-on : # apply the transformation on name or value split-on : # character to split on join-with : # character to join with ignore-first : # number of first items to ignore when joining ignore-last : # number of last items to ignore when joining - path-base : apply-on : # apply the transformation on name or value Examples # replace # processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - replace : apply-on : \"name\" old : \"-\" new : \"_\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier-transitions\" : \"1\" , \"in-error-packets\" : \"0\" , \"in-fcs-error-packets\" : \"0\" , \"in-octets\" : \"65382630\" , \"in-unicast-packets\" : \"107154\" , \"out-error-packets\" : \"0\" , \"out-octets\" : \"64721394\" , \"out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier_transitions\" : \"1\" , \"in_error_packets\" : \"0\" , \"in_fcs_error_packets\" : \"0\" , \"in_octets\" : \"65382630\" , \"in_unicast_packets\" : \"107154\" , \"out_error_packets\" : \"0\" , \"out_octets\" : \"64721394\" , \"out_unicast_packets\" : \"105876\" } } trim-prefix # processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - trim-prefix : apply-on : \"name\" prefix : \"/srl_nokia-interfaces:interface/statistics/\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier-transitions\" : \"1\" , \"in-broadcast-packets\" : \"3797\" , \"in-error-packets\" : \"0\" , \"in-fcs-error-packets\" : \"0\" , \"in-multicast-packets\" : \"288033\" , \"in-octets\" : \"65382630\" , \"in-unicast-packets\" : \"107154\" , \"out-broadcast-packets\" : \"614\" , \"out-error-packets\" : \"0\" , \"out-multicast-packets\" : \"11\" , \"out-octets\" : \"64721394\" , \"out-unicast-packets\" : \"105876\" } } to-upper # processors : # processor name sample-processor : # processor type event-strings : tag-names : - \"interface_name\" - \"subscription-name\" transforms : # strings function name - to-upper : apply-on : \"value\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"MGMT0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"DEFAULT\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } path-base # processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - path-base : apply-on : \"name\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier-transitions\" : \"1\" , \"in-broadcast-packets\" : \"3797\" , \"in-error-packets\" : \"0\" , \"in-fcs-error-packets\" : \"0\" , \"in-multicast-packets\" : \"288033\" , \"in-octets\" : \"65382630\" , \"in-unicast-packets\" : \"107154\" , \"out-broadcast-packets\" : \"614\" , \"out-error-packets\" : \"0\" , \"out-multicast-packets\" : \"11\" , \"out-octets\" : \"64721394\" , \"out-unicast-packets\" : \"105876\" } } split # processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - split : on : \"name\" split-on : \"/\" join-with : \"_\" ignore-first : 1 Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"statistics_carrier-transitions\" : \"1\" , \"statistics_in-broadcast-packets\" : \"3797\" , \"statistics_in-error-packets\" : \"0\" , \"statistics_in-fcs-error-packets\" : \"0\" , \"statistics_in-multicast-packets\" : \"288033\" , \"statistics_in-octets\" : \"65382630\" , \"statistics_in-unicast-packets\" : \"107154\" , \"statistics_out-broadcast-packets\" : \"614\" , \"statistics_out-error-packets\" : \"0\" , \"statistics_out-multicast-packets\" : \"11\" , \"statistics_out-octets\" : \"64721394\" , \"statistics_out-unicast-packets\" : \"105876\" } } multiple transforms # processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - path-base : apply-on : \"name\" - title : apply-on : \"name\" - replace : apply-on : \"name\" old : \"-\" new : \"_\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"Carrier_transitions\" : \"1\" , \"In_broadcast_packets\" : \"3797\" , \"In_error_packets\" : \"0\" , \"In_fcs_error_packets\" : \"0\" , \"In_multicast_packets\" : \"288033\" , \"In_octets\" : \"65382630\" , \"In_unicast_packets\" : \"107154\" , \"Out_broadcast_packets\" : \"614\" , \"Out_error_packets\" : \"0\" , \"Out_multicast_packets\" : \"11\" , \"Out_octets\" : \"64721394\" , \"Out_unicast_packets\" : \"105876\" } }","title":"Strings"},{"location":"user_guide/event_processors/event_strings/#examples","text":"","title":"Examples"},{"location":"user_guide/event_processors/event_strings/#replace","text":"processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - replace : apply-on : \"name\" old : \"-\" new : \"_\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier-transitions\" : \"1\" , \"in-error-packets\" : \"0\" , \"in-fcs-error-packets\" : \"0\" , \"in-octets\" : \"65382630\" , \"in-unicast-packets\" : \"107154\" , \"out-error-packets\" : \"0\" , \"out-octets\" : \"64721394\" , \"out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier_transitions\" : \"1\" , \"in_error_packets\" : \"0\" , \"in_fcs_error_packets\" : \"0\" , \"in_octets\" : \"65382630\" , \"in_unicast_packets\" : \"107154\" , \"out_error_packets\" : \"0\" , \"out_octets\" : \"64721394\" , \"out_unicast_packets\" : \"105876\" } }","title":"replace"},{"location":"user_guide/event_processors/event_strings/#trim-prefix","text":"processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - trim-prefix : apply-on : \"name\" prefix : \"/srl_nokia-interfaces:interface/statistics/\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier-transitions\" : \"1\" , \"in-broadcast-packets\" : \"3797\" , \"in-error-packets\" : \"0\" , \"in-fcs-error-packets\" : \"0\" , \"in-multicast-packets\" : \"288033\" , \"in-octets\" : \"65382630\" , \"in-unicast-packets\" : \"107154\" , \"out-broadcast-packets\" : \"614\" , \"out-error-packets\" : \"0\" , \"out-multicast-packets\" : \"11\" , \"out-octets\" : \"64721394\" , \"out-unicast-packets\" : \"105876\" } }","title":"trim-prefix"},{"location":"user_guide/event_processors/event_strings/#to-upper","text":"processors : # processor name sample-processor : # processor type event-strings : tag-names : - \"interface_name\" - \"subscription-name\" transforms : # strings function name - to-upper : apply-on : \"value\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"MGMT0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"DEFAULT\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } }","title":"to-upper"},{"location":"user_guide/event_processors/event_strings/#path-base","text":"processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - path-base : apply-on : \"name\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"carrier-transitions\" : \"1\" , \"in-broadcast-packets\" : \"3797\" , \"in-error-packets\" : \"0\" , \"in-fcs-error-packets\" : \"0\" , \"in-multicast-packets\" : \"288033\" , \"in-octets\" : \"65382630\" , \"in-unicast-packets\" : \"107154\" , \"out-broadcast-packets\" : \"614\" , \"out-error-packets\" : \"0\" , \"out-multicast-packets\" : \"11\" , \"out-octets\" : \"64721394\" , \"out-unicast-packets\" : \"105876\" } }","title":"path-base"},{"location":"user_guide/event_processors/event_strings/#split","text":"processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - split : on : \"name\" split-on : \"/\" join-with : \"_\" ignore-first : 1 Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"statistics_carrier-transitions\" : \"1\" , \"statistics_in-broadcast-packets\" : \"3797\" , \"statistics_in-error-packets\" : \"0\" , \"statistics_in-fcs-error-packets\" : \"0\" , \"statistics_in-multicast-packets\" : \"288033\" , \"statistics_in-octets\" : \"65382630\" , \"statistics_in-unicast-packets\" : \"107154\" , \"statistics_out-broadcast-packets\" : \"614\" , \"statistics_out-error-packets\" : \"0\" , \"statistics_out-multicast-packets\" : \"11\" , \"statistics_out-octets\" : \"64721394\" , \"statistics_out-unicast-packets\" : \"105876\" } }","title":"split"},{"location":"user_guide/event_processors/event_strings/#multiple-transforms","text":"processors : # processor name sample-processor : # processor type event-strings : value-names : - \".*\" transforms : # strings function name - path-base : apply-on : \"name\" - title : apply-on : \"name\" - replace : apply-on : \"name\" old : \"-\" new : \"_\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"3797\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"288033\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"65382630\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"107154\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"614\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"11\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"64721394\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"105876\" } } { \"name\" : \"default\" , \"timestamp\" : 1607291271894072397 , \"tags\" : { \"interface_name\" : \"mgmt0\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"Carrier_transitions\" : \"1\" , \"In_broadcast_packets\" : \"3797\" , \"In_error_packets\" : \"0\" , \"In_fcs_error_packets\" : \"0\" , \"In_multicast_packets\" : \"288033\" , \"In_octets\" : \"65382630\" , \"In_unicast_packets\" : \"107154\" , \"Out_broadcast_packets\" : \"614\" , \"Out_error_packets\" : \"0\" , \"Out_multicast_packets\" : \"11\" , \"Out_octets\" : \"64721394\" , \"Out_unicast_packets\" : \"105876\" } }","title":"multiple transforms"},{"location":"user_guide/event_processors/event_to_tag/","text":"The event-to-tag processor moves a value matching one of the regular expressions from the values section to the tags section. It's possible to keep the value under values section after moving it. Examples # processors : # processor name sample-processor : # processor type event-to-tag : value-names : - \".*-state$\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607305284170936330 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/admin-state\" : \"disable\" , \"/srl_nokia-interfaces:interface/ifindex\" : 54 , \"/srl_nokia-interfaces:interface/last-change\" : \"2020-11-20T05:52:21.459Z\" , \"/srl_nokia-interfaces:interface/oper-down-reason\" : \"port-admin-disabled\" , \"/srl_nokia-interfaces:interface/oper-state\" : \"down\" } } { \"name\" : \"default\" , \"timestamp\" : 1607305284170936330 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" , \"/srl_nokia-interfaces:interface/admin-state\" : \"disable\" , \"/srl_nokia-interfaces:interface/oper-state\" : \"down\" }, \"values\" : { \"/srl_nokia-interfaces:interface/ifindex\" : 54 , \"/srl_nokia-interfaces:interface/last-change\" : \"2020-11-20T05:52:21.459Z\" , \"/srl_nokia-interfaces:interface/oper-down-reason\" : \"port-admin-disabled\" } }","title":"To Tag"},{"location":"user_guide/event_processors/event_to_tag/#examples","text":"processors : # processor name sample-processor : # processor type event-to-tag : value-names : - \".*-state$\" Event format before Event format after { \"name\" : \"default\" , \"timestamp\" : 1607305284170936330 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" }, \"values\" : { \"/srl_nokia-interfaces:interface/admin-state\" : \"disable\" , \"/srl_nokia-interfaces:interface/ifindex\" : 54 , \"/srl_nokia-interfaces:interface/last-change\" : \"2020-11-20T05:52:21.459Z\" , \"/srl_nokia-interfaces:interface/oper-down-reason\" : \"port-admin-disabled\" , \"/srl_nokia-interfaces:interface/oper-state\" : \"down\" } } { \"name\" : \"default\" , \"timestamp\" : 1607305284170936330 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.23.23.2:57400\" , \"subscription-name\" : \"default\" , \"/srl_nokia-interfaces:interface/admin-state\" : \"disable\" , \"/srl_nokia-interfaces:interface/oper-state\" : \"down\" }, \"values\" : { \"/srl_nokia-interfaces:interface/ifindex\" : 54 , \"/srl_nokia-interfaces:interface/last-change\" : \"2020-11-20T05:52:21.459Z\" , \"/srl_nokia-interfaces:interface/oper-down-reason\" : \"port-admin-disabled\" } }","title":"Examples"},{"location":"user_guide/event_processors/event_trigger/","text":"The event-trigger processor takes event messages as input and triggers a list of actions (sequentially) if a configured condition evaluates to true . The condition is evaluated using the the Golang implementation of jq with the event message as a json input. jq tutorial jq manual jq playground Examples of conditions: The below expression checks if the value named counter1 has a value higher than 90 .values [ \"counter1\" ] > 90 This expression checks if the event name is sub1 , that the tag source is equal to r1:57400 .name == \"sub1\" and .tags [ \"source\" ] == \"r1:57400\" The trigger can be monitored over a configurable window of time (default 1 minute), during which only a certain number of occurrences (default 1) trigger the actions execution. The action types availabe can be found here processors : # processor name my_trigger_proc : # # processor type event-trigger : # trigger condition condition : '.values[\"counter1\"] > 90' # minimum number of condition occurrences within the configured window # required to trigger the action min-occurrences : 1 # max number of times the action is triggered within the configured window max-occurrences : 1 # window of time during which max-occurrences need to # be reached in order to trigger the action window : 60s # async, bool. default false. # If true the trigger is executed in the background and the triggering # message is passed to the next procesor. Otherwise it blocks until the trigger returns async : false # a dictionary of variables that is passed to the actions # and can be accessed in the actions templates using `.Vars` vars : # path to a file containing variables passed to the actions # the variable in the `vars` field override the ones read from the file. vars-file : # list of actions to be executed actions : - counter_alert Examples # Alerting when a threshold is crossed # The below example triggers an HTTP GET to http://remote-server:p8080/${router_name} if the value of counter \"counter1\" crosses 90 twice within 2 minutes. processors : my_trigger_proc : event-trigger : condition : '.values[\"counter1\"] > 90' min-occurrences : 1 max-occurrences : 2 window : 120s async : true actions : - alert actions : alert : name : alert type : http method : POST url : http://remote-server:8080/{{ index .Tags \"source\" }} headers : content-type : application/text timeout : 5s body : '\"counter1\" crossed threshold, value={{ index .Values \"counter1\" }}' Enabling backup interface # The below example shows a trigger that enables a router interface if another interface's operational status changes to \"down\". processors : interface_watch : # event-trigger : debug : true condition : '(.tags.interface_name == \"ethernet-1/1\" or .tags.interface_name == \"ethernet-1/2\") and .values[\"/srl_nokia-interfaces:interface/oper-state\"] == \"down\"' actions : - enable_interface actions : enable_interface : name : my_gnmi_action type : gnmi rpc : set target : '{{ index .Event.Tags \"source\" }}' paths : - | {{ $interfaceName := \"\" }} {{ if eq ( index .Event.Tags \"interface_name\" ) \"ethernet-1/1\"}} {{$interfaceName = \"ethernet-1/2\"}} {{ else if eq ( index E.vent.Tags \"interface_name\" ) \"ethernet-1/2\"}} {{$interfaceName = \"ethernet-1/1\"}} {{end}} /interface[name={{$interfaceName}}]/admin-state values : - \"enable\" encoding : json_ietf debug : true Clone a network topology and deploy it using containerlab # Using lldp neighbor information it's possible to build a containerlab topology using gnmic actions. In the below configuration file, an event processor called clone-topology is defined. When triggered it runs a series of actions to gather information (chassis type, lldp neighbors, configuration,...) from the defined targets. It then builds a containerlab topology from a defined template and the gathered info, writes it to a file and runs a clab deploy command. username : admin password : NokiaSrl1! skip-verify : true encoding : json_ietf # log: true targets : srl1 : srl2 : srl3 : processors : clone-topology : event-trigger : # debug: true actions : - chassis - lldp - read_config - write_config - clab_topo - deploy_topo actions : chassis : name : chassis type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /platform/chassis/type lldp : name : lldp type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /system/lldp/interface[name=ethernet-*] read_config : name : read_config type : gnmi target : all rpc : get data-type : config encoding : json_ietf #debug: true paths : - / write_config : name : write_config type : template template : | {{- range $n, $m := .Env.read_config }} {{- $filename := print $n \".json\"}} {{ file.Write $filename (index $m 0 \"updates\" 0 \"values\" \"\" | data.ToJSONPretty \" \" ) }} {{- end }} #debug: true clab_topo : name : clab_topo type : template # debug: true output : gnmic.clab.yaml template : | name: gNMIc-action-generated topology: defaults: kind: srl kinds: srl: image: ghcr.io/nokia/srlinux:latest nodes: {{- range $n, $m := .Env.lldp }} {{- $type := index $.Env.chassis $n 0 0 \"values\" \"/srl_nokia-platform:platform/srl_nokia-platform-chassis:chassis/type\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D1\" \"ixrd1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D2\" \"ixrd2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D3\" \"ixrd3\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-6\" \"ixr6\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-10\" \"ixr10\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H1\" \"ixrh1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H2\" \"ixrh2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H3\" \"ixrh3\" }} {{ $n | strings.TrimPrefix \"clab-\" }}: type: {{ $type }} startup-config: {{ print $n \".json\"}} {{- end }} links: {{- range $n, $m := .Env.lldp }} {{- range $rsp := $m }} {{- range $ev := $rsp }} {{- if index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $node1 := $ev.tags.source | strings.TrimPrefix \"clab-\" }} {{- $iface1 := $ev.tags.interface_name | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- $node2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $iface2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/port-id\" | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- if lt $node1 $node2 }} - endpoints: [\"{{ $node1 }}:{{ $iface1 }}\", \"{{ $node2 }}:{{ $iface2 }}\"] {{- end }} {{- end }} {{- end }} {{- end }} {{- end }} deploy_topo : name : deploy_topo type : script command : sudo clab dep -t gnmic.clab.yaml --reconfigure debug : true The above described processor can be triggered with the below command: gnmic --config clone.yaml get --path /system/name --processor clone-topology","title":"Trigger"},{"location":"user_guide/event_processors/event_trigger/#examples","text":"","title":"Examples"},{"location":"user_guide/event_processors/event_trigger/#alerting-when-a-threshold-is-crossed","text":"The below example triggers an HTTP GET to http://remote-server:p8080/${router_name} if the value of counter \"counter1\" crosses 90 twice within 2 minutes. processors : my_trigger_proc : event-trigger : condition : '.values[\"counter1\"] > 90' min-occurrences : 1 max-occurrences : 2 window : 120s async : true actions : - alert actions : alert : name : alert type : http method : POST url : http://remote-server:8080/{{ index .Tags \"source\" }} headers : content-type : application/text timeout : 5s body : '\"counter1\" crossed threshold, value={{ index .Values \"counter1\" }}'","title":"Alerting when a threshold is crossed"},{"location":"user_guide/event_processors/event_trigger/#enabling-backup-interface","text":"The below example shows a trigger that enables a router interface if another interface's operational status changes to \"down\". processors : interface_watch : # event-trigger : debug : true condition : '(.tags.interface_name == \"ethernet-1/1\" or .tags.interface_name == \"ethernet-1/2\") and .values[\"/srl_nokia-interfaces:interface/oper-state\"] == \"down\"' actions : - enable_interface actions : enable_interface : name : my_gnmi_action type : gnmi rpc : set target : '{{ index .Event.Tags \"source\" }}' paths : - | {{ $interfaceName := \"\" }} {{ if eq ( index .Event.Tags \"interface_name\" ) \"ethernet-1/1\"}} {{$interfaceName = \"ethernet-1/2\"}} {{ else if eq ( index E.vent.Tags \"interface_name\" ) \"ethernet-1/2\"}} {{$interfaceName = \"ethernet-1/1\"}} {{end}} /interface[name={{$interfaceName}}]/admin-state values : - \"enable\" encoding : json_ietf debug : true","title":"Enabling backup interface"},{"location":"user_guide/event_processors/event_trigger/#clone-a-network-topology-and-deploy-it-using-containerlab","text":"Using lldp neighbor information it's possible to build a containerlab topology using gnmic actions. In the below configuration file, an event processor called clone-topology is defined. When triggered it runs a series of actions to gather information (chassis type, lldp neighbors, configuration,...) from the defined targets. It then builds a containerlab topology from a defined template and the gathered info, writes it to a file and runs a clab deploy command. username : admin password : NokiaSrl1! skip-verify : true encoding : json_ietf # log: true targets : srl1 : srl2 : srl3 : processors : clone-topology : event-trigger : # debug: true actions : - chassis - lldp - read_config - write_config - clab_topo - deploy_topo actions : chassis : name : chassis type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /platform/chassis/type lldp : name : lldp type : gnmi target : all rpc : sub encoding : json_ietf #debug: true format : event paths : - /system/lldp/interface[name=ethernet-*] read_config : name : read_config type : gnmi target : all rpc : get data-type : config encoding : json_ietf #debug: true paths : - / write_config : name : write_config type : template template : | {{- range $n, $m := .Env.read_config }} {{- $filename := print $n \".json\"}} {{ file.Write $filename (index $m 0 \"updates\" 0 \"values\" \"\" | data.ToJSONPretty \" \" ) }} {{- end }} #debug: true clab_topo : name : clab_topo type : template # debug: true output : gnmic.clab.yaml template : | name: gNMIc-action-generated topology: defaults: kind: srl kinds: srl: image: ghcr.io/nokia/srlinux:latest nodes: {{- range $n, $m := .Env.lldp }} {{- $type := index $.Env.chassis $n 0 0 \"values\" \"/srl_nokia-platform:platform/srl_nokia-platform-chassis:chassis/type\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D1\" \"ixrd1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D2\" \"ixrd2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-D3\" \"ixrd3\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-6\" \"ixr6\" }} {{- $type = $type | strings.ReplaceAll \"7250 IXR-10\" \"ixr10\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H1\" \"ixrh1\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H2\" \"ixrh2\" }} {{- $type = $type | strings.ReplaceAll \"7220 IXR-H3\" \"ixrh3\" }} {{ $n | strings.TrimPrefix \"clab-\" }}: type: {{ $type }} startup-config: {{ print $n \".json\"}} {{- end }} links: {{- range $n, $m := .Env.lldp }} {{- range $rsp := $m }} {{- range $ev := $rsp }} {{- if index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $node1 := $ev.tags.source | strings.TrimPrefix \"clab-\" }} {{- $iface1 := $ev.tags.interface_name | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- $node2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name\" }} {{- $iface2 := index $ev.values \"/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/port-id\" | strings.ReplaceAll \"ethernet-\" \"e\" | strings.ReplaceAll \"/\" \"-\" }} {{- if lt $node1 $node2 }} - endpoints: [\"{{ $node1 }}:{{ $iface1 }}\", \"{{ $node2 }}:{{ $iface2 }}\"] {{- end }} {{- end }} {{- end }} {{- end }} {{- end }} deploy_topo : name : deploy_topo type : script command : sudo clab dep -t gnmic.clab.yaml --reconfigure debug : true The above described processor can be triggered with the below command: gnmic --config clone.yaml get --path /system/name --processor clone-topology","title":"Clone a network topology and deploy it using containerlab"},{"location":"user_guide/event_processors/event_value_tag/","text":"The event-value-tag processor applies specific values from event messages to tags of other messages, if event tag names match. Each gNMI subscribe Response Update in a gNMI subscribe Response Notification is transformed into an Event Message Additionally, if you are using an output cache, all gNMI subscribe Response Update messages are converted to Events on flush. The event-value-tag processor is used to extract Values as tags to apply to other Events that have the same K:V tag pairs from the original event message, without merging events with different timestamps. processors : # processor name intf-description : # processor-type event-value-tag : # name of the value to match. Usually a specific gNMI path value-name : \"/interfaces/interface/state/description\" # if set, use instead of the value name for tag tag-name : \"description\" # if true, remove value from original event when copying consume : false debug : false Event format before Event format after [ { \"name\" : \"sub1\" , \"timestamp\" : 1 , \"tags\" : { \"source\" : \"leaf1:6030\" , \"subscription-name\" : \"sub1\" , \"interface_name\" : \"Ethernet1\" }, \"values\" : { \"/interfaces/interface/state/counters/in-octets\" : 100 } }, { \"name\" : \"sub1\" , \"timestamp\" : 200 , \"tags\" : { \"source\" : \"leaf1:6030\" , \"subscription-name\" : \"sub1\" , \"interface_name\" : \"Ethernet1\" }, \"values\" : { \"/interfaces/interface/state/counters/out-octets\" : 100 } }, { \"name\" : \"sub1\" , \"timestamp\" : 200 , \"tags\" : { \"source\" : \"leaf1:6030\" , \"subscription-name\" : \"sub1\" , \"interface_name\" : \"Ethernet1\" }, \"values\" : { \"/interfaces/interface/state/description\" : \"Uplink\" } } ] [ { \"name\" : \"sub1\" , \"timestamp\" : 1 , \"tags\" : { \"source\" : \"leaf1:6030\" , \"subscription-name\" : \"sub1\" , \"interface_name\" : \"Ethernet1\" , \"description\" : \"Uplink\" }, \"values\" : { \"/interfaces/interface/state/counters/in-octets\" : 100 } }, { \"name\" : \"sub1\" , \"timestamp\" : 200 , \"tags\" : { \"source\" : \"leaf1:6030\" , \"subscription-name\" : \"sub1\" , \"interface_name\" : \"Ethernet1\" , \"description\" : \"Uplink\" }, \"values\" : { \"/interfaces/interface/state/counters/out-octets\" : 100 } }, { \"name\" : \"sub1\" , \"timestamp\" : 200 , \"tags\" : { \"source\" : \"leaf1:6030\" , \"subscription-name\" : \"sub1\" , \"interface_name\" : \"Ethernet1\" }, \"values\" : { \"/interfaces/interface/state/description\" : \"Uplink\" } } ] bgp-description : event-value-tag : value-name : \"neighbor_description\" consume : true Event format before Event format after [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" } }, { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : { \"neighbor_description\" : \"PeerRouter\" } } ] [ { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" \"neighbor_description\" : \"PeerRouter\" }, \"values\" : { \"bgp_neighbor_sent_messages_queue_depth\" : 0 , \"bgp_neighbor_sent_messages_total_messages\" : \"423\" , \"bgp_neighbor_sent_messages_total_non_updates\" : \"415\" , \"bgp_neighbor_sent_messages_total_updates\" : \"8\" , } }, { \"name\" : \"sub2\" , \"timestamp\" : 1615284691523204299 , \"tags\" : { \"neighbor_peer-address\" : \"2002::1:1:1:1\" , \"network-instance_name\" : \"default\" , \"source\" : \"leaf1:57400\" , \"subscription-name\" : \"sub2\" }, \"values\" : {} } ]","title":"Value Tag"},{"location":"user_guide/event_processors/event_write/","text":"The event-write processor writes a message that has a value or a tag matching one of the configured regular expressions to stdout , stderr or to a file. A custom separator (used between written messages) can be configured, it defaults to \\n processors : # processor name write-processor : # processor type event-write : # jq expression, if evaluated to true, the message is written to dst condition : # list of regular expressions to be matched against the tags names, if matched, the message is written to dst tag-names : # list of regular expressions to be matched against the tags values, if matched, the message is written to dst tags : # list of regular expressions to be matched against the values names, if matched, the message is written to dst value-names : # list of regular expressions to be matched against the values, if matched, the message is written to dst values : # path to the destination file dst : # separator to be written between messages separator : # indent to use when marshaling the event message to json indent : Examples # processors : # processor name write-processor : # processor type event-write : value-names : - \".\" dst : file.log separator : \"\\n####\\n\" indent : \" \" $ cat file.log { \"name\" : \"sub1\" , \"timestamp\" : 1607582483868459381 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.20.20.5:57400\" , \"subscription-name\" : \"sub1\" } , \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"8694\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"1740350\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"17\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"8696\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"1723262\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"17\" } } #### { \"name\" : \"sub1\" , \"timestamp\" : 1607582483868459381 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.20.20.5:57400\" , \"subscription-name\" : \"sub1\" } , \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"8694\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"1740350\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"17\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"8696\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"1723262\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"17\" } } ####","title":"Write"},{"location":"user_guide/event_processors/event_write/#examples","text":"processors : # processor name write-processor : # processor type event-write : value-names : - \".\" dst : file.log separator : \"\\n####\\n\" indent : \" \" $ cat file.log { \"name\" : \"sub1\" , \"timestamp\" : 1607582483868459381 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.20.20.5:57400\" , \"subscription-name\" : \"sub1\" } , \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"8694\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"1740350\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"17\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"8696\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"1723262\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"17\" } } #### { \"name\" : \"sub1\" , \"timestamp\" : 1607582483868459381 , \"tags\" : { \"interface_name\" : \"ethernet-1/1\" , \"source\" : \"172.20.20.5:57400\" , \"subscription-name\" : \"sub1\" } , \"values\" : { \"/srl_nokia-interfaces:interface/statistics/carrier-transitions\" : \"1\" , \"/srl_nokia-interfaces:interface/statistics/in-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/in-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/in-multicast-packets\" : \"8694\" , \"/srl_nokia-interfaces:interface/statistics/in-octets\" : \"1740350\" , \"/srl_nokia-interfaces:interface/statistics/in-unicast-packets\" : \"17\" , \"/srl_nokia-interfaces:interface/statistics/out-broadcast-packets\" : \"22\" , \"/srl_nokia-interfaces:interface/statistics/out-error-packets\" : \"0\" , \"/srl_nokia-interfaces:interface/statistics/out-multicast-packets\" : \"8696\" , \"/srl_nokia-interfaces:interface/statistics/out-octets\" : \"1723262\" , \"/srl_nokia-interfaces:interface/statistics/out-unicast-packets\" : \"17\" } } ####","title":"Examples"},{"location":"user_guide/event_processors/intro/","text":"The event processors provide an easy way to configure a set of functions in order to transform an event message that will be be written to a specific output. While the event format is the de facto format used by gNMIc in case the output is influxdb or prometheus , it can be used with any other output type. Transforming the received gNMI message is sometimes needed to accomodate the output system ( converting types, complying with name constraints,...), or simply filtering out values that you are not interested on. The event format # The event format is produced by gNMIc from the gNMI Notification messages received within a gNMI subscribe response update, it contains 5 fields: name : A string field populated by the subscription name, it is used as a part of the metric name in case of prometheus output or it can be used as the measurement name in case of influxdb output. timestamp : An int64 field containing the timestamp received within the gnmi Update. tags : A map of string keys and string values. The keys and values are extracted from the keys in the gNMI PathElement keys. gNMIc adds the subscription name and the target name/address. values : A map of string keys and generic values. The keys are build from a xpath representation of the gNMI path without the keys, while the values are extracted from the gNMI Node values . deletes : A string list built from the delete field of the gNMI Notification message . Defining an event processor # Event processors are defined under the section processors in gNMIc configuration file. Each processor is identified by a name, under which we specify the processor type as well as additional fields specific to each type. Note Processors names are case insensitive All processors support a debug field that enables extra debug log messages to help troubleshoot the processor transformation. Below is an example of an event-delete processor, which deletes all values with a name containing multicast or broadcast processors : # processor name my-processor : # processor type event-delete : value-names : - \".*multicast.*\" - \".*broadcast.*\" Linking an event processor to an output # Once the needed event processors are defined under section processors , they can be linked to the desired output(s) in the same file. Each output can be configured with different event processors allowing flexibility in the way the same data is written to different outputs. A list of event processors names can be added under an output configuration, the processors will apply in the order they are configured. In the below example, 3 event processors are configured and linked to output1 of type influxdb . The first processor converts all values type to integer if possible. The second deletes tags with name starting with subscription-name . Finally the third deletes values with name ending with out-unicast-packets . outputs : output1 : type : influxdb url : http://localhost:8086 bucket : telemetry token : srl:srl batch-size : 1000 flush-timer : 10s event-processors : - proc-convert-integer - proc-delete-tag-name - proc-delete-value-name processors : proc-convert-integer : event-convert : value-names : - \".*\" type : int proc-delete-tag-name : event-delete : tag-names : - \"^subscription-name\" proc-delete-value-name : event-delete : value-names : - \".*out-unicast-packets\" Event processors with cache # In the scenario where processors are configured under an output with caching enabled, the event messages retrieved from the cache are processed as a single set by each processor. This concurrent processing facilitates the application of a logic that merges or combines messages, enabling more complex and integrated processing strategies. Event processors pipeline # Processors under an output are applied in a strict sequential order for each group of event messages received. Event processors plugins # gNMIc incorporates the capability to extend its functionality through the use of event processors as plugins. To integrate seamlessly with gNMIc, these plugins need to be written in Golang. The communication between gNMIc and these plugins is facilitated by HashiCorp's go-plugin package, which employs netrpc as the underlying protocol for this interaction. See some plugin examples here","title":"Introduction"},{"location":"user_guide/event_processors/intro/#the-event-format","text":"The event format is produced by gNMIc from the gNMI Notification messages received within a gNMI subscribe response update, it contains 5 fields: name : A string field populated by the subscription name, it is used as a part of the metric name in case of prometheus output or it can be used as the measurement name in case of influxdb output. timestamp : An int64 field containing the timestamp received within the gnmi Update. tags : A map of string keys and string values. The keys and values are extracted from the keys in the gNMI PathElement keys. gNMIc adds the subscription name and the target name/address. values : A map of string keys and generic values. The keys are build from a xpath representation of the gNMI path without the keys, while the values are extracted from the gNMI Node values . deletes : A string list built from the delete field of the gNMI Notification message .","title":"The event format"},{"location":"user_guide/event_processors/intro/#defining-an-event-processor","text":"Event processors are defined under the section processors in gNMIc configuration file. Each processor is identified by a name, under which we specify the processor type as well as additional fields specific to each type. Note Processors names are case insensitive All processors support a debug field that enables extra debug log messages to help troubleshoot the processor transformation. Below is an example of an event-delete processor, which deletes all values with a name containing multicast or broadcast processors : # processor name my-processor : # processor type event-delete : value-names : - \".*multicast.*\" - \".*broadcast.*\"","title":"Defining an event processor"},{"location":"user_guide/event_processors/intro/#linking-an-event-processor-to-an-output","text":"Once the needed event processors are defined under section processors , they can be linked to the desired output(s) in the same file. Each output can be configured with different event processors allowing flexibility in the way the same data is written to different outputs. A list of event processors names can be added under an output configuration, the processors will apply in the order they are configured. In the below example, 3 event processors are configured and linked to output1 of type influxdb . The first processor converts all values type to integer if possible. The second deletes tags with name starting with subscription-name . Finally the third deletes values with name ending with out-unicast-packets . outputs : output1 : type : influxdb url : http://localhost:8086 bucket : telemetry token : srl:srl batch-size : 1000 flush-timer : 10s event-processors : - proc-convert-integer - proc-delete-tag-name - proc-delete-value-name processors : proc-convert-integer : event-convert : value-names : - \".*\" type : int proc-delete-tag-name : event-delete : tag-names : - \"^subscription-name\" proc-delete-value-name : event-delete : value-names : - \".*out-unicast-packets\"","title":"Linking an event processor to an output"},{"location":"user_guide/event_processors/intro/#event-processors-with-cache","text":"In the scenario where processors are configured under an output with caching enabled, the event messages retrieved from the cache are processed as a single set by each processor. This concurrent processing facilitates the application of a logic that merges or combines messages, enabling more complex and integrated processing strategies.","title":"Event processors with cache"},{"location":"user_guide/event_processors/intro/#event-processors-pipeline","text":"Processors under an output are applied in a strict sequential order for each group of event messages received.","title":"Event processors pipeline"},{"location":"user_guide/event_processors/intro/#event-processors-plugins","text":"gNMIc incorporates the capability to extend its functionality through the use of event processors as plugins. To integrate seamlessly with gNMIc, these plugins need to be written in Golang. The communication between gNMIc and these plugins is facilitated by HashiCorp's go-plugin package, which employs netrpc as the underlying protocol for this interaction. See some plugin examples here","title":"Event processors plugins"},{"location":"user_guide/golang_package/gnmi_options/","text":"The package github.com/openconfig/gnmic/api exposes a set of api.GNMIOption that can be used with api.NewGetRequest(...api.GNMIOption) GNMIOption , api.NewSetRequest(...api.GNMIOption) GNMIOption or api.NewSubscribeRequest(...api.GNMIOption) GNMIOption to create a gNMI Request. // Version sets the provided gNMI version string in a gnmi.CapabilityResponse message. func Version ( v string ) func ( msg proto . Message ) error // SupportedEncoding creates an GNMIOption that sets the provided encodings as supported encodings in a gnmi.CapabilitiesResponse func SupportedEncoding ( encodings ... string ) func ( msg proto . Message ) error // SupportedModel creates an GNMIOption that sets the provided name, org and version as a supported model in a gnmi.CapabilitiesResponse. func SupportedModel ( name , org , version string ) func ( msg proto . Message ) error // Extension creates a GNMIOption that applies the supplied gnmi_ext.Extension to the provided // proto.Message. func Extension ( ext * gnmi_ext . Extension ) func ( msg proto . Message ) error // Prefix creates a GNMIOption that creates a *gnmi.Path and adds it to the supplied // proto.Message (as a Path Prefix). // The proto.Message can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe. func Prefix ( prefix string ) func ( msg proto . Message ) error // Target creates a GNMIOption that set the gnmi Prefix target to the supplied string value. // The proto.Message can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe. func Target ( target string ) func ( msg proto . Message ) error // Path creates a GNMIOption that creates a *gnmi.Path and adds it to the supplied proto.Message // which can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.Subscription. func Path ( path string ) func ( msg proto . Message ) error // Encoding creates a GNMIOption that adds the encoding type to the supplied proto.Message // which can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe. func Encoding ( encoding string ) func ( msg proto . Message ) error // EncodingJSON creates a GNMIOption that sets the encoding type to JSON in a gnmi.GetRequest or // gnmi.SubscribeRequest. func EncodingJSON () func ( msg proto . Message ) error // EncodingBytes creates a GNMIOption that sets the encoding type to BYTES in a gnmi.GetRequest or // gnmi.SubscribeRequest. func EncodingBytes () func ( msg proto . Message ) error // EncodingPROTO creates a GNMIOption that sets the encoding type to PROTO in a gnmi.GetRequest or // gnmi.SubscribeRequest. func EncodingPROTO () func ( msg proto . Message ) error // EncodingASCII creates a GNMIOption that sets the encoding type to ASCII in a gnmi.GetRequest or // gnmi.SubscribeRequest. func EncodingASCII () func ( msg proto . Message ) error // EncodingJSON_IETF creates a GNMIOption that sets the encoding type to JSON_IETF in a gnmi.GetRequest or // gnmi.SubscribeRequest. func EncodingJSON_IETF () func ( msg proto . Message ) error // EncodingCustom creates a GNMIOption that adds the encoding type to the supplied proto.Message // which can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe. // Unlike Encoding, this GNMIOption does not validate if the provided encoding is defined by the gNMI spec. func EncodingCustom ( enc int ) func ( msg proto . Message ) error // DataType creates a GNMIOption that adds the data type to the supplied proto.Message // which must be a *gnmi.GetRequest. func DataType ( datat string ) func ( msg proto . Message ) error // DataTypeALL creates a GNMIOption that sets the gnmi.GetRequest data type to ALL func DataTypeALL () func ( msg proto . Message ) error // DataTypeCONFIG creates a GNMIOption that sets the gnmi.GetRequest data type to CONFIG func DataTypeCONFIG () func ( msg proto . Message ) error // DataTypeSTATE creates a GNMIOption that sets the gnmi.GetRequest data type to STATE func DataTypeSTATE () func ( msg proto . Message ) error // DataTypeOPERATIONAL creates a GNMIOption that sets the gnmi.GetRequest data type to OPERATIONAL func DataTypeOPERATIONAL () func ( msg proto . Message ) error // UseModel creates a GNMIOption that add a gnmi.DataModel to a gnmi.GetRequest or gnmi.SubscribeRequest // based on the name, org and version strings provided. func UseModel ( name , org , version string ) func ( msg proto . Message ) error // Update creates a GNMIOption that creates a *gnmi.Update message and adds it to the supplied proto.Message, // the supplied message must be a *gnmi.SetRequest. func Update ( opts ... GNMIOption ) func ( msg proto . Message ) error // Replace creates a GNMIOption that creates a *gnmi.Update message and adds it to the supplied proto.Message. // the supplied message must be a *gnmi.SetRequest. func Replace ( opts ... GNMIOption ) func ( msg proto . Message ) error // Value creates a GNMIOption that creates a *gnmi.TypedValue and adds it to the supplied proto.Message. // the supplied message must be a *gnmi.Update. // If a map is supplied as `data interface{}` it has to be a map[string]interface{}. func Value ( data interface {}, encoding string ) func ( msg proto . Message ) error // Delete creates a GNMIOption that creates a *gnmi.Path and adds it to the supplied proto.Message. // the supplied message must be a *gnmi.SetRequest. The *gnmi.Path is added the .Delete list. func Delete ( path string ) func ( msg proto . Message ) error // SubscriptionListMode creates a GNMIOption that sets the SubscribeRequest Mode. // The variable mode must be one of \"once\", \"poll\" or \"stream\". // The supplied proto.Message must be a *gnmi.SubscribeRequest with RequestType Subscribe. func SubscriptionListMode ( mode string ) func ( msg proto . Message ) error // SubscriptionListModeSTREAM creates a GNMIOption that sets the Subscription List Mode to STREAM func SubscriptionListModeSTREAM () func ( msg proto . Message ) error // SubscriptionListModeONCE creates a GNMIOption that sets the Subscription List Mode to ONCE func SubscriptionListModeONCE () func ( msg proto . Message ) error // SubscriptionListModePOLL creates a GNMIOption that sets the Subscription List Mode to POLL func SubscriptionListModePOLL () func ( msg proto . Message ) error // Qos creates a GNMIOption that sets the QosMarking field in a *gnmi.SubscribeRequest with RequestType Subscribe. func Qos ( qos uint32 ) func ( msg proto . Message ) error // UseAliases creates a GNMIOption that sets the UsesAliases field in a *gnmi.SubscribeRequest with RequestType Subscribe. func UseAliases ( b bool ) func ( msg proto . Message ) error // AllowAggregation creates a GNMIOption that sets the AllowAggregation field in a *gnmi.SubscribeRequest with RequestType Subscribe. func AllowAggregation ( b bool ) func ( msg proto . Message ) error // UpdatesOnly creates a GNMIOption that sets the UpdatesOnly field in a *gnmi.SubscribeRequest with RequestType Subscribe. func UpdatesOnly ( b bool ) func ( msg proto . Message ) error // UpdatesOnly creates a GNMIOption that creates a *gnmi.Subscription based on the supplied GNMIOption(s) and adds it the // supplied proto.Message which must be of type *gnmi.SubscribeRequest with RequestType Subscribe. func Subscription ( opts ... GNMIOption ) func ( msg proto . Message ) error // SubscriptionMode creates a GNMIOption that sets the Subscription mode in a proto.Message of type *gnmi.Subscription. func SubscriptionMode ( mode string ) func ( msg proto . Message ) error // SubscriptionModeTARGET_DEFINED creates a GNMIOption that sets the subscription mode to TARGET_DEFINED func SubscriptionModeTARGET_DEFINED () func ( msg proto . Message ) error // SubscriptionModeON_CHANGE creates a GNMIOption that sets the subscription mode to ON_CHANGE func SubscriptionModeON_CHANGE () func ( msg proto . Message ) error // SubscriptionModeSAMPLE creates a GNMIOption that sets the subscription mode to SAMPLE func SubscriptionModeSAMPLE () func ( msg proto . Message ) error // SampleInterval creates a GNMIOption that sets the SampleInterval in a proto.Message of type *gnmi.Subscription. func SampleInterval ( d time . Duration ) func ( msg proto . Message ) error // HeartbeatInterval creates a GNMIOption that sets the HeartbeatInterval in a proto.Message of type *gnmi.Subscription. func HeartbeatInterval ( d time . Duration ) func ( msg proto . Message ) error // SuppressRedundant creates a GNMIOption that sets the SuppressRedundant in a proto.Message of type *gnmi.Subscription. func SuppressRedundant ( s bool ) func ( msg proto . Message ) error // Notification creates a GNMIOption that builds a gnmi.Notification from the supplied GNMIOptions and adds it // to the supplied proto.Message func Notification ( opts ... GNMIOption ) func ( msg proto . Message ) error // Timestamp sets the supplied timestamp in a gnmi.Notification message func Timestamp ( t int64 ) func ( msg proto . Message ) error // TimestampNow is the same as Timestamp(time.Now().UnixNano()) func TimestampNow () func ( msg proto . Message ) error // Alias sets the supplied alias value in a gnmi.Notification message func Alias ( alias string ) func ( msg proto . Message ) error // Atomic sets the .Atomic field in a gnmi.Notification message func Atomic ( b bool ) func ( msg proto . Message ) error // UpdateResult creates a GNMIOption that creates a gnmi.UpdateResult and adds it to // a proto.Message of type gnmi.SetResponse. func UpdateResult ( opts ... GNMIOption ) func ( msg proto . Message ) error // Operation creates a GNMIOption that sets the gnmi.UpdateResult_Operation // value in a gnmi.UpdateResult. func Operation ( oper string ) func ( msg proto . Message ) error // OperationINVALID creates a GNMIOption that sets the gnmi.SetResponse Operation to INVALID func OperationINVALID () func ( msg proto . Message ) error // OperationDELETE creates a GNMIOption that sets the gnmi.SetResponse Operation to DELETE func OperationDELETE () func ( msg proto . Message ) error // OperationREPLACE creates a GNMIOption that sets the gnmi.SetResponse Operation to REPLACE func OperationREPLACE () func ( msg proto . Message ) error // OperationUPDATE creates a GNMIOption that sets the gnmi.SetResponse Operation to UPDATE func OperationUPDATE () func ( msg proto . Message ) error","title":"gNMI Options"},{"location":"user_guide/golang_package/intro/","text":"gnmic ( github.com/openconfig/gnmic/api ) can be imported as a dependency in your Golang programs. It acts as a wrapper around the openconfig/gnmi package providing a user friendly API to create a target and easily craft gNMI requests. Creating gNMI requests # Get Request # func NewGetRequest ( opts ... GNMIOption ) ( * gnmi . GetRequest , error ) The below 2 snippets create a Get Request with 2 paths, json_ietf encoding and data type STATE Using github.com/openconfig/gnmic/api getReq , err := api . NewGetRequest ( api . Encoding ( \"json_ietf\" ), api . DataType ( \"state\" ), api . Path ( \"interface/statistics\" ), api . Path ( \"interface/subinterface/statistics\" ), ) // check error Using github.com/openconfig/gnmi getReq := & gnmi . GetRequest { Path : [] * gnmi . Path { { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"statistics\" }, }, }, { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"subinterface\" }, { Name : \"statistics\" }, }, }, }, Type : gnmi . GetRequest_STATE , Encoding : gnmi . Encoding_JSON_IETF , } Set Request # func NewSetRequest ( opts ... GNMIOption ) ( * gnmi . SetRequest , error ) The below 2 snippets create a Set Request with one two updates, one replace and one delete messages: Using github.com/openconfig/gnmic/api setReq , err := api . NewSetRequest ( api . Update ( api . Path ( \"/system/name/host-name\" ), api . Value ( \"srl2\" , \"json_ietf\" ), ), api . Update ( api . Path ( \"/system/gnmi-server/unix-socket/admin-state\" ), api . Value ( \"enable\" , \"json_ietf\" ), ), api . Replace ( api . Path ( \"/network-instance[name=default]/admin-state\" ), api . Value ( \"enable\" , \"json_ietf\" ), ), api . Delete ( \"/interface[name=ethernet-1/1]/admin-state\" ), ) // check error Using github.com/openconfig/gnmi setReq := & gnmi . SetRequest { Update : [] * gnmi . Update { { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"system\" }, { Name : \"name\" }, { Name : \"host-name\" }, }, }, Val : & gnmi . TypedValue { Value : & gnmi . TypedValue_JsonIetfVal { JsonIetfVal : [] byte ( \"\\\"srl2\\\"\" ), }, }, }, { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"system\" }, { Name : \"gnmi-server\" }, { Name : \"unix-socket\" }, { Name : \"admin-state\" }, }, }, Val : & gnmi . TypedValue { Value : & gnmi . TypedValue_JsonIetfVal { JsonIetfVal : [] byte ( \"\\\"enable\\\"\" ), }, }, }, }, Replace : [] * gnmi . Update { { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"network-instance\" , Key : map [ string ] string { \"name\" : \"default\" , }, }, { Name : \"admin-state\" , }, }, }, Val : & gnmi . TypedValue { Value : & gnmi . TypedValue_JsonIetfVal { JsonIetfVal : [] byte ( \"\\\"enable\\\"\" ), }, }, }, }, Delete : [] * gnmi . Path { { Elem : [] * gnmi . PathElem { { Name : \"interface\" , Key : map [ string ] string { \"name\" : \"ethernet-1/1\" , }, }, { Name : \"admin-state\" , }, }, }, }, } Subscribe Request # Create a Subscribe Request func NewSubscribeRequest ( opts ... GNMIOption ) ( * gnmi . SubscribeRequest , error ) Create a Subscribe Poll Request func NewSubscribePollRequest ( opts ... GNMIOption ) * gnmi . SubscribeRequest The below 2 snippets create a stream subscribe request with 2 paths, json_ietf encoding and a sample interval of 10 seconds: Using github.com/openconfig/gnmic/api subReq , err := api . NewSubscribeRequest ( api . Encoding ( \"json_ietf\" ), api . SubscriptionListMode ( \"stream\" ), api . Subscription ( api . Path ( \"interface/statistics\" ), api . SubscriptionMode ( \"sample\" ), api . SampleInterval ( \"10s\" ), ), api . Subscription ( api . Path ( \"interface/subinterface/statistics\" ), api . SubscriptionMode ( \"sample\" ), api . SampleInterval ( \"10s\" ), ), ) // check error Using github.com/openconfig/gnmi subReq := & gnmi . SubscribeRequest_Subscribe { Subscribe : & gnmi . SubscriptionList { Subscription : [] * gnmi . Subscription { { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"statistics\" }, }, }, Mode : gnmi . SubscriptionMode_SAMPLE , SampleInterval : uint64 ( 10 * time . Second ), }, { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"subinterface\" }, { Name : \"statistics\" }, }, }, Mode : gnmi . SubscriptionMode_SAMPLE , SampleInterval : uint64 ( 10 * time . Second ), }, }, Mode : gnmi . SubscriptionList_STREAM , Encoding : gnmi . Encoding_JSON_IETF , }, } Creating Targets # A target can be created using func NewTarget(opts ...TargetOption) (*target.Target, error) . The full list of api.TargetOption can be found here tg , err := api . NewTarget ( api . Name ( \"srl1\" ), api . Address ( \"10.0.0.1:57400\" ), api . Username ( \"admin\" ), api . Password ( \"admin\" ), api . SkipVerify ( true ), ) // check error Once a Target is created, Multiple functions are available to run the desired RPCs, check the examples here","title":"Introduction"},{"location":"user_guide/golang_package/intro/#creating-gnmi-requests","text":"","title":"Creating gNMI requests"},{"location":"user_guide/golang_package/intro/#get-request","text":"func NewGetRequest ( opts ... GNMIOption ) ( * gnmi . GetRequest , error ) The below 2 snippets create a Get Request with 2 paths, json_ietf encoding and data type STATE Using github.com/openconfig/gnmic/api getReq , err := api . NewGetRequest ( api . Encoding ( \"json_ietf\" ), api . DataType ( \"state\" ), api . Path ( \"interface/statistics\" ), api . Path ( \"interface/subinterface/statistics\" ), ) // check error Using github.com/openconfig/gnmi getReq := & gnmi . GetRequest { Path : [] * gnmi . Path { { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"statistics\" }, }, }, { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"subinterface\" }, { Name : \"statistics\" }, }, }, }, Type : gnmi . GetRequest_STATE , Encoding : gnmi . Encoding_JSON_IETF , }","title":"Get Request"},{"location":"user_guide/golang_package/intro/#set-request","text":"func NewSetRequest ( opts ... GNMIOption ) ( * gnmi . SetRequest , error ) The below 2 snippets create a Set Request with one two updates, one replace and one delete messages: Using github.com/openconfig/gnmic/api setReq , err := api . NewSetRequest ( api . Update ( api . Path ( \"/system/name/host-name\" ), api . Value ( \"srl2\" , \"json_ietf\" ), ), api . Update ( api . Path ( \"/system/gnmi-server/unix-socket/admin-state\" ), api . Value ( \"enable\" , \"json_ietf\" ), ), api . Replace ( api . Path ( \"/network-instance[name=default]/admin-state\" ), api . Value ( \"enable\" , \"json_ietf\" ), ), api . Delete ( \"/interface[name=ethernet-1/1]/admin-state\" ), ) // check error Using github.com/openconfig/gnmi setReq := & gnmi . SetRequest { Update : [] * gnmi . Update { { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"system\" }, { Name : \"name\" }, { Name : \"host-name\" }, }, }, Val : & gnmi . TypedValue { Value : & gnmi . TypedValue_JsonIetfVal { JsonIetfVal : [] byte ( \"\\\"srl2\\\"\" ), }, }, }, { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"system\" }, { Name : \"gnmi-server\" }, { Name : \"unix-socket\" }, { Name : \"admin-state\" }, }, }, Val : & gnmi . TypedValue { Value : & gnmi . TypedValue_JsonIetfVal { JsonIetfVal : [] byte ( \"\\\"enable\\\"\" ), }, }, }, }, Replace : [] * gnmi . Update { { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"network-instance\" , Key : map [ string ] string { \"name\" : \"default\" , }, }, { Name : \"admin-state\" , }, }, }, Val : & gnmi . TypedValue { Value : & gnmi . TypedValue_JsonIetfVal { JsonIetfVal : [] byte ( \"\\\"enable\\\"\" ), }, }, }, }, Delete : [] * gnmi . Path { { Elem : [] * gnmi . PathElem { { Name : \"interface\" , Key : map [ string ] string { \"name\" : \"ethernet-1/1\" , }, }, { Name : \"admin-state\" , }, }, }, }, }","title":"Set Request"},{"location":"user_guide/golang_package/intro/#subscribe-request","text":"Create a Subscribe Request func NewSubscribeRequest ( opts ... GNMIOption ) ( * gnmi . SubscribeRequest , error ) Create a Subscribe Poll Request func NewSubscribePollRequest ( opts ... GNMIOption ) * gnmi . SubscribeRequest The below 2 snippets create a stream subscribe request with 2 paths, json_ietf encoding and a sample interval of 10 seconds: Using github.com/openconfig/gnmic/api subReq , err := api . NewSubscribeRequest ( api . Encoding ( \"json_ietf\" ), api . SubscriptionListMode ( \"stream\" ), api . Subscription ( api . Path ( \"interface/statistics\" ), api . SubscriptionMode ( \"sample\" ), api . SampleInterval ( \"10s\" ), ), api . Subscription ( api . Path ( \"interface/subinterface/statistics\" ), api . SubscriptionMode ( \"sample\" ), api . SampleInterval ( \"10s\" ), ), ) // check error Using github.com/openconfig/gnmi subReq := & gnmi . SubscribeRequest_Subscribe { Subscribe : & gnmi . SubscriptionList { Subscription : [] * gnmi . Subscription { { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"statistics\" }, }, }, Mode : gnmi . SubscriptionMode_SAMPLE , SampleInterval : uint64 ( 10 * time . Second ), }, { Path : & gnmi . Path { Elem : [] * gnmi . PathElem { { Name : \"interface\" }, { Name : \"subinterface\" }, { Name : \"statistics\" }, }, }, Mode : gnmi . SubscriptionMode_SAMPLE , SampleInterval : uint64 ( 10 * time . Second ), }, }, Mode : gnmi . SubscriptionList_STREAM , Encoding : gnmi . Encoding_JSON_IETF , }, }","title":"Subscribe Request"},{"location":"user_guide/golang_package/intro/#creating-targets","text":"A target can be created using func NewTarget(opts ...TargetOption) (*target.Target, error) . The full list of api.TargetOption can be found here tg , err := api . NewTarget ( api . Name ( \"srl1\" ), api . Address ( \"10.0.0.1:57400\" ), api . Username ( \"admin\" ), api . Password ( \"admin\" ), api . SkipVerify ( true ), ) // check error Once a Target is created, Multiple functions are available to run the desired RPCs, check the examples here","title":"Creating Targets"},{"location":"user_guide/golang_package/target_options/","text":"The package github.com/openconfig/gnmic/api exposes a set of api.TargetOption that can be used with api.NewTarget(...api.TargetOption) TargetOption to create target.Target . // Name sets the target name. func Name ( name string ) TargetOption // Address sets the target address. // This Option can be set multiple times. func Address ( addr string ) TargetOption // Username sets the target Username. func Username ( username string ) TargetOption // Password sets the target Password. func Password ( password string ) TargetOption // Timeout sets the gNMI client creation timeout. func Timeout ( timeout time . Duration ) TargetOption // Insecure sets the option to create a gNMI client with an // insecure gRPC connection func Insecure ( i bool ) TargetOption // SkipVerify sets the option to create a gNMI client with a // secure gRPC connection without verifying the target's certificates. func SkipVerify ( i bool ) TargetOption // TLSCA sets that path towards the TLS certificate authority file. func TLSCA ( tlsca string ) TargetOption // TLSCert sets that path towards the TLS certificate file. func TLSCert ( cert string ) TargetOption // TLSKey sets that path towards the TLS key file. func TLSKey ( key string ) TargetOption // TLSMinVersion sets the TLS minimum version used during the TLS handshake. func TLSMinVersion ( v string ) TargetOption // TLSMaxVersion sets the TLS maximum version used during the TLS handshake. func TLSMaxVersion ( v string ) TargetOption // TLSVersion sets the desired TLS version used during the TLS handshake. func TLSVersion ( v string ) TargetOption // LogTLSSecret, if set to true, // enables logging of the TLS master key. func LogTLSSecret ( b bool ) TargetOption // Gzip, if set to true, // adds gzip compression to the gRPC connection. func Gzip ( b bool ) TargetOption // Token sets the per RPC credentials for all RPC calls. func Token ( token string ) TargetOption","title":"Target Options"},{"location":"user_guide/golang_package/examples/capabilities/","text":"The below snippet shows how to create a target, send a Capabilities Request and print the response. package main import ( \"context\" \"fmt\" \"log\" \"github.com/openconfig/gnmic/pkg/api\" \"google.golang.org/protobuf/encoding/prototext\" ) func main () { // create a target tg , err := api . NewTarget ( api . Name ( \"srl1\" ), api . Address ( \"10.0.0.1:57400\" ), api . Username ( \"admin\" ), api . Password ( \"admin\" ), api . SkipVerify ( true ), ) if err != nil { log . Fatal ( err ) } ctx , cancel := context . WithCancel ( context . Background ()) defer cancel () // create a gNMI client err = tg . CreateGNMIClient ( ctx ) if err != nil { log . Fatal ( err ) } defer tg . Close () // send a gNMI capabilities request to the created target capResp , err := tg . Capabilities ( ctx ) if err != nil { log . Fatal ( err ) } fmt . Println ( prototext . Format ( capResp )) }","title":"Capabilities"},{"location":"user_guide/golang_package/examples/get/","text":"The below snippet shows how to create a target, send a Get Request and print the response. package main import ( \"context\" \"fmt\" \"log\" \"github.com/openconfig/gnmic/pkg/api\" \"google.golang.org/protobuf/encoding/prototext\" ) func main () { // create a target tg , err := api . NewTarget ( api . Name ( \"srl1\" ), api . Address ( \"10.0.0.1:57400\" ), api . Username ( \"admin\" ), api . Password ( \"admin\" ), api . SkipVerify ( true ), ) if err != nil { log . Fatal ( err ) } ctx , cancel := context . WithCancel ( context . Background ()) defer cancel () // create a gNMI client err = tg . CreateGNMIClient ( ctx ) if err != nil { log . Fatal ( err ) } defer tg . Close () // create a GetRequest getReq , err := api . NewGetRequest ( api . Path ( \"/system/name\" ), api . Encoding ( \"json_ietf\" )) if err != nil { log . Fatal ( err ) } fmt . Println ( prototext . Format ( getReq )) // send the created gNMI GetRequest to the created target getResp , err := tg . Get ( ctx , getReq ) if err != nil { log . Fatal ( err ) } fmt . Println ( prototext . Format ( getResp )) }","title":"Get"},{"location":"user_guide/golang_package/examples/set/","text":"The below snippet shows how to create a target, send a Set Request and print the reponse. package main import ( \"context\" \"fmt\" \"log\" \"github.com/openconfig/gnmic/pkg/api\" \"google.golang.org/protobuf/encoding/prototext\" ) func main () { // create a target tg , err := api . NewTarget ( api . Name ( \"srl1\" ), api . Address ( \"10.0.0.1:57400\" ), api . Username ( \"admin\" ), api . Password ( \"admin\" ), api . SkipVerify ( true ), ) if err != nil { log . Fatal ( err ) } ctx , cancel := context . WithCancel ( context . Background ()) defer cancel () err = tg . CreateGNMIClient ( ctx ) if err != nil { log . Fatal ( err ) } defer tg . Close () // create a gNMI SetRequest setReq , err := api . NewSetRequest ( api . Update ( api . Path ( \"/system/name/host-name\" ), api . Value ( \"srl2\" , \"json_ietf\" )), ) if err != nil { log . Fatal ( err ) } fmt . Println ( prototext . Format ( setReq )) // send the created gNMI SetRequest to the created target setResp , err := tg . Set ( ctx , setReq ) if err != nil { log . Fatal ( err ) } fmt . Println ( prototext . Format ( setResp )) }","title":"Set"},{"location":"user_guide/golang_package/examples/subscribe/","text":"The below snippet shows how to create a target and a Subscribe Request. It then starts a Stream subscription with 10s interval and listens to Responses and errors. package main import ( \"context\" \"fmt\" \"log\" \"time\" \"github.com/openconfig/gnmic/pkg/api\" \"google.golang.org/protobuf/encoding/prototext\" ) func main () { // create a target tg , err := api . NewTarget ( api . Name ( \"srl1\" ), api . Address ( \"srl1:57400\" ), api . Username ( \"admin\" ), api . Password ( \"admin\" ), api . SkipVerify ( true ), ) if err != nil { log . Fatal ( err ) } ctx , cancel := context . WithCancel ( context . Background ()) defer cancel () err = tg . CreateGNMIClient ( ctx ) if err != nil { log . Fatal ( err ) } defer tg . Close () // create a gNMI subscribeRequest subReq , err := api . NewSubscribeRequest ( api . Encoding ( \"json_ietf\" ), api . SubscriptionListMode ( \"stream\" ), api . Subscription ( api . Path ( \"system/name\" ), api . SubscriptionMode ( \"sample\" ), api . SampleInterval ( 10 * time . Second ), )) if err != nil { log . Fatal ( err ) } fmt . Println ( prototext . Format ( subReq )) // start the subscription go tg . Subscribe ( ctx , subReq , \"sub1\" ) // start a goroutine that will stop the subscription after x seconds go func () { select { case <- ctx . Done (): return case <- time . After ( 42 * time . Second ): tg . StopSubscription ( \"sub1\" ) } }() subRspChan , subErrChan := tg . ReadSubscriptions () for { select { case rsp := <- subRspChan : fmt . Println ( prototext . Format ( rsp . Response )) case tgErr := <- subErrChan : log . Fatalf ( \"subscription %q stopped: %v\" , tgErr . SubscriptionName , tgErr . Err ) } } }","title":"Subcribe"},{"location":"user_guide/inputs/input_intro/","text":"gnmic supports various Inputs to consume gnmi data, transform it and ultimately export it to one or multiple Outputs. The purpose of gnmic 's Inputs is to build a gnmi data pipeline by enabling the ingestion and export of gnmi data that was exported by gnmic 's outputs upstream. Currently supported input types: NATS messaging system NATS Streaming messaging bus (STAN) Kafka messaging bus Defining Inputs and matching Outputs # To define an Input a user needs to fill in the inputs section in the configuration file. Each Input is defined by its name ( input1 in the example below), a type field which determines the type of input to be created ( nats , stan , kafka ) and various other configuration fields which depend on the Input type. Note Inputs names are case insensitive All Input types have an outputs field, under which the user can defined the downstream destination(s) of the consumed data. This way, data consumed once, can be exported multiple times. Info The same gnmic instance can act as gNMI collector, input and output simultaneously. Example: # part of gnmic config file inputs : input1 : type : nats # input type # # other config fields depending on the input type # outputs : - output1 - output2 Inputs use cases # Clustering # Using gnmic Inputs, the user can aggregate all the collected data into one instance of gnmic that can make it available to a downstream off the shelf tool,typically Prometheus. Data reuse # Collect data once and use it multiple times. By chaining multiple instances of gnmic the user can process the same stream of data in different ways. A different set of event processors can be applied on the data stream before being exported to its intended outputs.","title":"Introduction"},{"location":"user_guide/inputs/input_intro/#defining-inputs-and-matching-outputs","text":"To define an Input a user needs to fill in the inputs section in the configuration file. Each Input is defined by its name ( input1 in the example below), a type field which determines the type of input to be created ( nats , stan , kafka ) and various other configuration fields which depend on the Input type. Note Inputs names are case insensitive All Input types have an outputs field, under which the user can defined the downstream destination(s) of the consumed data. This way, data consumed once, can be exported multiple times. Info The same gnmic instance can act as gNMI collector, input and output simultaneously. Example: # part of gnmic config file inputs : input1 : type : nats # input type # # other config fields depending on the input type # outputs : - output1 - output2","title":"Defining Inputs and matching Outputs"},{"location":"user_guide/inputs/input_intro/#inputs-use-cases","text":"","title":"Inputs use cases"},{"location":"user_guide/inputs/input_intro/#clustering","text":"Using gnmic Inputs, the user can aggregate all the collected data into one instance of gnmic that can make it available to a downstream off the shelf tool,typically Prometheus.","title":"Clustering"},{"location":"user_guide/inputs/input_intro/#data-reuse","text":"Collect data once and use it multiple times. By chaining multiple instances of gnmic the user can process the same stream of data in different ways. A different set of event processors can be applied on the data stream before being exported to its intended outputs.","title":"Data reuse"},{"location":"user_guide/inputs/kafka_input/","text":"When using Kafka as input, gnmic consumes data from a specific Kafka topic in event or proto format. Multiple consumers can be created per gnmic instance ( num-workers ). All the workers join the same Kafka consumer group ( group-id ) in order to load share the messages between the workers. Multiple instances of gnmic with the same Kafka input can be used to effectively consume the exported messages in parallel The Kafka input will export the received messages to the list of outputs configured under its outputs section. inputs : input1 : # string, required, specifies the type of input type : kafka # Kafka subscriber name # If left empty, it will be populated with the string from flag --instance-name appended with `--kafka-cons`. # If --instance-name is also empty, a random name is generated in the format `gnmic-$uuid` # note that each kafka worker (consumer) will get name=$name-$index name : \"\" # Kafka SASL configuration sasl : # SASL user name user : # SASL password password : # SASL mechanism: PLAIN, SCRAM-SHA-256, SCRAM-SHA-512 and OAUTHBEARER are supported mechanism : # token url for OAUTHBEARER SASL mechanism token-url : # string, comma separated Kafka servers addresses address : localhost:9092 # string, comma separated topics the Kafka consumer group consumes messages from. topics : telemetry # consumer group all gnmic Kafka input workers join, # so that Kafka server can load share the messages between them. Defaults to `gnmic-consumers` group-id : gnmic-consumers # duration, the timeout used to detect consumer failures when using Kafka's group management facility. # If no heartbeats are received by the broker before the expiration of this session timeout, # then the broker will remove this consumer from the group and initiate a rebalance. session-timeout : 10s # duration, the expected time between heartbeats to the consumer coordinator when using Kafka's group # management facilities. heartbeat-interval : 3s # duration, wait time before reconnection attempts after any error recovery-wait-time : 2s # string, kafka version, defaults to 2.5.0 version : # string, consumed message expected format, one of: proto, event format : event # bool, enables extra logging debug : false # integer, number of kafka consumers to be created num-workers : 1 # list of processors to apply on the message when received, # only applies if format is 'event' event-processors : # []string, list of named outputs to export data to. # Must be configured under root level `outputs` section outputs :","title":"Kafka"},{"location":"user_guide/inputs/nats_input/","text":"When using NATS as input, gnmic consumes data from a specific NATS subject in event or proto format. Multiple consumers can be created per gnmic instance ( num-workers ). All the workers join the same NATS queue group ( queue ) in order to load share the messages between the workers. Multiple instances of gnmic with the same NATS input can be used to effectively consume the exported messages in parallel The NATS input will export the received messages to the list of outputs configured under its outputs section. inputs : input1 : # string, required, specifies the type of input type : nats # NATS subscriber name # If left empty, it will be populated with the string from flag --instance-name appended with `--nats-sub`. # If --instance-name is also empty, a random name is generated in the format `gnmic-$uuid` # note that each nats worker (subscriber) will get name=$name-$index name : \"\" # string, comma separated NATS servers addresses address : localhost:4222 # The subject name gnmic NATS consumers subscribe to. subject : telemetry # subscribe queue group all gnmic NATS input workers join, # so that NATS server can load share the messages between them. queue : # string, NATS username username : # string, NATS password password : # duration, wait time before reconnection attempts connect-time-wait : 2s # string, consumed message expected format, one of: proto, event format : event # bool, enables extra logging debug : false # integer, number of nats consumers to be created num-workers : 1 # integer, sets the size of the local buffer where received # NATS messages are stored before being sent to outputs. # This value is set per worker. Defaults to 100 messages buffer-size : 100 # list of processors to apply on the message when received, # only applies if format is 'event' event-processors : # []string, list of named outputs to export data to. # Must be configured under root level `outputs` section outputs :","title":"NATS"},{"location":"user_guide/inputs/stan_input/","text":"When using STAN as input, gnmic consumes data from a specific STAN subject in event or proto format. Multiple consumers can be created per gnmic instance ( num-workers ). All the workers join the same STAN queue group ( queue ) in order to load share the messages between the workers. Multiple instances of gnmic with the same STAN input can be used to effectively consume the exported messages in parallel The STAN input will export the received messages to the list of outputs configured under its outputs section. inputs : input1 : # string, required, specifies the type of input type : stan # STAN subscriber name # If left empty, it will be populated with the string from flag --instance-name appended with `--stan-sub`. # If --instance-name is also empty, a random name is generated in the format `gnmic-$uuid` # note that each stan worker (subscriber) will get name=$name-$index name : \"\" # string, comma separated STAN servers addresses address : localhost:4222 # The subject name gnmic STAN consumers subscribe to. subject : telemetry # subscribe queue group all gnmic STAN input workers join, # so that STAN server can load share the messages between them. queue : # string, STAN username username : # string, STAN password password : # duration, wait time before reconnection attempts connect-time-wait : 2s # string, the STAN cluster name. defaults to test-cluster cluster-name : # integer, interval (in seconds) at which # a connection sends a PING to the server. min=1 ping-interval : # integer, number of PINGs without a response # before the connection is considered lost. min=2 ping-retry : # string, consumed message expected format, one of: proto, event format : event # bool, enables extra logging debug : false # integer, number of stan consumers to be created num-workers : 1 # list of processors to apply on the message when received, # only applies if format is 'event' event-processors : # []string, list of named outputs to export data to. # Must be configured under root level `outputs` section outputs :","title":"STAN"},{"location":"user_guide/outputs/asciigraph_output/","text":"gnmic supports displaying collected metrics as an ASCII graph on the terminal. The graph is generated using the asciigraph package. Configuration sample # outputs : output1 : # required type : asciigraph # string, the graph caption caption : # integer, the graph height. If unset, defaults to the terminal height height : # integer, the graph width. If unset, defaults to the terminal width width : # float, the graph minimum value for the vertical axis. lower-bound : # float, the graph minimum value for the vertical axis. upper-bound : # integer, the graph left offset. offset : # integer, the decimal point precision of the label values. precision : # string, the caption color. one of ANSI colors. caption-color : # string, the axis color. one of ANSI colors. axis-color : # string, the label color. one of ANSI colors. label-color : # duration, the graph refresh timer. refresh-timer : 1s # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allows for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # list of processors to apply on the message before writing event-processors : # bool enable debug debug : false Example # This example shows how to use the asciigraph output. gNMIc config cat gnmic_asciiout.yaml targets : clab-nfd33-spine1-1 : username : admin password : NokiaSrl1! skip-verify : true subscriptions : sub1 : paths : - /interface[name=ethernet-1/3]/statistics/out-octets - /interface[name=ethernet-1/3]/statistics/in-octets stream-mode : sample sample-interval : 1s encoding : ascii outputs : out1 : type : asciigraph caption : in/out octets per second event-processors : - rate processors : rate : event-starlark : script : rate.star Starlark processor cat rate.star cache = {} values_names = [ '/interface/statistics/out-octets' , '/interface/statistics/in-octets' ] N = 2 def apply ( * events ): for e in events : for value_name in values_names : v = e . values . get ( value_name ) # check if v is not None and is a digit to proceed if not v : continue if not v . isdigit (): continue # update cache with the latest value val_key = \"_\" . join ([ e . tags [ \"source\" ], e . tags [ \"interface_name\" ], value_name ]) if not cache . get ( val_key ): # initialize the cache entry if empty cache . update ({ val_key : []}) if len ( cache [ val_key ]) > N : # remove the oldest entry if the number of entries reached N cache [ val_key ] = cache [ val_key ][ 1 :] # update cache entry cache [ val_key ] . append (( int ( v ), e . timestamp )) # get the list of values val_list = cache [ val_key ] # calculate rate e . values [ value_name + \"_rate\" ] = rate ( val_list ) e . values . pop ( value_name ) return events def rate ( vals ): previous_value , previous_timestamp = None , None for value , timestamp in vals : if previous_value != None and previous_timestamp != None : time_diff = ( timestamp - previous_timestamp ) / 1000000000 # 1 000 000 000 if time_diff > 0 : value_diff = value - previous_value rate = value_diff / time_diff return rate previous_value = value previous_timestamp = timestamp return 0","title":"ASCII Graph"},{"location":"user_guide/outputs/asciigraph_output/#configuration-sample","text":"outputs : output1 : # required type : asciigraph # string, the graph caption caption : # integer, the graph height. If unset, defaults to the terminal height height : # integer, the graph width. If unset, defaults to the terminal width width : # float, the graph minimum value for the vertical axis. lower-bound : # float, the graph minimum value for the vertical axis. upper-bound : # integer, the graph left offset. offset : # integer, the decimal point precision of the label values. precision : # string, the caption color. one of ANSI colors. caption-color : # string, the axis color. one of ANSI colors. axis-color : # string, the label color. one of ANSI colors. label-color : # duration, the graph refresh timer. refresh-timer : 1s # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allows for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # list of processors to apply on the message before writing event-processors : # bool enable debug debug : false","title":"Configuration sample"},{"location":"user_guide/outputs/asciigraph_output/#example","text":"This example shows how to use the asciigraph output. gNMIc config cat gnmic_asciiout.yaml targets : clab-nfd33-spine1-1 : username : admin password : NokiaSrl1! skip-verify : true subscriptions : sub1 : paths : - /interface[name=ethernet-1/3]/statistics/out-octets - /interface[name=ethernet-1/3]/statistics/in-octets stream-mode : sample sample-interval : 1s encoding : ascii outputs : out1 : type : asciigraph caption : in/out octets per second event-processors : - rate processors : rate : event-starlark : script : rate.star Starlark processor cat rate.star cache = {} values_names = [ '/interface/statistics/out-octets' , '/interface/statistics/in-octets' ] N = 2 def apply ( * events ): for e in events : for value_name in values_names : v = e . values . get ( value_name ) # check if v is not None and is a digit to proceed if not v : continue if not v . isdigit (): continue # update cache with the latest value val_key = \"_\" . join ([ e . tags [ \"source\" ], e . tags [ \"interface_name\" ], value_name ]) if not cache . get ( val_key ): # initialize the cache entry if empty cache . update ({ val_key : []}) if len ( cache [ val_key ]) > N : # remove the oldest entry if the number of entries reached N cache [ val_key ] = cache [ val_key ][ 1 :] # update cache entry cache [ val_key ] . append (( int ( v ), e . timestamp )) # get the list of values val_list = cache [ val_key ] # calculate rate e . values [ value_name + \"_rate\" ] = rate ( val_list ) e . values . pop ( value_name ) return events def rate ( vals ): previous_value , previous_timestamp = None , None for value , timestamp in vals : if previous_value != None and previous_timestamp != None : time_diff = ( timestamp - previous_timestamp ) / 1000000000 # 1 000 000 000 if time_diff > 0 : value_diff = value - previous_value rate = value_diff / time_diff return rate previous_value = value previous_timestamp = timestamp return 0","title":"Example"},{"location":"user_guide/outputs/file_output/","text":"gnmic supports exporting subscription updates to multiple local files A file output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : file # filename to write telemetry data to. # will be ignored if `file-type` is set filename : /path/to/filename # file-type, stdout or stderr. # overwrites `filename` file-type : # stdout or stderr # string, message formatting, json, protojson, prototext, event format : # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # string, a GoTemplate that is executed using the received gNMI message as input. # the template execution is the last step before the data is written to the file, # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any # then finally the msg-template is executed. msg-template : # boolean, if true the message timestamp is changed to current time override-timestamps : # boolean, format the output in indented form with every element on a new line. multiline : # string, indent specifies the set of indentation characters to use in a multiline formatted output indent : # string, separator is the set of characters to write between messages, defaults to new line separator : # integer, specifies the maximum number of allowed concurrent file writes concurrency-limit : 1000 # boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : The file output can be used to write to file on the disk, to stdout or to stderr. For a disk file, a file name is required. For stdout or stderr, only file-type is required.","title":"File"},{"location":"user_guide/outputs/gnmi_output/","text":"gnmic supports acting as a gNMI Server to expose the subscribed telemetry data to a gNMI Client using the Subcribe RPC, or to act as a gateway for Get and Set RPCs. Configuration # outputs : output1 : # required type : gnmi # gNMI server address, either a TCP socket or UNIX socket. # In the latter case, the prefix `unix:///` should be present. address : \":57400\" # maximum number of active subscriptions. max-subscriptions : 64 # maximum number of ongoing Get/Set RPCs. max-unary-rpc : 64 # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the returned Prefix.Target is empty. # if left empty, it defaults to: # `{{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present). target-template : # boolean, enables extra logging for the gNMI Server debug : false # boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false Insecure Mode # By default, the server runs in insecure mode, as long as skip-verify is false and none of ca-file , cert-file and key-file are set. Secure Mode # To run this gNMI server in secure mode, there are a few options: Using self signed certificates, without client certificate verification: skip-verify : true Using self signed certificates, with client certificate verification: # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile Using CA provided certificates, without client certificate verification: skip-verify : true # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key Using CA provided certificates, with client certificate verification: # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key Supported RPCs # This gNMI Server supports Get , Set and Subscribe RPCs. gNMI Subscribe RPC # The server keeps a cache of gNMI notifications synched with the configured targets based on the configured subscriptions. This means that a client cannot get updates about a leaf that gNMIc did not subscribe to upstream. As soon as there is an update to the cache, the added gNMI notification is sent to all the client which subscription matches the new notification. Clients can subscribe to specific target using the gNMI Prefix Target field, leaving the Target field empty or setting it to * is equivalent to subscribing to all known targets. gNMI Get RPC # The server supports the gNMI Get RPC. It relies on the Prefix.Target field to select the target(s) to relay the received GetRequest to. If Prefix.Target is empty or is equal to * , a Get RPC is performed for all known targets. The received GetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Get RPC to. Once all GetResponses are received back successfully, the notifications contained in each GetResponse are combined into a single GetResponse with their Prefix.Target populated, if empty. The resulting GetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client. If the Get Request has the origin field set to gnmic , the request is performed against the internal server configuration. Currently only the path targets is supported. gnmic -a localhost:57400 --skip-verify get --path gnmic:/targets [ { \"timestamp\" : 1626759382486891218 , \"time\" : \"2021-07-20T13:36:22.486891218+08:00\" , \"prefix\" : \"gnmic:targets[name=clab-gw-srl1:57400]\" , \"updates\" : [ { \"Path\" : \"address\" , \"values\" : { \"address\" : \"clab-gw-srl1:57400\" } }, { \"Path\" : \"username\" , \"values\" : { \"username\" : \"admin\" } }, { \"Path\" : \"insecure\" , \"values\" : { \"insecure\" : \"false\" } }, { \"Path\" : \"skip-verify\" , \"values\" : { \"skip-verify\" : \"true\" } }, { \"Path\" : \"timeout\" , \"values\" : { \"timeout\" : \"10s\" } } ] }, { \"timestamp\" : 1626759382486900697 , \"time\" : \"2021-07-20T13:36:22.486900697+08:00\" , \"prefix\" : \"gnmic:targets[name=clab-gw-srl2:57400]\" , \"updates\" : [ { \"Path\" : \"address\" , \"values\" : { \"address\" : \"clab-gw-srl2:57400\" } }, { \"Path\" : \"username\" , \"values\" : { \"username\" : \"admin\" } }, { \"Path\" : \"insecure\" , \"values\" : { \"insecure\" : \"false\" } }, { \"Path\" : \"skip-verify\" , \"values\" : { \"skip-verify\" : \"true\" } }, { \"Path\" : \"timeout\" , \"values\" : { \"timeout\" : \"10s\" } } ] } ] gNMI Set RPC # The gNMI server supports the gNMI Set RPC. Just like in the case of Get RPC, the server relies on the Prefix.Target field to select the target(s) to relay the received SetRequest to. If Prefix.Target is empty or is equal to * , a Set RPC is performed for all known targets. The received SetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Set RPC to. Once all SetResponses are received back successfully, the UpdateResult s from each response are merged into a single SetResponse, with the addition of the target name set in Path.Target . This is not compliant with the gNMI specification which stipulates that the Target field should only be present in Prefix Paths The resulting SetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client.","title":"gNMI Server"},{"location":"user_guide/outputs/gnmi_output/#configuration","text":"outputs : output1 : # required type : gnmi # gNMI server address, either a TCP socket or UNIX socket. # In the latter case, the prefix `unix:///` should be present. address : \":57400\" # maximum number of active subscriptions. max-subscriptions : 64 # maximum number of ongoing Get/Set RPCs. max-unary-rpc : 64 # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the returned Prefix.Target is empty. # if left empty, it defaults to: # `{{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present). target-template : # boolean, enables extra logging for the gNMI Server debug : false # boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false","title":"Configuration"},{"location":"user_guide/outputs/gnmi_output/#insecure-mode","text":"By default, the server runs in insecure mode, as long as skip-verify is false and none of ca-file , cert-file and key-file are set.","title":"Insecure Mode"},{"location":"user_guide/outputs/gnmi_output/#secure-mode","text":"To run this gNMI server in secure mode, there are a few options: Using self signed certificates, without client certificate verification: skip-verify : true Using self signed certificates, with client certificate verification: # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile Using CA provided certificates, without client certificate verification: skip-verify : true # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key Using CA provided certificates, with client certificate verification: # a valid CA certificate to verify the client provided certificates ca-file : /path/to/caFile # a valid server certificate cert-file : /path/to/server-cert # a valid server key key-file : /path/to/server-key","title":"Secure Mode"},{"location":"user_guide/outputs/gnmi_output/#supported-rpcs","text":"This gNMI Server supports Get , Set and Subscribe RPCs.","title":"Supported RPCs"},{"location":"user_guide/outputs/gnmi_output/#gnmi-subscribe-rpc","text":"The server keeps a cache of gNMI notifications synched with the configured targets based on the configured subscriptions. This means that a client cannot get updates about a leaf that gNMIc did not subscribe to upstream. As soon as there is an update to the cache, the added gNMI notification is sent to all the client which subscription matches the new notification. Clients can subscribe to specific target using the gNMI Prefix Target field, leaving the Target field empty or setting it to * is equivalent to subscribing to all known targets.","title":"gNMI Subscribe RPC"},{"location":"user_guide/outputs/gnmi_output/#gnmi-get-rpc","text":"The server supports the gNMI Get RPC. It relies on the Prefix.Target field to select the target(s) to relay the received GetRequest to. If Prefix.Target is empty or is equal to * , a Get RPC is performed for all known targets. The received GetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Get RPC to. Once all GetResponses are received back successfully, the notifications contained in each GetResponse are combined into a single GetResponse with their Prefix.Target populated, if empty. The resulting GetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client. If the Get Request has the origin field set to gnmic , the request is performed against the internal server configuration. Currently only the path targets is supported. gnmic -a localhost:57400 --skip-verify get --path gnmic:/targets [ { \"timestamp\" : 1626759382486891218 , \"time\" : \"2021-07-20T13:36:22.486891218+08:00\" , \"prefix\" : \"gnmic:targets[name=clab-gw-srl1:57400]\" , \"updates\" : [ { \"Path\" : \"address\" , \"values\" : { \"address\" : \"clab-gw-srl1:57400\" } }, { \"Path\" : \"username\" , \"values\" : { \"username\" : \"admin\" } }, { \"Path\" : \"insecure\" , \"values\" : { \"insecure\" : \"false\" } }, { \"Path\" : \"skip-verify\" , \"values\" : { \"skip-verify\" : \"true\" } }, { \"Path\" : \"timeout\" , \"values\" : { \"timeout\" : \"10s\" } } ] }, { \"timestamp\" : 1626759382486900697 , \"time\" : \"2021-07-20T13:36:22.486900697+08:00\" , \"prefix\" : \"gnmic:targets[name=clab-gw-srl2:57400]\" , \"updates\" : [ { \"Path\" : \"address\" , \"values\" : { \"address\" : \"clab-gw-srl2:57400\" } }, { \"Path\" : \"username\" , \"values\" : { \"username\" : \"admin\" } }, { \"Path\" : \"insecure\" , \"values\" : { \"insecure\" : \"false\" } }, { \"Path\" : \"skip-verify\" , \"values\" : { \"skip-verify\" : \"true\" } }, { \"Path\" : \"timeout\" , \"values\" : { \"timeout\" : \"10s\" } } ] } ]","title":"gNMI Get RPC"},{"location":"user_guide/outputs/gnmi_output/#gnmi-set-rpc","text":"The gNMI server supports the gNMI Set RPC. Just like in the case of Get RPC, the server relies on the Prefix.Target field to select the target(s) to relay the received SetRequest to. If Prefix.Target is empty or is equal to * , a Set RPC is performed for all known targets. The received SetRequest is cloned, enriched with each target name and sent to the corresponding destination. Comma separated target names are also supported and allow to select a list of specific targets to send the Set RPC to. Once all SetResponses are received back successfully, the UpdateResult s from each response are merged into a single SetResponse, with the addition of the target name set in Path.Target . This is not compliant with the gNMI specification which stipulates that the Target field should only be present in Prefix Paths The resulting SetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client.","title":"gNMI Set RPC"},{"location":"user_guide/outputs/influxdb_output/","text":"gnmic supports exporting subscription updates to influxDB time series database Configuration # An influxdb output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : influxdb # influxDB server address url : http://localhost:8086 # empty if using influxdb1.8.x org : myOrg # string in the form database/retention-policy. Skip retention policy for the default on bucket : telemetry # influxdb 1.8.x use a string in the form: \"username:password\" token : # number of points to buffer before writing to the server batch-size : 1000 # flush period after which the buffer is written to the server whether the batch_size is reached or not flush-timer : 10s # if true, the influxdb client will use gzip compression in write requests. use-gzip : false # (deprecated, use tls.skip-verify: true) #if true, the influxdb client will use a secure connection to the server. enable-tls : false # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # boolean, if true the message timestamp is changed to current time override-timestamps : false # server health check period, used to recover from server connectivity failure. # health check is disabled by default, can be enabled by setting the below field to any value other that zero. # with a minimum allowed period of 30s. health-check-period : 0s # defines the write timestamp precision, # one of `s` for second, `ms` for millisecond, `us` for microsecond and `ns` for nanoseconds # any other value defaults to `ns`. timestamp-precision : ns # server health check period, used to recover from server connectivity failure health-check-period : 30s # enable debug debug : false # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # NOT IMPLEMENTED boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : [] # cache, if present enables the influxdb output to cache received updates and write them all together # at `cache-flush-timer` expiry. cache : # duration, if > 0, enables the expiry of values written to the cache. expiration : 0s # debug, if true enable extra logging debug : false # cache-flush-timer cache-flush-timer : 5s gnmic uses the event format to generate the measurements written to InfluxDB. When an event has been processed through gnmic processors, the final value of the subscription-name tag will be used as an InfluxDB measurement name and the tag will be removed. If the subscription-name tag does not exist in the event, the event's Name will be used as InfluxDB measurement. Caching # When caching is enabled, the received messages are not written directly to InfluxDB, they are first cached as gNMI updates and written in batch when the cache-flush-timer is reached. The below diagram shows how an InfluxDB output works with and without cache enabled: When caching is enabled, the cached gNMI updates are periodically retrieved in batch, converted to events . If processors are defined under the output, they are applied to the whole list of events at once. This allows augmenting some messages with values from other messages even if they where collected from a different target/subscription.","title":"InfluxDB"},{"location":"user_guide/outputs/influxdb_output/#configuration","text":"An influxdb output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : influxdb # influxDB server address url : http://localhost:8086 # empty if using influxdb1.8.x org : myOrg # string in the form database/retention-policy. Skip retention policy for the default on bucket : telemetry # influxdb 1.8.x use a string in the form: \"username:password\" token : # number of points to buffer before writing to the server batch-size : 1000 # flush period after which the buffer is written to the server whether the batch_size is reached or not flush-timer : 10s # if true, the influxdb client will use gzip compression in write requests. use-gzip : false # (deprecated, use tls.skip-verify: true) #if true, the influxdb client will use a secure connection to the server. enable-tls : false # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # boolean, if true the message timestamp is changed to current time override-timestamps : false # server health check period, used to recover from server connectivity failure. # health check is disabled by default, can be enabled by setting the below field to any value other that zero. # with a minimum allowed period of 30s. health-check-period : 0s # defines the write timestamp precision, # one of `s` for second, `ms` for millisecond, `us` for microsecond and `ns` for nanoseconds # any other value defaults to `ns`. timestamp-precision : ns # server health check period, used to recover from server connectivity failure health-check-period : 30s # enable debug debug : false # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # NOT IMPLEMENTED boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : [] # cache, if present enables the influxdb output to cache received updates and write them all together # at `cache-flush-timer` expiry. cache : # duration, if > 0, enables the expiry of values written to the cache. expiration : 0s # debug, if true enable extra logging debug : false # cache-flush-timer cache-flush-timer : 5s gnmic uses the event format to generate the measurements written to InfluxDB. When an event has been processed through gnmic processors, the final value of the subscription-name tag will be used as an InfluxDB measurement name and the tag will be removed. If the subscription-name tag does not exist in the event, the event's Name will be used as InfluxDB measurement.","title":"Configuration"},{"location":"user_guide/outputs/influxdb_output/#caching","text":"When caching is enabled, the received messages are not written directly to InfluxDB, they are first cached as gNMI updates and written in batch when the cache-flush-timer is reached. The below diagram shows how an InfluxDB output works with and without cache enabled: When caching is enabled, the cached gNMI updates are periodically retrieved in batch, converted to events . If processors are defined under the output, they are applied to the whole list of events at once. This allows augmenting some messages with values from other messages even if they where collected from a different target/subscription.","title":"Caching"},{"location":"user_guide/outputs/jetstream_output/","text":"gnmic supports exporting subscription updates NATS Jetstream servers. A Jetstream output can be defined using the below format in gnmic config file under outputs section: configuration # outputs : output1 : # required type : jetstream # NATS publisher name # if left empty, this field is populated with the output name used as output ID (output1 in this example). # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name). # note that each jetstream worker (publisher) will get a client name=$name-$index name : \"\" # Comma separated NATS servers address : localhost:4222 # string, stream name to write update to, # if `create-stream` is set, it will be created # # may not contain spaces, tabs, period (.), greater than (>) or asterisk (*) stream : # defines stream parameters that gNMIc will create on the target jetstream server(s) create-stream : # string, stream description description : created by gNMIc # string list, list of subjects allowed on the stream # defaults to `.create-stream.$name.>` subjects : # string, one of `memory`, `file`. # defines the storage type to use for the stream. # defaults to `memory` storage : # int64, max number of messages in the stream. max-msgs : # int64, max bytes the stream may contain. max-bytes : # duration, max age of any message in the stream. max-age : # int32, maximum message size max-msg-size : # string, one of `static`, `subscription.target`, `subscription.target.path` # or `subscription.target.pathKeys`. # Defines the subject format. # `static`: # all updates will be written to the subject name set under `outputs.$output_name.subject` # `subscription.target`: # updates from each subscription, target will be written # to subject $subscription_name.$target_name # `subscription.target.path`: # updates from a certain subscription, target and path # will be written to subject $subscription_name.$target_name.$path. # The path is built by joining the gNMI path pathElements with a dot (.). # e.g: /interface[name=ethernet-1/1]/statistics/in-octets # --> interface.statistics.in-octets # `subscription.target.pathKeys`: # updates from a certain subscription, a certain target and a certain path # will be written to subject $subscription_name.$target_name.$path. # The path is built by joining the gNMI path pathElements and Keys with a dot (.). # e.g: /interface[name=ethernet-1/1]/statistics/in-octets # --> interface.{name=ethernet-1/1}.statistics.in-octets # `target.subscription`: # updates from each subscription, target will be written with a prefix of the `subject` # to subject $subject.$target_name.$subscription_name if `subject` is present. If not, # it will write to $target_name.$subscription_name. subject-format : static # If a subject-format is `static`, gnmic will publish all subscriptions updates # to a single subject configured under this field. Defaults to 'telemetry' # If a subject-format is `target.subscription`, gnmic will publish subscripion # updates prefixed with this subject. subject : telemetry # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # NATS username username : # NATS password password : # wait time before reconnection attempts connect-time-wait : 2s # Exported message format, one of: proto, prototext, protojson, json, event format : event # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # string, a GoTemplate that is executed using the received gNMI message as input. # the template execution is the last step before the data is written to the file. # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any # then finally the msg-template is executed. msg-template : # boolean, if true the message timestamp is changed to current time override-timestamps : false # integer, number of nats publishers to be created num-workers : 1 # duration after which a message waiting to be handled by a worker gets discarded write-timeout : 5s # boolean, enables extra logging for the nats output debug : false # boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply to the message before writing event-processors : subject-format # The subject-format field is used to control how the received gNMI notifications are written into the configured stream. static # All notifications will be written to the subject name set under outputs.$output_name.subject subscription.target # Notifications from each subscription and target pair will be written to subject $subscription_name.$target_name subscription.target.path # Notifications from a subscription, target and path tuple will be written to subject subscription_name. subscription_name. target_name.$path. The path is built by joining the gNMI path pathElements with a period (.) . Notifications containing more than one update, will be expanded into multiple notifications with one update each. E.g: An update from target target1 and subscription sub1 containing path /interface[name=ethernet-1/1]/statistics/in-octets , will be written to subject: $stream_name.sub1.target1.interface.statistics.in-octets subscription.target.pathKeys # Updates from a certain subscription, a certain target and a certain path will be written to subject $subscription_name.$target_name.$path . The path is built by joining the gNMI path pathElements and Keys with a period (.) . Notifications containing more than one update, will be expanded into multiple notifications with one update each. E.g: An update from target target1 and subscription sub1 containing path /interface[name=ethernet-1/1]/statistics/in-octets , will be written to subject: $stream_name.sub1.target1.interface.{name=ethernet-1/1}.statistics.in-octets","title":"Jetstream"},{"location":"user_guide/outputs/jetstream_output/#configuration","text":"outputs : output1 : # required type : jetstream # NATS publisher name # if left empty, this field is populated with the output name used as output ID (output1 in this example). # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name). # note that each jetstream worker (publisher) will get a client name=$name-$index name : \"\" # Comma separated NATS servers address : localhost:4222 # string, stream name to write update to, # if `create-stream` is set, it will be created # # may not contain spaces, tabs, period (.), greater than (>) or asterisk (*) stream : # defines stream parameters that gNMIc will create on the target jetstream server(s) create-stream : # string, stream description description : created by gNMIc # string list, list of subjects allowed on the stream # defaults to `.create-stream.$name.>` subjects : # string, one of `memory`, `file`. # defines the storage type to use for the stream. # defaults to `memory` storage : # int64, max number of messages in the stream. max-msgs : # int64, max bytes the stream may contain. max-bytes : # duration, max age of any message in the stream. max-age : # int32, maximum message size max-msg-size : # string, one of `static`, `subscription.target`, `subscription.target.path` # or `subscription.target.pathKeys`. # Defines the subject format. # `static`: # all updates will be written to the subject name set under `outputs.$output_name.subject` # `subscription.target`: # updates from each subscription, target will be written # to subject $subscription_name.$target_name # `subscription.target.path`: # updates from a certain subscription, target and path # will be written to subject $subscription_name.$target_name.$path. # The path is built by joining the gNMI path pathElements with a dot (.). # e.g: /interface[name=ethernet-1/1]/statistics/in-octets # --> interface.statistics.in-octets # `subscription.target.pathKeys`: # updates from a certain subscription, a certain target and a certain path # will be written to subject $subscription_name.$target_name.$path. # The path is built by joining the gNMI path pathElements and Keys with a dot (.). # e.g: /interface[name=ethernet-1/1]/statistics/in-octets # --> interface.{name=ethernet-1/1}.statistics.in-octets # `target.subscription`: # updates from each subscription, target will be written with a prefix of the `subject` # to subject $subject.$target_name.$subscription_name if `subject` is present. If not, # it will write to $target_name.$subscription_name. subject-format : static # If a subject-format is `static`, gnmic will publish all subscriptions updates # to a single subject configured under this field. Defaults to 'telemetry' # If a subject-format is `target.subscription`, gnmic will publish subscripion # updates prefixed with this subject. subject : telemetry # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # NATS username username : # NATS password password : # wait time before reconnection attempts connect-time-wait : 2s # Exported message format, one of: proto, prototext, protojson, json, event format : event # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # string, a GoTemplate that is executed using the received gNMI message as input. # the template execution is the last step before the data is written to the file. # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any # then finally the msg-template is executed. msg-template : # boolean, if true the message timestamp is changed to current time override-timestamps : false # integer, number of nats publishers to be created num-workers : 1 # duration after which a message waiting to be handled by a worker gets discarded write-timeout : 5s # boolean, enables extra logging for the nats output debug : false # boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply to the message before writing event-processors :","title":"configuration"},{"location":"user_guide/outputs/jetstream_output/#subject-format","text":"The subject-format field is used to control how the received gNMI notifications are written into the configured stream.","title":"subject-format"},{"location":"user_guide/outputs/jetstream_output/#static","text":"All notifications will be written to the subject name set under outputs.$output_name.subject","title":"static"},{"location":"user_guide/outputs/jetstream_output/#subscriptiontarget","text":"Notifications from each subscription and target pair will be written to subject $subscription_name.$target_name","title":"subscription.target"},{"location":"user_guide/outputs/jetstream_output/#subscriptiontargetpath","text":"Notifications from a subscription, target and path tuple will be written to subject subscription_name. subscription_name. target_name.$path. The path is built by joining the gNMI path pathElements with a period (.) . Notifications containing more than one update, will be expanded into multiple notifications with one update each. E.g: An update from target target1 and subscription sub1 containing path /interface[name=ethernet-1/1]/statistics/in-octets , will be written to subject: $stream_name.sub1.target1.interface.statistics.in-octets","title":"subscription.target.path"},{"location":"user_guide/outputs/jetstream_output/#subscriptiontargetpathkeys","text":"Updates from a certain subscription, a certain target and a certain path will be written to subject $subscription_name.$target_name.$path . The path is built by joining the gNMI path pathElements and Keys with a period (.) . Notifications containing more than one update, will be expanded into multiple notifications with one update each. E.g: An update from target target1 and subscription sub1 containing path /interface[name=ethernet-1/1]/statistics/in-octets , will be written to subject: $stream_name.sub1.target1.interface.{name=ethernet-1/1}.statistics.in-octets","title":"subscription.target.pathKeys"},{"location":"user_guide/outputs/kafka_output/","text":"gnmic supports exporting subscription updates to multiple Apache Kafka brokers/clusters simultaneously Configuration sample # A Kafka output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : kafka # kafka client name. # if left empty, this field is populated with the output name used as output ID (output1 in this example). # the full name will be '$(name)-kafka-prod'. # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name)-kafka-prod. # note that each kafka worker (producer) will get client name=$name-$index name : \"\" # Comma separated brokers addresses address : localhost:9092 # Kafka topic name topic : telemetry # Kafka topic prefix # If supplied, overrides the `topic` key and outputs to a separate topic per source # named like `$topic_$subscriptionName_$targetName`. If `source` contains a port number separated with a colon, # the colon will be replaced with an underscore due to restrictions on the naming of kafka topics. # ex: telemetry_bgp_neighbor_state_device1_6030 topic-prefix : telemetry # starts a sync-producer if set to true. sync-producer : false # required-acks is used in Produce Requests to tell the broker how many replica acknowledgements # it must see before responding. One of `no-response`, `wait-for-local`, `wait-for-all`. required-acks : wait-for-local # Kafka SASL configuration sasl : # SASL user name user : # SASL password password : # SASL mechanism: PLAIN, SCRAM-SHA-256, SCRAM-SHA-512 and OAUTHBEARER are supported mechanism : # token url for OAUTHBEARER SASL mechanism token-url : # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # The total number of times to retry sending a message max-retry : 2 # Kafka connection timeout timeout : 5s # Wait time to reestablish the kafka producer connection after a failure recovery-wait-time : 10s # Exported msg format, json, protojson, prototext, proto, event format : event # boolean, if true the kafka producer will add a key to # the message written to the broker. The key value is ${source}_${subscription-name}. # this is useful for Kafka topics with multiple partitions, it allows to keep messages from the same source and subscription in sequence. insert-key : false # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allows for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # string, a GoTemplate that is executed using the received gNMI message as input. # the template execution is the last step before the data is written to the file, # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any # then finally the msg-template is executed. msg-template : # boolean, if true the message timestamp is changed to current time override-timestamps : false # Number of kafka producers to be created num-workers : 1 # (bool) enable debug debug : false # (int) number of messages to buffer before being picked up by the workers buffer-size : 0 # (string) enables compression of produced message. One of gzip, snappy, zstd, lz4 compression-codec : gzip # (bool) enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : Currently all subscriptions updates (all targets and all subscriptions) are published to the defined topic name unless the topic-prefix configuration option is set. Kafka Security protocol # Kafka clients can operate with 4 security protocols , their configuration is controlled via both .tls and .sasl fields under the output config. Security Protocol Description Configuration PLAINTEXT Un-authenticated, non-encrypted channel .tls and .sasl are NOT present SASL_PLAINTEXT SASL authenticated, non-encrypted channel only .sasl is present SASL_SSL SASL authenticated, SSL channel both .tls and .sasl are present SSL SSL channel only .tls is present Security Configuration Examples # PLAINTEXT SASL_PLAINTEXT SASL_SSL SSL outputs : output1 : type : kafka topic : my_kafka_topic # other fields # no tls and no sasl fields outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret # other fields # no tls field Example1: Without server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : skip-verify : true # other fields # ... Example2: With server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : ca-file : /path/to/ca-file # other fields # ... Example3: With client certificates outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file # other fields # ... Example4: With both server certificate verification and client certificates outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file ca-file : /path/to/ca-file # other fields # ... Example1: Without server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic tls : skip-verify : true # other fields # no sasl field Example2: With server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic tls : ca-file : /path/to/ca-file # other fields # no sasl field Example3: With client certificates outputs : output1 : type : kafka topic : my_kafka_topic tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file # other fields # no sasl field Example4: With both server certificate verification and client certificates outputs : output1 : type : kafka topic : my_kafka_topic tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file ca-file : /path/to/ca-file # other fields # no sasl field Kafka Output Metrics # When a Prometheus server is enabled, gnmic kafka output exposes 4 prometheus metrics, 3 Counters and 1 Gauge: number_of_kafka_msgs_sent_success_total : Number of msgs successfully sent by gnmic kafka output. This Counter is labeled with the kafka producerID number_of_written_kafka_bytes_total : Number of bytes written by gnmic kafka output. This Counter is labeled with the kafka producerID number_of_kafka_msgs_sent_fail_total : Number of failed msgs sent by gnmic kafka output. This Counter is labeled with the kafka producerID as well as the failure reason msg_send_duration_ns : gnmic kafka output send duration in nanoseconds. This Gauge is labeled with the kafka producerID","title":"Kafka"},{"location":"user_guide/outputs/kafka_output/#configuration-sample","text":"A Kafka output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : kafka # kafka client name. # if left empty, this field is populated with the output name used as output ID (output1 in this example). # the full name will be '$(name)-kafka-prod'. # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name)-kafka-prod. # note that each kafka worker (producer) will get client name=$name-$index name : \"\" # Comma separated brokers addresses address : localhost:9092 # Kafka topic name topic : telemetry # Kafka topic prefix # If supplied, overrides the `topic` key and outputs to a separate topic per source # named like `$topic_$subscriptionName_$targetName`. If `source` contains a port number separated with a colon, # the colon will be replaced with an underscore due to restrictions on the naming of kafka topics. # ex: telemetry_bgp_neighbor_state_device1_6030 topic-prefix : telemetry # starts a sync-producer if set to true. sync-producer : false # required-acks is used in Produce Requests to tell the broker how many replica acknowledgements # it must see before responding. One of `no-response`, `wait-for-local`, `wait-for-all`. required-acks : wait-for-local # Kafka SASL configuration sasl : # SASL user name user : # SASL password password : # SASL mechanism: PLAIN, SCRAM-SHA-256, SCRAM-SHA-512 and OAUTHBEARER are supported mechanism : # token url for OAUTHBEARER SASL mechanism token-url : # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # The total number of times to retry sending a message max-retry : 2 # Kafka connection timeout timeout : 5s # Wait time to reestablish the kafka producer connection after a failure recovery-wait-time : 10s # Exported msg format, json, protojson, prototext, proto, event format : event # boolean, if true the kafka producer will add a key to # the message written to the broker. The key value is ${source}_${subscription-name}. # this is useful for Kafka topics with multiple partitions, it allows to keep messages from the same source and subscription in sequence. insert-key : false # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allows for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # string, a GoTemplate that is executed using the received gNMI message as input. # the template execution is the last step before the data is written to the file, # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any # then finally the msg-template is executed. msg-template : # boolean, if true the message timestamp is changed to current time override-timestamps : false # Number of kafka producers to be created num-workers : 1 # (bool) enable debug debug : false # (int) number of messages to buffer before being picked up by the workers buffer-size : 0 # (string) enables compression of produced message. One of gzip, snappy, zstd, lz4 compression-codec : gzip # (bool) enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : Currently all subscriptions updates (all targets and all subscriptions) are published to the defined topic name unless the topic-prefix configuration option is set.","title":"Configuration sample"},{"location":"user_guide/outputs/kafka_output/#kafka-security-protocol","text":"Kafka clients can operate with 4 security protocols , their configuration is controlled via both .tls and .sasl fields under the output config. Security Protocol Description Configuration PLAINTEXT Un-authenticated, non-encrypted channel .tls and .sasl are NOT present SASL_PLAINTEXT SASL authenticated, non-encrypted channel only .sasl is present SASL_SSL SASL authenticated, SSL channel both .tls and .sasl are present SSL SSL channel only .tls is present","title":"Kafka Security protocol"},{"location":"user_guide/outputs/kafka_output/#security-configuration-examples","text":"PLAINTEXT SASL_PLAINTEXT SASL_SSL SSL outputs : output1 : type : kafka topic : my_kafka_topic # other fields # no tls and no sasl fields outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret # other fields # no tls field Example1: Without server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : skip-verify : true # other fields # ... Example2: With server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : ca-file : /path/to/ca-file # other fields # ... Example3: With client certificates outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file # other fields # ... Example4: With both server certificate verification and client certificates outputs : output1 : type : kafka topic : my_kafka_topic sasl : user : admin password : secret tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file ca-file : /path/to/ca-file # other fields # ... Example1: Without server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic tls : skip-verify : true # other fields # no sasl field Example2: With server certificate verification outputs : output1 : type : kafka topic : my_kafka_topic tls : ca-file : /path/to/ca-file # other fields # no sasl field Example3: With client certificates outputs : output1 : type : kafka topic : my_kafka_topic tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file # other fields # no sasl field Example4: With both server certificate verification and client certificates outputs : output1 : type : kafka topic : my_kafka_topic tls : cert-file : /path/to/cert-file key-file : /path/to/cert-file ca-file : /path/to/ca-file # other fields # no sasl field","title":"Security Configuration Examples"},{"location":"user_guide/outputs/kafka_output/#kafka-output-metrics","text":"When a Prometheus server is enabled, gnmic kafka output exposes 4 prometheus metrics, 3 Counters and 1 Gauge: number_of_kafka_msgs_sent_success_total : Number of msgs successfully sent by gnmic kafka output. This Counter is labeled with the kafka producerID number_of_written_kafka_bytes_total : Number of bytes written by gnmic kafka output. This Counter is labeled with the kafka producerID number_of_kafka_msgs_sent_fail_total : Number of failed msgs sent by gnmic kafka output. This Counter is labeled with the kafka producerID as well as the failure reason msg_send_duration_ns : gnmic kafka output send duration in nanoseconds. This Gauge is labeled with the kafka producerID","title":"Kafka Output Metrics"},{"location":"user_guide/outputs/nats_output/","text":"gnmic supports exporting subscription updates to multiple NATS servers/clusters simultaneously A NATS output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : nats # NATS publisher name # if left empty, this field is populated with the output name used as output ID (output1 in this example). # the full name will be '$(name)-nats-pub'. # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name)-nats-pub. # note that each nats worker (publisher) will get client name=$name-$index name : \"\" # Comma separated NATS servers address : localhost:4222 # This prefix is used to to build the subject name for each target/subscription subject-prefix : telemetry # If a subject-prefix is not specified, gnmic will publish all subscriptions updates to a single subject configured under this field. Defaults to 'telemetry' subject : telemetry # NATS username username : # NATS password password : # wait time before reconnection attempts connect-time-wait : 2s # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # Exported message format, one of: proto, prototext, protojson, json, event format : json # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # string, a GoTemplate that is executed using the received gNMI message as input. # the template execution is the last step before the data is written to the file, # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any # then finally the msg-template is executed. msg-template : # boolean, if true the message timestamp is changed to current time override-timestamps : false # integer, number of nats publishers to be created num-workers : 1 # duration after which a message waiting to be handled by a worker gets discarded write-timeout : 5s # boolean, enables extra logging for the nats output debug : false # boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : Using subject config value, a user can specify the NATS subject to which to send all subscriptions updates for all targets If a user wants to separate updates by targets and by subscriptions, subject-prefix can be used. if subject-prefix is specified subject is ignored. gnmic takes advantage of NATS subject hierarchy by publishing gNMI subscription updates to a separate subject per target per subscription. The NATS subject name is built out of the subject-prefix , name under the target definition and subscription-name resulting in the following format: subject-prefix.name.subscription-name e.g: for a target router1 , a subscription name port-stats and subject-prefix telemetry the subject name will be telemetry.router1.port-stats If the target name is an IP address, or a hostname (meaning potentially contains . ), the . characters are replaced with a - e.g: for a target 172.17.0.100:57400 , the previous subject name becomes telemetry.172-17-0-100:57400.port-stats This way a user can subscribe to different subsets of updates by tweaking the subject name: \"telemetry.>\" gets all updates sent to NATS by all targets, all subscriptions \"telemetry.router1.>\" gets all NATS updates for target router1 \"telemetry.*.port-stats\" gets all updates from subscription port-stats, for all targets","title":"NATS"},{"location":"user_guide/outputs/output_intro/","text":"In the context of gnmi subscriptions (on top of terminal output) gnmic supports multiple output options: Local file NATS messaging system NATS Streaming messaging bus (STAN) NATS JetStream Kafka messaging bus InfluxDB Time Series Database Prometheus Server Prometheus Remote Write UDP Server TCP Server These outputs can be mixed and matched at will with the different gnmi subscribe targets. With multiple outputs defined in the configuration file you can collect once and export the subscriptions updates to multiple locations formatted differently. Defining outputs # To define an output a user needs to create the outputs section in the configuration file: # part of ~/gnmic.yml config file outputs : output1 : type : file # output type file-type : stdout # or stderr format : json output2 : type : file filename : /path/to/localFile.log format : protojson output3 : type : nats # output type address : 127.0.0.1:4222 # comma separated nats servers addresses subject-prefix : telemetry # format : event output4 : type : file filename : /path/to/localFile.log format : json output5 : type : stan # output type address : 127.0.0.1:4223 # comma separated nats streaming servers addresses subject : telemetry # cluster-name : test-cluster # format : proto output6 : type : kafka # output type address : localhost:9092 # comma separated kafka brokers addresses topic : telemetry # kafka topic format : proto output7 : type : stan # output type address : 127.0.0.1:4223 # comma separated nats streaming servers addresses subject : telemetry cluster-name : test-cluster Note Outputs names are case insensitive Output formats # Different formats are supported for all outputs Format/output proto protojson prototext json event File NATS / STAN Kafka UDP / TCP InfluxDB NA NA NA NA NA Prometheus NA NA NA NA NA Formats examples # protojson prototext json event { \"update\" : { \"timestamp\" : \"1595491618677407414\" , \"prefix\" : { \"elem\" : [ { \"name\" : \"configure\" }, { \"name\" : \"system\" } ] }, \"update\" : [ { \"path\" : { \"elem\" : [ { \"name\" : \"name\" } ] }, \"val\" : { \"stringVal\" : \"sr123\" } } ] } } update : { timestamp : 1595491704850352047 prefix : { elem : { name : \"configure\" } elem : { name : \"system\" } } update : { path : { elem : { name : \"name\" } } val : { string_val : \"sr123\" } } } { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"sub1\" , \"timestamp\" : 1595491557144228652 , \"time\" : \"2020-07-23T16:05:57.144228652+08:00\" , \"prefix\" : \"configure/system\" , \"updates\" : [ { \"Path\" : \"name\" , \"values\" : { \"name\" : \"sr123\" } } ] } [ { \"name\" : \"sub1\" , \"timestamp\" : 1595491586073072000 , \"tags\" : { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"sub1\" }, \"values\" : { \"/configure/system/name\" : \"sr123\" } } ] Binding outputs # Once the outputs are defined, they can be flexibly associated with the targets. # part of ~/gnmic.yml config file targets : router1.lab.com : username : admin password : secret outputs : - output1 - output3 router2.lab.com : username : gnmi password : telemetry outputs : - output2 - output3 - output4 Caching # By default, gNMIc outputs write the received gNMI updates as they arrive (i.e without caching). Caching messages before writing them to a remote location can yield a few benefits like rate limiting , batch processing , data replication , etc. Both influxdb and prometheus outputs support caching messages before exporting. Caching support for other outputs is planned. See more details about caching here","title":"Introduction"},{"location":"user_guide/outputs/output_intro/#defining-outputs","text":"To define an output a user needs to create the outputs section in the configuration file: # part of ~/gnmic.yml config file outputs : output1 : type : file # output type file-type : stdout # or stderr format : json output2 : type : file filename : /path/to/localFile.log format : protojson output3 : type : nats # output type address : 127.0.0.1:4222 # comma separated nats servers addresses subject-prefix : telemetry # format : event output4 : type : file filename : /path/to/localFile.log format : json output5 : type : stan # output type address : 127.0.0.1:4223 # comma separated nats streaming servers addresses subject : telemetry # cluster-name : test-cluster # format : proto output6 : type : kafka # output type address : localhost:9092 # comma separated kafka brokers addresses topic : telemetry # kafka topic format : proto output7 : type : stan # output type address : 127.0.0.1:4223 # comma separated nats streaming servers addresses subject : telemetry cluster-name : test-cluster Note Outputs names are case insensitive","title":"Defining outputs"},{"location":"user_guide/outputs/output_intro/#output-formats","text":"Different formats are supported for all outputs Format/output proto protojson prototext json event File NATS / STAN Kafka UDP / TCP InfluxDB NA NA NA NA NA Prometheus NA NA NA NA NA","title":"Output formats"},{"location":"user_guide/outputs/output_intro/#formats-examples","text":"protojson prototext json event { \"update\" : { \"timestamp\" : \"1595491618677407414\" , \"prefix\" : { \"elem\" : [ { \"name\" : \"configure\" }, { \"name\" : \"system\" } ] }, \"update\" : [ { \"path\" : { \"elem\" : [ { \"name\" : \"name\" } ] }, \"val\" : { \"stringVal\" : \"sr123\" } } ] } } update : { timestamp : 1595491704850352047 prefix : { elem : { name : \"configure\" } elem : { name : \"system\" } } update : { path : { elem : { name : \"name\" } } val : { string_val : \"sr123\" } } } { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"sub1\" , \"timestamp\" : 1595491557144228652 , \"time\" : \"2020-07-23T16:05:57.144228652+08:00\" , \"prefix\" : \"configure/system\" , \"updates\" : [ { \"Path\" : \"name\" , \"values\" : { \"name\" : \"sr123\" } } ] } [ { \"name\" : \"sub1\" , \"timestamp\" : 1595491586073072000 , \"tags\" : { \"source\" : \"172.17.0.100:57400\" , \"subscription-name\" : \"sub1\" }, \"values\" : { \"/configure/system/name\" : \"sr123\" } } ]","title":"Formats examples"},{"location":"user_guide/outputs/output_intro/#binding-outputs","text":"Once the outputs are defined, they can be flexibly associated with the targets. # part of ~/gnmic.yml config file targets : router1.lab.com : username : admin password : secret outputs : - output1 - output3 router2.lab.com : username : gnmi password : telemetry outputs : - output2 - output3 - output4","title":"Binding outputs"},{"location":"user_guide/outputs/output_intro/#caching","text":"By default, gNMIc outputs write the received gNMI updates as they arrive (i.e without caching). Caching messages before writing them to a remote location can yield a few benefits like rate limiting , batch processing , data replication , etc. Both influxdb and prometheus outputs support caching messages before exporting. Caching support for other outputs is planned. See more details about caching here","title":"Caching"},{"location":"user_guide/outputs/prometheus_output/","text":"Introduction # gNMIc offers the capability to present gNMI updates on a Prometheus server, allowing a Prometheus system to perform scrapes. The Prometheus metric name and its labels are generated according to the subscription name, gNMI path, and the value name. To define a gNMIc Prometheus output, use the following format in the gnmic configuration file under the outputs section: outputs : sample-prom-output : type : prometheus # required # address to listen on for incoming scrape requests listen : :9804 # path to query to get the metrics path : /metrics # maximum lifetime of metrics in the local cache, # # a zero value defaults to 60s, a negative duration (e.g: -1s) disables the expiration expiration : 60s # a string to be used as the metric namespace metric-prefix : \"\" # a boolean, if true the subscription name will be appended to the metric name after the prefix append-subscription-name : false # boolean, if true the message timestamp is changed to current time override-timestamps : false # a boolean, enables exporting timestamps received from the gNMI target as part of the metrics export-timestamps : false # a boolean, enables setting string type values as prometheus metric labels. strings-as-labels : false # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # see https://gnmic.openconfig.net/user_guide/caching/, # if enabled, the received gNMI notifications are stored in a cache. # the prometheus metrics are generated at the time a prometheus server sends scrape request. # this behavior allows the processors (if defined) to be run on all the generated events at once. # this mode uses more resource compared to the default one, but offers more flexibility when it comes # to manipulating the data to customize the returned metrics using event-processors. cache : # duration, scrape request timeout. # this timer is started when a scrape request is received, # if it is reached, the metrics generation/collection is stopped. timeout : 10s # enable debug for prometheus output debug : false # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # list of processors to apply on the message before writing event-processors : # an integer, sets the number of worker handling messages to be converted into Prometheus metrics num-workers : 1 # Enables Consul service registration service-registration : # Consul server address, default to localhost:8500 address : # Consul Data center, defaults to dc1 datacenter : # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token which overrides the agent's default token token : # address and port number to be registered as a service address in Consul. # if the field is empty the address is derived from the listen field. # if the address does not contain a port number, the port number fmro the listen field is used. service-address : # Prometheus service check interval, for both http and TTL Consul checks, # defaults to 5s check-interval : # Maximum number of failed checks before the service is deleted by Consul # defaults to 3 max-fail : # Consul service name name : # List of tags to be added to the service registration, # if available, the instance-name and cluster-name will be added as tags, # in the format: gnmic-instance=$instance-name and gnmic-cluster=$cluster-name tags : # bool, enables http service check on top of the TTL check enable-http-check : # string, if enable-http-check is true, this field can be used to specify the http endpoint to be used to the check # if provided, this filed with be prepended with 'http://' (if not already present), # and appended with the value in 'path' field. # if not specified, it will be derived from the fields 'listen' and 'path' http-check-address : # if set to true, the gnmic instance will try to ac quire a lock before registering the prometheus output in consul. # this allows to register a single instance of the cluster in consul. # if the instance which acquired the lock fails, one of the remaining ones will take over. use-lock : false Fields definition # type # The output type, prometheus in this case. listen # Address to listen on for incoming scrape requests, defaults to :9804 path # URL Path to query in order to retrieve the metrics, defaults to /metrics expiration # Maximum lifetime of metrics in the local cache, A zero value defaults to 60s, a negative duration (e.g: -1s) disables the expiration metric-prefix # A string to be used as the metric namespace append-subscription-name # A boolean, if true the subscription name will be appended to the metric name after the prefix override-timestamps # A boolean, if true the message timestamp is changed to current time export-timestamps # A boolean, enables exporting timestamps received from the gNMI target as part of the metrics strings-as-labels # A boolean, enables setting string type values as prometheus metric labels. tls # ca-file # A string, path to the CA certificate file. This certificate is used to verify the clients certificates. cert-file # A string, path to server certificate file. key-file # A string, server key file. client-auth # A string, use to control whether the server requests a client certificate or not and how it validates it. One of: \"\": The server does not request a certificate from the client. \"request\": The server requests a certificate from the client but does not require the client to send a certificate. If the client sends a certificate, it is not required to be valid. \"require\": The server requires the client to send a certificate and does not fail if the client certificate is not valid. \"verify-if-given\": The server requests a certificate, does not fail if no certificate is sent. If a certificate is sent it is required to be valid. \"require-verify\": The server requires the client to send a valid certificate. If the ca-file is not provided, the default value for client-auth is an empty string (\"\"). However, if a ca-file is specified, the default value for client-auth becomes \"require-verify\". cache # Refer to the cache docs for more information. When enabled, gNMI notifications are stored in a cache upon receipt. Prometheus metrics are subsequently generated when a Prometheus system sends a scrape request. This approach allows processors (if defined) to operate on all generated events simultaneously. While this mode consumes more resources compared to the default, it provides increased flexibility for data manipulation and metric customization through the use of event-processors. timeout # A Duration such as 10s, 1m or 1m30s, defines the scrape request timeout. This timer is started when a scrape request is received from a Prometheus system. If the timer is is reached, the metrics generation/collection is stopped. debug # A boolean. Enables debug for prometheus output add-target # A string, one of overwrite , if-not-present or ``. This field allows populating/changing the value of Prefix.Target in the received message. If left empty (\"\"), no changes will be made. If set to \"overwrite\", the target value will be replaced with the configuration specified under target-template. If set to \"if-not-present\", the target value will be populated only if it is empty, utilizing the target-template. target-template # A string, a GoTemplate that allow for the customization of the target field in Prefix.Target . It applies only if the previous field add-target is not empty. If left target-template is left empty, it defaults to: {{- if index . \"subscription-target\" -}} {{ index . \"subscription-target\" }} {{- else -}} {{ index . \"source\" | host }} {{- end -}} The above template sets the target to the value configured under subscription.$subscription-name.target if any, otherwise it will set it to the target name stripped of the port number (if present) event-processors # A string list. List of processors to apply on the message before writing service-registration # Enables Consul service registration address # Consul server address, default to localhost:8500 datacenter # Consul Data center, defaults to dc1 username # Consul username, to be used as part of HTTP basicAuth password # Consul password, to be used as part of HTTP basicAuth token # Consul Token, is used to provide a per-request ACL token which overrides the agent's default token service-address # Address and port number to be registered as a service address in Consul. if the field is empty the address is derived from the listen field. if the address does not contain a port number, the port number from the listen field is used. check-interval # Prometheus service check interval, for both http and TTL Consul checks, defaults to 5s max-fail # Maximum number of failed checks before the service is deleted by Consul defaults to 3 name # Consul service name tags # List of tags to be added to the service registration, if available, the instance-name and cluster-name will be added as tags, in the format: gnmic-instance= instance-name and gnmic-cluster= instance-name and gnmic-cluster= cluster-name enable-http-check # A boolean, enables http service check on top of the TTL check http-check-address # A string, if enable-http-check is true, this field can be used to specify the http endpoint to be used to the check if provided, this filed with be prepended with 'http://' (if not already present), and appended with the value in 'path' field. if not specified, it will be derived from the fields 'listen' and 'path' use-lock # A boolean, if set to true, the gnmic instance will try to acquire a lock before registering the prometheus output. This knob allows to register a single instance of the cluster in Consul. if the instance which acquired the lock fails, one of the remaining ones takes over by acquiring the lost lock. Metric Generation # The below diagram shows an example of a prometheus metric generation from a gnmi update Metric Naming # The metric name starts with the string configured under metric-prefix . Then if append-subscription-name is true , the subscription-name as specified in gnmic configuration file is appended. The resulting string is followed by the gNMI path stripped of its keys if there are any. All non-alphanumeric characters are replaced with an underscore \" _ \" The 3 strings are then joined with an underscore \" _ \" If further customization of the metric name is required, the processors can be used to transform the metric name. For example, a gNMI update from subscription port-stats with path: /interfaces/interface [ name = 1 /1/1 ] /subinterfaces/subinterface [ index = 0 ] /state/counters/in-octets is exposed as a metric named: gnmic_port_stats_interfaces_interface_subinterfaces_subinterface_state_counters_in_octets Metric Labels # The metrics labels are generated from the subscription metadata (e.g: subscription-name and source ) and the keys present in the gNMI path elements. For the previous example the labels would be: { interface_name = \"1/1/1\" ,subinterface_index = 0 ,source = \" $routerIP :Port\" ,subscription_name = \"port-stats\" } Service Registration # gnmic supports prometheus_output service registration via Consul . It allows prometheus to dynamically discover new instances of gnmic exposing a prometheus server ready for scraping via its service discovery feature . If the configuration section service-registration is present under the output definition, gnmic will register the prometheus_output service in Consul . Configuration Example # The below configuration, registers a service name gnmic-prom-srv with IP=10.1.1.1 and port=9804 # gnmic.yaml outputs : output1 : type : prometheus listen : 10.1.1.1:9804 path : /metrics service-registration : address : consul-agent.local:8500 name : gnmic-prom-srv This allows running multiple instances of gnmic with minimal configuration changes by using prometheus service discovery feature . Simplified scrape configuration in the prometheus client: # prometheus.yaml scrape_configs : - job_name : 'gnmic' scrape_interval : 10s consul_sd_configs : - server : $CONSUL_ADDRESS services : - $service_name Service Name and ID # The $service_name to be discovered by prometheus is configured under outputs.$output_name.service-registration.name . If the service registration name field is not present, the name prometheus-${output_name} will be used. In both cases the service ID will be prometheus-${output_name}-${instance_name} . Service Checks # gnmic registers the service in Consul with a ttl check enabled by default: ttl : gnmic periodically updates the service definition in Consul . The goal is to allow Consul to detect a same instance restarting with a different service name. If service-registration.enable-http-check is true , an http check is added: http : Consul periodically scrapes the prometheus server endpoint to check its availability. # gnmic.yaml outputs : output1 : type : prometheus listen : 10.1.1.1:9804 path : /metrics service-registration : address : consul-agent.local:8500 name : gnmic-prom-srv enable-http-check : true Note that for the http check to work properly, a reachable address ( IP or name ) should be specified under listen . Otherwise, a reachable address should be added under service-registration.http-check-address . Caching # When caching is enabled, the received messages are not immediately converted into metrics, they are cached as gNMI updates. The conversion from gNMI update to Prometheus metrics happens only when a scrape request is received. The below diagram shows how a prometheus output works with and without cache enabled: When caching is enabled, the received gNMI updates are not processed and converted into metrics immediately, they are rather stored as is in the configured gNMI cache. Once a scrape request is received from Prometheus , all the cached gNMI updates are retrieved from the cache, converted to events , the configured processors, if any, are then applied to the whole list of events. Finally, The resulting event are converted into metrics and written back to Prometheus within the scrape response. Prometheus Output Metrics # When a Prometheus server (gNMI API) is enabled, gnmic prometheus output exposes 2 prometheus Gauges: number_of_prometheus_metrics_total : Number of metrics stored by the prometheus output. number_of_prometheus_cached_metrics_total : Number of metrics cached by the prometheus output. Examples # A simple Prometheus output # A basic Prometheus output utilizing all default values converts each received gNMI update into a Prometheus metric, retaining it in the cache until a scrape request is received from a Prometheus system. outputs : simple-prom : type : prometheus Promote string values to labels # A straightforward Prometheus output, utilizing default values for the most part, transforms each incoming gNMI update into a Prometheus metric. In this process, if a value is a string, it is incorporated as a label in the final metric. These metrics are retained in the cache, awaiting a scrape request from a Prometheus system. outputs : simple-prom : type : prometheus strings-as-labels : true Use a gNMI cache # A Prometheus output leveraging a gNMI cache stores incoming gNMI updates in their original form, only converting them into Prometheus metrics upon receiving a scrape request from a Prometheus system. This mode enables batch processing of all updates simultaneously during their conversion into Prometheus metrics. outputs : simple-prom : type : prometheus cache : {} ****Register as a Consul service**** # A Prometheus output that dynamically registers its endpoint within Consul, enabling the Prometheus system to seamlessly discover the associated address and port number. outputs : simple-prom : type : prometheus service-registration : address : consul-server-address:8500","title":"Scrape Based (Pull)"},{"location":"user_guide/outputs/prometheus_output/#introduction","text":"gNMIc offers the capability to present gNMI updates on a Prometheus server, allowing a Prometheus system to perform scrapes. The Prometheus metric name and its labels are generated according to the subscription name, gNMI path, and the value name. To define a gNMIc Prometheus output, use the following format in the gnmic configuration file under the outputs section: outputs : sample-prom-output : type : prometheus # required # address to listen on for incoming scrape requests listen : :9804 # path to query to get the metrics path : /metrics # maximum lifetime of metrics in the local cache, # # a zero value defaults to 60s, a negative duration (e.g: -1s) disables the expiration expiration : 60s # a string to be used as the metric namespace metric-prefix : \"\" # a boolean, if true the subscription name will be appended to the metric name after the prefix append-subscription-name : false # boolean, if true the message timestamp is changed to current time override-timestamps : false # a boolean, enables exporting timestamps received from the gNMI target as part of the metrics export-timestamps : false # a boolean, enables setting string type values as prometheus metric labels. strings-as-labels : false # tls config tls : # string, path to the CA certificate file, # this certificate is used to verify the clients certificates. ca-file : # string, server certificate file. cert-file : # string, server key file. key-file : # string, one of `\"\", \"request\", \"require\", \"verify-if-given\", or \"require-verify\" # - request: The server requests a certificate from the client but does not # require the client to send a certificate. # If the client sends a certificate, it is not required to be valid. # - require: The server requires the client to send a certificate and does not # fail if the client certificate is not valid. # - verify-if-given: The server requests a certificate, # does not fail if no certificate is sent. # If a certificate is sent it is required to be valid. # - require-verify: The server requires the client to send a valid certificate. # # if no ca-file is present, `client-auth` defaults to \"\"` # if a ca-file is set, `client-auth` defaults to \"require-verify\"` client-auth : \"\" # see https://gnmic.openconfig.net/user_guide/caching/, # if enabled, the received gNMI notifications are stored in a cache. # the prometheus metrics are generated at the time a prometheus server sends scrape request. # this behavior allows the processors (if defined) to be run on all the generated events at once. # this mode uses more resource compared to the default one, but offers more flexibility when it comes # to manipulating the data to customize the returned metrics using event-processors. cache : # duration, scrape request timeout. # this timer is started when a scrape request is received, # if it is reached, the metrics generation/collection is stopped. timeout : 10s # enable debug for prometheus output debug : false # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # list of processors to apply on the message before writing event-processors : # an integer, sets the number of worker handling messages to be converted into Prometheus metrics num-workers : 1 # Enables Consul service registration service-registration : # Consul server address, default to localhost:8500 address : # Consul Data center, defaults to dc1 datacenter : # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token which overrides the agent's default token token : # address and port number to be registered as a service address in Consul. # if the field is empty the address is derived from the listen field. # if the address does not contain a port number, the port number fmro the listen field is used. service-address : # Prometheus service check interval, for both http and TTL Consul checks, # defaults to 5s check-interval : # Maximum number of failed checks before the service is deleted by Consul # defaults to 3 max-fail : # Consul service name name : # List of tags to be added to the service registration, # if available, the instance-name and cluster-name will be added as tags, # in the format: gnmic-instance=$instance-name and gnmic-cluster=$cluster-name tags : # bool, enables http service check on top of the TTL check enable-http-check : # string, if enable-http-check is true, this field can be used to specify the http endpoint to be used to the check # if provided, this filed with be prepended with 'http://' (if not already present), # and appended with the value in 'path' field. # if not specified, it will be derived from the fields 'listen' and 'path' http-check-address : # if set to true, the gnmic instance will try to ac quire a lock before registering the prometheus output in consul. # this allows to register a single instance of the cluster in consul. # if the instance which acquired the lock fails, one of the remaining ones will take over. use-lock : false","title":"Introduction"},{"location":"user_guide/outputs/prometheus_output/#fields-definition","text":"","title":"Fields definition"},{"location":"user_guide/outputs/prometheus_output/#type","text":"The output type, prometheus in this case.","title":"type"},{"location":"user_guide/outputs/prometheus_output/#listen","text":"Address to listen on for incoming scrape requests, defaults to :9804","title":"listen"},{"location":"user_guide/outputs/prometheus_output/#path","text":"URL Path to query in order to retrieve the metrics, defaults to /metrics","title":"path"},{"location":"user_guide/outputs/prometheus_output/#expiration","text":"Maximum lifetime of metrics in the local cache, A zero value defaults to 60s, a negative duration (e.g: -1s) disables the expiration","title":"expiration"},{"location":"user_guide/outputs/prometheus_output/#metric-prefix","text":"A string to be used as the metric namespace","title":"metric-prefix"},{"location":"user_guide/outputs/prometheus_output/#append-subscription-name","text":"A boolean, if true the subscription name will be appended to the metric name after the prefix","title":"append-subscription-name"},{"location":"user_guide/outputs/prometheus_output/#override-timestamps","text":"A boolean, if true the message timestamp is changed to current time","title":"override-timestamps"},{"location":"user_guide/outputs/prometheus_output/#export-timestamps","text":"A boolean, enables exporting timestamps received from the gNMI target as part of the metrics","title":"export-timestamps"},{"location":"user_guide/outputs/prometheus_output/#strings-as-labels","text":"A boolean, enables setting string type values as prometheus metric labels.","title":"strings-as-labels"},{"location":"user_guide/outputs/prometheus_output/#tls","text":"","title":"tls"},{"location":"user_guide/outputs/prometheus_output/#ca-file","text":"A string, path to the CA certificate file. This certificate is used to verify the clients certificates.","title":"ca-file"},{"location":"user_guide/outputs/prometheus_output/#cert-file","text":"A string, path to server certificate file.","title":"cert-file"},{"location":"user_guide/outputs/prometheus_output/#key-file","text":"A string, server key file.","title":"key-file"},{"location":"user_guide/outputs/prometheus_output/#client-auth","text":"A string, use to control whether the server requests a client certificate or not and how it validates it. One of: \"\": The server does not request a certificate from the client. \"request\": The server requests a certificate from the client but does not require the client to send a certificate. If the client sends a certificate, it is not required to be valid. \"require\": The server requires the client to send a certificate and does not fail if the client certificate is not valid. \"verify-if-given\": The server requests a certificate, does not fail if no certificate is sent. If a certificate is sent it is required to be valid. \"require-verify\": The server requires the client to send a valid certificate. If the ca-file is not provided, the default value for client-auth is an empty string (\"\"). However, if a ca-file is specified, the default value for client-auth becomes \"require-verify\".","title":"client-auth"},{"location":"user_guide/outputs/prometheus_output/#cache","text":"Refer to the cache docs for more information. When enabled, gNMI notifications are stored in a cache upon receipt. Prometheus metrics are subsequently generated when a Prometheus system sends a scrape request. This approach allows processors (if defined) to operate on all generated events simultaneously. While this mode consumes more resources compared to the default, it provides increased flexibility for data manipulation and metric customization through the use of event-processors.","title":"cache"},{"location":"user_guide/outputs/prometheus_output/#timeout","text":"A Duration such as 10s, 1m or 1m30s, defines the scrape request timeout. This timer is started when a scrape request is received from a Prometheus system. If the timer is is reached, the metrics generation/collection is stopped.","title":"timeout"},{"location":"user_guide/outputs/prometheus_output/#debug","text":"A boolean. Enables debug for prometheus output","title":"debug"},{"location":"user_guide/outputs/prometheus_output/#add-target","text":"A string, one of overwrite , if-not-present or ``. This field allows populating/changing the value of Prefix.Target in the received message. If left empty (\"\"), no changes will be made. If set to \"overwrite\", the target value will be replaced with the configuration specified under target-template. If set to \"if-not-present\", the target value will be populated only if it is empty, utilizing the target-template.","title":"add-target"},{"location":"user_guide/outputs/prometheus_output/#target-template","text":"A string, a GoTemplate that allow for the customization of the target field in Prefix.Target . It applies only if the previous field add-target is not empty. If left target-template is left empty, it defaults to: {{- if index . \"subscription-target\" -}} {{ index . \"subscription-target\" }} {{- else -}} {{ index . \"source\" | host }} {{- end -}} The above template sets the target to the value configured under subscription.$subscription-name.target if any, otherwise it will set it to the target name stripped of the port number (if present)","title":"target-template"},{"location":"user_guide/outputs/prometheus_output/#event-processors","text":"A string list. List of processors to apply on the message before writing","title":"event-processors"},{"location":"user_guide/outputs/prometheus_output/#service-registration","text":"Enables Consul service registration","title":"service-registration"},{"location":"user_guide/outputs/prometheus_output/#address","text":"Consul server address, default to localhost:8500","title":"address"},{"location":"user_guide/outputs/prometheus_output/#datacenter","text":"Consul Data center, defaults to dc1","title":"datacenter"},{"location":"user_guide/outputs/prometheus_output/#username","text":"Consul username, to be used as part of HTTP basicAuth","title":"username"},{"location":"user_guide/outputs/prometheus_output/#password","text":"Consul password, to be used as part of HTTP basicAuth","title":"password"},{"location":"user_guide/outputs/prometheus_output/#token","text":"Consul Token, is used to provide a per-request ACL token which overrides the agent's default token","title":"token"},{"location":"user_guide/outputs/prometheus_output/#service-address","text":"Address and port number to be registered as a service address in Consul. if the field is empty the address is derived from the listen field. if the address does not contain a port number, the port number from the listen field is used.","title":"service-address"},{"location":"user_guide/outputs/prometheus_output/#check-interval","text":"Prometheus service check interval, for both http and TTL Consul checks, defaults to 5s","title":"check-interval"},{"location":"user_guide/outputs/prometheus_output/#max-fail","text":"Maximum number of failed checks before the service is deleted by Consul defaults to 3","title":"max-fail"},{"location":"user_guide/outputs/prometheus_output/#name","text":"Consul service name","title":"name"},{"location":"user_guide/outputs/prometheus_output/#tags","text":"List of tags to be added to the service registration, if available, the instance-name and cluster-name will be added as tags, in the format: gnmic-instance= instance-name and gnmic-cluster= instance-name and gnmic-cluster= cluster-name","title":"tags"},{"location":"user_guide/outputs/prometheus_output/#enable-http-check","text":"A boolean, enables http service check on top of the TTL check","title":"enable-http-check"},{"location":"user_guide/outputs/prometheus_output/#http-check-address","text":"A string, if enable-http-check is true, this field can be used to specify the http endpoint to be used to the check if provided, this filed with be prepended with 'http://' (if not already present), and appended with the value in 'path' field. if not specified, it will be derived from the fields 'listen' and 'path'","title":"http-check-address"},{"location":"user_guide/outputs/prometheus_output/#use-lock","text":"A boolean, if set to true, the gnmic instance will try to acquire a lock before registering the prometheus output. This knob allows to register a single instance of the cluster in Consul. if the instance which acquired the lock fails, one of the remaining ones takes over by acquiring the lost lock.","title":"use-lock"},{"location":"user_guide/outputs/prometheus_output/#metric-generation","text":"The below diagram shows an example of a prometheus metric generation from a gnmi update","title":"Metric Generation"},{"location":"user_guide/outputs/prometheus_output/#metric-naming","text":"The metric name starts with the string configured under metric-prefix . Then if append-subscription-name is true , the subscription-name as specified in gnmic configuration file is appended. The resulting string is followed by the gNMI path stripped of its keys if there are any. All non-alphanumeric characters are replaced with an underscore \" _ \" The 3 strings are then joined with an underscore \" _ \" If further customization of the metric name is required, the processors can be used to transform the metric name. For example, a gNMI update from subscription port-stats with path: /interfaces/interface [ name = 1 /1/1 ] /subinterfaces/subinterface [ index = 0 ] /state/counters/in-octets is exposed as a metric named: gnmic_port_stats_interfaces_interface_subinterfaces_subinterface_state_counters_in_octets","title":"Metric Naming"},{"location":"user_guide/outputs/prometheus_output/#metric-labels","text":"The metrics labels are generated from the subscription metadata (e.g: subscription-name and source ) and the keys present in the gNMI path elements. For the previous example the labels would be: { interface_name = \"1/1/1\" ,subinterface_index = 0 ,source = \" $routerIP :Port\" ,subscription_name = \"port-stats\" }","title":"Metric Labels"},{"location":"user_guide/outputs/prometheus_output/#service-registration_1","text":"gnmic supports prometheus_output service registration via Consul . It allows prometheus to dynamically discover new instances of gnmic exposing a prometheus server ready for scraping via its service discovery feature . If the configuration section service-registration is present under the output definition, gnmic will register the prometheus_output service in Consul .","title":"Service Registration"},{"location":"user_guide/outputs/prometheus_output/#configuration-example","text":"The below configuration, registers a service name gnmic-prom-srv with IP=10.1.1.1 and port=9804 # gnmic.yaml outputs : output1 : type : prometheus listen : 10.1.1.1:9804 path : /metrics service-registration : address : consul-agent.local:8500 name : gnmic-prom-srv This allows running multiple instances of gnmic with minimal configuration changes by using prometheus service discovery feature . Simplified scrape configuration in the prometheus client: # prometheus.yaml scrape_configs : - job_name : 'gnmic' scrape_interval : 10s consul_sd_configs : - server : $CONSUL_ADDRESS services : - $service_name","title":"Configuration Example"},{"location":"user_guide/outputs/prometheus_output/#service-name-and-id","text":"The $service_name to be discovered by prometheus is configured under outputs.$output_name.service-registration.name . If the service registration name field is not present, the name prometheus-${output_name} will be used. In both cases the service ID will be prometheus-${output_name}-${instance_name} .","title":"Service Name and ID"},{"location":"user_guide/outputs/prometheus_output/#service-checks","text":"gnmic registers the service in Consul with a ttl check enabled by default: ttl : gnmic periodically updates the service definition in Consul . The goal is to allow Consul to detect a same instance restarting with a different service name. If service-registration.enable-http-check is true , an http check is added: http : Consul periodically scrapes the prometheus server endpoint to check its availability. # gnmic.yaml outputs : output1 : type : prometheus listen : 10.1.1.1:9804 path : /metrics service-registration : address : consul-agent.local:8500 name : gnmic-prom-srv enable-http-check : true Note that for the http check to work properly, a reachable address ( IP or name ) should be specified under listen . Otherwise, a reachable address should be added under service-registration.http-check-address .","title":"Service Checks"},{"location":"user_guide/outputs/prometheus_output/#caching","text":"When caching is enabled, the received messages are not immediately converted into metrics, they are cached as gNMI updates. The conversion from gNMI update to Prometheus metrics happens only when a scrape request is received. The below diagram shows how a prometheus output works with and without cache enabled: When caching is enabled, the received gNMI updates are not processed and converted into metrics immediately, they are rather stored as is in the configured gNMI cache. Once a scrape request is received from Prometheus , all the cached gNMI updates are retrieved from the cache, converted to events , the configured processors, if any, are then applied to the whole list of events. Finally, The resulting event are converted into metrics and written back to Prometheus within the scrape response.","title":"Caching"},{"location":"user_guide/outputs/prometheus_output/#prometheus-output-metrics","text":"When a Prometheus server (gNMI API) is enabled, gnmic prometheus output exposes 2 prometheus Gauges: number_of_prometheus_metrics_total : Number of metrics stored by the prometheus output. number_of_prometheus_cached_metrics_total : Number of metrics cached by the prometheus output.","title":"Prometheus Output Metrics"},{"location":"user_guide/outputs/prometheus_output/#examples","text":"","title":"Examples"},{"location":"user_guide/outputs/prometheus_output/#a-simple-prometheus-output","text":"A basic Prometheus output utilizing all default values converts each received gNMI update into a Prometheus metric, retaining it in the cache until a scrape request is received from a Prometheus system. outputs : simple-prom : type : prometheus","title":"A simple Prometheus output"},{"location":"user_guide/outputs/prometheus_output/#promote-string-values-to-labels","text":"A straightforward Prometheus output, utilizing default values for the most part, transforms each incoming gNMI update into a Prometheus metric. In this process, if a value is a string, it is incorporated as a label in the final metric. These metrics are retained in the cache, awaiting a scrape request from a Prometheus system. outputs : simple-prom : type : prometheus strings-as-labels : true","title":"Promote string values to labels"},{"location":"user_guide/outputs/prometheus_output/#use-a-gnmi-cache","text":"A Prometheus output leveraging a gNMI cache stores incoming gNMI updates in their original form, only converting them into Prometheus metrics upon receiving a scrape request from a Prometheus system. This mode enables batch processing of all updates simultaneously during their conversion into Prometheus metrics. outputs : simple-prom : type : prometheus cache : {}","title":"Use a gNMI cache"},{"location":"user_guide/outputs/prometheus_output/#register-as-a-consul-service","text":"A Prometheus output that dynamically registers its endpoint within Consul, enabling the Prometheus system to seamlessly discover the associated address and port number. outputs : simple-prom : type : prometheus service-registration : address : consul-server-address:8500","title":"****Register as a Consul service****"},{"location":"user_guide/outputs/prometheus_write_output/","text":"gnmic supports writing metrics to Prometheus using its remote write API . gNMIc 's prometheus remote write can be used to push metrics to a variety of monitoring systems like Prometheus , Mimir , CortexMetrics , VictoriaMetrics , Thanos ... A Prometheus write output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : prometheus_write # url to push metrics towards, scheme is required url : http://:9009/api/v1/push # a map of string:string, # custom HTTP headers to be sent along with each remote write request. headers : # header: value # sets the `Authorization` header on every remote write request with the # configured username and password. authentication : username : password : # sets the `Authorization` header with type `.authorization.type` and the token value. authorization : type : Bearer credentials : # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # duration, defaults to 10s, time interval between write requests interval : 10s # integer, defaults to 1000. # Buffer size for time series to be sent to the remote system. # metrics are sent to the remote system every `.interval` or when the buffer is full. Whichever one is reached first. buffer-size : 1000 # integer, defaults to 500, sets the maximum number of timeSeries per write request to remote. max-time-series-per-write : 500 # integer, defaults to 0 # number of retries per write, retries will have a back off of 100ms. max-retries : 0 # metadata configuration metadata : # boolean, # if true, metrics metadata is sent. include : false # duration, defaults to 60s. # Applies if `metadata.include` is set to true # Interval after which all metadata entries are sent to the remote write address interval : 60s # integer, defaults to 500 # applies if `metadata.include` is set to true # Max number of metadata entries per write request. max-entries-per-write : 500 # string, to be used as the metric namespace metric-prefix : \"\" # boolean, if true the subscription name will be appended to the metric name after the prefix append-subscription-name : false # boolean, enables setting string type values as prometheus metric labels. strings-as-labels : false # duration, defaults to 10s # Push request timeout. timeout : 10s # boolean, defaults to false # Enables debug for prometheus write output. debug : false # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # list of processors to apply on the message before writing event-processors : # an integer, sets the number of worker handling messages to be converted into Prometheus metrics num-workers : 1 # an integer, sets the number of writers draining the buffer and writing to Prometheus num-writers : 1 gnmic creates the prometheus metric name and its labels from the subscription name, the gnmic path and the value name. Metric Generation # The below diagram shows an example of a prometheus metric generation from a gnmi update Metric Naming # The metric name starts with the string configured under metric-prefix . Then if append-subscription-name is true , the subscription-name as specified in gnmic configuration file is appended. The resulting string is followed by the gNMI path stripped of its keys if there are any. All non-alphanumeric characters are replaced with an underscore \" _ \" The 3 strings are then joined with an underscore \" _ \" If further customization of the metric name is required, the processors can be used to transform the metric name. For example, a gNMI update from subscription port-stats with path: /interfaces/interface [ name = 1 /1/1 ] /subinterfaces/subinterface [ index = 0 ] /state/counters/in-octets is exposed as a metric named: gnmic_port_stats_interfaces_interface_subinterfaces_subinterface_state_counters_in_octets Metric Labels # The metrics labels are generated from the subscription metadata (e.g: subscription-name and source ) and the keys present in the gNMI path elements. For the previous example the labels would be: { interface_name = \"1/1/1\" ,subinterface_index = 0 ,source = \" $routerIP :Port\" ,subscription_name = \"port-stats\" } Prometheus Write Metrics # When a Prometheus server (gNMI API) is enabled, gnmic prometheus write output exposes 4 prometheus counters and 2 prometheus Gauges: number_of_prometheus_write_msgs_sent_success_total : Number of msgs successfully sent by gnmic prometheus_write output. number_of_prometheus_write_msgs_sent_fail_total : Number of failed msgs sent by gnmic prometheus_write output. msg_send_duration_ns : gnmic prometheus_write output send duration in ns. number_of_prometheus_write_metadata_msgs_sent_success_total : Number of metadata msgs successfully sent by gnmic prometheus_write output. number_of_prometheus_write_metadata_msgs_sent_fail_total : Number of failed metadata msgs sent by gnmic prometheus_write output. metadata_msg_send_duration_ns : gnmic prometheus_write output metadata send duration in ns.","title":"Remote Write (Push)"},{"location":"user_guide/outputs/prometheus_write_output/#metric-generation","text":"The below diagram shows an example of a prometheus metric generation from a gnmi update","title":"Metric Generation"},{"location":"user_guide/outputs/prometheus_write_output/#metric-naming","text":"The metric name starts with the string configured under metric-prefix . Then if append-subscription-name is true , the subscription-name as specified in gnmic configuration file is appended. The resulting string is followed by the gNMI path stripped of its keys if there are any. All non-alphanumeric characters are replaced with an underscore \" _ \" The 3 strings are then joined with an underscore \" _ \" If further customization of the metric name is required, the processors can be used to transform the metric name. For example, a gNMI update from subscription port-stats with path: /interfaces/interface [ name = 1 /1/1 ] /subinterfaces/subinterface [ index = 0 ] /state/counters/in-octets is exposed as a metric named: gnmic_port_stats_interfaces_interface_subinterfaces_subinterface_state_counters_in_octets","title":"Metric Naming"},{"location":"user_guide/outputs/prometheus_write_output/#metric-labels","text":"The metrics labels are generated from the subscription metadata (e.g: subscription-name and source ) and the keys present in the gNMI path elements. For the previous example the labels would be: { interface_name = \"1/1/1\" ,subinterface_index = 0 ,source = \" $routerIP :Port\" ,subscription_name = \"port-stats\" }","title":"Metric Labels"},{"location":"user_guide/outputs/prometheus_write_output/#prometheus-write-metrics","text":"When a Prometheus server (gNMI API) is enabled, gnmic prometheus write output exposes 4 prometheus counters and 2 prometheus Gauges: number_of_prometheus_write_msgs_sent_success_total : Number of msgs successfully sent by gnmic prometheus_write output. number_of_prometheus_write_msgs_sent_fail_total : Number of failed msgs sent by gnmic prometheus_write output. msg_send_duration_ns : gnmic prometheus_write output send duration in ns. number_of_prometheus_write_metadata_msgs_sent_success_total : Number of metadata msgs successfully sent by gnmic prometheus_write output. number_of_prometheus_write_metadata_msgs_sent_fail_total : Number of failed metadata msgs sent by gnmic prometheus_write output. metadata_msg_send_duration_ns : gnmic prometheus_write output metadata send duration in ns.","title":"Prometheus Write Metrics"},{"location":"user_guide/outputs/snmp_output/","text":"gnmic supports generating SNMP traps based on received gNMI updates. This output type is useful when trying to integrate legacy systems that ingest SNMP traps with more modern telemetry/alarms stacks. Only SNMPv2c is supported. Configuration # The SNMP output can be defined using the below format in gnmic config file under outputs section: outputs : # the output name snmp_trap : # the output type type : snmp # the traps destination address address : # the trap destination port, defaults to 162 port : 162 # the SNMP trap community community : public # duration, wait time before the first trap evaluation. # defaults to 5s and minimum allowed value is 5s. start-delay : 5s # traps definition traps : # if true, the SNMP message generated is an inform request, not a trap. - inform : false # trap trigger definition, # the trigger section of the trap defines which received path trigger the trap # as well as the variable binding to append to it. trigger : # xpath, if present in the received event message, the trap is triggered path : # a jq script that is executed with the trigger event message as input. # must return a valid OID. oid : # a static string, defining the type of the OID value, # one of: bool, int, bitString, octetString, null, objectID, objectDescription, # ipAddress, counter32, gauge32, timeTicks, opaque, nsapAddress, counter64, # uint32, opaqueFloat, opaqueDouble type : # a jq script that is executed with the trigger event message as input. # must return a value matching the above configured type. value : # trap variable bindings definition, # the bindings section defines the extra variable bindings to append to the trap. # multiple bindings can be defined here. bindings : # A jq script that is executed with the trigger message as input. # Must return a valid xpath. # The local cache is queried using the resulting xpath, the resulting event message is used # as input to execute the below oid and value jq scripts - path : # A jq script that is executed with the message obtained from the cache as input. # must return a valid OID. oid : # a static string, defining the type of the OID value, # one of: bool, int, bitString, octetString, null, objectID, objectDescription, # ipAddress, counter32, gauge32, timeTicks, opaque, nsapAddress, counter64, # uint32, opaqueFloat, opaqueDouble type : # A jq script that is executed with the message obtained from the cache as input. # must return a value matching the above configured type. value : How does it work? # The SNMP output stores each received update message in a local cache (1.a), then checks if the message should trigger any of the configured traps (1.b). If the received message triggers a trap (2), an SNMP variable binding is generated from the trap trigger configuration section ( OID , type and value ) based on the triggering event. The OID and value can be jq scripts. Then (3) for each configured binding, the configured path ( jq script) is rendered based on the triggering event then used to retrieve an event message from the cache, that message is then used to generate the variable binding ( OID , type and value ). Once all bindings are generated, a sysUpTimeInstance (OID= 1.3.6.1.2.1.1.3.0 ) binding is prepended to the PDU list of the trap, its value is the number of seconds since gNMIc SNMP output startup. Metrics # The SNMP output exposes 4 Prometheus metrics: Number of failed trap generation Number of SNMP trap sending failures SNMP trap generation duration in ns gnmic_snmp_output_number_of_snmp_trap_failed_generation{name=\"snmp_trap\",reason=\"\",trap_index=\"0\"} 0 gnmic_snmp_output_number_of_snmp_trap_sent_fail_total{name=\"snmp_trap\",reason=\"\",trap_index=\"0\"} 0 gnmic_snmp_output_number_of_snmp_traps_sent_total{name=\"snmp_trap\",trap_index=\"0\"} 114 gnmic_snmp_output_snmp_trap_generation_duration_ns{name=\"snmp_trap\",trap_index=\"0\"} 380215 Examples # interface operational state trap # The below example generates an SNMPV2 trap whenever the operational state of an interface changes ( ifOperStatus ). It adds sysName , ifAdminStatus and ifIndex variable bindings to the trap before sending it out. username : admin password : NokiaSrl1! skip-verify : true targets : clab-snmp-srl1 : clab-snmp-srl2 : subscriptions : sub1 : paths : - /interface/admin-state - /interface/oper-state - /interface/ifindex - /system/name/host-name stream-mode : on-change encoding : ascii outputs : snmp_trap : type : snmp address : snmptrap.server # port: 162 # community: public traps : - trigger : path : /interface/oper-state # static path oid : '\".1.3.6.1.2.1.2.2.1.8\"' # ifOperStatus type : int value : if (.values.\"/interface/oper-state\" == \"up\") then 1 else 2 end bindings : - path : '\"/system/name/host-name\"' # jq script oid : '\".1.3.6.1.2.1.1.5\"' # sysName type : octetString value : '.values.\"/system/name/host-name\"' - path : '\"/interface[name=\"+.tags.interface_name+\"]/admin-state\"' # jq script oid : '\".1.3.6.1.2.1.2.2.1.7\"' # ifAdminStatus type : int value : if (.values.\"/interface/admin-state\" == \"enable\") then 1 else 2 end - path : '\"/interface[name=\"+.tags.interface_name+\"]/ifindex\"' # jq script oid : '\".1.3.6.1.2.1.2.2.1.1\"' # ifIndex type : int value : '.values.\"/interface/ifindex\" | tonumber' # jq script","title":"SNMP"},{"location":"user_guide/outputs/snmp_output/#configuration","text":"The SNMP output can be defined using the below format in gnmic config file under outputs section: outputs : # the output name snmp_trap : # the output type type : snmp # the traps destination address address : # the trap destination port, defaults to 162 port : 162 # the SNMP trap community community : public # duration, wait time before the first trap evaluation. # defaults to 5s and minimum allowed value is 5s. start-delay : 5s # traps definition traps : # if true, the SNMP message generated is an inform request, not a trap. - inform : false # trap trigger definition, # the trigger section of the trap defines which received path trigger the trap # as well as the variable binding to append to it. trigger : # xpath, if present in the received event message, the trap is triggered path : # a jq script that is executed with the trigger event message as input. # must return a valid OID. oid : # a static string, defining the type of the OID value, # one of: bool, int, bitString, octetString, null, objectID, objectDescription, # ipAddress, counter32, gauge32, timeTicks, opaque, nsapAddress, counter64, # uint32, opaqueFloat, opaqueDouble type : # a jq script that is executed with the trigger event message as input. # must return a value matching the above configured type. value : # trap variable bindings definition, # the bindings section defines the extra variable bindings to append to the trap. # multiple bindings can be defined here. bindings : # A jq script that is executed with the trigger message as input. # Must return a valid xpath. # The local cache is queried using the resulting xpath, the resulting event message is used # as input to execute the below oid and value jq scripts - path : # A jq script that is executed with the message obtained from the cache as input. # must return a valid OID. oid : # a static string, defining the type of the OID value, # one of: bool, int, bitString, octetString, null, objectID, objectDescription, # ipAddress, counter32, gauge32, timeTicks, opaque, nsapAddress, counter64, # uint32, opaqueFloat, opaqueDouble type : # A jq script that is executed with the message obtained from the cache as input. # must return a value matching the above configured type. value :","title":"Configuration"},{"location":"user_guide/outputs/snmp_output/#how-does-it-work","text":"The SNMP output stores each received update message in a local cache (1.a), then checks if the message should trigger any of the configured traps (1.b). If the received message triggers a trap (2), an SNMP variable binding is generated from the trap trigger configuration section ( OID , type and value ) based on the triggering event. The OID and value can be jq scripts. Then (3) for each configured binding, the configured path ( jq script) is rendered based on the triggering event then used to retrieve an event message from the cache, that message is then used to generate the variable binding ( OID , type and value ). Once all bindings are generated, a sysUpTimeInstance (OID= 1.3.6.1.2.1.1.3.0 ) binding is prepended to the PDU list of the trap, its value is the number of seconds since gNMIc SNMP output startup.","title":"How does it work?"},{"location":"user_guide/outputs/snmp_output/#metrics","text":"The SNMP output exposes 4 Prometheus metrics: Number of failed trap generation Number of SNMP trap sending failures SNMP trap generation duration in ns gnmic_snmp_output_number_of_snmp_trap_failed_generation{name=\"snmp_trap\",reason=\"\",trap_index=\"0\"} 0 gnmic_snmp_output_number_of_snmp_trap_sent_fail_total{name=\"snmp_trap\",reason=\"\",trap_index=\"0\"} 0 gnmic_snmp_output_number_of_snmp_traps_sent_total{name=\"snmp_trap\",trap_index=\"0\"} 114 gnmic_snmp_output_snmp_trap_generation_duration_ns{name=\"snmp_trap\",trap_index=\"0\"} 380215","title":"Metrics"},{"location":"user_guide/outputs/snmp_output/#examples","text":"","title":"Examples"},{"location":"user_guide/outputs/snmp_output/#interface-operational-state-trap","text":"The below example generates an SNMPV2 trap whenever the operational state of an interface changes ( ifOperStatus ). It adds sysName , ifAdminStatus and ifIndex variable bindings to the trap before sending it out. username : admin password : NokiaSrl1! skip-verify : true targets : clab-snmp-srl1 : clab-snmp-srl2 : subscriptions : sub1 : paths : - /interface/admin-state - /interface/oper-state - /interface/ifindex - /system/name/host-name stream-mode : on-change encoding : ascii outputs : snmp_trap : type : snmp address : snmptrap.server # port: 162 # community: public traps : - trigger : path : /interface/oper-state # static path oid : '\".1.3.6.1.2.1.2.2.1.8\"' # ifOperStatus type : int value : if (.values.\"/interface/oper-state\" == \"up\") then 1 else 2 end bindings : - path : '\"/system/name/host-name\"' # jq script oid : '\".1.3.6.1.2.1.1.5\"' # sysName type : octetString value : '.values.\"/system/name/host-name\"' - path : '\"/interface[name=\"+.tags.interface_name+\"]/admin-state\"' # jq script oid : '\".1.3.6.1.2.1.2.2.1.7\"' # ifAdminStatus type : int value : if (.values.\"/interface/admin-state\" == \"enable\") then 1 else 2 end - path : '\"/interface[name=\"+.tags.interface_name+\"]/ifindex\"' # jq script oid : '\".1.3.6.1.2.1.2.2.1.1\"' # ifIndex type : int value : '.values.\"/interface/ifindex\" | tonumber' # jq script","title":"interface operational state trap"},{"location":"user_guide/outputs/stan_output/","text":"gnmic supports exporting subscription updates to multiple NATS Streaming (STAN) servers/clusters simultaneously A STAN output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : type : stan # required # comma separated STAN servers address : localhost:4222 # stan subject subject : telemetry # stan subject prefix, the subject prefix is built the same way as for NATS output subject-prefix : telemetry # STAN username username : # STAN password password : # STAN publisher name # if left empty, this field is populated with the output name used as output ID (output1 in this example). # the full name will be '$(name)-stan-pub'. # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name)-stan-pub. # note that each stan worker (publisher) will get client name=$name-$index name : \"\" # cluster name, mandatory cluster-name : test-cluster # STAN ping interval ping-interval : 5 # STAN ping retry ping-retry : 2 # string, message marshaling format, one of: proto, prototext, protojson, json, event format : event # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, if true the message timestamp is changed to current time override-timestamps : false # duration to wait before re establishing a lost connection to a stan server recovery-wait-time : 2s # integer, number of stan publishers to be created num-workers : 1 # boolean, enables extra logging for the STAN output debug : false # duration after which a message waiting to be handled by a worker gets discarded write-timeout : 10s # boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : Using subject config value a user can specify the STAN subject to which to send all subscriptions updates for all targets If a user wants to separate updates by targets and by subscriptions, subject-prefix can be used. if subject-prefix is specified subject is ignored.","title":"STAN"},{"location":"user_guide/outputs/tcp_output/","text":"gnmic supports exporting subscription updates to a TCP server A TCP output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : tcp # a UDP server address address : IPAddress:Port # maximum sending rate, e.g: 1ns, 10ms rate : 10ms # number of messages to buffer in case of sending failure buffer-size : # export format. json, protobuf, prototext, protojson, event format : json # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # boolean, if true the message timestamp is changed to current time override-timestamps : false # string, a delimiter to be sent after each message. # useful when writing to logstash TCP input. delimiter : # enable TCP keepalive and specify the timer, e.g: 1s, 30s keep-alive : # time duration to wait before re-dial in case there is a failure retry-interval : # NOT IMPLEMENTED boolean, enables the collection and export (via prometheus) of output specific metricss enable-metrics : false # list of processors to apply on the message before writing event-processors : A TCP output can be used to export data to an ELK stack, using Logstash TCP input","title":"TCP"},{"location":"user_guide/outputs/udp_output/","text":"gnmic supports exporting subscription updates to a UDP server A UDP output can be defined using the below format in gnmic config file under outputs section: outputs : output1 : # required type : udp # a UDP server address address : IPAddress:Port # maximum sending rate, e.g: 1ns, 10ms rate : 10ms # number of messages to buffer in case of sending failure buffer-size : # export format. json, protobuf, prototext, protojson, event format : json # string, one of `overwrite`, `if-not-present`, `` # This field allows populating/changing the value of Prefix.Target in the received message. # if set to ``, nothing changes # if set to `overwrite`, the target value is overwritten using the template configured under `target-template` # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template` add-target : # string, a GoTemplate that allow for the customization of the target field in Prefix.Target. # it applies only if the previous field `add-target` is not empty. # if left empty, it defaults to: # {{- if index . \"subscription-target\" -}} # {{ index . \"subscription-target\" }} # {{- else -}} # {{ index . \"source\" | host }} # {{- end -}}` # which will set the target to the value configured under `subscription.$subscription-name.target` if any, # otherwise it will set it to the target name stripped of the port number (if present) target-template : # boolean, valid only if format is `event`. # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts. split-events : false # boolean, if true the message timestamp is changed to current time override-timestamps : false # time duration to wait before re-dial in case there is a failure retry-interval : # NOT IMPLEMENTED boolean, enables the collection and export (via prometheus) of output specific metrics enable-metrics : false # list of processors to apply on the message before writing event-processors : A UDP output can be used to export data to an ELK stack, using Logstash UDP input","title":"UDP"},{"location":"user_guide/targets/targets/","text":"Targets # Sometimes it is needed to perform an operation on multiple devices; be it getting the same leaf value from a given set of the network elements or setting a certain configuration element to some value. For cases like that gnmic offers support for multiple targets operations which a user can configure both via CLI flags as well as with the file-based configuration . CLI configuration # Specifying multiple targets in the CLI is as easy as repeating the --address flag. \u276f gnmic -a router1.lab.net:57400 \\ -a router2.lab.net:57400 \\ get --path /configure/system/name File-based configuration # With the file-based configuration a user has two options to specify multiple targets: using address option using targets option address option # With address option the user must provide a list of addresses. In the YAML format that would look like that: address : - \"router1.lab.net:57400\" - \"router2.lab.net:57400\" The limitation this approach has is that it is impossible to set different credentials for the targets, they will essentially share the credentials specified in a file or via flags. target option # With the targets option it is possible to set target specific options (such as credentials, subscriptions, TLS config, outputs), and thus this option is recommended to use: targets : router1.lab.net : timeout : 2s username : r1 password : gnmi_pass router2.lab.net:57000 : username : r2 password : gnmi_pass tls-key : /path/file1 tls-cert : /path/file2 The target address is defined as the key under the targets section of the configuration file. The default port (57400) can be omitted as demonstrated with router1.lab.net target address. Have a look at the file-based targets configuration example to get a glimpse of what it is capable of. The target inherits the globally defined options if the matching options are not set on a target level. For example, if a target doesn't have a username defined, it will use the username value set on a global level. secure/insecure connections # gnmic supports both secure and insecure gRPC connections to the target. insecure connection # Using the --insecure flag it is possible to establish an insecure gRPC connection to the target. gnmic -a router1:57400 \\ --insecure \\ get --path /configure/system/name secure connection # A one way secure connection without target certificate verification can be established using the --skip-verify flag. gnmic -a router1:57400 \\ --skip-verify \\ get --path /configure/system/name Adding target certificate verification can be done using the --tls-ca flag. gnmic -a router1:57400 \\ --tls-ca /path/to/ca/file \\ get --path /configure/system/name A two way secure connection can be established using the --tls-cert --tls-key flags. gnmic -a router1:57400 \\ --tls-cert /path/to/certificate/file \\ --tls-key /path/to/certificate/file \\ get --path /configure/system/name It is also possible to control the negotiated TLS version using the --tls-min-version , --tls-max-version and --tls-version (preferred TLS version) flags. target configuration options # Target supported options: targets : # target name or an address (IP or DNS name). # if an address is set it can include a port number or not, # if a port is not included, the default gRPC port will be added. target_key : # target name, will default to the target_key if not specified name : target_key # target address, if missing the target_key is used as an address. # supports comma separated addresses. # if any of the addresses is missing a port, the default gRPC port will be added. # if multiple addresses are set, all of them will be tried simultaneously, # the first established gRPC connection will be used, the other attempts will be canceled. address : # target username username : # target password password : # authentication token, # applied only in the case of a secure gRPC connection. token : # target RPC timeout timeout : # establish an insecure connection insecure : # path to tls ca file tls-ca : # path to tls certificate tls-cert : # path to tls key tls-key : # max tls version to use during negotiation tls-max-version : # min tls version to use during negotiation tls-min-version : # preferred tls version to use during negotiation tls-version : # enable logging of a pre-master TLS secret log-tls-secret : # do not verify the target certificate when using tls skip-verify : # server name used to verify the hostname on the returned # certificates unless skip-verify is true. tls-server-name : # list of subscription names to establish for this target. # if empty it defaults to all subscriptions defined under # the main level `subscriptions` field subscriptions : # string, case insensitive, defines the gNMI encoding to be used for # the subscriptions to be established for this target. # This encoding value applies only if the subscription configuration does # NOT explicitly define an encoding. encoding : # list of output names to which the gnmi data will be written. # if empty if defaults to all outputs defined under # the main level `outputs` field outputs : # number of subscribe responses to keep in buffer before writing # the target outputs buffer-size : # target retry period retry : # list of tags, relevant when clustering is enabled. tags : # a mapping of static tags to add to all events from this target. # each key/value pair in this mapping will be added to metadata # on all events event-tags : # list of proto file names to decode protoBytes values proto-files : # list of directories to look for the proto files proto-dirs : # enable grpc gzip compression gzip : # proxy type and address, only SOCKS5 is supported currently # example: socks5://
    : proxy : Example # Whatever configuration option you choose, the multi-targeted operations will uniformly work across the commands that support them. Consider the get command acting on two routers getting their names: \u276f gnmic -a router1.lab.net:57400 \\ -a router2.lab.net:57400 \\ get --path /configure/system/name [ router1.lab.net:57400 ] { [ router1.lab.net:57400 ] \"source\" : \"router1.lab.net:57400\" , [ router1.lab.net:57400 ] \"timestamp\" : 1593009759618786781 , [ router1.lab.net:57400 ] \"time\" : \"2020-06-24T16:42:39.618786781+02:00\" , [ router1.lab.net:57400 ] \"updates\" : [ [ router1.lab.net:57400 ] { [ router1.lab.net:57400 ] \"Path\" : \"configure/system/name\" , [ router1.lab.net:57400 ] \"values\" : { [ router1.lab.net:57400 ] \"configure/system/name\" : \"gnmic_r1\" [ router1.lab.net:57400 ] } [ router1.lab.net:57400 ] } [ router1.lab.net:57400 ] ] [ router1.lab.net:57400 ] } [ router2.lab.net:57400 ] { [ router2.lab.net:57400 ] \"source\" : \"router2.lab.net:57400\" , [ router2.lab.net:57400 ] \"timestamp\" : 1593009759748265232 , [ router2.lab.net:57400 ] \"time\" : \"2020-06-24T16:42:39.748265232+02:00\" , [ router2.lab.net:57400 ] \"updates\" : [ [ router2.lab.net:57400 ] { [ router2.lab.net:57400 ] \"Path\" : \"configure/system/name\" , [ router2.lab.net:57400 ] \"values\" : { [ router2.lab.net:57400 ] \"configure/system/name\" : \"gnmic_r2\" [ router2.lab.net:57400 ] } [ router2.lab.net:57400 ] } [ router2.lab.net:57400 ] ] [ router2.lab.net:57400 ] } Notice how in the output the different gNMI targets are prefixed with the target address to make the output easy to read. If those prefixes are not needed, you can make them disappear with --no-prefix global flag.","title":"Configuration"},{"location":"user_guide/targets/targets/#targets","text":"Sometimes it is needed to perform an operation on multiple devices; be it getting the same leaf value from a given set of the network elements or setting a certain configuration element to some value. For cases like that gnmic offers support for multiple targets operations which a user can configure both via CLI flags as well as with the file-based configuration .","title":"Targets"},{"location":"user_guide/targets/targets/#cli-configuration","text":"Specifying multiple targets in the CLI is as easy as repeating the --address flag. \u276f gnmic -a router1.lab.net:57400 \\ -a router2.lab.net:57400 \\ get --path /configure/system/name","title":"CLI configuration"},{"location":"user_guide/targets/targets/#file-based-configuration","text":"With the file-based configuration a user has two options to specify multiple targets: using address option using targets option","title":"File-based configuration"},{"location":"user_guide/targets/targets/#address-option","text":"With address option the user must provide a list of addresses. In the YAML format that would look like that: address : - \"router1.lab.net:57400\" - \"router2.lab.net:57400\" The limitation this approach has is that it is impossible to set different credentials for the targets, they will essentially share the credentials specified in a file or via flags.","title":"address option"},{"location":"user_guide/targets/targets/#target-option","text":"With the targets option it is possible to set target specific options (such as credentials, subscriptions, TLS config, outputs), and thus this option is recommended to use: targets : router1.lab.net : timeout : 2s username : r1 password : gnmi_pass router2.lab.net:57000 : username : r2 password : gnmi_pass tls-key : /path/file1 tls-cert : /path/file2 The target address is defined as the key under the targets section of the configuration file. The default port (57400) can be omitted as demonstrated with router1.lab.net target address. Have a look at the file-based targets configuration example to get a glimpse of what it is capable of. The target inherits the globally defined options if the matching options are not set on a target level. For example, if a target doesn't have a username defined, it will use the username value set on a global level.","title":"target option"},{"location":"user_guide/targets/targets/#secureinsecure-connections","text":"gnmic supports both secure and insecure gRPC connections to the target.","title":"secure/insecure connections"},{"location":"user_guide/targets/targets/#insecure-connection","text":"Using the --insecure flag it is possible to establish an insecure gRPC connection to the target. gnmic -a router1:57400 \\ --insecure \\ get --path /configure/system/name","title":"insecure connection"},{"location":"user_guide/targets/targets/#secure-connection","text":"A one way secure connection without target certificate verification can be established using the --skip-verify flag. gnmic -a router1:57400 \\ --skip-verify \\ get --path /configure/system/name Adding target certificate verification can be done using the --tls-ca flag. gnmic -a router1:57400 \\ --tls-ca /path/to/ca/file \\ get --path /configure/system/name A two way secure connection can be established using the --tls-cert --tls-key flags. gnmic -a router1:57400 \\ --tls-cert /path/to/certificate/file \\ --tls-key /path/to/certificate/file \\ get --path /configure/system/name It is also possible to control the negotiated TLS version using the --tls-min-version , --tls-max-version and --tls-version (preferred TLS version) flags.","title":"secure connection"},{"location":"user_guide/targets/targets/#target-configuration-options","text":"Target supported options: targets : # target name or an address (IP or DNS name). # if an address is set it can include a port number or not, # if a port is not included, the default gRPC port will be added. target_key : # target name, will default to the target_key if not specified name : target_key # target address, if missing the target_key is used as an address. # supports comma separated addresses. # if any of the addresses is missing a port, the default gRPC port will be added. # if multiple addresses are set, all of them will be tried simultaneously, # the first established gRPC connection will be used, the other attempts will be canceled. address : # target username username : # target password password : # authentication token, # applied only in the case of a secure gRPC connection. token : # target RPC timeout timeout : # establish an insecure connection insecure : # path to tls ca file tls-ca : # path to tls certificate tls-cert : # path to tls key tls-key : # max tls version to use during negotiation tls-max-version : # min tls version to use during negotiation tls-min-version : # preferred tls version to use during negotiation tls-version : # enable logging of a pre-master TLS secret log-tls-secret : # do not verify the target certificate when using tls skip-verify : # server name used to verify the hostname on the returned # certificates unless skip-verify is true. tls-server-name : # list of subscription names to establish for this target. # if empty it defaults to all subscriptions defined under # the main level `subscriptions` field subscriptions : # string, case insensitive, defines the gNMI encoding to be used for # the subscriptions to be established for this target. # This encoding value applies only if the subscription configuration does # NOT explicitly define an encoding. encoding : # list of output names to which the gnmi data will be written. # if empty if defaults to all outputs defined under # the main level `outputs` field outputs : # number of subscribe responses to keep in buffer before writing # the target outputs buffer-size : # target retry period retry : # list of tags, relevant when clustering is enabled. tags : # a mapping of static tags to add to all events from this target. # each key/value pair in this mapping will be added to metadata # on all events event-tags : # list of proto file names to decode protoBytes values proto-files : # list of directories to look for the proto files proto-dirs : # enable grpc gzip compression gzip : # proxy type and address, only SOCKS5 is supported currently # example: socks5://
    : proxy :","title":"target configuration options"},{"location":"user_guide/targets/targets/#example","text":"Whatever configuration option you choose, the multi-targeted operations will uniformly work across the commands that support them. Consider the get command acting on two routers getting their names: \u276f gnmic -a router1.lab.net:57400 \\ -a router2.lab.net:57400 \\ get --path /configure/system/name [ router1.lab.net:57400 ] { [ router1.lab.net:57400 ] \"source\" : \"router1.lab.net:57400\" , [ router1.lab.net:57400 ] \"timestamp\" : 1593009759618786781 , [ router1.lab.net:57400 ] \"time\" : \"2020-06-24T16:42:39.618786781+02:00\" , [ router1.lab.net:57400 ] \"updates\" : [ [ router1.lab.net:57400 ] { [ router1.lab.net:57400 ] \"Path\" : \"configure/system/name\" , [ router1.lab.net:57400 ] \"values\" : { [ router1.lab.net:57400 ] \"configure/system/name\" : \"gnmic_r1\" [ router1.lab.net:57400 ] } [ router1.lab.net:57400 ] } [ router1.lab.net:57400 ] ] [ router1.lab.net:57400 ] } [ router2.lab.net:57400 ] { [ router2.lab.net:57400 ] \"source\" : \"router2.lab.net:57400\" , [ router2.lab.net:57400 ] \"timestamp\" : 1593009759748265232 , [ router2.lab.net:57400 ] \"time\" : \"2020-06-24T16:42:39.748265232+02:00\" , [ router2.lab.net:57400 ] \"updates\" : [ [ router2.lab.net:57400 ] { [ router2.lab.net:57400 ] \"Path\" : \"configure/system/name\" , [ router2.lab.net:57400 ] \"values\" : { [ router2.lab.net:57400 ] \"configure/system/name\" : \"gnmic_r2\" [ router2.lab.net:57400 ] } [ router2.lab.net:57400 ] } [ router2.lab.net:57400 ] ] [ router2.lab.net:57400 ] } Notice how in the output the different gNMI targets are prefixed with the target address to make the output easy to read. If those prefixes are not needed, you can make them disappear with --no-prefix global flag.","title":"Example"},{"location":"user_guide/targets/targets_session_sec/","text":"Targets session security # In line with the guidelines detailed in the gNMI Specification , it is mandatory to establish an encrypted TLS session between the client and the server. This measure is essential to ensure secure communication within the gNMI protocol. The session between the client and server MUST be encrypted using TLS - and a target or client MUST NOT fall back to unencrypted sessions. The target and client SHOULD implement TLS >= 1.2. gNMIc provides the ability to tailor and modify the TLS session parameters of the gNMI client according to your specific requirements. TLS session types # When it comes to establishing a TLS session using gNMIc , various options are available to suit different use cases and environmental requirements. Whether it's a one-way TLS session, a session without certificate validation, or a mutual TLS (mTLS) session, each type caters to specific needs. The selection largely depends on the user's scenario and the degree of security and validation necessary. The upcoming sections will detail each of these session types, offering guidelines to aid in choosing the most appropriate for your specific requirements. Simple TLS session w/o server certificate validation # For scenarios requiring a simple TLS session without server certificate validation, such as in certain testing or development environments, you can use gNMIc's --skip-verify flag or the skip-verify attribute. This mode bypasses the typical certificate verification process and establishes a secure connection without validating the server's identity. Please exercise caution when using this feature, as it may expose the connection to potential security vulnerabilities. It is recommended primarily for non-production environments or controlled testing situations. cli file gnmic -a router1 --skip-verify \\ get --path /interface/oper-state targets : router1 : address : router1 skip-verify : true Simple TLS session with server certificate validation # When establishing a simple TLS session with server certificate validation for enhanced security, gNMIc offers the --tls-ca flag or the tls-ca attribute. These options allow you to point to a Certificate Authority (CA) certificate file. By doing so, the session not only ensures encrypted communication but also verifies the server's identity through its certificate. This validation process greatly enhances the security of the connection, ensuring the client is communicating with the intended server. It's an advisable setting for production environments where data security and integrity are crucial. cli file gnmic -a router1 --tls-ca ./ca.pem \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem Simple TLS session with server certificate validation and server name override # There are circumstances where the server's identity, as indicated by its certificate, doesn't match its expected hostname. For such scenarios, gNMIc enables the initiation of a simple TLS session with both server certificate validation and server name override. This functionality can be utilized by employing the --tls-server-name flag or the tls-server-name attribute. By overriding the server name in the TLS session, users can specify a different hostname that matches the server's certificate, even if it's not the actual hostname of the server. This allows for successful validation and secure communication even in cases of server name discrepancies due to reasons like load balancing, proxying, etc... This feature is particularly beneficial in complex network scenarios or during migrations, where server names might not yet align with their certificates. By ensuring both secure encrypted communication and flexible server name accommodation, it adds an extra layer of adaptability for secure communication, particularly in dynamic or complex network environments. cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-server-name server1 \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-server-name : server1 Mutual TLS (mTLS) session # For heightened security scenarios, gNMIc supports mutual TLS (mTLS) sessions. mTLS not only verifies the server's identity to the client, but also the client's identity to the server. This reciprocal verification is achieved using the --tls-cert and --tls-key flags, or the tls-cert and tls-key attributes. These options allow the user to specify a client certificate and client key, respectively. By providing a client certificate ( --tls-cert or tls-cert attribute) and a client key ( --tls-key or tls-key attribute), gNMIc allows the server to confirm the identity of the client, ensuring that the client is legitimate and authorized to access the server resources. Mutual TLS is particularly beneficial in use cases where both ends of a connection need to confirm the other's identity, providing a significantly higher level of trust and security. It reduces the risk of man-in-the-middle attacks and is especially valuable in environments where sensitive data is transmitted or strict access control is required. cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-cert : ./router1.cert tls-key : ./router1.key mTLS session with server name override # cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-server-name server1 \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-server-name : server1 tls-cert : ./router1.cert tls-key : ./router1.key Configuring the client's TLS version # By default, gNMIc establishes a TLS session using the Golang's default TLS version (1.2), minimum version (1.2), and maximum version (1.3). However, there might be scenarios where users need to control the TLS session negotiation to either test the server behavior or force the session into a specific version. To accommodate these needs, gNMIc provides flexibility by allowing users to explicitly set the TLS version. Users can manipulate the negotiated TLS version using the flags (or target attributes) --tls-version , --tls-min-version , and --tls-max-version . These flags give control over the TLS session parameters, facilitating testing and customization of the communication session according to specific requirements. Example: Forcing the client and server to use TLS1.3 cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ --tls-version 1 .3 \\ --tls-min-version 1 .3 \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-cert : ./router1.cert tls-key : ./router1.key tls-version : 1.3 tls-min-version : 1.3 Decrypting gNMI traffic using Wireshark # To facilitate advanced debugging or network analysis, gNMIc allows for the decryption of gNMI TLS traffic using the popular network protocol analyzer, Wireshark. The --log-tls-secret flag is instrumental in achieving this, as it stores the session pre-master secret, which can subsequently be used to decrypt TLS traffic. When --log-tls-secret is used, the session's pre-master secret will be stored in a file named .tlssecret.log . This secret enables Wireshark to decrypt the otherwise secure and encrypted TLS traffic between the client and the server. Decryption of TLS traffic is particularly useful for network troubleshooting, performance optimization, or security audits. It allows network administrators or developers to deeply inspect packet data, diagnose network issues, and better understand data flows. However, this practice should be used carefully and ethically, given the sensitive nature of decrypted traffic, especially in production environments. cli file gnmic -a router1 --tls-ca ./ca.pem \\ --log-tls-secret \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem log-tls-secret : true tls-cert : ./router1.cert tls-key : ./router1.key","title":"Session Security"},{"location":"user_guide/targets/targets_session_sec/#targets-session-security","text":"In line with the guidelines detailed in the gNMI Specification , it is mandatory to establish an encrypted TLS session between the client and the server. This measure is essential to ensure secure communication within the gNMI protocol. The session between the client and server MUST be encrypted using TLS - and a target or client MUST NOT fall back to unencrypted sessions. The target and client SHOULD implement TLS >= 1.2. gNMIc provides the ability to tailor and modify the TLS session parameters of the gNMI client according to your specific requirements.","title":"Targets session security"},{"location":"user_guide/targets/targets_session_sec/#tls-session-types","text":"When it comes to establishing a TLS session using gNMIc , various options are available to suit different use cases and environmental requirements. Whether it's a one-way TLS session, a session without certificate validation, or a mutual TLS (mTLS) session, each type caters to specific needs. The selection largely depends on the user's scenario and the degree of security and validation necessary. The upcoming sections will detail each of these session types, offering guidelines to aid in choosing the most appropriate for your specific requirements.","title":"TLS session types"},{"location":"user_guide/targets/targets_session_sec/#simple-tls-session-wo-server-certificate-validation","text":"For scenarios requiring a simple TLS session without server certificate validation, such as in certain testing or development environments, you can use gNMIc's --skip-verify flag or the skip-verify attribute. This mode bypasses the typical certificate verification process and establishes a secure connection without validating the server's identity. Please exercise caution when using this feature, as it may expose the connection to potential security vulnerabilities. It is recommended primarily for non-production environments or controlled testing situations. cli file gnmic -a router1 --skip-verify \\ get --path /interface/oper-state targets : router1 : address : router1 skip-verify : true","title":"Simple TLS session w/o server certificate validation"},{"location":"user_guide/targets/targets_session_sec/#simple-tls-session-with-server-certificate-validation","text":"When establishing a simple TLS session with server certificate validation for enhanced security, gNMIc offers the --tls-ca flag or the tls-ca attribute. These options allow you to point to a Certificate Authority (CA) certificate file. By doing so, the session not only ensures encrypted communication but also verifies the server's identity through its certificate. This validation process greatly enhances the security of the connection, ensuring the client is communicating with the intended server. It's an advisable setting for production environments where data security and integrity are crucial. cli file gnmic -a router1 --tls-ca ./ca.pem \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem","title":"Simple TLS session with server certificate validation"},{"location":"user_guide/targets/targets_session_sec/#simple-tls-session-with-server-certificate-validation-and-server-name-override","text":"There are circumstances where the server's identity, as indicated by its certificate, doesn't match its expected hostname. For such scenarios, gNMIc enables the initiation of a simple TLS session with both server certificate validation and server name override. This functionality can be utilized by employing the --tls-server-name flag or the tls-server-name attribute. By overriding the server name in the TLS session, users can specify a different hostname that matches the server's certificate, even if it's not the actual hostname of the server. This allows for successful validation and secure communication even in cases of server name discrepancies due to reasons like load balancing, proxying, etc... This feature is particularly beneficial in complex network scenarios or during migrations, where server names might not yet align with their certificates. By ensuring both secure encrypted communication and flexible server name accommodation, it adds an extra layer of adaptability for secure communication, particularly in dynamic or complex network environments. cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-server-name server1 \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-server-name : server1","title":"Simple TLS session with server certificate validation and server name override"},{"location":"user_guide/targets/targets_session_sec/#mutual-tls-mtls-session","text":"For heightened security scenarios, gNMIc supports mutual TLS (mTLS) sessions. mTLS not only verifies the server's identity to the client, but also the client's identity to the server. This reciprocal verification is achieved using the --tls-cert and --tls-key flags, or the tls-cert and tls-key attributes. These options allow the user to specify a client certificate and client key, respectively. By providing a client certificate ( --tls-cert or tls-cert attribute) and a client key ( --tls-key or tls-key attribute), gNMIc allows the server to confirm the identity of the client, ensuring that the client is legitimate and authorized to access the server resources. Mutual TLS is particularly beneficial in use cases where both ends of a connection need to confirm the other's identity, providing a significantly higher level of trust and security. It reduces the risk of man-in-the-middle attacks and is especially valuable in environments where sensitive data is transmitted or strict access control is required. cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-cert : ./router1.cert tls-key : ./router1.key","title":"Mutual TLS (mTLS) session"},{"location":"user_guide/targets/targets_session_sec/#mtls-session-with-server-name-override","text":"cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-server-name server1 \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-server-name : server1 tls-cert : ./router1.cert tls-key : ./router1.key","title":"mTLS session with server name override"},{"location":"user_guide/targets/targets_session_sec/#configuring-the-clients-tls-version","text":"By default, gNMIc establishes a TLS session using the Golang's default TLS version (1.2), minimum version (1.2), and maximum version (1.3). However, there might be scenarios where users need to control the TLS session negotiation to either test the server behavior or force the session into a specific version. To accommodate these needs, gNMIc provides flexibility by allowing users to explicitly set the TLS version. Users can manipulate the negotiated TLS version using the flags (or target attributes) --tls-version , --tls-min-version , and --tls-max-version . These flags give control over the TLS session parameters, facilitating testing and customization of the communication session according to specific requirements. Example: Forcing the client and server to use TLS1.3 cli file gnmic -a router1 --tls-ca ./ca.pem \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ --tls-version 1 .3 \\ --tls-min-version 1 .3 \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem tls-cert : ./router1.cert tls-key : ./router1.key tls-version : 1.3 tls-min-version : 1.3","title":"Configuring the client's TLS version"},{"location":"user_guide/targets/targets_session_sec/#decrypting-gnmi-traffic-using-wireshark","text":"To facilitate advanced debugging or network analysis, gNMIc allows for the decryption of gNMI TLS traffic using the popular network protocol analyzer, Wireshark. The --log-tls-secret flag is instrumental in achieving this, as it stores the session pre-master secret, which can subsequently be used to decrypt TLS traffic. When --log-tls-secret is used, the session's pre-master secret will be stored in a file named .tlssecret.log . This secret enables Wireshark to decrypt the otherwise secure and encrypted TLS traffic between the client and the server. Decryption of TLS traffic is particularly useful for network troubleshooting, performance optimization, or security audits. It allows network administrators or developers to deeply inspect packet data, diagnose network issues, and better understand data flows. However, this practice should be used carefully and ethically, given the sensitive nature of decrypted traffic, especially in production environments. cli file gnmic -a router1 --tls-ca ./ca.pem \\ --log-tls-secret \\ --tls-cert ./router1.cert \\ --tls-key ./router.key \\ get --path /interface/oper-state targets : router1 : address : router1 tls-ca : ./ca.pem log-tls-secret : true tls-cert : ./router1.cert tls-key : ./router1.key","title":"Decrypting gNMI traffic using Wireshark"},{"location":"user_guide/targets/target_discovery/consul_discovery/","text":"The Consul target loader discovers gNMI targets registered as service instances in a Consul Server. The loader watches services registered in Consul defined by a service name and optionally a set of tags. Services watch # When at least one service name is set, gNMIc consul loader will watch the instances registered under that service name and build a target configuration using the service ID as the target name and the registered address and port as the target address. The remaining configuration can be set under the service name definition. loader : type : consul services : - name : cluster1-gnmi-server config : insecure : true username : admin password : admin Configuration # loader : type : consul # address of the loader server address : localhost:8500 # Consul Data center, defaults to dc1 datacenter : dc1 # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token which overrides the agent's default token token : # the key prefix to watch for targets configuration, defaults to \"gnmic/config/targets\" key-prefix : gnmic/config/targets # if true, registers consulLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # list of services to watch and derive target configurations from. services : # name of the Consul service - name : # a list of strings to further filter the service instances tags : # configuration map to apply to target discovered from this service config : # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file :","title":"Consul Discovery"},{"location":"user_guide/targets/target_discovery/consul_discovery/#services-watch","text":"When at least one service name is set, gNMIc consul loader will watch the instances registered under that service name and build a target configuration using the service ID as the target name and the registered address and port as the target address. The remaining configuration can be set under the service name definition. loader : type : consul services : - name : cluster1-gnmi-server config : insecure : true username : admin password : admin","title":"Services watch"},{"location":"user_guide/targets/target_discovery/consul_discovery/#configuration","text":"loader : type : consul # address of the loader server address : localhost:8500 # Consul Data center, defaults to dc1 datacenter : dc1 # Consul username, to be used as part of HTTP basicAuth username : # Consul password, to be used as part of HTTP basicAuth password : # Consul Token, is used to provide a per-request ACL token which overrides the agent's default token token : # the key prefix to watch for targets configuration, defaults to \"gnmic/config/targets\" key-prefix : gnmic/config/targets # if true, registers consulLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # list of services to watch and derive target configurations from. services : # name of the Consul service - name : # a list of strings to further filter the service instances tags : # configuration map to apply to target discovered from this service config : # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file :","title":"Configuration"},{"location":"user_guide/targets/target_discovery/discovery_intro/","text":"Introduction # gnmic supports dynamic loading of gNMI targets from external systems. This feature allows adding and deleting gNMI targets without the need to restart gnmic . Depending on the discovery method, gnmic will either: Subscribe to changes on the remote system, Or poll the defined targets from the remote systems. When a change is detected, the new targets are added and the corresponding subscriptions are immediately established. The removed targets are deleted together with their subscriptions. Actions can be run on target discovery (on-add or on-delete), this can be useful to add initial configurations to target ahead of gNMI subscriptions or run checks before subscribing. In the case of on-add actions, Notes Only one discovery type is supported at a time. Target updates are not supported, delete and re-add is the way to update a target configuration. Discovery types # Four types of target discovery methods are supported: File Loader # Watches changes to a local file containing gNMI targets definitions. Consul Server Loader # Subscribes to Consul KV key prefix changes, the keys and their value represent a target configuration fields. Docker Engine Loader # Polls containers from a Docker Engine host matching some predefined criteria (docker filters). HTTP Loader # Queries an HTTP endpoint periodically, expected a well formatted JSON dict of targets configurations. Running actions on discovery # All actions support fields on-add and on-delete which take a list of predefined action names that will be run sequentially on target discovery or deletion. The below configuration example defines 3 actions configure_interfaces , configure_subinterfaces and configure_network_instance which will run when the docker loader discovers a target with label clab-node-kind=srl loader : type : docker filters : - containers : - label : clab-node-kind=srl config : skip-verify : true username : admin password : NokiaSrl1! on-add : - configure_interfaces - configure_subinterfaces - configure_network_instances actions : configure_interfaces : name : configure_interfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/admin-state - /interface[name=ethernet-1/2]/admin-state values : - enable - enable configure_subinterfaces : name : configure_subinterfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/subinterface[index=0]/admin-state - /interface[name=ethernet-1/2]/subinterface[index=0]/admin-state values : - enable - enable configure_network_instances : name : configure_network_instances type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /network-instance[name=default]/admin-state - /network-instance[name=default]/interface - /network-instance[name=default]/interface values : - enable - '{\"name\": \"ethernet-1/1.0\"}' - '{\"name\": \"ethernet-1/2.0\"}'","title":"Introduction"},{"location":"user_guide/targets/target_discovery/discovery_intro/#introduction","text":"gnmic supports dynamic loading of gNMI targets from external systems. This feature allows adding and deleting gNMI targets without the need to restart gnmic . Depending on the discovery method, gnmic will either: Subscribe to changes on the remote system, Or poll the defined targets from the remote systems. When a change is detected, the new targets are added and the corresponding subscriptions are immediately established. The removed targets are deleted together with their subscriptions. Actions can be run on target discovery (on-add or on-delete), this can be useful to add initial configurations to target ahead of gNMI subscriptions or run checks before subscribing. In the case of on-add actions, Notes Only one discovery type is supported at a time. Target updates are not supported, delete and re-add is the way to update a target configuration.","title":"Introduction"},{"location":"user_guide/targets/target_discovery/discovery_intro/#discovery-types","text":"Four types of target discovery methods are supported:","title":"Discovery types"},{"location":"user_guide/targets/target_discovery/discovery_intro/#file-loader","text":"Watches changes to a local file containing gNMI targets definitions.","title":"File Loader"},{"location":"user_guide/targets/target_discovery/discovery_intro/#consul-server-loader","text":"Subscribes to Consul KV key prefix changes, the keys and their value represent a target configuration fields.","title":"Consul Server Loader"},{"location":"user_guide/targets/target_discovery/discovery_intro/#docker-engine-loader","text":"Polls containers from a Docker Engine host matching some predefined criteria (docker filters).","title":"Docker Engine Loader"},{"location":"user_guide/targets/target_discovery/discovery_intro/#http-loader","text":"Queries an HTTP endpoint periodically, expected a well formatted JSON dict of targets configurations.","title":"HTTP Loader"},{"location":"user_guide/targets/target_discovery/discovery_intro/#running-actions-on-discovery","text":"All actions support fields on-add and on-delete which take a list of predefined action names that will be run sequentially on target discovery or deletion. The below configuration example defines 3 actions configure_interfaces , configure_subinterfaces and configure_network_instance which will run when the docker loader discovers a target with label clab-node-kind=srl loader : type : docker filters : - containers : - label : clab-node-kind=srl config : skip-verify : true username : admin password : NokiaSrl1! on-add : - configure_interfaces - configure_subinterfaces - configure_network_instances actions : configure_interfaces : name : configure_interfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/admin-state - /interface[name=ethernet-1/2]/admin-state values : - enable - enable configure_subinterfaces : name : configure_subinterfaces type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /interface[name=ethernet-1/1]/subinterface[index=0]/admin-state - /interface[name=ethernet-1/2]/subinterface[index=0]/admin-state values : - enable - enable configure_network_instances : name : configure_network_instances type : gnmi target : '{{ .Input }}' rpc : set encoding : json_ietf debug : true paths : - /network-instance[name=default]/admin-state - /network-instance[name=default]/interface - /network-instance[name=default]/interface values : - enable - '{\"name\": \"ethernet-1/1.0\"}' - '{\"name\": \"ethernet-1/2.0\"}'","title":"Running actions on discovery"},{"location":"user_guide/targets/target_discovery/docker_discovery/","text":"The Docker target loader allows discovering gNMI targets from Docker Engine hosts. It discovers containers as well as their gNMI address, based on a list of Docker filters One gNMI target is added per discovered container. Individual Target configurations are derived from the container exposed ports and labels, as well as the global configuration. Configuration # loader : # the loader type: docker type : docker # string, the docker daemon address, # leave empty to use the local docker daemon # possible values: # - unix:///var/run/docker.sock # - tcp://:port # - http://:port address : \"\" # duration, check interval for discovering # new docker containers, default: 30s interval : 30s # duration, the docker queries timeout, # defaults to half of `interval` if left unset or is invalid. timeout : 15s # time to wait before the fist docker query start-delay : 0s # bool, print loader debug statements. debug : false # if true, registers dockerLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # containers, network filters: # see https://docs.docker.com/engine/reference/commandline/ps/#filtering # for the possible values. filters : # containers filters - containers : # containers returned by `docker ps -f \"label=clab-node-kind=srl\"` - label : clab-node-kind=srl # network filters network : # networks returned by `docker network ls -f \"label=containerlab\"` label : containerlab # gNMI port value for the containers discovered by this filter. # It can be a port value or a label name set on the container. # valid values: # `port: \"57400\"` # `port: \"label=gnmi-port\"` port : # target config for containers discovered by this filter. # These fields will override the matching global config fields. config : username : admin password : secret1 skip-verify : true # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file : Filter fields explanation # containers : (Optional) A list of lists of docker filters used to select containers from the Docker Engine host. The docker filter status=running is implicitly added. If not set, all containers with status=running are selected. network : (Optional) A set of docker filters used to select the network to connect to the container. If not filter is set, all docker networks are considered. port : (Optional) This field is used to specify the gNMI port for the discovered containers. An integer can be specified in which case it will be used as the gNMI port for all discovered containers. Alternatively, a string in the format label= can be set, where is a docker label containing the gNMI port value. If no value is set, the global flag/value port is used. config : (Optional) A set of configuration parameters to be applied to all discovered targets by the container filter. The target config fields as defined here can be set, except name and address which are discovered by the loader. Examples # Simple1 # A simple docker loader with a single docker container filter. It loads all containers deployed with containerlab , in lab called lab1 . loader : type : docker filters : - containers : - label : containerlab=lab1 In the above example, gnmic docker loader connects to the local Docker Daemon. It will discover containers having label containerlab=lab1 and add them as gNMI targets. Default configuration applies to those added targets Simple2 # A simple docker loader with a single docker container filter. It loads all containers deployed with containerlab , having kind srl . loader : type : docker filters : - containers : - label : clab-node-kind=srl In the above example, gnmic docker loader connects to the local Docker Daemon. It will discover containers having label clab-node-kind=srl and add them as gNMI targets. Default configuration applies to those added targets Advanced Example # A more advanced docker loader, with 2 filers, custom networks, ports and target configuration. loader : type : docker address : unix:///var/run/docker.sock filters : # filter 1 - containers : # containers returned by `docker ps -f \"label=clab-node-kind=srl\"` - label : clab-node-kind=srl network : # networks returned by `docker network ls -f \"label=containerlab\"` label : containerlab port : \"57400\" config : username : admin password : secret1 skip-verify : true # filter 2 - containers : # containers returned by `docker ps -f \"label=clab-node-kind=ceos\"` - label : clab-node-kind=ceos # containers returned by `docker ps -f \"label=clab-node-kind=vr-sros\"` - label : clab-node-kind=vr-sros network : # networks returned by `docker network ls -f \"name=mgmt\"` name : mgmt # the value of label=gnmi-port exported by each container` port : \"label=gnmi-port\" config : username : admin password : secret2 insecure : true In the above example, gnmic docker loader connects to the docker daemon using the local unix socket address. It will discover 2 sets of containers matching 2 filters: Filter1: Containers with label clab-node-kind=srl . Use network with label containerlab to connect to them. The port number is the same for all containers and is set to 57400 . The config fields username: admin , password: secret1 and skip-verify: true will be applied to all the containers discovered by this filter. Filter2: Containers with labels clab-node-kind-ceos or clab-node-vr-sros Use network with name=mgmt to connect to them. Note that Docker returns all networks with names containing mgmt The port number is discovered from the label gnmi-port set on each container. The config fields username: admin , password: secret2 and insecure: true will be applied to all the containers discovered by this filter.","title":"Docker Discovery"},{"location":"user_guide/targets/target_discovery/docker_discovery/#configuration","text":"loader : # the loader type: docker type : docker # string, the docker daemon address, # leave empty to use the local docker daemon # possible values: # - unix:///var/run/docker.sock # - tcp://:port # - http://:port address : \"\" # duration, check interval for discovering # new docker containers, default: 30s interval : 30s # duration, the docker queries timeout, # defaults to half of `interval` if left unset or is invalid. timeout : 15s # time to wait before the fist docker query start-delay : 0s # bool, print loader debug statements. debug : false # if true, registers dockerLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # containers, network filters: # see https://docs.docker.com/engine/reference/commandline/ps/#filtering # for the possible values. filters : # containers filters - containers : # containers returned by `docker ps -f \"label=clab-node-kind=srl\"` - label : clab-node-kind=srl # network filters network : # networks returned by `docker network ls -f \"label=containerlab\"` label : containerlab # gNMI port value for the containers discovered by this filter. # It can be a port value or a label name set on the container. # valid values: # `port: \"57400\"` # `port: \"label=gnmi-port\"` port : # target config for containers discovered by this filter. # These fields will override the matching global config fields. config : username : admin password : secret1 skip-verify : true # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file :","title":"Configuration"},{"location":"user_guide/targets/target_discovery/docker_discovery/#filter-fields-explanation","text":"containers : (Optional) A list of lists of docker filters used to select containers from the Docker Engine host. The docker filter status=running is implicitly added. If not set, all containers with status=running are selected. network : (Optional) A set of docker filters used to select the network to connect to the container. If not filter is set, all docker networks are considered. port : (Optional) This field is used to specify the gNMI port for the discovered containers. An integer can be specified in which case it will be used as the gNMI port for all discovered containers. Alternatively, a string in the format label= can be set, where is a docker label containing the gNMI port value. If no value is set, the global flag/value port is used. config : (Optional) A set of configuration parameters to be applied to all discovered targets by the container filter. The target config fields as defined here can be set, except name and address which are discovered by the loader.","title":"Filter fields explanation"},{"location":"user_guide/targets/target_discovery/docker_discovery/#examples","text":"","title":"Examples"},{"location":"user_guide/targets/target_discovery/docker_discovery/#simple1","text":"A simple docker loader with a single docker container filter. It loads all containers deployed with containerlab , in lab called lab1 . loader : type : docker filters : - containers : - label : containerlab=lab1 In the above example, gnmic docker loader connects to the local Docker Daemon. It will discover containers having label containerlab=lab1 and add them as gNMI targets. Default configuration applies to those added targets","title":"Simple1"},{"location":"user_guide/targets/target_discovery/docker_discovery/#simple2","text":"A simple docker loader with a single docker container filter. It loads all containers deployed with containerlab , having kind srl . loader : type : docker filters : - containers : - label : clab-node-kind=srl In the above example, gnmic docker loader connects to the local Docker Daemon. It will discover containers having label clab-node-kind=srl and add them as gNMI targets. Default configuration applies to those added targets","title":"Simple2"},{"location":"user_guide/targets/target_discovery/docker_discovery/#advanced-example","text":"A more advanced docker loader, with 2 filers, custom networks, ports and target configuration. loader : type : docker address : unix:///var/run/docker.sock filters : # filter 1 - containers : # containers returned by `docker ps -f \"label=clab-node-kind=srl\"` - label : clab-node-kind=srl network : # networks returned by `docker network ls -f \"label=containerlab\"` label : containerlab port : \"57400\" config : username : admin password : secret1 skip-verify : true # filter 2 - containers : # containers returned by `docker ps -f \"label=clab-node-kind=ceos\"` - label : clab-node-kind=ceos # containers returned by `docker ps -f \"label=clab-node-kind=vr-sros\"` - label : clab-node-kind=vr-sros network : # networks returned by `docker network ls -f \"name=mgmt\"` name : mgmt # the value of label=gnmi-port exported by each container` port : \"label=gnmi-port\" config : username : admin password : secret2 insecure : true In the above example, gnmic docker loader connects to the docker daemon using the local unix socket address. It will discover 2 sets of containers matching 2 filters: Filter1: Containers with label clab-node-kind=srl . Use network with label containerlab to connect to them. The port number is the same for all containers and is set to 57400 . The config fields username: admin , password: secret1 and skip-verify: true will be applied to all the containers discovered by this filter. Filter2: Containers with labels clab-node-kind-ceos or clab-node-vr-sros Use network with name=mgmt to connect to them. Note that Docker returns all networks with names containing mgmt The port number is discovered from the label gnmi-port set on each container. The config fields username: admin , password: secret2 and insecure: true will be applied to all the containers discovered by this filter.","title":"Advanced Example"},{"location":"user_guide/targets/target_discovery/file_discovery/","text":"gnmic is able to watch changes happening to a file that contains the gNMI targets configuration. The file can be located in the local file system or a remote one. In case of remote file, ftp , sftp , http(s) protocols are supported. The read timeout of remote files is set to half of the read interval Newly added targets are discovered and subscribed to. Deleted targets are moved from gNMIc's list and their subscriptions are terminated. Configuration # A file target loader can be configured in a couple of ways: using the --targets-file flag: gnmic --targets-file ./targets-config.yaml subscribe gnmic --targets-file sftp://user:pass@server.com/path/to/targets-file.yaml subscribe using the main configuration file: loader : type : file # path to the file path : ./targets-config.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # time to wait before the first file read start-delay : 0s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file : The --targets-file flag takes precedence over the loader configuration section. The targets file can be either a YAML or a JSON file (identified by its extension json, yaml or yml), and follows the same format as the main configuration file targets section. See here Examples # Local File # loader : type : file # path to the file path : ./targets-config.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false Remote File # SFTP remote file loader : type : file # path to the file path : sftp://user:pass@server.com/path/to/targets-file.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false FTP remote file loader : type : file # path to the file path : ftp://user:pass@server.com/path/to/targets-file.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false HTTP remote file loader : type : file # path to the file path : http://user:pass@server.com/path/to/targets-file.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false Targets file format # YAML JSON 10.10.10.10 : username : admin insecure : true 10.10.10.11 : username : admin 10.10.10.12 : 10.10.10.13 : 10.10.10.14 : { \"10.10.10.10\" : { \"username\" : \"admin\" , \"insecure\" : true }, \"10.10.10.11\" : { \"username\" : \"admin\" , }, \"10.10.10.12\" : {}, \"10.10.10.13\" : {}, \"10.10.10.14\" : {} } Just like the targets in the main configuration file, the missing configuration fields get filled with the global flags, the ENV variables first, the config file main section next and then the default values.","title":"File Discovery"},{"location":"user_guide/targets/target_discovery/file_discovery/#configuration","text":"A file target loader can be configured in a couple of ways: using the --targets-file flag: gnmic --targets-file ./targets-config.yaml subscribe gnmic --targets-file sftp://user:pass@server.com/path/to/targets-file.yaml subscribe using the main configuration file: loader : type : file # path to the file path : ./targets-config.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # time to wait before the first file read start-delay : 0s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file : The --targets-file flag takes precedence over the loader configuration section. The targets file can be either a YAML or a JSON file (identified by its extension json, yaml or yml), and follows the same format as the main configuration file targets section. See here","title":"Configuration"},{"location":"user_guide/targets/target_discovery/file_discovery/#examples","text":"","title":"Examples"},{"location":"user_guide/targets/target_discovery/file_discovery/#local-file","text":"loader : type : file # path to the file path : ./targets-config.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false","title":"Local File"},{"location":"user_guide/targets/target_discovery/file_discovery/#remote-file","text":"SFTP remote file loader : type : file # path to the file path : sftp://user:pass@server.com/path/to/targets-file.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false FTP remote file loader : type : file # path to the file path : ftp://user:pass@server.com/path/to/targets-file.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false HTTP remote file loader : type : file # path to the file path : http://user:pass@server.com/path/to/targets-file.yaml # watch interval at which the file # is read again to determine if a target was added or deleted. interval : 30s # if true, registers fileLoader prometheus metrics with the provided # prometheus registry enable-metrics : false","title":"Remote File"},{"location":"user_guide/targets/target_discovery/file_discovery/#targets-file-format","text":"YAML JSON 10.10.10.10 : username : admin insecure : true 10.10.10.11 : username : admin 10.10.10.12 : 10.10.10.13 : 10.10.10.14 : { \"10.10.10.10\" : { \"username\" : \"admin\" , \"insecure\" : true }, \"10.10.10.11\" : { \"username\" : \"admin\" , }, \"10.10.10.12\" : {}, \"10.10.10.13\" : {}, \"10.10.10.14\" : {} } Just like the targets in the main configuration file, the missing configuration fields get filled with the global flags, the ENV variables first, the config file main section next and then the default values.","title":"Targets file format"},{"location":"user_guide/targets/target_discovery/http_discovery/","text":"The HTTP target loader can be used to query targets configurations from a remote HTTP server. It expects a well formatted application/json body and a code 200 response. It supports secure connections, basic authentication using a username and password and/or Oauth2 token based authentication. Configuration # loader : type : http # resource URL, must include the http(s) schema url : # watch interval at which the HTTP endpoint is queried again # to determine if a target was added or deleted. interval : 60s # HTTP request timeout timeout : 50s # time to wait before the fist HTTP query start-delay : 0s # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # username to be used with basic authentication username : # password to be used with basic authentication password : # token to be used with Oauth2 token based authentication token : # auth scheme (default is `Bearer`) auth-scheme : # text template template : # path to a text template file template-file : # if true, registers httpLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file :","title":"HTTP Discovery"},{"location":"user_guide/targets/target_discovery/http_discovery/#configuration","text":"loader : type : http # resource URL, must include the http(s) schema url : # watch interval at which the HTTP endpoint is queried again # to determine if a target was added or deleted. interval : 60s # HTTP request timeout timeout : 50s # time to wait before the fist HTTP query start-delay : 0s # tls config tls : # string, path to the CA certificate file, # this will be used to verify the clients certificates when `skip-verify` is false ca-file : # string, client certificate file. cert-file : # string, client key file. key-file : # boolean, if true, the client will not verify the server # certificate against the available certificate chain. skip-verify : false # username to be used with basic authentication username : # password to be used with basic authentication password : # token to be used with Oauth2 token based authentication token : # auth scheme (default is `Bearer`) auth-scheme : # text template template : # path to a text template file template-file : # if true, registers httpLoader prometheus metrics with the provided # prometheus registry enable-metrics : false # list of actions to run on target discovery on-add : # list of actions to run on target removal on-delete : # variable dict to pass to actions to be run vars : # path to variable file, the variables defined will be passed to the actions to be run # values in this file will be overwritten by the ones defined in `vars` vars-file :","title":"Configuration"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..8306316d --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,578 @@ + + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + + None + 2024-02-06 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 00000000..d4cff593 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/stylesheets/extra.css b/stylesheets/extra.css new file mode 100644 index 00000000..e1839751 --- /dev/null +++ b/stylesheets/extra.css @@ -0,0 +1,3 @@ +.md-typeset code { + background-color: transparent ; +} \ No newline at end of file diff --git a/user_guide/HA/index.html b/user_guide/HA/index.html new file mode 100644 index 00000000..69f3f008 --- /dev/null +++ b/user_guide/HA/index.html @@ -0,0 +1,85 @@ + Clustering - gNMIc

    Clustering

    Multiple instances ofgnmic can be run in clustered mode in order to load share the targets connections and protect against failures.

    The cluster mode allows gnmic to scale and be highly available at the same time

    To join the cluster, the instances rely on a service discovery system and distributed KV store such as Consul,

    Clustering process#

    At startup, all instances belonging to a cluster:

    • Enter an election process in order to become the cluster leader.
    • Register their API service gnmic-api in a configured service discovery system.

    Upon becoming the leader:

    • The gnmic instance starts watching the registered gnmic-api services, and maintains a local cache of the active ones. These are essentially the instances restAPI addresses.
    • The leader then waits for clustering/leader-wait-timer to allow the other instances to register their API services as well. This is useful in case an instance is slow to boot, which leaves it out of the initial load sharing process.
    • The leader then enters a "target watch loop" (clustering/targets-watch-timer), at each iteration the leader tries to determine if all configured targets are handled by an instance of the cluster, this is done by checking if there is a lock maintained for each configured target.

    The instances which failed to become the leader, continue to try to acquire the leader lock.

    Target distribution process#

    If the leader detects that a target does not have a lock, it triggers the target distribution process:

    • Query all the targets keys from the KV store and calculate each instance load (number of maintained gNMI targets).
    • If the target configuration includes tags, the leader selects the instance with the most matching tags (in order). If multiple instances have the same matching tags, the one with the lowest load is selected.
    • If the target doesn't have configured tags, the leader simply select the least loaded instance to handle the target's subscriptions.
    • Retrieve the selected instance API address from the local services cache.
    • Send both the target configuration as well as a target activation action to the selected instance.

    When a cluster instance gets assigned a target (target activation):

    • Acquire a key lock for that specific target.
    • Once the lock is acquired, create the configured gNMI subscriptions.
    • Maintain the target lock for the duration of the gNMI subscription.

    The whole target distribution process is repeated for each target missing a lock.

    Configuration#

    The cluster configuration is as simple as:

    # rest api address, format "address:port"
    +api: ""
    +# clustering related configuration fields
    +clustering:
    +  # the cluster name, tells with instances belong to the same cluster
    +  # it is used as part of the leader key lock, and the targets key locks
    +  # if no value is configured, the value from flag --cluster-name is used.
    +  # if the flag has the empty string as value, "default-cluster" is used.
    +  cluster-name: default-cluster
    +  # unique instance name within the cluster,
    +  # used as the value in the target locks,
    +  # used as the value in the leader lock.
    +  # if no value is configured, the value from flag --instance-name is used.
    +  # if the flag has the empty string as value, a value is generated in 
    +  # the format `gnmic-$UUID`
    +  instance-name: ""
    +  # service address to be registered in the locker(Consul)
    +  # if not defined, it defaults to the address part of the API address:port
    +  service-address: ""
    +  # gnmic instances API service watch timer
    +  # this is a long timer used by the cluster leader 
    +  # in a consul long-blocking query: 
    +  # https://www.consul.io/api-docs/features/blocking#implementation-details
    +  services-watch-timer: 60s
    +  # targets-watch-timer, targets watch timer, duration the leader waits 
    +  # between consecutive targets distributions
    +  targets-watch-timer: 20s
    +  # target-assignment-timeout, max time a leader waits for an instance to 
    +  # lock an assigned target.
    +  # if the timeout is reached the leader unassigns the target and reselects 
    +  # a different instance.
    +  target-assignment-timeout: 10s
    +  # leader wait timer, allows to configure a wait time after an instance
    +  # acquires the leader key.
    +  # this wait time goal is to give more chances to other instances to register 
    +  # their API services before the target distribution starts
    +  leader-wait-timer: 5s
    +  # ordered list of strings to be added as tags during api service 
    +  # registration in addition to `cluster-name=${cluster-name}` and 
    +  # `instance-name=${instance-name}`
    +  tags: []
    +  # locker is used to configure the KV store used for 
    +  # service registration, service discovery, leader election and targets locks
    +  locker:
    +    # type of locker, only consul is supported currently
    +    type: consul
    +    # address of the locker server
    +    address: localhost:8500
    +    # Consul Data center, defaults to dc1
    +    datacenter: 
    +    # Consul username, to be used as part of HTTP basicAuth
    +    username:
    +    # Consul password, to be used as part of HTTP basicAuth
    +    password:
    +    # Consul Token, is used to provide a per-request ACL token which overrides 
    +    # the agent's default token
    +    token:
    +    # session-ttl, session time-to-live after which a session is considered 
    +    # invalid if not renewed
    +    # upon session invalidation, all services and locks created using this session
    +    # are considered invalid.
    +    session-ttl: 10s
    +    # delay, a time duration (0s to 60s), in the event of  a session invalidation 
    +    # consul will prevent the lock from being acquired for this duration.
    +    # The purpose is to allow a gnmic instance to stop active subscriptions before 
    +    # another one takes over.
    +    delay: 5s
    +    # retry-timer, wait period between retries to acquire a lock 
    +    # in the event of client failure, key is already locked or lock lost.
    +    retry-timer: 2s
    +    # renew-period, session renew period, must be lower that session-ttl. 
    +    # if the value is greater or equal than session-ttl, is will be set to half 
    +    # of session-ttl.
    +    renew-period: 5s
    +    # debug, enable extra logging messages
    +    debug: false
    +

    A gnmic instance creates gNMI subscriptions only towards targets for which it acquired locks. It is also responsible for maintaining that lock for the duration of the subscription.

    Instance affinity#

    The target distribution process can be influenced using tags added to the target configuration.

    By default, gnmic instances register their API service with 2 tags;

    cluster-name=${clustering/cluster-name} instance-name=${clustering/instance-name}

    By adding the same tags to a target router1 configuration (below YAML), the cluster leader will "assign" router1 to instance gnmic1 in cluster my-cluster regardless of the instance load.

    targets:
    +  router1:
    +    tags:
    +      - cluster-name=my-cluster
    +      - instance-name=gnmic1
    +

    Custom tags can be added to an instance API service registration in order to customize the instance affinity logic.

    clustering:
    +  tags:
    +    - my-custom-tag=value1
    +

    Instance failure#

    In the event of an instance failure, its maintained targets locks expire, which on the next clustering/targets-watch-timer interval will be detected by the cluster leader.

    The leader then performs the same target distribution process for those targets without a lock.

    Leader reelection#

    If a cluster leader fails, one of the other instances in the cluster eventually acquires the leader lock and becomes the cluster leader.

    It then, proceeds with the targets distribution process to assign the unhandled targets to an instance in the cluster.

    Scalability#

    Using the same above-mentioned clustering mechanism, gnmic can horizontally scale the number of supported gNMI connections distributed across multiple gnmic instances.

    The collected gNMI data can then be aggregated and made available through any of the running gnmic instances, regardless of whether that instance collected the data from the target or not.

    The data aggregation is done by chaining gnmic outputs and inputs to build a gNMI data pipeline.

    In the diagram below, the gnmic instances on the left and right side of NATS server can be identical.

    \ No newline at end of file diff --git a/user_guide/actions/actions/index.html b/user_guide/actions/actions/index.html new file mode 100644 index 00000000..9784845f --- /dev/null +++ b/user_guide/actions/actions/index.html @@ -0,0 +1,270 @@ + Actions - gNMIc

    Actions#

    gNMIc supports running actions as result of an event, possible triggering events are:

    • A gNMI SubscribeResponse or GetReponse message is received and matches certain criteria.
    • A target is discovered or deleted by a target loader.

    There are 4 types of actions:

    • http: build and send an HTTP request
    • gNMI: run a Get, Set or Subscribe ONCE gNMI RPC as a gNMI client
    • template: execute a Go template against the received input
    • script: run arbitrary shell scripts/commands.

    The actions are executed in sequence.

    An action can use the result of any previous action as one of it inputs using the Go Template syntax {{ .Env.$action_name }} or {{ index .Env "$action_name"}}

    HTTP Action#

    Using the HTTP action you can send an HTTP request to a server.

    The request body can be customized using Go Templates that take the event message or the discovered target as input.

    actions:
    +  counter1_alert:
    +    # action type
    +    type: http
    +    # HTTP method
    +    method: POST
    +    # target url, can be a go template
    +    url: http://remote-server:8080/
    +    # http headers to add to the request
    +    headers: 
    +      content-type: application/text
    +    # http request timeout
    +    timeout: 5s
    +    # go template used to build the request body.
    +    # if left empty the whole event message is added as a json object to the request's body
    +    body: '"counter1" crossed threshold, value={{ index .Values "counter1" }}'
    +    # enable extra logging
    +    debug: false
    +

    gNMI Action#

    Using the gNMI action you can trigger a gNMI Get, Set or Subscribe ONCE RPC.

    Just like the HTTP action the RPC fields can be customized using Go Templates

    actions:
    +  my_gnmi_action:
    +    # action type
    +    type: gnmi
    +    # gNMI rpc, defaults to `get`, 
    +    # if `set` is used it will default to a set update.
    +    # to trigger a set replace, use `set-replace`.
    +    # `subscribe` is always a subscribe with mode=ONCE
    +    # possible values: `get`, `set`, `set-update`, `set-replace`, `set-delete`, `sub`, `subscribe`
    +    rpc: set
    +    # the target router, it defaults to the value in tag "source"
    +    # the value `all` means all known targets
    +    target: '{{ index .Event.Tags "source" }}'
    +    # paths templates to build xpaths
    +    paths:
    +      - | 
    +        {{ if eq ( index .Event.Tags "interface_name" ) "ethernet-1/1"}}
    +          {{$interfaceName := "ethernet-1/2"}}
    +        {{else}}
    +          {{$interfaceName := "ethernet-1/1"}}
    +        {{end}}
    +        /interfaces/interface[name={{$interfaceName}}]/admin-state
    +    # values templates to build the values in case of set-update or set-replace
    +    values:
    +      - "enable"
    +    # data-type in case of get RPC, one of: ALL, CONFIG, STATE, OPERATIONAL
    +    data-type: ALL
    +    # gNMI encoding, defaults to json
    +    encoding: json
    +    # debug, enable extra logging
    +    debug: false
    +

    Template Action#

    The Template action allows to combine different data sources and produce custom payloads to be writen to a remote server or simply to a file.

    The template is a Go Template that is executed against the Input message that triggered the action, any variable defined by the trigger processor as well as the results of any previous action.

    Data Template syntax
    Input Messge {{ .Input }}
    Trigger Variables {{ .Vars }}
    Previous actions results {{ .Env.$action_name }} or {{ index .Env "$action_name"}}
    actions:
    +  awesome_template:
    +    # action type
    +    type: template
    +    # template string, if not present template-file applies.
    +    template: '{{ . }}'
    +    # path to a file, or a glob.
    +    # applies only if `.template `is not set.
    +    # if not template and template-file are not set, 
    +    # the default template `{{ . }}` is used.
    +    template-file:
    +    # string, either `stdout` or a path to a file
    +    # the result of executing to template will be written to the file
    +    # specified by .output
    +    output:
    +    # debug, enable extra logging
    +    debug: false
    +

    Script Action#

    The Script action allows to run arbitrary scripts as a result of an event trigger.

    The commands to be executed can be specified using the field command, e.g:

    actions:
    +  weather:
    +    type: script
    +    shell: /bin/bash
    +    command: | 
    +      curl wttr.in
    +      curl cheat.sh
    +

    Or using the field file, e.g:

    actions:
    +  exec:
    +    type: script
    +    file: ./my_executable_script.sh
    +

    When using command, the shell interpreter can be set using shell field. Otherwise it defaults to /bin/bash.

    Examples#

    Add basic configuration to targets upon discovery#

    Referencing Actions under a target loader allows to run then in sequence when a target is discovered.

    This allows to add some basic configuration to a target upon discovery before starting the gNMI subscriptions

    In the below example, a docker loader is defined. It discovers Docker containers with label clab-node-kind=srl and adds them as gNMI targets. Before the targets are added to the target's list for subscriptions, a list of actions are executed: config_interfaces, config_subinterfaces and config_network_instances

    username: admin
    +password: NokiaSrl1!
    +skip-verify: true
    +encoding: ascii
    +log: true
    +
    +subscriptions:
    +  sub1:
    +    paths:
    +      - /interface/statistics
    +      - /network-instance/statistics
    +
    +loader:
    +  type: docker
    +  filters:
    +    - containers:
    +      - label: clab-node-kind=srl
    +
    +  on-add:
    +    - config_interfaces
    +    - config_sub_interfaces
    +    - config_netins
    +
    +outputs:
    +  out:
    +    type: file
    +    format: event
    +    filename: /path/to/file
    +
    +actions:
    +  config_interfaces:
    +    name: config_interfaces
    +    type: gnmi
    +    target: '{{ .Input }}'
    +    rpc: set
    +    encoding: json_ietf
    +    debug: true
    +    paths:
    +      - /interface[name=ethernet-1/1]/admin-state
    +      - /interface[name=ethernet-1/2]/admin-state 
    +    values:
    +      - enable
    +      - enable
    +  config_subinterfaces:
    +    name: config_subinterfaces
    +    type: gnmi
    +    target: '{{ .Input }}'
    +    rpc: set
    +    encoding: json_ietf
    +    debug: true
    +    paths:
    +      - /interface[name=ethernet-1/1]/subinterface[index=0]/admin-state
    +      - /interface[name=ethernet-1/2]/subinterface[index=0]/admin-state 
    +    values:
    +      - enable
    +      - enable
    +  config_network_instances:
    +    name: config_network_instances
    +    type: gnmi
    +    target: '{{ .Input }}'
    +    rpc: set
    +    encoding: json_ietf
    +    debug: true
    +    paths:
    +      - /network-instance[name=default]/admin-state
    +      - /network-instance[name=default]/interface
    +      - /network-instance[name=default]/interface
    +    values:
    +      - enable
    +      - '{"name": "ethernet-1/1.0"}'
    +      - '{"name": "ethernet-1/2.0"}'
    +

    Clone a network topology and deploy it using containerlab#

    Using lldp neighbor information it's possible to build a containerlab topology using gnmic actions.

    In the below confoguration file, an event processor called clone-topology is defined.

    When triggered it will run a series of actions to gather information (chassis type, lldp neighbors, configuration,...) from the defined targets.

    It then builds a containerlab topology from a defined template and the gathered info, writes it to a file and runs a clab deploy command.

    username: admin
    +password: NokiaSrl1!
    +skip-verify: true
    +encoding: json_ietf
    +# log: true
    +
    +targets:
    +  srl1:
    +  srl2:
    +  srl3:
    +
    +processors:
    +  clone-topology:
    +    event-trigger:
    +      # debug: true
    +      actions:
    +        - chassis  
    +        - lldp  
    +        - read_config  
    +        - write_config 
    +        - clab_topo         
    +        - deploy_topo
    +
    +actions:
    +  chassis:
    +    name: chassis
    +    type: gnmi
    +    target: all
    +    rpc: sub
    +    encoding: json_ietf
    +    #debug: true
    +    format: event
    +    paths:
    +      - /platform/chassis/type
    +
    +  lldp:
    +    name: lldp
    +    type: gnmi
    +    target: all
    +    rpc: sub
    +    encoding: json_ietf
    +    #debug: true
    +    format: event
    +    paths:
    +      - /system/lldp/interface[name=ethernet-*]
    +
    +  read_config:
    +    name: read_config
    +    type: gnmi
    +    target: all
    +    rpc: get
    +    data-type: config
    +    encoding: json_ietf
    +    #debug: true
    +    paths:
    +      - /
    +
    +  write_config:
    +    name: write_config
    +    type: template
    +    template: |
    +      {{- range $n, $m := .Env.read_config }}
    +      {{- $filename := print $n  ".json"}}
    +          {{ file.Write $filename (index $m 0 "updates" 0 "values" "" | data.ToJSONPretty "  " ) }}
    +          {{- end }}
    +        #debug: true
    +
    +  clab_topo:
    +    name: clab_topo
    +    type: template
    +    #debug: true
    +    output: gnmic.clab.yaml
    +    template: |
    +          name: gNMIc-action-generated
    +
    +          topology:
    +            defaults:
    +              kind: srl
    +            kinds:
    +              srl:
    +                image: ghcr.io/nokia/srlinux:latest
    +
    +            nodes:
    +          {{- range $n, $m := .Env.lldp }}
    +            {{- $type := index $.Env.chassis $n 0 0 "values" "/srl_nokia-platform:platform/srl_nokia-platform-chassis:chassis/type" }}
    +            {{- $type = $type | strings.ReplaceAll "7220 IXR-D1" "ixrd1" }}
    +            {{- $type = $type | strings.ReplaceAll "7220 IXR-D2" "ixrd2" }}
    +            {{- $type = $type | strings.ReplaceAll "7220 IXR-D3" "ixrd3" }}
    +            {{- $type = $type | strings.ReplaceAll "7250 IXR-6" "ixr6" }}
    +            {{- $type = $type | strings.ReplaceAll "7250 IXR-10" "ixr10" }}
    +            {{- $type = $type | strings.ReplaceAll "7220 IXR-H1" "ixrh1" }}
    +            {{- $type = $type | strings.ReplaceAll "7220 IXR-H2" "ixrh2" }}
    +            {{- $type = $type | strings.ReplaceAll "7220 IXR-H3" "ixrh3" }}
    +              {{ $n | strings.TrimPrefix "clab-test1-" }}:
    +                type: {{ $type }}
    +                startup-config: {{ print $n ".json"}}
    +          {{- end }}
    +
    +            links:
    +          {{- range $n, $m := .Env.lldp }}
    +            {{- range $rsp := $m }}
    +              {{- range $ev := $rsp }}
    +                {{- if index $ev.values "/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name" }}
    +                {{- $node1 := $ev.tags.source | strings.TrimPrefix "clab-test1-" }}
    +                {{- $iface1 := $ev.tags.interface_name | strings.ReplaceAll "ethernet-" "e" | strings.ReplaceAll "/" "-" }}
    +                {{- $node2 := index $ev.values "/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name" }}
    +                {{- $iface2 := index $ev.values "/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/port-id" | strings.ReplaceAll "ethernet-" "e" | strings.ReplaceAll "/" "-" }}
    +                  {{- if lt $node1 $node2 }}
    +              - endpoints: ["{{ $node1 }}:{{ $iface1 }}", "{{ $node2 }}:{{ $iface2 }}"]
    +                  {{- end }}
    +                {{- end }}
    +              {{- end }}
    +            {{- end }}
    +          {{- end }}
    +
    +  deploy_topo:  
    +    name: deploy_topo
    +    type: script
    +    command: sudo clab dep -t gnmic.clab.yaml --reconfigure
    +    debug: true
    +

    The above described processor can be triggered with the below command:

    gnmic --config clone.yaml get --path /system/name --processor clone-topology
    +
    \ No newline at end of file diff --git a/user_guide/api/api_intro/index.html b/user_guide/api/api_intro/index.html new file mode 100644 index 00000000..c3a3c4f4 --- /dev/null +++ b/user_guide/api/api_intro/index.html @@ -0,0 +1,39 @@ + Introduction - gNMIc

    Introduction

    A limited set of REST endpoints are supported, these are mainly used to allow for a clustered deployment for multiple gnmic instances.

    The API can be used to automate (to a certain extent) the targets configuration loading and starting/stopping subscriptions.

    Configuration#

    Enabling the API server can be done via a command line flag:

    gnmic --config gnmic.yaml subscribe --api ":7890"
    +

    via ENV variable: GNMIC_API=':7890'

    Or via file configuration, by adding the below line to the config file:

    api: ":7890"
    +

    More advanced API configuration options (like a secure API Server) can be achieved by setting the fields under api-server.

    api-server:
    +  # string, in the form IP:port, the IP part can be omitted.
    +  # if not set, it defaults to the value of `api` in the file main level.
    +  # if `api` is not set, the default is `:7890`
    +  address: :7890
    +  # duration, the server timeout.
    +  # The set value is equally split between read and write timeouts
    +  timeout: 10s
    +  # tls config
    +  tls:
    +    # string, path to the CA certificate file,
    +    # this certificate is used to verify the clients certificates.
    +    ca-file:
    +    # string, server certificate file.
    +    cert-file:
    +    # string, server key file.
    +    key-file:
    +    # string, one of `"", "request", "require", "verify-if-given", or "require-verify" 
    +    #  - request:         The server requests a certificate from the client but does not 
    +    #                     require the client to send a certificate. 
    +    #                     If the client sends a certificate, it is not required to be valid.
    +    #  - require:         The server requires the client to send a certificate and does not 
    +    #                     fail if the client certificate is not valid.
    +    #  - verify-if-given: The server requests a certificate, 
    +    #                     does not fail if no certificate is sent. 
    +    #                     If a certificate is sent it is required to be valid.
    +    #  - require-verify:  The server requires the client to send a valid certificate.
    +    #
    +    # if no ca-file is present, `client-auth` defaults to ""`
    +    # if a ca-file is set, `client-auth` defaults to "require-verify"`
    +    client-auth: ""
    +  # boolean, if true, the server will also handle the path /metrics and serve 
    +  # gNMIc's enabled prometheus metrics.
    +  enable-metrics: false
    +  # boolean, enables extra debug log printing
    +  debug: false
    +

    API Endpoints#

    \ No newline at end of file diff --git a/user_guide/api/cluster/index.html b/user_guide/api/cluster/index.html new file mode 100644 index 00000000..bfbe7ec9 --- /dev/null +++ b/user_guide/api/cluster/index.html @@ -0,0 +1,245 @@ + Cluster - gNMIc

    Cluster

    GET /api/v1/cluster#

    Request gNMIc cluster state and details

    Returns gNMIc cluster state and details

    curl --request GET gnmic-api-address:port/api/v1/cluster
    +
    {
    +    "name": "collectors",
    +    "number-of-locked-targets": 70,
    +    "leader": "clab-telemetry-gnmic1",
    +    "members": [
    +        {
    +            "name": "clab-telemetry-gnmic1",
    +            "api-endpoint": "clab-telemetry-gnmic1:7890",
    +            "is-leader": true,
    +            "number-of-locked-nodes": 23,
    +            "locked-targets": [
    +                "clab-lab2-leaf6",
    +                "clab-lab5-spine2",
    +                "clab-lab4-leaf4",
    +                "clab-lab2-leaf8",
    +                "clab-lab3-leaf2",
    +                "clab-lab5-spine1",
    +                "clab-lab1-spine1",
    +                "clab-lab2-super-spine2",
    +                "clab-lab3-super-spine1",
    +                "clab-lab4-spine3",
    +                "clab-lab2-spine3",
    +                "clab-lab3-leaf7",
    +                "clab-lab5-leaf7",
    +                "clab-lab5-leaf8",
    +                "clab-lab1-spine2",
    +                "clab-lab4-leaf8",
    +                "clab-lab4-leaf1",
    +                "clab-lab4-spine1",
    +                "clab-lab2-spine2",
    +                "clab-lab3-spine2",
    +                "clab-lab1-leaf8",
    +                "clab-lab3-leaf8",
    +                "clab-lab4-leaf2"
    +            ]
    +        },
    +        {
    +            "name": "clab-telemetry-gnmic2",
    +            "api-endpoint": "clab-telemetry-gnmic2:7891",
    +            "number-of-locked-nodes": 24,
    +            "locked-targets": [
    +                "clab-lab3-leaf6",
    +                "clab-lab1-leaf7",
    +                "clab-lab2-leaf3",
    +                "clab-lab5-leaf5",
    +                "clab-lab1-super-spine1",
    +                "clab-lab3-leaf5",
    +                "clab-lab4-super-spine1",
    +                "clab-lab5-leaf6",
    +                "clab-lab2-spine1",
    +                "clab-lab3-leaf3",
    +                "clab-lab4-leaf3",
    +                "clab-lab2-leaf4",
    +                "clab-lab4-super-spine2",
    +                "clab-lab1-spine3",
    +                "clab-lab3-leaf4",
    +                "clab-lab5-spine4",
    +                "clab-lab1-leaf4",
    +                "clab-lab2-leaf2",
    +                "clab-lab2-super-spine1",
    +                "clab-lab4-spine4",
    +                "clab-lab5-leaf2",
    +                "clab-lab5-leaf4",
    +                "clab-lab4-leaf7",
    +                "clab-lab1-spine4"
    +            ]
    +        },
    +            {
    +            "name": "clab-telemetry-gnmic3",
    +            "api-endpoint": "clab-telemetry-gnmic3:7892",
    +            "number-of-locked-nodes": 23,
    +            "locked-targets": [
    +                "clab-lab1-leaf5",
    +                "clab-lab3-spine3",
    +                "clab-lab1-leaf1",
    +                "clab-lab2-spine4",
    +                "clab-lab1-super-spine2",
    +                "clab-lab5-leaf3",
    +                "clab-lab4-spine2",
    +                "clab-lab1-leaf3",
    +                "clab-lab5-spine3",
    +                "clab-lab3-super-spine2",
    +                "clab-lab2-leaf5",
    +                "clab-lab1-leaf2",
    +                "clab-lab1-leaf6",
    +                "clab-lab4-leaf5",
    +                "clab-lab2-leaf7",
    +                "clab-lab3-leaf1",
    +                "clab-lab2-leaf1",
    +                "clab-lab3-spine1",
    +                "clab-lab5-leaf1",
    +                "clab-lab5-super-spine2",
    +                "clab-lab4-leaf6",
    +                "clab-lab3-spine4",
    +                "clab-lab5-super-spine1"
    +            ]
    +        }
    +    ]
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    GET /api/v1/cluster/members#

    Query gNMIc cluster members

    Returns a list of gNMIc cluster members with details

    curl --request GET gnmic-api-address:port/api/v1/cluster/members
    +
    [
    +    {
    +        "name": "clab-telemetry-gnmic1",
    +        "api-endpoint": "http://clab-telemetry-gnmic1:7890",
    +        "is-leader": true,
    +        "number-of-locked-nodes": 23,
    +        "locked-targets": [
    +            "clab-lab2-spine3",
    +            "clab-lab5-spine1",
    +            "clab-lab2-super-spine2",
    +            "clab-lab4-leaf2",
    +            "clab-lab4-leaf4",
    +            "clab-lab5-spine2",
    +            "clab-lab1-leaf8",
    +            "clab-lab4-spine1",
    +            "clab-lab5-leaf7",
    +            "clab-lab2-spine2",
    +            "clab-lab3-super-spine1",
    +            "clab-lab1-spine1",
    +            "clab-lab3-leaf2",
    +            "clab-lab3-spine2",
    +            "clab-lab2-leaf6",
    +            "clab-lab4-leaf1",
    +            "clab-lab4-spine3",
    +            "clab-lab1-spine2",
    +            "clab-lab2-leaf8",
    +            "clab-lab3-leaf8",
    +            "clab-lab5-leaf8",
    +            "clab-lab3-leaf7",
    +            "clab-lab4-leaf8"
    +        ]
    +    },
    +    {
    +        "name": "clab-telemetry-gnmic2",
    +        "api-endpoint": "http://clab-telemetry-gnmic2:7891",
    +        "number-of-locked-nodes": 24,
    +        "locked-targets": [
    +            "clab-lab1-spine4",
    +            "clab-lab2-leaf2",
    +            "clab-lab3-leaf3",
    +            "clab-lab4-super-spine1",
    +            "clab-lab5-leaf4",
    +            "clab-lab1-spine3",
    +            "clab-lab1-leaf4",
    +            "clab-lab3-leaf6",
    +            "clab-lab5-leaf2",
    +            "clab-lab2-leaf4",
    +            "clab-lab3-leaf4",
    +            "clab-lab4-leaf3",
    +            "clab-lab5-spine4",
    +            "clab-lab3-leaf5",
    +            "clab-lab4-super-spine2",
    +            "clab-lab1-leaf7",
    +            "clab-lab2-leaf3",
    +            "clab-lab2-super-spine1",
    +            "clab-lab5-leaf6",
    +            "clab-lab2-spine1",
    +            "clab-lab1-super-spine1",
    +            "clab-lab4-leaf7",
    +            "clab-lab4-spine4",
    +            "clab-lab5-leaf5"
    +        ]
    +    },
    +    {
    +        "name": "clab-telemetry-gnmic3",
    +        "api-endpoint": "http://clab-telemetry-gnmic3:7892",
    +        "number-of-locked-nodes": 23,
    +        "locked-targets": [
    +            "clab-lab1-leaf3",
    +            "clab-lab1-leaf5",
    +            "clab-lab3-spine4",
    +            "clab-lab3-spine3",
    +            "clab-lab1-leaf1",
    +            "clab-lab1-leaf6",
    +            "clab-lab2-leaf5",
    +            "clab-lab4-leaf6",
    +            "clab-lab5-leaf1",
    +            "clab-lab5-leaf3",
    +            "clab-lab5-super-spine2",
    +            "clab-lab2-spine4",
    +            "clab-lab5-super-spine1",
    +            "clab-lab4-spine2",
    +            "clab-lab3-spine1",
    +            "clab-lab4-leaf5",
    +            "clab-lab5-spine3",
    +            "clab-lab1-super-spine2",
    +            "clab-lab2-leaf1",
    +            "clab-lab3-super-spine2",
    +            "clab-lab3-leaf1",
    +            "clab-lab1-leaf2",
    +            "clab-lab2-leaf7"
    +        ]
    +    }
    +]
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    GET /api/v1/cluster/leader#

    Queries the cluster leader deatils

    Returns details of the gNMIc cluster leader.

    curl --request POST gnmic-api-address:port/api/v1/cluster/leader
    +
    [
    +    {
    +        "name": "clab-telemetry-gnmic1",
    +        "api-endpoint": "http://clab-telemetry-gnmic1:7890",
    +        "is-leader": true,
    +        "number-of-locked-nodes": 23,
    +        "locked-targets": [
    +            "clab-lab4-leaf8",
    +            "clab-lab5-leaf8",
    +            "clab-lab1-spine2",
    +            "clab-lab3-leaf7",
    +            "clab-lab4-leaf4",
    +            "clab-lab2-leaf8",
    +            "clab-lab2-spine3",
    +            "clab-lab4-leaf1",
    +            "clab-lab4-leaf2",
    +            "clab-lab4-spine3",
    +            "clab-lab5-spine2",
    +            "clab-lab1-spine1",
    +            "clab-lab2-leaf6",
    +            "clab-lab5-leaf7",
    +            "clab-lab1-leaf8",
    +            "clab-lab3-leaf8",
    +            "clab-lab3-spine2",
    +            "clab-lab3-super-spine1",
    +            "clab-lab5-spine1",
    +            "clab-lab2-super-spine2",
    +            "clab-lab3-leaf2",
    +            "clab-lab2-spine2",
    +            "clab-lab4-spine1"
    +        ]
    +    }
    +]
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +
    \ No newline at end of file diff --git a/user_guide/api/configuration/index.html b/user_guide/api/configuration/index.html new file mode 100644 index 00000000..e42bb70b --- /dev/null +++ b/user_guide/api/configuration/index.html @@ -0,0 +1,159 @@ + Configuration - gNMIc

    Configuration#

    /api/v1/config#

    GET /api/v1/config#

    Request all gnmic configuration

    Returns the whole configuration as json

    curl --request GET gnmic-api-address:port/api/v1/config
    +
    {
    +    "username": "admin",
    +    "password": "admin",
    +    "port": "57400",
    +    "encoding": "json_ietf",
    +    "insecure": true,
    +    "timeout": 10000000000,
    +    "log": true,
    +    "max-msg-size": 536870912,
    +    "prometheus-address": ":8989",
    +    "retry": 10000000000,
    +    "api": ":7890",
    +    "get-type": "ALL",
    +    "set-delimiter": ":::",
    +    "subscribe-mode": "stream",
    +    "subscribe-stream-mode": "target-defined",
    +    "subscribe-cluster-name": "default-cluster",
    +    "subscribe-lock-retry": 5000000000,
    +    "path-path-type": "xpath",
    +    "prompt-max-suggestions": 10,
    +    "prompt-prefix-color": "dark_blue",
    +    "prompt-suggestions-bg-color": "dark_blue",
    +    "prompt-description-bg-color": "dark_gray",
    +    "targets": {
    +        "192.168.1.131:57400": {
    +            "name": "192.168.1.131:57400",
    +            "address": "192.168.1.131:57400",
    +            "username": "admin",
    +            "password": "admin",
    +            "timeout": 10000000000,
    +            "insecure": true,
    +            "skip-verify": false,
    +            "buffer-size": 1000,
    +            "retry-timer": 10000000000
    +        },
    +        "192.168.1.132:57400": {
    +            "name": "192.168.1.132:57400",
    +            "address": "192.168.1.131:57400",
    +            "username": "admin",
    +            "password": "admin",
    +            "timeout": 10000000000,
    +            "insecure": true,
    +            "skip-verify": false,
    +            "buffer-size": 1000,
    +            "retry-timer": 10000000000
    +        }
    +    },
    +    "subscriptions": {
    +        "sub1": {
    +            "name": "sub1",
    +            "paths": [
    +                "/interface/statistics"
    +            ],
    +            "mode": "stream",
    +            "stream-mode": "sample",
    +            "encoding": "json_ietf",
    +            "sample-interval": 1000000000
    +        }
    +    },
    +    "Outputs": {
    +        "output2": {
    +            "address": "192.168.1.131:4222",
    +            "format": "event",
    +            "subject": "telemetry",
    +            "type": "nats",
    +            "write-timeout": "10s"
    +        }
    +    },
    +    "inputs": {},
    +    "processors": {},
    +    "clustering": {
    +        "cluster-name": "cluster1",
    +        "instance-name": "gnmic1",
    +        "service-address": "gnmic1",
    +        "services-watch-timer": 60000000000,
    +        "targets-watch-timer": 5000000000,
    +        "leader-wait-timer": 5000000000,
    +        "locker": {
    +            "address": "consul-agent:8500",
    +            "type": "consul"
    +        }
    +    }
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    /api/v1/config/targets#

    GET /api/v1/config/targets#

    Request all targets configuration

    returns the targets configuration as json

    curl --request GET gnmic-api-address:port/api/v1/config/targets
    +
    {
    +    "192.168.1.131:57400": {
    +        "name": "192.168.1.131:57400",
    +        "address": "192.168.1.131:57400",
    +        "username": "admin",
    +        "password": "admin",
    +        "timeout": 10000000000,
    +        "insecure": true,
    +        "skip-verify": false,
    +        "buffer-size": 1000,
    +        "retry-timer": 10000000000
    +    },
    +    "192.168.1.132:57400": {
    +        "name": "192.168.1.132:57400",
    +        "address": "192.168.1.131:57400",
    +        "username": "admin",
    +        "password": "admin",
    +        "timeout": 10000000000,
    +        "insecure": true,
    +        "skip-verify": false,
    +        "buffer-size": 1000,
    +        "retry-timer": 10000000000
    +    }
    +}
    +
    {
    +    "errors": [
    +        "no targets found",
    +    ]
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    GET /api/v1/config/targets/{id}#

    Request a single target configuration

    Returns a single target configuration as json, where {id} is the target ID

    curl --request GET gnmic-api-address:port/api/v1/config/targets/192.168.1.131:57400
    +
    {
    +    "name": "192.168.1.131:57400",
    +    "address": "192.168.1.131:57400",
    +    "username": "admin",
    +    "password": "admin",
    +    "timeout": 10000000000,
    +    "insecure": true,
    +    "skip-verify": false,
    +    "buffer-size": 1000,
    +    "retry-timer": 10000000000
    +}
    +
    {
    +    "errors": [
    +        "target $target not found",
    +    ]
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    POST /api/v1/config/targets#

    Add a new target to gnmic configuration

    Expected request body is a single target config as json

    Returns an empty body if successful.

    curl --request POST -H "Content-Type: application/json" \
    +     -d '{"address": "10.10.10.10:57400", "username": "admin", "password": "admin", "insecure": true}' \
    +     gnmic-api-address:port/api/v1/config/targets
    +
    
    +
    
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    DELETE /api/v1/config/targets/{id}#

    Deletes a target {id} configuration, all active subscriptions are terminated.

    Returns an empty body

    curl --request DELETE gnmic-api-address:port/api/v1/config/targets/192.168.1.131:57400
    +
    
    +

    /api/v1/config/subscriptions#

    GET /api/v1/config/subscriptions#

    Request all the configured subscriptions.

    Returns the subscriptions configuration as json

    /api/v1/config/outputs#

    GET /api/v1/config/outputs#

    Request all the configured outputs.

    Returns the outputs configuration as json

    /api/v1/config/inputs#

    GET /api/v1/config/inputs#

    Request all the configured inputs.

    Returns the outputs configuration as json

    /api/v1/config/processors#

    GET /api/v1/config/processors#

    Request all the configured processors.

    Returns the processors configuration as json

    /api/v1/config/clustering#

    GET /api/v1/config/clustering#

    Request the clustering configuration.

    Returns the clustering configuration as json

    \ No newline at end of file diff --git a/user_guide/api/other/index.html b/user_guide/api/other/index.html new file mode 100644 index 00000000..81b671d1 --- /dev/null +++ b/user_guide/api/other/index.html @@ -0,0 +1,5 @@ + Other - gNMIc

    Other#

    /api/v1/healthz#

    GET /api/v1/healthz#

    Health check endpoint for Kubernetes or similar

    curl --request GET gnmic-api-address:port/api/v1/healthz
    +
    {
    +    "status": "healthy"
    +}
    +
    \ No newline at end of file diff --git a/user_guide/api/targets/index.html b/user_guide/api/targets/index.html new file mode 100644 index 00000000..1cff1230 --- /dev/null +++ b/user_guide/api/targets/index.html @@ -0,0 +1,124 @@ + Targets - gNMIc

    Targets

    GET /api/v1/targets#

    Request all active targets details.

    Returns all active targets as json

    curl --request GET gnmic-api-address:port/api/v1/targets
    +
    {
    +    "192.168.1.131:57400": {
    +        "config": {
    +            "name": "192.168.1.131:57400",
    +            "address": "192.168.1.131:57400",
    +            "username": "admin",
    +            "password": "admin",
    +            "timeout": 10000000000,
    +            "insecure": true,
    +            "skip-verify": false,
    +            "buffer-size": 1000,
    +            "retry-timer": 10000000000
    +        },
    +        "subscriptions": {
    +            "sub1": {
    +                "name": "sub1",
    +                "paths": [
    +                    "/interface/statistics"
    +                ],
    +                "mode": "stream",
    +                "stream-mode": "sample",
    +                "encoding": "json_ietf",
    +                "sample-interval": 1000000000
    +            }
    +        }
    +    },
    +    "192.168.1.131:57401": {
    +        "config": {
    +            "name": "192.168.1.131:57401",
    +            "address": "192.168.1.131:57401",
    +            "username": "admin",
    +            "password": "admin",
    +            "timeout": 10000000000,
    +            "insecure": true,
    +            "skip-verify": false,
    +            "buffer-size": 1000,
    +            "retry-timer": 10000000000
    +        },
    +        "subscriptions": {
    +            "sub1": {
    +                "name": "sub1",
    +            "paths": [
    +                "/interface/statistics"
    +            ],
    +            "mode": "stream",
    +            "stream-mode": "sample",
    +            "encoding": "json_ietf",
    +            "sample-interval": 1000000000
    +            }
    +        }
    +    }
    +}
    +
    {
    +    "errors": [
    +        "no targets found"
    +    ]
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    GET /api/v1/targets/{id}#

    Query a single target details, if active.

    Returns a single target if active as json, where {id} is the target ID

    curl --request GET gnmic-api-address:port/targets/192.168.1.131:57400
    +
    {
    +    "config": {
    +        "name": "192.168.1.131:57400",
    +        "address": "192.168.1.131:57400",
    +        "username": "admin",
    +        "password": "admin",
    +        "timeout": 10000000000,
    +        "insecure": true,
    +        "skip-verify": false,
    +        "buffer-size": 1000,
    +        "retry-timer": 10000000000
    +    },
    +    "subscriptions": {
    +        "sub1": {
    +            "name": "sub1",
    +            "paths": [
    +                "/interface/statistics"
    +            ],
    +            "mode": "stream",
    +            "stream-mode": "sample",
    +            "encoding": "json_ietf",
    +            "sample-interval": 1000000000
    +        }
    +    }
    +}
    +
    {
    +    "errors": [
    +        "no targets found"
    +    ]
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    POST /api/v1/targets/{id}#

    Starts a single target subscriptions, where {id} is the target ID

    Returns an empty body if successful.

    curl --request POST gnmic-api-address:port/api/v1/targets/192.168.1.131:57400
    +
    
    +
    {
    +    "errors": [
    +        "target $target not found"
    +    ]
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +

    DELETE /api/v1/targets/{id}#

    Stops a single target active subscriptions, where {id} is the target ID

    Returns an empty body if successful.

    curl --request DELETE gnmic-api-address:port/api/v1/targets/192.168.1.131:57400
    +
    
    +
    {
    +    "errors": [
    +        "target $target not found"
    +    ]
    +}
    +
    {
    +    "errors": [
    +        "Error Text"
    +    ]
    +}
    +
    \ No newline at end of file diff --git a/user_guide/caching/index.html b/user_guide/caching/index.html new file mode 100644 index 00000000..50bfdd6a --- /dev/null +++ b/user_guide/caching/index.html @@ -0,0 +1,96 @@ + Caching - gNMIc

    Caching

    Caching refers to the process of storing the collected gNMI updates before sending them out to the intended output(s).

    By default, gNMIc outputs send out the received gNMI updates as they arrive (i.e without storing them).

    A cache is used to store the received updates when the gnmi-server functionality is enabled and (optionally) when influxdb and prometheus outputs are enabled to allow for advanced data pipeline processing.

    Caching messages before writing them to a remote location allows implementing a few use cases like rate limiting, batch processing, data replication, etc.

    Caching support for other outputs is planned.

    How does it work?#

    When caching is enabled for a certain output, the received gNMI updates are not written directly to the output remote server (for e.g: InfluxDB server), but rather cached locally until the cache-flush-timer is reached (in the case of an influxdb output) or when the output receives a Prometheus scrape request (in the case of a prometheus output).

    The below diagram shows how an InfluxDB output works with and without cache enabled:

    The cached gNMI updates are periodically retrieved from the cache in batch then converted to events.

    If processors are defined under the output config section, they are applied to the whole list of events at once. This allows for augmentation of messages with values from other messages even if they where received in separate updates or collected from a different target/subscription.

    Enable caching#

    gnmi-server#

    The gNMI server has caching enabled by default. The cache type and its behavior can be tweaked, see here

    gnmi-server:
    +  #
    +  # other gnmi-server related attributes
    +  #
    +  cache: {}
    +

    outputs#

    Caching can be enabled per output by populating the cache attribute under the desired output:

    outputs:
    +  output1:
    +    type: prometheus
    +    #
    +    # other output related attributes
    +    #
    +    cache: {}
    +

    This enables output1 to use a cache of type oc.

    Each output has its own cache. Using a single global cache will be implemented in a future release.

    Distributed caches#

    When running multiple instances of gNMIc it's possible to synchronize the collected data between all the instances using a distributed cache.

    Each output that is configured with a remote cache will write the collected gNMI updates to the remote cache first, then syncs back all the cached data to its local cache then eventually write it to the output.

    (1) The received gNMI updates are written to the remote cache.

    (2) The output syncs the remote cache data to its local cache.

    (3) The locally cached data is written to the remote output periodically or on scape request.

    This is useful when different instances collect data from different targets and/or subscriptions. A single instance can be responsible for writing all the collected data to the output or each instance would be writing to a different output.

    Cache types#

    gNMIc supports 4 cache types. There is 1 local cache and 3 distributed caches "flavors".

    The choice of cache to use depends on the use case you are trying to implement.

    A local cache is local to the gNMIc instance i.e not exposed externally, while a distributed cache is external to the gNMIc instance, potentially shared by multiple gNMIc instances and is always combined with a local cache to sync updates between gNMIc instances.

    gNMI cache (local)#

    Is an in-memory gNMI cache based on the Openconfig gNMI cache published here

    This type of cache is ideal when running a single gNMIc instance. It is also the default cache type for the gNMI server and for an output when caching is enabled.

    Configuration:

    outputs:
    +  output1:
    +    type: prometheus # or influxdb
    +    #
    +    # other output related fields
    +    #
    +    cache: 
    +      type: oc
    +      # duration, default: 60s.
    +      # updates older than the expiration value will not be read from the cache.
    +      expiration: 60s
    +      # enable extra logging
    +      debug: false
    +

    NATS cache (distributed)#

    Is a cache type that relies on a NATS server to distribute the collected updates between gNMIc instances.

    This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths.

    Configuration:

    outputs:
    +  output1:
    +    type: prometheus # or influxdb
    +    #
    +    # other output related fields
    +    #
    +    cache:
    +      type: nats
    +      # string, address of the remote NATS server,
    +      # if left empty an in memory NATS server will be created an used.
    +      address:
    +      # string, the NATS server username.
    +      username:
    +      # string, the NATS server password.
    +      password:
    +      # string, expiration period of received messages.
    +      expiration: 60s
    +      # enable extra logging
    +      debug: false
    +

    JetStream cache (distributed)#

    Is a cache type that relies on a JetStream server to distribute the collected updates between gNMIc instances.

    This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths.

    It is planned to add gNMI historical subscriptions support using the jetstream cache type.

    Configuration:

    outputs:
    +  output1:
    +    type: prometheus # or influxdb
    +    #
    +    # other output related fields
    +    #
    +    cache:
    +      type: jetstream
    +      # string, address of the remote NATS JetStream server,
    +      # if left empty an in memory NATS JetStream server will be created an used.
    +      address:
    +      # string, the JetStream server username.
    +      username:
    +      # string, the JetStream server password.
    +      password:
    +      # duration, default: 60s.
    +      # Expiration period of received messages.
    +      expiration: 60s
    +      # int64, default: 1073741824 (1 GiB). 
    +      # Max number of bytes stored in the cache per subscription.
    +      max-bytes:
    +      # int64, default: 1048576. 
    +      # Max number of messages stored per subscription.
    +      max-msgs-per-subscription:
    +      # int, default 100. 
    +      # Batch size used by the JetStream pull subscriber.
    +      fetch-batch-size:
    +      # duration, default 100ms. 
    +      # Wait time used by the JetStream pull subscriber.
    +      fetch-wait-time:
    +      # enable extra logging
    +      debug: false      
    +

    Redis cache (distributed)#

    Is a cache type that relies on a Redis PUBSUB server to distribute the collected updates between gNMIc instances.

    This type of cache is useful when multiple gNMIc instances are subscribed to different targets and/or different gNMI paths.

    outputs:
    +  output1:
    +    type: prometheus # or influxdb
    +    #
    +    # other output related fields
    +    #
    +    cache:
    +      type: redis
    +      # string, redis server address
    +      address:
    +      # string, the Redis server username.
    +      username:
    +      # string, the Redis server password.
    +      password:
    +      # duration, default: 60s.
    +      # Expiration period of received messages.
    +      expiration: 60s
    +      # enable extra logging
    +      debug: false
    +
    \ No newline at end of file diff --git a/user_guide/configuration_env/index.html b/user_guide/configuration_env/index.html new file mode 100644 index 00000000..6f33f1b5 --- /dev/null +++ b/user_guide/configuration_env/index.html @@ -0,0 +1,8 @@ + Environment variables - gNMIc

    Environment variables

    gnmic can be configured using environment variables, it will read the environment variables starting with GNMIC_.

    The Env variable names are inline with the flag names as well as the configuration hierarchy.

    For e.g to set the gNMI username, the env variable GNMIC_USERNAME should be set.

    Constructing environment variables names#

    Flags to environment variables mapping#

    Global flags to env variable name mapping:

    Flag name ENV variable name
    --address GNMIC_ADDRESS
    --encoding GNMIC_ENCODING
    --format GNMIC_FORMAT
    --insecure GNMIC_INSECURE
    --log GNMIC_LOG
    --log-file GNMIC_LOG_FILE
    --no-prefix GNMIC_NO_PREFIX
    --password GNMIC_PASSWORD
    --prometheus-address GNMIC_PROMETHEUS_ADDRESS
    --proxy-from-env GNMIC_PROXY_FROM_ENV
    --retry GNMIC_RETRY
    --skip-verify GNMIC_SKIP_VERIFY
    --timeout GNMIC_TIMEOUT
    --tls-ca GNMIC_TLS_CA
    --tls-cert GNMIC_TLS_CERT
    --tls-key GNMIC_TLS_KEY
    --tls-max-version GNMIC_TLS_MAX_VERSION
    --tls-min-version GNMIC_TLS_MIN_VERSION
    --tls-version GNMIC_TLS_VERSION
    --log-tls-secret GNMIC_LOG_TLS_SECRET
    --username GNMIC_USERNAME
    --cluster-name GNMIC_CLUSTER_NAME
    --instance-name GNMIC_INSTANCE_NAME
    --proto-file GNMIC_PROTO_FILE
    --proto-dir GNMIC_PROTO_DIR
    --token GNMIC_TOKEN

    Configuration file to environment variables mapping#

    For configuration items that do not have a corresponding flag, the env variable will be constructed from the path elements to the variable name joined with a _.

    For e.g to set the clustering locker address, as in the yaml blob below:

    clustering:
    +  locker:
    +    address: 
    +

    the env variable GNMIC_CLUSTERING_LOCKER_ADDRESS should be set

    Note

    • Configuration items of type list cannot be set using env vars.
    • Intermediate configuration keys should not contain _ or -.

    Example:

    outputs:
    +  output1:  # <-- should not contain `_` or `-`
    +    type: prometheus
    +    listen: :9804
    +

    Is equivalent to:
    GNMIC_OUTPUTS_OUTPUT1_TYPE=prometheus
    GNMIC_OUTPUTS_OUTPUT1_LISTEN=:9804

    \ No newline at end of file diff --git a/user_guide/configuration_file/index.html b/user_guide/configuration_file/index.html new file mode 100644 index 00000000..313f5e9c --- /dev/null +++ b/user_guide/configuration_file/index.html @@ -0,0 +1,32 @@ + File configuration - gNMIc

    File configuration

    gnmic configuration by means of the command line flags is both consistent and reliable. But sometimes its not the best way forward.

    With lots of configuration options that gnmic supports it might get tedious to pass them all via CLI flags. In cases like that the file-based configuration comes handy.

    With a configuration file a user can specify all the command line flags by means of a single file. gnmic will read this file and retrieve the configuration options from it.

    What options can be in a file?#

    Configuration file allows a user to specify everything that can be supplied over the CLI and more.

    Global flags#

    All of the global flags can be put in a conf file. Consider the following example of a typical configuration file in YAML format:

    # gNMI target address; CLI flag `--address`
    +address: "10.0.0.1:57400"
    +# gNMI target user name; CLI flag `--username`
    +username: admin
    +# gNMI target user password; CLI flag `--password`
    +password: NokiaSrl1!
    +# connection mode; CLI flag `--insecure`
    +insecure: true
    +# log file location; CLI flag `--log-file`
    +log-file: /tmp/gnmic.log
    +
    With such a file located at a default path the gNMI requests can be made in a very short and concise form:

    # configuration file is read by its default path
    +gnmi capabilities
    +
    +# cfg file has all the global options set, so only the local flags are needed
    +gnmi get --path /configure/system/name
    +

    Local flags#

    Local flags have the scope of the command where they have been defined. Local flags can be put in a configuration file as well.

    To avoid flags names overlap between the different commands a command name should prepend the flag name - <cmd name>-<flag name>.

    So, for example, we can provide the path flag of a get command in the file by adding the get- prefix to the local flag name:

    address: "router.lab:57400"
    +username: admin
    +password: NokiaSrl1!
    +insecure: true
    +get-path: /configure/system/name  # `get` command local flag
    +

    Another example: the update-path flag of a set will be set-update-path in the configuration file.

    Targets#

    It is possible to specify multiple targets with different configurations (credentials, timeout,...). This is described in Multiple targets documentation article.

    Subscriptions#

    It is possible to specify multiple subscriptions and associate them with different targets in a flexible way. This configuration option is described in Multiple subscriptions documentation article.

    Outputs#

    The other mode gnmic supports (in contrast to CLI) is running as a daemon and exporting the data received from gNMI subscriptions to multiple outputs like stan/nats, kafka, file, prometheus, influxdb, etc...

    Inputs#

    gnmic supports reading gNMI data from a set of inputs and export the data to any of the configured outputs. This is used when building data pipelines with gnmic

    Repeated flags#

    If a flag can appear more than once on the CLI, it can be represented as a list in the file.

    For example one can set multiple paths for get/set/subscribe operations. In the following example we define multiple paths for the get command to operate on:

    address: "router.lab:57400"
    +username: admin
    +password: NokiaSrl1!
    +insecure: true
    +get-path:
    +    - /configure/system/name
    +    - /state/system/version
    +

    Options preference#

    Configuration passed via CLI flags and Env variables take precedence over the file config.

    Environment variables in file#

    Environment variables can be used in the configuration file and will be expanded at the time the configuration is read.

    outputs:
    +  output1:
    +    type: nats
    +    address: ${NATS_IP}:4222
    +
    \ No newline at end of file diff --git a/user_guide/configuration_flags/index.html b/user_guide/configuration_flags/index.html new file mode 100644 index 00000000..631fbfe9 --- /dev/null +++ b/user_guide/configuration_flags/index.html @@ -0,0 +1 @@ + Configuration flags - gNMIc
    \ No newline at end of file diff --git a/user_guide/configuration_intro/index.html b/user_guide/configuration_intro/index.html new file mode 100644 index 00000000..07f81ec9 --- /dev/null +++ b/user_guide/configuration_intro/index.html @@ -0,0 +1,10 @@ + Introduction - gNMIc

    Introduction

    gnmic reads configuration from three different sources, Global and local flags, environment variables and local system file.

    The different sources follow a precedence order where a configuration variable from a source take precedence over the next one in the below list:

    • global and local flags
    • Environment variables
    • configuration file

    Flags#

    See here for a complete list of the supported global and local flags.

    Environment variables#

    gnmic can also be configured using environment variables, it will read the environment variables starting with GNMIC_.

    The Env variable names are inline with the flag names as well as the configuration hierarchy.

    See here for more details on environment variables.

    File configuration#

    Configuration file that gnmic reads must be in one of the following formats: JSON, YAML, TOML, HCL or Properties.

    By default, gnmic will search for a file named .gnmic.[yml/yaml, toml, json] in the following locations and will use the first file that exists:

    • $PWD
    • $HOME
    • $XDG_CONFIG_HOME
    • $XDG_CONFIG_HOME/gnmic

    The default path can be overridden with --config flag.

    # config file default path is :
    +# $PWD/.gnmic.[yml, toml, json], or
    +# $HOME/.gnmic.[yml, toml, json], or
    +# $XDG_CONFIG_HOME/.gnmic.[yml, toml, json], or
    +# $XDG_CONFIG_HOME/gnmic/.gnmic.[yml, toml, json]
    +gnmic capabilities
    +
    +# read `cfg.yml` file located in the current directory
    +gnmic --config ./cfg.yml capabilities
    +

    If the file referenced by --config flag is not present, the default path won't be tried.

    Example of the gnmic config files are provided in the following formats: YAML, JSON, TOML.

    \ No newline at end of file diff --git a/user_guide/event_processors/event_add_tag/index.html b/user_guide/event_processors/event_add_tag/index.html new file mode 100644 index 00000000..650f13d3 --- /dev/null +++ b/user_guide/event_processors/event_add_tag/index.html @@ -0,0 +1,75 @@ + Add Tag - gNMIc

    Add Tag

    The event-add-tag processor adds a set of tags to an event message if one of the configured regular expressions in the values, value names, tags or tag names sections matches.

    It is possible to overwrite a tag if it's name already exists.

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-add-tag:
    +      # jq expression, if evaluated to true, the tags are added
    +      condition: 
    +      # list of regular expressions to be matched against the tags names, if matched, the tags are added
    +      tag-names:
    +      # list of regular expressions to be matched against the tags values, if matched, the tags are added
    +      tags:
    +      # list of regular expressions to be matched against the values names, if matched, the tags are added
    +      value-names:
    +      # list of regular expressions to be matched against the values, if matched, the tags are added
    +      values:
    +      # list of regular expressions to be matched against the deleted paths, if matched, the tags are added
    +      deletes:
    +      # boolean, if true tags are over-written with the added ones if they already exist.
    +      overwrite:
    +      # map of tags to be added
    +      add: 
    +        tag_name: tag_value
    +

    Examples#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-add-tag:
    +      value-names:
    +        - "."
    +      add: 
    +        tag_name: tag_value
    +
    {
    +  "name": "sub1",
    +  "timestamp": 1607678293684962443,
    +  "tags": {
    +    "interface_name": "mgmt0",
    +    "source": "172.20.20.5:57400"
    +  },
    +  "values": {
    +    "Carrier_Transitions": 1,
    +    "In_Broadcast_Packets": 448,
    +    "In_Error_Packets": 0,
    +    "In_Fcs_Error_Packets": 0,
    +    "In_Multicast_Packets": 47578,
    +    "In_Octets": 15557349,
    +    "In_Unicast_Packets": 6482,
    +    "Out_Broadcast_Packets": 110,
    +    "Out_Error_Packets": 0,
    +    "Out_Multicast_Packets": 10,
    +    "Out_Octets": 464766
    +  }
    +}
    +
    {
    +  "name": "sub1",
    +  "timestamp": 1607678293684962443,
    +  "tags": {
    +    "interface_name": "mgmt0",
    +    "source": "172.20.20.5:57400",
    +    "tag_name": "tag_value"
    +},
    +  "values": {
    +    "Carrier_Transitions": 1,
    +    "In_Broadcast_Packets": 448,
    +    "In_Error_Packets": 0,
    +    "In_Fcs_Error_Packets": 0,
    +    "In_Multicast_Packets": 47578,
    +    "In_Octets": 15557349,
    +    "In_Unicast_Packets": 6482,
    +    "Out_Broadcast_Packets": 110,
    +    "Out_Error_Packets": 0,
    +    "Out_Multicast_Packets": 10,
    +    "Out_Octets": 464766
    +  }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_allow/index.html b/user_guide/event_processors/event_allow/index.html new file mode 100644 index 00000000..56af6580 --- /dev/null +++ b/user_guide/event_processors/event_allow/index.html @@ -0,0 +1,101 @@ + Allow - gNMIc

    Allow

    The event-allow processor allows only messages matching the configured condition or one of the regular expressions under tags, tag-names, values or value-names.

    Non matching messages are dropped.

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-allow:
    +      # jq expression, if evaluated to true, the message is allowed
    +      condition: 
    +      # list of regular expressions to be matched against the tags names, 
    +      # if matched, the message is allowed
    +      tag-names:
    +      # list of regular expressions to be matched against the tags values,
    +      # if matched, the message is allowed
    +      tags:
    +      # list of regular expressions to be matched against the values names,
    +      # if matched, the message is allowed
    +      value-names:
    +      # list of regular expressions to be matched against the values,
    +      # if matched, the message is allowed
    +      values:
    +

    Examples#

    processors:
    +  # processor name
    +  allow-processor:
    +    # processor type
    +    event-allow:
    +      condition: ".tags.interface_name == 1/1/1"
    +
    [
    +  {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +  },
    +  {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "1/1/1",
    +        "source": "172.23.23.3:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +  }
    +]
    +
    [
    +  {
    +  },
    +  {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "1/1/1",
    +        "source": "172.23.23.3:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +  }
    +]
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_combine/index.html b/user_guide/event_processors/event_combine/index.html new file mode 100644 index 00000000..244af459 --- /dev/null +++ b/user_guide/event_processors/event_combine/index.html @@ -0,0 +1,48 @@ + Combine - gNMIc

    Combine

    The event-combine processor combines multiple processors together. This allows to declare processors once and reuse them to build more complex processors.

    Configuration#

    processors:
    +  # processor name
    +  pipeline1:
    +    # processor type
    +    event-combine:
    +      # list of regex to be matched with the values names
    +      processors: 
    +          # The "sub" processor execution condition. A jq expression.
    +        - condition: 
    +          # the processor name, should be declared in the
    +          # `processors` section.
    +          name: 
    +      # enable extra logging
    +      debug: false
    +

    Conditional Execution of Subprocessors#

    The workflow for processing event messages can include multiple subprocessors, each potentially governed by its own condition. These conditions are defined using the jq query language, enabling dynamic and precise control over when each subprocessor should be executed.

    Defining Conditions for Subprocessors#

    When configuring your subprocessors, you have the option to attach a jq-based condition to each one. The specified condition acts as a gatekeeper, determining whether the corresponding subprocessor should be activated for a particular event message.

    Condition Evaluation Process#

    For a subprocessor to run, the following criteria must be met:

    Condition Presence: If a condition is specified for the subprocessor, it must be evaluated.

    Condition Outcome: The result of the jq condition evaluation must be true.

    Combined Conditions: In scenarios where both the main processor and the subprocessor have associated conditions, both conditions must independently evaluate to true for the subprocessor to be triggered.

    Only when all relevant conditions are met will the subprocessor execute its designated operations on the event message.

    It is important to note that the absence of a condition is equivalent to a condition that always evaluates to true. Thus, if no condition is provided for a subprocessor, it will execute as long as the main processor's condition (if any) is met.

    By using conditional execution, you can build sophisticated and efficient event message processing workflows that react dynamically to the content of the messages.

    Examples#

    In the below example, we define 3 regular processors and 2 event-combine processors.

    • proc1: Allows event message that have tag "interface_name = ethernet-1/1

    • proc2: Renames values names to their path base. e.g: interface/statistics/out-octetsout-octets

    • proc3: Converts any values with a name ending with octets to int.

    • pipeline1: Combines proc1, proc2 and proc3, applying proc2 only to subscription sub1

    • pipeline2: Combines proc2 and proc3, applying proc2 only to subscription sub2

    The 2 combine processors can be linked with different outputs.

    processors:
    +  proc1:
    +    event-allow:
    +      condition: '.tags.interface_name == "ethernet-1/1"'
    +
    +  proc2:
    +    event-strings:
    +      value-names:
    +        - ".*"
    +      transforms:
    +        - path-base:
    +            apply-on: "name"
    +  proc3:
    +    event-convert:
    +      value-names: 
    +        - ".*octets$"
    +      type: int 
    +
    +
    +  pipeline1:
    +    event-combine:
    +      processors: 
    +        - name: proc1
    +        - condition: '.tags["subscription-name"] == "sub1"'
    +          name: proc2
    +        - name: proc3
    +
    +  pipeline2:
    +    event-combine:
    +      processors: 
    +        - condition: '.tags["subscription-name"] == "sub2"'
    +          name: proc2
    +        - name: proc3
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_convert/index.html b/user_guide/event_processors/event_convert/index.html new file mode 100644 index 00000000..5153cbb0 --- /dev/null +++ b/user_guide/event_processors/event_convert/index.html @@ -0,0 +1,35 @@ + Convert - gNMIc

    Convert

    The event-convert processor converts the values matching one of the regular expressions to a specific type: uint, int, string, float or bool

    Examples#

    processors:
    +  # processor name
    +  convert-int-processor:
    +    # processor type
    +    event-convert:
    +      # list of regex to be matched with the values names
    +      value-names: 
    +        - ".*octets$"
    +      # the desired value type, one of: int, uint, string, float, bool
    +      type: int 
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "/state/port/ethernet/statistics/in-octets": "7753940"
    +  }
    +}
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "/state/port/ethernet/statistics/in-octets": 7753940
    +  }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_data_convert/index.html b/user_guide/event_processors/event_data_convert/index.html new file mode 100644 index 00000000..9a920d8d --- /dev/null +++ b/user_guide/event_processors/event_data_convert/index.html @@ -0,0 +1,96 @@ + Data Convert - gNMIc

    Data Convert

    The event-data-convert processor converts data values matching one of the regular expressions from/to a specific data unit:

    Symbol Unit Symbol Unit Symbol Unit
    b Bit B Byte KiB KibiByte
    kb kiloBit KB KiloByte MiB MebiByte
    mb MegaBit MB MegaByte GiB GibiByte
    gb GigaBit GB GigaByte TiB TebiByte
    tb TeraBit TB TeraByte EiB ExbiByte
    eb ExaBit EB ExaByte ZiB ZebiByte
    ZB ZetaByte YiB YobiByte
    YB YottaByte

    The source values can be of any numeric type including a string with or without a unit, e.g: 2.3, 1KB or 1.1 TB.

    The unit of the original value can be derived as Byte from its name if it ends with -bytes, -octets, _bytes or _octets.

    Examples#

    simple conversion#

    The below processor will convert any value with a name ending in -octets from Byte to KiloByte.

    processors:
    +  # processor name
    +  convert-data-unit:
    +    # processor type
    +    event-data-convert:
    +      # list of regex to be matched with the values names
    +      value-names: 
    +        - ".*-octets$"
    +      # the source value unit, defaults to B (Byte)
    +      from: B
    +      # the desired value unit, defaults to B (Byte)
    +      to: KB
    +      # keep the original value, 
    +      # a new value name will be added with the converted value,
    +      # the new value name will be the original name with _$to as suffix 
    +      # if no regex renaming is defined using `old` and `new`
    +      keep: false
    +      # old, a regex to be used to rename the converted value
    +      old: 
    +      # new, the replacement string
    +      new:
    +      # debug, enables this processor logging
    +      debug: false
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "/state/port/ethernet/statistics/in-octets": "2048"
    +  }
    +}
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "/state/port/ethernet/statistics/in-octets": 2
    +  }
    +}
    +

    conversion with renaming#

    The below data convert processor converts any value with a name ending in -octets from Byte to Kilobyte. It will retain the original value while renaming the new value name by replacing -octets with -kilobytes.

    processors:
    +  # processor name
    +  convert-data-unit:
    +    # processor type
    +    event-data-convert:
    +      # list of regex to be matched with the values names
    +      value-names: 
    +        - ".*-octets$"
    +      # the source value unit, defaults to B (Byte)
    +      from: B
    +      # the desired value unit, defaults to B (Byte)
    +      to: KB
    +      # keep the original value, 
    +      # a new value name will be added with the converted value,
    +      # the new value name will be the original name with _$to as suffix 
    +      # if no regex renaming is defined using `old` and `new`
    +      keep: true
    +      # old, a regex to be used to rename the converted value
    +      old: ^(\S+)-octets$
    +      # new, the replacement string
    +      new: ${1}-kilobytes
    +      # debug, enables this processor logging
    +      debug: false
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "/state/port/ethernet/statistics/in-octets": "2048"
    +  }
    +}
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "/state/port/ethernet/statistics/in-octets": "2048"
    +    "/state/port/ethernet/statistics/in-kilobytes": 2
    +  }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_date_string/index.html b/user_guide/event_processors/event_date_string/index.html new file mode 100644 index 00000000..53709c9f --- /dev/null +++ b/user_guide/event_processors/event_date_string/index.html @@ -0,0 +1,15 @@ + Date string - gNMIc

    Date string

    The event-date-string processor converts a specific timestamp value (under tags or values) to a string representation. The format and location can be configured.

    Examples#

    processors:
    +  # processor name
    +  convert-timestamp-processor:
    +    # processor type
    +    event-date-string:
    +      # list of regex to be matched with the values names
    +      value-names: 
    +        - "timestamp"
    +      # received timestamp unit
    +      precision: ms
    +      # desired date string format, defaults to RFC3339
    +      format: "2006-01-02T15:04:05Z07:00"
    +      # timezone, defaults to the local timezone
    +      location: Asia/Taipei
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_delete/index.html b/user_guide/event_processors/event_delete/index.html new file mode 100644 index 00000000..a614cfa5 --- /dev/null +++ b/user_guide/event_processors/event_delete/index.html @@ -0,0 +1,51 @@ + Delete - gNMIc

    Delete

    The event-delete processor deletes all tags or values matching a set of regular expressions from the event message.

    Examples#

    processors:
    +  # processor name
    +  delete-processor:
    +    # processor type
    +    event-delete:
    +      value-names:
    +        - ".*multicast.*"
    +        - ".*broadcast.*"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_drop/index.html b/user_guide/event_processors/event_drop/index.html new file mode 100644 index 00000000..eb7c0f9f --- /dev/null +++ b/user_guide/event_processors/event_drop/index.html @@ -0,0 +1,48 @@ + Drop - gNMIc

    Drop

    The event-drop processor drops the whole message if it matches the configured condition or one of the regexes undertags, tag-names, values or value-names.

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-drop:
    +      # jq expression, if evaluated to true, the message is dropped
    +      condition: 
    +      # list of regular expressions to be matched against the tags names, if matched, the message is dropped
    +      tag-names:
    +      # list of regular expressions to be matched against the tags values, if matched, the message is dropped
    +      tags:
    +      # list of regular expressions to be matched against the values names, if matched, the message is dropped
    +      value-names:
    +      # list of regular expressions to be matched against the values, if matched, the message is dropped
    +      values:
    +

    Examples#

    processors:
    +  # processor name
    +  drop-processor:
    +    # processor type
    +    event-drop:
    +      tags:
    +        - "172.23.23.2*"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_duration_convert/index.html b/user_guide/event_processors/event_duration_convert/index.html new file mode 100644 index 00000000..44147bdc --- /dev/null +++ b/user_guide/event_processors/event_duration_convert/index.html @@ -0,0 +1,39 @@ + Duration Convert - gNMIc

    Duration Convert

    The event-duration-convert processor converts duration written as string to a integer with second precision.

    The string format supported is a series of digits and a single letter indicating the unit, e.g 1w3d (1 week 3 days) The highest unit is w for week and the lowest is s for second. Any of the units may or may not be present.

    Examples#

    simple conversion#

    processors:
    +  # processor name
    +  convert-uptime:
    +    # processor type
    +    event-duration-convert:
    +      # list of regex to be matched with the values names
    +      value-names: 
    +        - ".*_uptime$"
    +      # keep the original value, 
    +      # a new value name will be added with the converted value,
    +      # the new value name will be the original name with _seconds as suffix 
    +      keep: false
    +      # debug, enables this processor logging
    +      debug: false
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "connection_uptime": "1w5s"
    +  }
    +}
    +
    {
    +  "name": "default",
    +  "timestamp": 1607290633806716620,
    +  "tags": {
    +    "port_port-id": "A/1",
    +    "source": "172.17.0.100:57400",
    +    "subscription-name": "default"
    +  },
    +  "values": {
    +    "connection_uptime": 604805
    +  }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_extract_tags/index.html b/user_guide/event_processors/event_extract_tags/index.html new file mode 100644 index 00000000..62c5312b --- /dev/null +++ b/user_guide/event_processors/event_extract_tags/index.html @@ -0,0 +1,72 @@ + Extract Tags - gNMIc

    Extract Tags

    The event-extract-tags processor extracts tags from a value, a value name, a tag name or a tag value using regex named groups.

    It is possible to overwrite a tag if its name already exists.

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-extract-tags:
    +      # list of regular expressions to be used to extract strings to be added as a tag.
    +      tag-names:
    +      # list of regular expressions to be used to extract strings to be added as a tag.
    +      tags:
    +      # list of regular expressions to be used to extract strings to be added as a tag.
    +      value-names:
    +      # list of regular expressions to be used to extract strings to be added as a tag.
    +      values:
    +      # boolean, if true tags are over-written with the added ones if they already exist.
    +      overwrite:
    +      # boolean, enable extra logging
    +      debug:
    +

    Examples#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-extract-tags:
    +      value-names:
    +        - /([a-zA-Z0-9-_:]+)/(?P<group>[a-zA-Z0-9-_:]+)/([a-zA-Z0-9-_:]+)
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "group": "statistics",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_group_by/index.html b/user_guide/event_processors/event_group_by/index.html new file mode 100644 index 00000000..30c1d4ab --- /dev/null +++ b/user_guide/event_processors/event_group_by/index.html @@ -0,0 +1,128 @@ + Group by - gNMIc

    Group by

    The event-group-by processor groups values under the same event message based on a list of tag names.

    This processor is intended to be used together with an output with cached gNMI notifications, like prometheus output with cache: {}.

    Configuration#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-group-by:
    +      # list of strings defining the tags to group by the values under 
    +      # a single event 
    +      tags: []
    +      # a boolean, if true only the values from events of the same name
    +      # are grouped together according to the list of tags
    +      by-name:
    +      # boolean
    +      debug: false
    +

    Examples#

    group by a single tag#

    processors:
    +  group-by-source:
    +    event-group-by:
    +      tags:
    +        - source
    +
    [
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "bgp_neighbor_sent_messages_queue_depth": 0,
    +            "bgp_neighbor_sent_messages_total_messages": "423",
    +            "bgp_neighbor_sent_messages_total_non_updates": "415",
    +            "bgp_neighbor_sent_messages_total_updates": "8"
    +        }
    +    },
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "bgp_neighbor_received_messages_malformed_updates": "0",
    +            "bgp_neighbor_received_messages_queue_depth": 0,
    +            "bgp_neighbor_received_messages_total_messages": "424",
    +            "bgp_neighbor_received_messages_total_non_updates": "418",
    +            "bgp_neighbor_received_messages_total_updates": "6"
    +        }
    +    }
    +]
    +
    [
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "bgp_neighbor_sent_messages_queue_depth": 0,
    +            "bgp_neighbor_sent_messages_total_messages": "423",
    +            "bgp_neighbor_sent_messages_total_non_updates": "415",
    +            "bgp_neighbor_sent_messages_total_updates": "8",
    +            "bgp_neighbor_received_messages_malformed_updates": "0",
    +            "bgp_neighbor_received_messages_queue_depth": 0,
    +            "bgp_neighbor_received_messages_total_messages": "424",
    +            "bgp_neighbor_received_messages_total_non_updates": "418",
    +            "bgp_neighbor_received_messages_total_updates": "6"
    +        }
    +    }
    +]
    +

    group by multiple tags#

    processors:
    +  group-by-queue-id:
    +    event-group-by:
    +      tags:
    +        - source
    +        - interface_name
    +        - multicast-queue_queue-id
    +
    [
    +  {
    +    "name": "sub1",
    +    "timestamp": 1627997491187771616,
    +    "tags": {
    +      "interface_name": "ethernet-1/1",
    +      "multicast-queue_queue-id": "5",
    +      "source": "clab-ndk-srl1:57400",
    +      "subscription-name": "sub1",
    +  },
    +    "values": {
    +      "/interface/qos/output/multicast-queue/queue-depth/maximum-burst-size": "0"
    +    }
    +  },
    +  {
    +    "name": "sub1",
    +    "timestamp": 1627997491187771616,
    +    "tags": {
    +      "interface_name": "ethernet-1/1",
    +      "multicast-queue_queue-id": "5",
    +      "source": "clab-ndk-srl1:57400",
    +      "subscription-name": "sub1",
    +    },
    +    "values": {
    +      "/interface/qos/output/multicast-queue/scheduling/peak-rate-bps": "0"
    +    }
    +  }
    +]
    +
    [
    +  {
    +    "name": "sub1",
    +    "timestamp": 1627997491187771616,
    +    "tags": {
    +      "interface_name": "ethernet-1/1",
    +      "multicast-queue_queue-id": "5",
    +      "source": "clab-ndk-srl1:57400",
    +      "subscription-name": "sub1",
    +  },
    +    "values": {
    +      "/interface/qos/output/multicast-queue/queue-depth/maximum-burst-size": "0",
    +      "/interface/qos/output/multicast-queue/scheduling/peak-rate-bps": "0"
    +    }
    +  }
    +]
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_jq/index.html b/user_guide/event_processors/event_jq/index.html new file mode 100644 index 00000000..bc0737ee --- /dev/null +++ b/user_guide/event_processors/event_jq/index.html @@ -0,0 +1,18 @@ + JQ - gNMIc

    JQ

    The event-jq processor applies a jq expression on the received event messages.

    jq expressions are a powerful tool that can be used to slice, filter, map, transform JSON object.

    The event-jq processor uses two configuration fields, condition and expression, both support jq expressions.

    • condition (that needs to return a boolean value) determines if the processor is to be applied on the event message. if false the message is returned as is.

    • expression is used to transform, filter and/or enrich the messages. It needs to return a JSON object that can be mapped to an array of event messages.

    The event messages resulting from a single gNMI Notification are passed to the jq expression as a JSON array.

    Some jq expression examples:

    • Select messages with name "sub1" that include a value called "counter1" with value higher than 90

      expression: .[] | select(.name=="sub1" and .values.counter1 > 90)
      +

    • Delete values with name "counter1"

    expression: .[] | del(.values.counter1)
    +
    • Delete values with names "counter1" or "counter2"
    expression: .[] | del(.values.["counter1", "counter2"])
    +
    • Delete tags with names "tag1" or "tag2"

      expression: .[] | del(.tags.["tag1", "tag2"])
      +

    • Add a tag called "my_new_tag" with value "tag1"

      expression: .[] |= (.tags.my_new_tag = "tag1")
      +

    • Move a value to tag under a custom key

      expression: .[] |= (.tags.my_new_tag_name = .values.value_name)
      +

    Configuration#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-jq:
    +      # condition of application of the processor
    +      condition:
    +      # jq expression to transform/filter/enrich the message
    +      expression:
    +      # boolean enabling extra logging
    +      debug:
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_merge/index.html b/user_guide/event_processors/event_merge/index.html new file mode 100644 index 00000000..3e1311e1 --- /dev/null +++ b/user_guide/event_processors/event_merge/index.html @@ -0,0 +1,68 @@ + Merge - gNMIc

    Merge

    The event-merge processor merges multiple event messages together based on some criteria.

    Each gNMI subscribe Response Update in a gNMI subscribe Response Notification is transformed into an Event Message

    The event-merge processor is used to merge the updates into one event message if it's needed.

    The default merge strategy is based on the timestamp, the updates with the same timestamp will be merged into the same event message.

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-merge:
    +      # if always is set to true, 
    +      # the updates are merged regardless of the timestamp values
    +      always: false
    +      debug: false
    +
    [
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "bgp_neighbor_sent_messages_queue_depth": 0,
    +            "bgp_neighbor_sent_messages_total_messages": "423",
    +            "bgp_neighbor_sent_messages_total_non_updates": "415",
    +            "bgp_neighbor_sent_messages_total_updates": "8"
    +        }
    +    },
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "bgp_neighbor_received_messages_malformed_updates": "0",
    +            "bgp_neighbor_received_messages_queue_depth": 0,
    +            "bgp_neighbor_received_messages_total_messages": "424",
    +            "bgp_neighbor_received_messages_total_non_updates": "418",
    +            "bgp_neighbor_received_messages_total_updates": "6"
    +        }
    +    }
    +]
    +
    [
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "bgp_neighbor_sent_messages_queue_depth": 0,
    +            "bgp_neighbor_sent_messages_total_messages": "423",
    +            "bgp_neighbor_sent_messages_total_non_updates": "415",
    +            "bgp_neighbor_sent_messages_total_updates": "8",
    +            "bgp_neighbor_received_messages_malformed_updates": "0",
    +            "bgp_neighbor_received_messages_queue_depth": 0,
    +            "bgp_neighbor_received_messages_total_messages": "424",
    +            "bgp_neighbor_received_messages_total_non_updates": "418",
    +            "bgp_neighbor_received_messages_total_updates": "6"
    +        }
    +    }
    +]
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_override_ts/index.html b/user_guide/event_processors/event_override_ts/index.html new file mode 100644 index 00000000..4cbfe28c --- /dev/null +++ b/user_guide/event_processors/event_override_ts/index.html @@ -0,0 +1,8 @@ + Override TS - gNMIc

    Override TS

    The event-override-ts processor overrides the message timestamp with time.Now(). The precision s, ms, us or ns (default) can be configured.

    Examples#

    processors:
    +  # processor name
    +  set-timestamp-processor:
    +    # processor type
    +    event-override-ts:
    +      # timestamp precision, s, ms, us, ns (default)
    +      precision: ms
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_plugin/index.html b/user_guide/event_processors/event_plugin/index.html new file mode 100644 index 00000000..85b2455f --- /dev/null +++ b/user_guide/event_processors/event_plugin/index.html @@ -0,0 +1,101 @@ + Event plugin - gNMIc

    Event plugin

    The event-plugin processor initializes a processor that gNMIc loaded from the configured path under the plugins: section.

    plugins:
    +  # path to load plugin binaries from.
    +  path: /path/to/plugin/bin
    +  # glob to match binaries against.
    +  glob: "*"
    +  # sets a start timeout for plugins.
    +  start-timeout: 0s
    +

    The specific configuration of an event-plugin processor varies from one plugin to another. But they are configured just like any other processor i.e under the processors: section of the config file and linked to outputs by name reference.

    The below configuration snippet initializes the event-add-hostname processor (a binary stored under plugins.path) and links to output out1.

    processors:
    +  proc1:
    +    event-add-hostname:
    +      debug: true
    +      # the tag name to add with the host hostname as a tag value.
    +      hostname-tag-name: "collector-host"
    +      # read-interval controls how often the plugin runs the hostname cmd to get the host hostanme
    +      # by default it's at most every 1 minute
    +      read-interval: 1m
    +
    +outputs:
    +  out1:
    +    type: file
    +    format: event
    +    event-processors:
    +      - proc1
    +

    Examples#

    See here.

    Writing a plugin processor#

    Currently plugin processor can only be written in Golang. It relies on Hashicorp's go-plugin package for discovery and communication with gNMIc's main process.

    To write your own processor you can use the below skeleton code as a starting point. Can be found here as well.

    1. Choose a name for your processor
    2. Add struct fields to decode the processor's config into.
    3. Implement your processor's logic under the Apply method
    4. Optionally, store the targets,actions and processors config maps given to the processor under your processor's struct (myProcessor) if they are relevant to your processor's logic.
    package main
    +
    +import (
    +    "log"
    +    "os"
    +
    +    "github.com/hashicorp/go-hclog"
    +    "github.com/hashicorp/go-plugin"
    +
    +    "github.com/openconfig/gnmic/pkg/formatters"
    +    "github.com/openconfig/gnmic/pkg/formatters/event_plugin"
    +    "github.com/openconfig/gnmic/pkg/types"
    +)
    +
    +const (
    +    // TODO: Choose a name for your processor
    +    processorType = "event-my-processor"
    +)
    +
    +type myProcessor struct {
    +    // TODO: Add your config struct fields here
    +}
    +
    +func (p *myProcessor) Init(cfg interface{}, opts ...formatters.Option) error {
    +    // decode the plugin config
    +    err := formatters.DecodeConfig(cfg, p)
    +    if err != nil {
    +        return err
    +    }
    +    // apply options
    +    for _, o := range opts {
    +        o(p)
    +    }
    +    // TODO: Other initialization steps...
    +    return nil
    +}
    +
    +func (p *myProcessor) Apply(event ...*formatters.EventMsg) []*formatters.EventMsg {
    +    // TODO: The processor's logic is applied here
    +    return event
    +}
    +
    +func (p *myProcessor) WithActions(act map[string]map[string]interface{}) {
    +}
    +
    +func (p *myProcessor) WithTargets(tcs map[string]*types.TargetConfig) {
    +}
    +
    +func (p *myProcessor) WithProcessors(procs map[string]map[string]any) {
    +}
    +
    +func (p *myProcessor) WithLogger(l *log.Logger) {
    +}
    +
    +func main() {
    +    logger := hclog.New(&hclog.LoggerOptions{
    +        Output:      os.Stderr,
    +        DisableTime: true,
    +    })
    +
    +    logger.Info("starting plugin processor", "name", processorType)
    +
    +    // TODO: Create and initialize your processor's struct
    +    plug := &myProcessor{}
    +    // start it
    +    plugin.Serve(&plugin.ServeConfig{
    +        HandshakeConfig: plugin.HandshakeConfig{
    +            ProtocolVersion:  1,
    +            MagicCookieKey:   "GNMIC_PLUGIN",
    +            MagicCookieValue: "gnmic",
    +        },
    +        Plugins: map[string]plugin.Plugin{
    +            processorType: &event_plugin.EventProcessorPlugin{Impl: plug},
    +        },
    +        Logger: logger,
    +    })
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_rate_limit/index.html b/user_guide/event_processors/event_rate_limit/index.html new file mode 100644 index 00000000..8f836aa4 --- /dev/null +++ b/user_guide/event_processors/event_rate_limit/index.html @@ -0,0 +1,13 @@ + Rate Limit - gNMIc

    Rate Limit

    The event-rate-limit processor rate-limits each event with matching tags to the configured amount per-seconds.

    All the tags for each event is hashed, and if the hash matches a previously seen event, then the timestamp of the event itself is compared to assess if the configured limit has been exceeded. If it has, then this new event is dropped from the pipeline.

    The cache for comparing timestamp is an LRU cache, with a default size of 1000 that can be increased for bigger deployments.

    To account for cases where the device will artificially split the event into multiple chunks (with the same timestamp), the rate-limiter will ignore events with exactly the same timestamp.

    Examples#

    processors:
    +  # processor name
    +  rate-limit-100pps:
    +    # processor type
    +    event-rate-limit:
    +      # rate of filtering, in events per seconds
    +      per-second: 100
    +      # set the cache size for doing the rate-limiting comparison
    +      # default value is 1000
    +      cache-size: 10000
    +      # debug for additionnal logging of dropped events
    +      debug: true
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_starlark/index.html b/user_guide/event_processors/event_starlark/index.html new file mode 100644 index 00000000..3af92981 --- /dev/null +++ b/user_guide/event_processors/event_starlark/index.html @@ -0,0 +1,163 @@ + Starlark - gNMIc

    Starlark

    Intro#

    The event-starlark processor applies a Starlark function on a list of event messages before returning them to the processors pipeline and then to the output.

    starlark is a dialect of Python, developed initially for the Bazel build tool but found multiple uses as a configuration language embedded in a larger application.

    There are a few differences between Python and Starlark, programs written in Starlark are supposed to be short-lived and have no external side effects, their main result is structured data or side effects on the host application. As a result, Starlark has no need for classes, exceptions, reflection, concurrency, and other such features of Python.

    gNMIc uses the Go implementation of Starlark.

    A Starlark program running as a gNMIc processor should define an apply function that takes an arbitrary number of arguments of type Event and returns zero or more Events.

    An Event is the transformed gNMI update message as gNMIc processes it.

    def apply(*events)
    +  # events transformed/augmented/filtered here
    +  return events
    +

    Configuration#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-starlark:
    +      # the source of the starlark program.
    +      source: |
    +        def apply(*events):
    +          # processor logic here
    +          return events
    +      # path to a file containing the starlark program to run.
    +      # Mutually exclusive with `source` parameter. 
    +      script:
    +      # boolean enabling extra logging
    +      debug: false
    +

    Writing a Starlark processor#

    To write a starlark processor all that is needed is writing a function called apply that will read/modify/delete a list of Event messages.

    Starlark specification defines multiple builtin types and functions.

    gNMIc provides additional builtin functions like Event(name) which creates a new Event message and copy_event(Event) which duplicates a given Event message.

    The Event message comprises a few fields:

    • name: string

    • timestamp: int64

    • tags: dictionary of string to string

    • values: dictionary of string to any

    • deletes: list of strings

    Starlark allows for the dynamic loading of other modules. In the context of gNMIc, the following two modules are available for loading within a starlark program:

    • time: load("time.star", "time") loads the time library which provides the following functions to work with the Event message timestamp field:

      • time.from_timestamp(sec, nsec):

        Converts the given Unix time corresponding to the number of seconds and (optionally) nanoseconds since January 1, 1970 UTC into an object of type Time.

        For more details, refer to https://pkg.go.dev/time#Unix.

      • time.is_valid_timezone(loc):

        Reports whether loc is a valid time zone name.

      • time.now():

        Returns the current local time.

      • time.parse_duration(d):

        Parses the given duration string.

        For more details, refer to https://pkg.go.dev/time#ParseDuration.

      • time.parse_time(x, format, location):

        Parses the given time string using a specific time format and location. The expected arguments are a time string (mandatory), a time format (optional, set to RFC3339 by default, e.g. "2021-03-22T23:20:50.52Z") and a name of location (optional, set to UTC by default).

        For more details, refer to https://pkg.go.dev/time#Parse and https://pkg.go.dev/time#ParseInLocation.

      • time.time(year, month, day, hour, minute, second, nanosecond, location):

        Returns the Time corresponding to yyyy-mm-dd hh:mm:ss + nsec nanoseconds in the appropriate zone for that time in the given location. All the parameters are optional.

    • math: load("math.star", "math") loads the math library which provides a set of constants and math-related functions:

      • ceil(x):

        Returns the ceiling of x, the smallest integer greater than or equal to x.

      • copysign(x, y):

        Returns a value with the magnitude of x and the sign of y.

      • fabs(x):

        Returns the absolute value of x as float.

      • floor(x):

        Returns the floor of x, the largest integer less than or equal to x.

      • mod(x, y):

        Returns the floating-point remainder of x/y. The magnitude of the result is less than y and its sign agrees with that of x.

      • pow(x, y):

        Returns x**y, the base-x exponential of y.

      • remainder(x, y):

        Returns the IEEE 754 floating-point remainder of x/y.

      • round(x):

        Returns the nearest integer, rounding half away from zero.

      • exp(x):

        Returns e raised to the power x, where e = 2.718281… is the base of natural logarithms.

      • sqrt(x):

        Returns the square root of x.

      • acos(x):

        Returns the arc cosine of x, in radians.

      • asin(x):

        Returns the arc sine of x, in radians.

      • atan(x):

        Returns the arc tangent of x, in radians.

      • atan2(y, x):

        Returns atan(y / x), in radians. The result is between -pi and pi. The vector in the plane from the origin to point (x, y) makes this angle with the positive X axis. The point of atan2() is that the signs of both inputs are known to it, so it can compute the correct quadrant for the angle. For example, atan(1) and atan2(1, 1) are both pi/4, but atan2(-1, -1) is -3*pi/4.

      • cos(x):

        Returns the cosine of x, in radians.

      • hypot(x, y):

        Returns the Euclidean norm, sqrt(x*x + y*y). This is the length of the vector from the origin to point (x, y).

      • sin(x):

        Returns the sine of x, in radians.

      • tan(x):

        Returns the tangent of x, in radians.

      • degrees(x):

        Converts angle x from radians to degrees.

      • radians(x):

        Converts angle x from degrees to radians.

      • acosh(x):

        Returns the inverse hyperbolic cosine of x.

      • asinh(x):

        Returns the inverse hyperbolic sine of x.

      • atanh(x):

        Returns the inverse hyperbolic tangent of x.

      • cosh(x):

        Returns the hyperbolic cosine of x.

      • sinh(x):

        Returns the hyperbolic sine of x.

      • tanh(x):

        Returns the hyperbolic tangent of x.

      • log(x, base):

        Returns the logarithm of x in the given base, or natural logarithm by default.

      • gamma(x):

        Returns the Gamma function of x.

    Examples#

    Move a value to a tag#

    def apply(*events):
    +  dels = []
    +  for e in events:
    +    for k, v in e.values.items():
    +      if k == "val1":
    +        e.tags[k] = str(v)
    +        dels.append(k)
    +    for d in dels:
    +      e.values.pop(d)
    +  return events
    +

    Rename values#

    val_map = {
    +  "val1": "new_val",
    +}
    +
    +def apply(*events):
    +  for e in events:
    +    for k, v in e.values.items():
    +      if k in val_map:
    +        e.values[val_map[k]] = v
    +        e.values.pop(k)
    +  return events
    +

    Convert strings to integers#

    def apply(*events):
    +  for e in events:
    +    for k, v in e.values.items():
    +      if v.isdigit():
    +        e.values[k] = int(v)
    +  return events
    +

    Set an interface description as a tag#

    This script stores each interface description per target/interface in a cache and adds it to other values as a tag.

    cache = {}
    +
    +def apply(*events):
    +  evs = []
    +  # check if on the event messages contains an interface description
    +  # and store in th cache dict
    +  for e in events:
    +    if e.values.get("/interface/description"):
    +      target_if = e.tags["source"] + "_" + e.tags["interface_name"]
    +      cache[target_if] = e.values["/interface/description"]
    +  # for each event get the 'source' and 'interface_name', check
    +  # if a corresponding cache entry exists and set it as a 
    +  # 'description' tag
    +  for e in events:
    +    if e.tags.get("source") and e.tags.get("interface_name"):
    +      target_if = e.tags["source"] + "_" + e.tags["interface_name"]
    +      if cache.get(target_if):
    +        e.tags["description"] = cache[target_if]
    +    evs.append(e)
    +  return evs
    +

    Calculate new values based on the received ones#

    The below script calculates the avg, min, max of a list of values over their last N=10 values

    cache = {}
    +
    +values_names = [
    +  '/interface/statistics/out-octets',
    +  '/interface/statistics/in-octets'
    +]
    +
    +N=10
    +
    +def apply(*events):
    +  for e in events:
    +    for value_name in values_names:
    +      v = e.values.get(value_name)
    +      # check if v is not None and is a digit to proceed
    +      if not v.isdigit():
    +        continue
    +      # update cache with the latest value
    +      val_key = "_".join([e.tags["source"], e.tags["interface_name"], value_name])
    +      if not cache.get(val_key):
    +        # initialize the cache entry if empty
    +        cache.update({val_key: []})
    +      if len(cache[val_key]) >= N:
    +        # remove the oldest entry if the number of entries reached N
    +        cache[val_key] = cache[val_key][1:]
    +      # update cache entry
    +      cache[val_key].append(int(v))
    +      # get the list of values
    +      val_list = cache[val_key]
    +      # calculate min, max and avg
    +      e.values[value_name+"_min"] = min(val_list)
    +      e.values[value_name+"_max"] = max(val_list)
    +      e.values[value_name+"_avg"] = avg(val_list)
    +  return events
    +
    +def avg(vals):
    +  sum = 0
    +  for v in vals:
    +    sum = sum + v
    +  return sum/len(vals)
    +

    The below script builds on top of the previous one by adding the rate calculation to the added values. Now the cache contains a timestamp as well as the value.

    cache = {}
    +
    +values_names=[
    +  '/interface/statistics/out-octets',
    +  '/interface/statistics/in-octets'
    +]
    +
    +N=10
    +
    +def apply(*events):
    +  for e in events:
    +    for value_name in values_names:
    +      v = e.values.get(value_name)
    +      # check if v is not None and is a digit to proceed
    +      if not v.isdigit():
    +        continue
    +      # update cache with the latest value
    +      val_key = "_".join([e.tags["source"], e.tags["interface_name"], value_name])
    +      if not cache.get(val_key):
    +        # initialize the cache entry if empty
    +        cache.update({val_key: []})
    +      if len(cache[val_key]) >= N:
    +        # remove the oldest entry if the number of entries reached N
    +        cache[val_key] = cache[val_key][1:]
    +      # update cache entry
    +      cache[val_key].append((e.timestamp, int(v)))
    +      # get the list of values
    +      val_list = cache[val_key]
    +      # calculate min, max and avg
    +      vals = [x[1] for x in val_list]
    +      e.values[value_name+"_min"] = min(vals)
    +      e.values[value_name+"_max"] = max(vals)
    +      e.values[value_name+"_avg"] = avg(vals)
    +      if len(val_list) > 1:
    +        e.values[value_name+"_rate"] = rate(val_list[-2:])
    +  return events
    +
    +def avg(vals):
    +  sum = 0
    +  for v in vals:
    +    sum = sum + v
    +  return sum/len(vals)
    +
    +def rate(vals):
    +  period = (vals[1][0] - vals[0][0]) / 1000000000
    +  change = vals[1][1] - vals[0][1]
    +  return change / period
    +

    Ungroup values#

    The below script ungroups values part of the same event message producing an event message per value.

    def apply(*events):
    +  ungrouped_events = []
    +  for e in events:
    +    for k, v in e.values.items():
    +      # create a new event without any value
    +      new_event = Event(e.name, e.timestamp, e.tags)
    +      # add a single value to the new event
    +      new_event.values[k] = v
    +      # add the new event to the array
    +      ungrouped_events.append(new_event)
    +  return ungrouped_events
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_strings/index.html b/user_guide/event_processors/event_strings/index.html new file mode 100644 index 00000000..6ca96021 --- /dev/null +++ b/user_guide/event_processors/event_strings/index.html @@ -0,0 +1,384 @@ + Strings - gNMIc

    Strings

    The event-strings processor exposes a few of Golang strings transformation functions, there functions can be applied to tags, tag names, values or value names.

    Supported functions:

    • strings.Replace
    • strings.TrimPrefix
    • strings.TrimSuffix
    • strings.Title
    • strings.ToLower
    • strings.ToUpper
    • strings.Split
    • filepath.Base
    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-strings:
    +      value-names: []
    +      tag-names: []
    +      values: []
    +      tags: []
    +      transforms:
    +        # strings function name
    +        - replace:
    +            apply-on:  # apply the transformation on name or value
    +            keep: # keep the old value or not if the name changed
    +            old: # string to be replaced
    +            new: #replacement string of old
    +        - trim-prefix:
    +            apply-on: # apply the transformation on name or value
    +            prefix: # prefix to be trimmed
    +        - trim_suffix:
    +            apply-on: # apply the transformation on name or value
    +            suffix: # suffix to be trimmed
    +        - title:
    +            apply-on: # apply the transformation on name or value
    +        - to-upper:
    +            apply-on: # apply the transformation on name or value
    +        - to-lower:
    +            apply-on: # apply the transformation on name or value
    +        - split:
    +            apply-on: # apply the transformation on name or value
    +            split-on: # character to split on
    +            join-with: # character to join with
    +            ignore-first: # number of first items to ignore when joining
    +            ignore-last: # number of last items to ignore when joining
    +        - path-base:
    +            apply-on: # apply the transformation on name or value 
    +

    Examples#

    replace#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-strings:
    +      value-names:
    +        - ".*"
    +      transforms:
    +        # strings function name
    +        - replace:
    +            apply-on: "name"
    +            old: "-"
    +            new: "_"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "carrier-transitions": "1",
    +        "in-error-packets": "0",
    +        "in-fcs-error-packets": "0",
    +        "in-octets": "65382630",
    +        "in-unicast-packets": "107154",
    +        "out-error-packets": "0",
    +        "out-octets": "64721394",
    +        "out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "carrier_transitions": "1",
    +        "in_error_packets": "0",
    +        "in_fcs_error_packets": "0",
    +        "in_octets": "65382630",
    +        "in_unicast_packets": "107154",
    +        "out_error_packets": "0",
    +        "out_octets": "64721394",
    +        "out_unicast_packets": "105876"
    +    }
    +}
    +

    trim-prefix#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-strings:
    +      value-names:
    +        - ".*"
    +      transforms:
    +        # strings function name
    +        - trim-prefix:
    +            apply-on: "name"
    +            prefix: "/srl_nokia-interfaces:interface/statistics/"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "carrier-transitions": "1",
    +        "in-broadcast-packets": "3797",
    +        "in-error-packets": "0",
    +        "in-fcs-error-packets": "0",
    +        "in-multicast-packets": "288033",
    +        "in-octets": "65382630",
    +        "in-unicast-packets": "107154",
    +        "out-broadcast-packets": "614",
    +        "out-error-packets": "0",
    +        "out-multicast-packets": "11",
    +        "out-octets": "64721394",
    +        "out-unicast-packets": "105876"
    +    }
    +}
    +

    to-upper#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-strings:
    +      tag-names:
    +        - "interface_name"
    +        - "subscription-name"
    +      transforms:
    +        # strings function name
    +        - to-upper:
    +            apply-on: "value"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
       {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "MGMT0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "DEFAULT"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +

    path-base#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-strings:
    +      value-names:
    +        - ".*"
    +      transforms:
    +        # strings function name
    +        - path-base:
    +            apply-on: "name"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "carrier-transitions": "1",
    +        "in-broadcast-packets": "3797",
    +        "in-error-packets": "0",
    +        "in-fcs-error-packets": "0",
    +        "in-multicast-packets": "288033",
    +        "in-octets": "65382630",
    +        "in-unicast-packets": "107154",
    +        "out-broadcast-packets": "614",
    +        "out-error-packets": "0",
    +        "out-multicast-packets": "11",
    +        "out-octets": "64721394",
    +        "out-unicast-packets": "105876"
    +    }
    +}
    +

    split#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-strings:
    +      value-names:
    +        - ".*"
    +      transforms:
    +        # strings function name
    +        - split:
    +            on: "name"
    +            split-on: "/"
    +            join-with: "_"
    +            ignore-first: 1
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "statistics_carrier-transitions": "1",
    +        "statistics_in-broadcast-packets": "3797",
    +        "statistics_in-error-packets": "0",
    +        "statistics_in-fcs-error-packets": "0",
    +        "statistics_in-multicast-packets": "288033",
    +        "statistics_in-octets": "65382630",
    +        "statistics_in-unicast-packets": "107154",
    +        "statistics_out-broadcast-packets": "614",
    +        "statistics_out-error-packets": "0",
    +        "statistics_out-multicast-packets": "11",
    +        "statistics_out-octets": "64721394",
    +        "statistics_out-unicast-packets": "105876"
    +    }
    +}
    +

    multiple transforms#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-strings:
    +      value-names:
    +        - ".*"
    +      transforms:
    +        # strings function name
    +        - path-base:
    +            apply-on: "name"
    +        - title:
    +            apply-on: "name"
    +        - replace:
    +            apply-on: "name"
    +            old: "-"
    +            new: "_"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +        "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "3797",
    +        "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "288033",
    +        "/srl_nokia-interfaces:interface/statistics/in-octets": "65382630",
    +        "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "107154",
    +        "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "614",
    +        "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +        "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "11",
    +        "/srl_nokia-interfaces:interface/statistics/out-octets": "64721394",
    +        "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "105876"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607291271894072397,
    +    "tags": {
    +        "interface_name": "mgmt0",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "Carrier_transitions": "1",
    +        "In_broadcast_packets": "3797",
    +        "In_error_packets": "0",
    +        "In_fcs_error_packets": "0",
    +        "In_multicast_packets": "288033",
    +        "In_octets": "65382630",
    +        "In_unicast_packets": "107154",
    +        "Out_broadcast_packets": "614",
    +        "Out_error_packets": "0",
    +        "Out_multicast_packets": "11",
    +        "Out_octets": "64721394",
    +        "Out_unicast_packets": "105876"
    +    }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_to_tag/index.html b/user_guide/event_processors/event_to_tag/index.html new file mode 100644 index 00000000..5582751b --- /dev/null +++ b/user_guide/event_processors/event_to_tag/index.html @@ -0,0 +1,40 @@ + To Tag - gNMIc

    To Tag

    The event-to-tag processor moves a value matching one of the regular expressions from the values section to the tags section. It's possible to keep the value under values section after moving it.

    Examples#

    processors:
    +  # processor name
    +  sample-processor:
    +    # processor type
    +    event-to-tag:
    +      value-names:
    +        - ".*-state$"
    +
    {
    +    "name": "default",
    +    "timestamp": 1607305284170936330,
    +    "tags": {
    +        "interface_name": "ethernet-1/1",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/admin-state": "disable",
    +        "/srl_nokia-interfaces:interface/ifindex": 54,
    +        "/srl_nokia-interfaces:interface/last-change": "2020-11-20T05:52:21.459Z",
    +        "/srl_nokia-interfaces:interface/oper-down-reason": "port-admin-disabled",
    +        "/srl_nokia-interfaces:interface/oper-state": "down"
    +    }
    +}
    +
    {
    +    "name": "default",
    +    "timestamp": 1607305284170936330,
    +    "tags": {
    +        "interface_name": "ethernet-1/1",
    +        "source": "172.23.23.2:57400",
    +        "subscription-name": "default",
    +        "/srl_nokia-interfaces:interface/admin-state": "disable",
    +        "/srl_nokia-interfaces:interface/oper-state": "down"
    +    },
    +    "values": {
    +        "/srl_nokia-interfaces:interface/ifindex": 54,
    +        "/srl_nokia-interfaces:interface/last-change": "2020-11-20T05:52:21.459Z",
    +        "/srl_nokia-interfaces:interface/oper-down-reason": "port-admin-disabled"
    +    }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_trigger/index.html b/user_guide/event_processors/event_trigger/index.html new file mode 100644 index 00000000..8b93f4f2 --- /dev/null +++ b/user_guide/event_processors/event_trigger/index.html @@ -0,0 +1,200 @@ + Trigger - gNMIc

    Trigger

    The event-trigger processor takes event messages as input and triggers a list of actions (sequentially) if a configured condition evaluates to true.

    The condition is evaluated using the the Golang implementation of jq with the event message as a json input.

    jq tutorial

    jq manual

    jq playground

    Examples of conditions:

    • The below expression checks if the value named counter1 has a value higher than 90
    .values["counter1"] > 90
    +
    • This expression checks if the event name is sub1, that the tag source is equal to r1:57400
    .name == "sub1" and .tags["source"] == "r1:57400" 
    +

    The trigger can be monitored over a configurable window of time (default 1 minute), during which only a certain number of occurrences (default 1) trigger the actions execution.

    The action types availabe can be found here

    processors:
    +  # processor name
    +  my_trigger_proc: # 
    +    # processor type
    +    event-trigger:
    +      # trigger condition
    +      condition: '.values["counter1"] > 90'
    +      # minimum number of condition occurrences within the configured window 
    +      # required to trigger the action
    +      min-occurrences: 1
    +      # max number of times the action is triggered within the configured window
    +      max-occurrences: 1
    +      # window of time during which max-occurrences need to 
    +      # be reached in order to trigger the action
    +      window: 60s
    +      # async, bool. default false.
    +      # If true the trigger is executed in the background and the triggering
    +      # message is passed to the next procesor. Otherwise it blocks until the trigger returns
    +      async: false
    +      # a dictionary of variables that is passed to the actions
    +      # and can be accessed in the actions templates using `.Vars`
    +      vars:
    +      # path to a file containing variables passed to the actions
    +      # the variable in the `vars` field override the ones read from the file.
    +      vars-file: 
    +      # list of actions to be executed
    +      actions:
    +        - counter_alert
    +

    Examples#

    Alerting when a threshold is crossed#

    The below example triggers an HTTP GET to http://remote-server:p8080/${router_name} if the value of counter "counter1" crosses 90 twice within 2 minutes.

    processors:
    +  my_trigger_proc:
    +    event-trigger:
    +      condition: '.values["counter1"] > 90'
    +      min-occurrences: 1
    +      max-occurrences: 2
    +      window: 120s
    +      async: true
    +      actions:
    +        - alert
    +
    +actions:
    +  alert:
    +    name: alert
    +    type: http
    +    method: POST
    +    url: http://remote-server:8080/{{ index .Tags "source" }}
    +    headers: 
    +      content-type: application/text
    +    timeout: 5s
    +    body: '"counter1" crossed threshold, value={{ index .Values "counter1" }}'
    +

    Enabling backup interface#

    The below example shows a trigger that enables a router interface if another interface's operational status changes to "down".

    processors:
    +  interface_watch: # 
    +    event-trigger:
    +      debug: true
    +      condition: '(.tags.interface_name == "ethernet-1/1" or .tags.interface_name == "ethernet-1/2") and .values["/srl_nokia-interfaces:interface/oper-state"] == "down"'
    +      actions:
    +      - enable_interface
    +
    +actions:
    +  enable_interface:
    +    name: my_gnmi_action
    +    type: gnmi
    +    rpc: set
    +    target: '{{ index .Event.Tags "source" }}'
    +    paths:
    +      - |
    +        {{ $interfaceName := "" }}
    +        {{ if eq ( index .Event.Tags "interface_name" ) "ethernet-1/1"}}
    +        {{$interfaceName = "ethernet-1/2"}}
    +        {{ else if eq ( index E.vent.Tags "interface_name" ) "ethernet-1/2"}}
    +        {{$interfaceName = "ethernet-1/1"}}
    +        {{end}}
    +        /interface[name={{$interfaceName}}]/admin-state
    +    values:
    +      - "enable"
    +    encoding: json_ietf
    +    debug: true
    +

    Clone a network topology and deploy it using containerlab#

    Using lldp neighbor information it's possible to build a containerlab topology using gnmic actions.

    In the below configuration file, an event processor called clone-topology is defined.

    When triggered it runs a series of actions to gather information (chassis type, lldp neighbors, configuration,...) from the defined targets.

    It then builds a containerlab topology from a defined template and the gathered info, writes it to a file and runs a clab deploy command.

    username: admin
    +password: NokiaSrl1!
    +skip-verify: true
    +encoding: json_ietf
    +# log: true
    +
    +targets:
    +  srl1:
    +  srl2:
    +  srl3:
    +
    +processors:
    +  clone-topology:
    +    event-trigger:
    +      # debug: true
    +      actions:
    +        - chassis  
    +        - lldp  
    +        - read_config  
    +        - write_config 
    +        - clab_topo         
    +        - deploy_topo
    +
    +actions:
    +  chassis:
    +    name: chassis
    +    type: gnmi
    +    target: all
    +    rpc: sub
    +    encoding: json_ietf
    +    #debug: true
    +    format: event
    +    paths:
    +      - /platform/chassis/type
    +
    +  lldp:
    +    name: lldp
    +    type: gnmi
    +    target: all
    +    rpc: sub
    +    encoding: json_ietf
    +    #debug: true
    +    format: event
    +    paths:
    +      - /system/lldp/interface[name=ethernet-*]
    +
    +  read_config:
    +    name: read_config
    +    type: gnmi
    +    target: all
    +    rpc: get
    +    data-type: config
    +    encoding: json_ietf
    +    #debug: true
    +    paths:
    +      - /
    +
    +  write_config:
    +    name: write_config
    +    type: template
    +    template: |
    +      {{- range $n, $m := .Env.read_config }}
    +      {{- $filename := print $n  ".json"}}
    +          {{ file.Write $filename (index $m 0 "updates" 0 "values" "" | data.ToJSONPretty "  " ) }}
    +          {{- end }}
    +        #debug: true
    +
    +  clab_topo:
    +    name: clab_topo
    +    type: template
    +    # debug: true
    +    output: gnmic.clab.yaml
    +    template: |
    +      name: gNMIc-action-generated
    +
    +      topology:
    +        defaults:
    +          kind: srl
    +        kinds:
    +          srl:
    +            image: ghcr.io/nokia/srlinux:latest
    +
    +        nodes:
    +      {{- range $n, $m := .Env.lldp }}
    +        {{- $type := index $.Env.chassis $n 0 0 "values" "/srl_nokia-platform:platform/srl_nokia-platform-chassis:chassis/type" }}
    +        {{- $type = $type | strings.ReplaceAll "7220 IXR-D1" "ixrd1" }}
    +        {{- $type = $type | strings.ReplaceAll "7220 IXR-D2" "ixrd2" }}
    +        {{- $type = $type | strings.ReplaceAll "7220 IXR-D3" "ixrd3" }}
    +        {{- $type = $type | strings.ReplaceAll "7250 IXR-6" "ixr6" }}
    +        {{- $type = $type | strings.ReplaceAll "7250 IXR-10" "ixr10" }}
    +        {{- $type = $type | strings.ReplaceAll "7220 IXR-H1" "ixrh1" }}
    +        {{- $type = $type | strings.ReplaceAll "7220 IXR-H2" "ixrh2" }}
    +        {{- $type = $type | strings.ReplaceAll "7220 IXR-H3" "ixrh3" }}
    +          {{ $n | strings.TrimPrefix "clab-" }}:
    +            type: {{ $type }}
    +            startup-config: {{ print $n ".json"}}
    +      {{- end }}
    +
    +        links:
    +      {{- range $n, $m := .Env.lldp }}
    +        {{- range $rsp := $m }}
    +          {{- range $ev := $rsp }}
    +            {{- if index $ev.values "/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name" }}
    +            {{- $node1 := $ev.tags.source | strings.TrimPrefix "clab-" }}
    +            {{- $iface1 := $ev.tags.interface_name | strings.ReplaceAll "ethernet-" "e" | strings.ReplaceAll "/" "-" }}
    +            {{- $node2 := index $ev.values "/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/system-name" }}
    +            {{- $iface2 := index $ev.values "/srl_nokia-system:system/srl_nokia-lldp:lldp/interface/neighbor/port-id" | strings.ReplaceAll "ethernet-" "e" | strings.ReplaceAll "/" "-" }}
    +              {{- if lt $node1 $node2 }}
    +          - endpoints: ["{{ $node1 }}:{{ $iface1 }}", "{{ $node2 }}:{{ $iface2 }}"]
    +              {{- end }}
    +            {{- end }}
    +          {{- end }}
    +        {{- end }}
    +      {{- end }}
    +
    +  deploy_topo:  
    +    name: deploy_topo
    +    type: script
    +    command: sudo clab dep -t gnmic.clab.yaml --reconfigure
    +    debug: true
    +

    The above described processor can be triggered with the below command:

    gnmic --config clone.yaml get --path /system/name --processor clone-topology
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_value_tag/index.html b/user_guide/event_processors/event_value_tag/index.html new file mode 100644 index 00000000..db66627c --- /dev/null +++ b/user_guide/event_processors/event_value_tag/index.html @@ -0,0 +1,156 @@ + Value Tag - gNMIc

    Value Tag

    The event-value-tag processor applies specific values from event messages to tags of other messages, if event tag names match.

    Each gNMI subscribe Response Update in a gNMI subscribe Response Notification is transformed into an Event Message Additionally, if you are using an output cache, all gNMI subscribe Response Update messages are converted to Events on flush.

    The event-value-tag processor is used to extract Values as tags to apply to other Events that have the same K:V tag pairs from the original event message, without merging events with different timestamps.

    processors:
    +  # processor name
    +  intf-description:
    +    # processor-type
    +    event-value-tag:
    +      # name of the value to match.  Usually a specific gNMI path
    +      value-name: "/interfaces/interface/state/description"
    +      # if set, use instead of the value name for tag
    +      tag-name: "description"
    +      # if true, remove value from original event when copying
    +      consume: false
    +      debug: false
    +
    [
    +    {
    +        "name": "sub1",
    +        "timestamp": 1,
    +        "tags": {
    +            "source": "leaf1:6030",
    +            "subscription-name": "sub1",
    +            "interface_name": "Ethernet1"
    +        },
    +        "values": {
    +            "/interfaces/interface/state/counters/in-octets": 100
    +        }
    +    },
    +    {
    +        "name": "sub1",
    +        "timestamp": 200,
    +        "tags": {
    +            "source": "leaf1:6030",
    +            "subscription-name": "sub1",
    +            "interface_name": "Ethernet1"
    +        },
    +        "values": {
    +            "/interfaces/interface/state/counters/out-octets": 100
    +        }
    +    },
    +    {
    +        "name": "sub1",
    +        "timestamp": 200,
    +        "tags": {
    +            "source": "leaf1:6030",
    +            "subscription-name": "sub1",
    +            "interface_name": "Ethernet1"
    +        },
    +        "values": {
    +            "/interfaces/interface/state/description": "Uplink"
    +        }
    +    }
    +]
    +
    [
    +    {
    +        "name": "sub1",
    +        "timestamp": 1,
    +        "tags": {
    +            "source": "leaf1:6030",
    +            "subscription-name": "sub1",
    +            "interface_name": "Ethernet1",
    +            "description": "Uplink"
    +        },
    +        "values": {
    +            "/interfaces/interface/state/counters/in-octets": 100
    +        }
    +    },
    +    {
    +        "name": "sub1",
    +        "timestamp": 200,
    +        "tags": {
    +            "source": "leaf1:6030",
    +            "subscription-name": "sub1",
    +            "interface_name": "Ethernet1",
    +            "description": "Uplink"
    +        },
    +        "values": {
    +            "/interfaces/interface/state/counters/out-octets": 100
    +        }
    +    },
    +    {
    +        "name": "sub1",
    +        "timestamp": 200,
    +        "tags": {
    +            "source": "leaf1:6030",
    +            "subscription-name": "sub1",
    +            "interface_name": "Ethernet1"
    +        },
    +        "values": {
    +            "/interfaces/interface/state/description": "Uplink"
    +        }
    +    }
    +]
    +
      bgp-description:
    +    event-value-tag:
    +      value-name: "neighbor_description"
    +      consume: true
    +
    [
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "bgp_neighbor_sent_messages_queue_depth": 0,
    +            "bgp_neighbor_sent_messages_total_messages": "423",
    +            "bgp_neighbor_sent_messages_total_non_updates": "415",
    +            "bgp_neighbor_sent_messages_total_updates": "8"
    +        }
    +    },
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {
    +            "neighbor_description": "PeerRouter"
    +        }
    +    }
    +]
    +
    [
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +            "neighbor_description": "PeerRouter"
    +        },
    +        "values": {
    +            "bgp_neighbor_sent_messages_queue_depth": 0,
    +            "bgp_neighbor_sent_messages_total_messages": "423",
    +            "bgp_neighbor_sent_messages_total_non_updates": "415",
    +            "bgp_neighbor_sent_messages_total_updates": "8",
    +        }
    +    },
    +    {
    +        "name": "sub2",
    +        "timestamp": 1615284691523204299,
    +        "tags": {
    +            "neighbor_peer-address": "2002::1:1:1:1",
    +            "network-instance_name": "default",
    +            "source": "leaf1:57400",
    +            "subscription-name": "sub2"
    +        },
    +        "values": {}
    +    }
    +]
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/event_write/index.html b/user_guide/event_processors/event_write/index.html new file mode 100644 index 00000000..bc1a6fc3 --- /dev/null +++ b/user_guide/event_processors/event_write/index.html @@ -0,0 +1,81 @@ + Write - gNMIc

    Write

    The event-write processor writes a message that has a value or a tag matching one of the configured regular expressions to stdout, stderr or to a file. A custom separator (used between written messages) can be configured, it defaults to \n

    processors:
    +  # processor name
    +  write-processor:
    +    # processor type
    +    event-write:
    +      # jq expression, if evaluated to true, the message is written to dst
    +      condition: 
    +      # list of regular expressions to be matched against the tags names, if matched, the message is written to dst
    +      tag-names:
    +      # list of regular expressions to be matched against the tags values, if matched, the message is written to dst
    +      tags:
    +      # list of regular expressions to be matched against the values names, if matched, the message is written to dst
    +      value-names:
    +      # list of regular expressions to be matched against the values, if matched, the message is written to dst
    +      values:
    +      # path to the destination file
    +      dst:
    +      # separator to be written between messages
    +      separator: 
    +      # indent to use when marshaling the event message to json
    +      indent:
    +

    Examples#

    processors:
    +  # processor name
    +  write-processor:
    +    # processor type
    +    event-write:
    +      value-names:
    +        - "."
    +      dst: file.log
    +      separator: "\n####\n"
    +      indent: "  "
    +
    $ cat file.log
    +{
    +  "name": "sub1",
    +  "timestamp": 1607582483868459381,
    +  "tags": {
    +    "interface_name": "ethernet-1/1",
    +    "source": "172.20.20.5:57400",
    +    "subscription-name": "sub1"
    +  },
    +  "values": {
    +    "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +    "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "22",
    +    "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +    "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +    "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "8694",
    +    "/srl_nokia-interfaces:interface/statistics/in-octets": "1740350",
    +    "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "17",
    +    "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "22",
    +    "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +    "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "8696",
    +    "/srl_nokia-interfaces:interface/statistics/out-octets": "1723262",
    +    "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "17"
    +  }
    +}
    +####
    +{
    +  "name": "sub1",
    +  "timestamp": 1607582483868459381,
    +  "tags": {
    +    "interface_name": "ethernet-1/1",
    +    "source": "172.20.20.5:57400",
    +    "subscription-name": "sub1"
    +  },
    +  "values": {
    +    "/srl_nokia-interfaces:interface/statistics/carrier-transitions": "1",
    +    "/srl_nokia-interfaces:interface/statistics/in-broadcast-packets": "22",
    +    "/srl_nokia-interfaces:interface/statistics/in-error-packets": "0",
    +    "/srl_nokia-interfaces:interface/statistics/in-fcs-error-packets": "0",
    +    "/srl_nokia-interfaces:interface/statistics/in-multicast-packets": "8694",
    +    "/srl_nokia-interfaces:interface/statistics/in-octets": "1740350",
    +    "/srl_nokia-interfaces:interface/statistics/in-unicast-packets": "17",
    +    "/srl_nokia-interfaces:interface/statistics/out-broadcast-packets": "22",
    +    "/srl_nokia-interfaces:interface/statistics/out-error-packets": "0",
    +    "/srl_nokia-interfaces:interface/statistics/out-multicast-packets": "8696",
    +    "/srl_nokia-interfaces:interface/statistics/out-octets": "1723262",
    +    "/srl_nokia-interfaces:interface/statistics/out-unicast-packets": "17"
    +  }
    +}
    +####
    +
    \ No newline at end of file diff --git a/user_guide/event_processors/intro/index.html b/user_guide/event_processors/intro/index.html new file mode 100644 index 00000000..bc44343c --- /dev/null +++ b/user_guide/event_processors/intro/index.html @@ -0,0 +1,43 @@ + Introduction - gNMIc

    Introduction

    The event processors provide an easy way to configure a set of functions in order to transform an event message that will be be written to a specific output.

    While the event format is the de facto format used by gNMIc in case the output is influxdb or prometheus, it can be used with any other output type.

    Transforming the received gNMI message is sometimes needed to accomodate the output system ( converting types, complying with name constraints,...), or simply filtering out values that you are not interested on.

    The event format#

    The event format is produced by gNMIc from the gNMI Notification messages received within a gNMI subscribe response update, it contains 5 fields:

    • name: A string field populated by the subscription name, it is used as a part of the metric name in case of prometheus output or it can be used as the measurement name in case of influxdb output.
    • timestamp: An int64 field containing the timestamp received within the gnmi Update.
    • tags: A map of string keys and string values. The keys and values are extracted from the keys in the gNMI PathElement keys. gNMIc adds the subscription name and the target name/address.
    • values: A map of string keys and generic values. The keys are build from a xpath representation of the gNMI path without the keys, while the values are extracted from the gNMI Node values.
    • deletes: A string list built from the delete field of the gNMI Notification message.

    Defining an event processor#

    Event processors are defined under the section processors in gNMIc configuration file.

    Each processor is identified by a name, under which we specify the processor type as well as additional fields specific to each type.

    Note

    Processors names are case insensitive

    All processors support a debug field that enables extra debug log messages to help troubleshoot the processor transformation.

    Below is an example of an event-delete processor, which deletes all values with a name containing multicast or broadcast

    processors:
    +  # processor name
    +  my-processor:
    +    # processor type
    +    event-delete:
    +      value-names:
    +        - ".*multicast.*"
    +        - ".*broadcast.*"
    +

    Linking an event processor to an output#

    Once the needed event processors are defined under section processors, they can be linked to the desired output(s) in the same file.

    Each output can be configured with different event processors allowing flexibility in the way the same data is written to different outputs.

    A list of event processors names can be added under an output configuration, the processors will apply in the order they are configured.

    In the below example, 3 event processors are configured and linked to output1 of type influxdb.

    The first processor converts all values type to integer if possible.

    The second deletes tags with name starting with subscription-name.

    Finally the third deletes values with name ending with out-unicast-packets.

    outputs:
    +  output1:
    +    type: influxdb
    +    url: http://localhost:8086
    +    bucket: telemetry
    +    token: srl:srl
    +    batch-size: 1000
    +    flush-timer: 10s
    +    event-processors:
    +      - proc-convert-integer
    +      - proc-delete-tag-name
    +      - proc-delete-value-name
    +
    +processors:
    +  proc-convert-integer:
    +    event-convert:
    +      value-names:
    +        - ".*"
    +      type: int
    +
    +  proc-delete-tag-name:
    +    event-delete:
    +      tag-names:
    +        - "^subscription-name"
    +
    +  proc-delete-value-name:
    +    event-delete:
    +      value-names:
    +        - ".*out-unicast-packets"
    +

    Event processors with cache#

    In the scenario where processors are configured under an output with caching enabled, the event messages retrieved from the cache are processed as a single set by each processor. This concurrent processing facilitates the application of a logic that merges or combines messages, enabling more complex and integrated processing strategies.

    Event processors pipeline#

    Processors under an output are applied in a strict sequential order for each group of event messages received.

    Event processors plugins#

    gNMIc incorporates the capability to extend its functionality through the use of event processors as plugins. To integrate seamlessly with gNMIc, these plugins need to be written in Golang.

    The communication between gNMIc and these plugins is facilitated by HashiCorp's go-plugin package, which employs netrpc as the underlying protocol for this interaction.

    See some plugin examples here

    \ No newline at end of file diff --git a/user_guide/gnmi_server/index.html b/user_guide/gnmi_server/index.html new file mode 100644 index 00000000..092e8f68 --- /dev/null +++ b/user_guide/gnmi_server/index.html @@ -0,0 +1,151 @@ + gNMI Server - gNMIc

    gNMI Server#

    Introduction#

    On top of acting as gNMI client gNMIc can run a gNMI server that supports Get, Set and Subscribe RPCs.

    The goal is to act as a caching point for the collected gNMI notifications and make them available to other collectors via the Subscribe RPC.

    Using this gNMI server feature it is possible to build gNMI based clusters and pipelines with gNMIc.

    The server keeps a cache of the gNMI notifications received from the defined subscriptions and utilizes it to build the Subscribe RPC responses.

    The unary RPCs, Get and Set, are relayed to known targets based on the Prefix.Target field.

    Supported features#

    • Supports gNMI RPCs, Get, Set, Subscribe
    • Acts as a gNMI gateway for Get and Set RPCs.
    • Supports Service registration with Consul server.
    • Supports all types of gNMI subscriptions, once, poll, stream.
    • Supports all types of stream subscriptions, on-change, target-defined and sample.
    • Supports updates-only with stream and once subscriptions.
    • Supports suppress-redundant.
    • Supports heartbeat-interval with on-change and sample stream subscriptions.

    Get RPC#

    The server supports the gNMI Get RPC, it allows a client to retrieve gNMI notifications from multiple targets into a single GetResponse.

    It relies on the GetRequest Prefix.Target field to select the target(s) against which it will run the Get RPC.

    If Prefix.Target is left empty or is equal to *, the Get RPC is performed against all known targets. The received GetRequest is cloned, enriched with each target name and sent to the corresponding destination.

    Comma separated target names are also supported and allow to select a list of specific targets to send the Get RPC to.

    gnmic -a gnmic-server:57400 get --path /interfaces \
    +                                --target router1,router2,router3
    +

    Once all GetResponses are received back successfully, the notifications contained in each GetResponse are combined into a single GetResponse with each notification's Prefix.Target populated, if empty.

    The resulting GetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client.

    If the GetRequest Path has the Origin field set to gnmic, the request is performed against the internal gNMIc server configuration. Currently only the paths targets and subscriptions are supported.

    gnmic -a gnmic-server:57400 get --path gnmic:/targets
    +gnmic -a gnmic-server:57400 get --path gnmic:/subscriptions
    +

    Set RPC#

    This gNMI server supports the gNMI Set RPC, it allows a client to run a single Set RPC against multiple targets.

    Just like in the case of Get RPC, the server relies on the Prefix.Target field to select the target(s) against which it will run the Set RPC.

    If Prefix.Target is left empty or is equal to *, a Set RPC is performed against all known targets. The received SetRequest is cloned, enriched with each target name and sent to the corresponding destination.

    Comma separated target names are also supported and allow to select a list of specific targets to send the Set RPC to.

    gnmic -a gnmic-server:57400 set \
    +        --update /system/ssh-server/admin-state:::json:::disable \
    +        --target router1,router2,router3
    +

    Once all SetResponses are received back successfully, the UpdateResults from each response are merged into a single SetResponse, with the addition of the target name set in Path.Target.

    Note

    Adding a target value to a non prefix path is not compliant with the gNMI specification which stipulates that the Target field should only be present in Prefix Paths

    The resulting SetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client.

    Subscribe RPC#

    The gNMIc server keeps a cache of gNMI notifications synched with the configured targets based on the configured subscriptions.

    The Subscribe requests received from a client are run against the afore mentioned cache, this means that a client cannot get updates about a leaf that gNMIc did not subscribe to as a client.

    Clients can subscribe to specific target using the gNMI Prefix.Target field, while leaving the Prefix.Target field empty or setting it to * is equivalent to subscribing to all known targets.

    Subscription Mode#

    gNMIc gNMI Server supports the 3 gNMI specified subscription modes: Once, Poll and Stream.

    It also supports some subscription behavior modifiers:

    • updates-only with stream and once subscriptions.
    • suppress-redundant.
    • heartbeat-interval with on-change and sample stream subscriptions.

    Once#

    A subscription operating in the ONCE mode acts as a single request/response channel. The target creates the relevant update messages, transmits them, and subsequently closes the RPC.

    In this subscription mode, gNMIc server supports the updates-only knob.

    Poll#

    Polling subscriptions are used for on-demand retrieval of data items via long-lived RPCs. A poll subscription relates to a certain set of subscribed paths, and is initiated by sending a SubscribeRequest message with encapsulated SubscriptionList. Subscription messages contained within the SubscriptionList indicate the set of paths that are of interest to the polling client.

    Stream#

    Stream subscriptions are long-lived subscriptions which continue to transmit updates relating to the set of paths that are covered within the subscription indefinitely.

    In this subscription mode, gNMIc server supports the updates-only knob.

    On Change#

    When a subscription is defined to be on-change, data updates are only sent to the client when the value of the data item changes.

    In the case of gNMIc gNMI server, on-change subscriptions depend on the subscription writing data to the local cache, if it is a sample subscription, each update from a target will trigger an on-change update to the server client.

    gNMIc gNMI server supports on-change subscriptions with heartbeat-interval. If the heartbeat-interval value is set to a non zero value, the value of the data item(s) MUST be re-sent once per heartbeat interval regardless of whether the value has changed or not.

    Note

    The minimum heartbeat-interval is configurable using the field min-heartbeat-interval. It defaults to 1s

    If the received heartbeat-interval value is greater than zero but lower than min-heartbeat-interval, the min-heartbeat-interval value is used instead.

    Target Defined#

    When a client creates a subscription specifying the target defined mode, the target MUST determine the best type of subscription to be created on a per-leaf basis.

    In the case of gNMIc gNMI server, a target-defined stream subscription, is treated as an on-change subscription.

    Note that this does not mean that gNMIc will filter out the unchanged values received from a sample subscription to the actual targets.

    Sample#

    A sample subscription is one where data items are sent to the client once per sample-interval.

    The minimum supported sample-interval is configurable using the field min-sample-interval, defaults to 1ms.

    If within a SubscribeRequest the received sample-interval is zero, the default-sample-interval is used, defaults to 1s.

    Configuration#

    gnmi-server:
    +  # the address the gNMI server will listen to
    +  address: :57400
    +  # tls config
    +  tls:
    +    # string, path to the CA certificate file,
    +    # this certificate is used to verify the clients certificates.
    +    ca-file:
    +    # string, server certificate file.
    +    cert-file:
    +    # string, server key file.
    +    key-file:
    +    # string, one of `"", "request", "require", "verify-if-given", or "require-verify" 
    +    #  - request:         The server requests a certificate from the client but does not 
    +    #                     require the client to send a certificate. 
    +    #                     If the client sends a certificate, it is not required to be valid.
    +    #  - require:         The server requires the client to send a certificate and does not 
    +    #                     fail if the client certificate is not valid.
    +    #  - verify-if-given: The server requests a certificate, 
    +    #                     does not fail if no certificate is sent. 
    +    #                     If a certificate is sent it is required to be valid.
    +    #  - require-verify:  The server requires the client to send a valid certificate.
    +    #
    +    # if no ca-file is present, `client-auth` defaults to ""`
    +    # if a ca-file is set, `client-auth` defaults to "require-verify"`
    +    client-auth: ""
    +  max-subscriptions: 64
    +  # maximum number of active Get/Set RPCs
    +  max-unary-rpc: 64
    +  # defines the minimum allowed sample interval, this value is used when the received sample-interval 
    +  # is greater than zero but lower than this minimum value.
    +  min-sample-interval: 1ms
    +  # defines the default sample interval, 
    +  # this value is used when the received sample-interval is zero within a stream/sample subscription.
    +  default-sample-interval: 1s
    +  # defines the minimum heartbeat-interval
    +  # this value is used when the received heartbeat-interval is greater than zero but
    +  # lower than this minimum value
    +  min-heartbeat-interval: 1s
    +  # enables the collection of Prometheus gRPC server metrics
    +  enable-metrics: false
    +  # enable additional debug logs
    +  debug: false
    +  # Enables Consul service registration
    +  service-registration:
    +    # Consul server address, default to localhost:8500
    +    address:
    +    # Consul Data center, defaults to dc1
    +    datacenter: 
    +    # Consul username, to be used as part of HTTP basicAuth
    +    username:
    +    # Consul password, to be used as part of HTTP basicAuth
    +    password:
    +    # Consul Token, is used to provide a per-request ACL token 
    +    # which overrides the agent's default token
    +    token:
    +    # gnmi server service check interval, only TTL Consul check is enabled
    +    # defaults to 5s
    +    check-interval:
    +    # Maximum number of failed checks before the service is deleted by Consul
    +    # defaults to 3
    +    max-fail:
    +    # Consul service name
    +    name:
    +    # List of tags to be added to the service registration, 
    +    # if available, the instance-name and cluster-name will be added as tags,
    +    # in the format: gnmic-instance=$instance-name and gnmic-cluster=$cluster-name
    +    tags:
    +  # cache configuration
    +  cache:
    +    # cache type, defaults to `oc`
    +    type: oc
    +    # string, address of the remote cache server,
    +    # irrelevant if type is `oc`
    +    address:
    +    # string, the remote server username.
    +    username:
    +    # string, the remote server password.
    +    password:
    +    # string, expiration period of received messages.
    +    expiration: 60s
    +    # enable extra logging
    +    debug: false
    +    # int64, default: 1073741824 (1 GiB). 
    +    # Max number of bytes stored in the cache per subscription.
    +    max-bytes:
    +    # int64, default: 1048576. 
    +    # Max number of messages stored per subscription.
    +    max-msgs-per-subscription:
    +    # int, default 100. 
    +    # Batch size used by the JetStream pull subscriber.
    +    fetch-batch-size:
    +    # duration, default 100ms. 
    +    # Wait time used by the JetStream pull subscriber.
    +    fetch-wait-time:  
    +

    Secure vs Insecure Server#

    Insecure Mode#

    By default, the server runs in insecure mode, as long as skip-verify is false and none of ca-file, cert-file and key-file are set.

    Secure Mode#

    To run this gNMI server in secure mode, there are a few options:

    • Using self signed certificates, without client certificate verification:
    gnmi-server:
    +  skip-verify: true
    +
    • Using self signed certificates, with client certificate verification:
    gnmi-server:
    +# a valid CA certificate to verify the client provided certificates
    +  ca-file: /path/to/caFile 
    +
    • Using CA provided certificates, without client certificate verification:
    gnmi-server:
    +  skip-verify: true
    +  # a valid server certificate
    +  cert-file: /path/to/server-cert
    +  # a valid server key
    +  key-file:  /path/to/server-key
    +
    • Using CA provided certificates, with client certificate verification:
    gnmi-server:
    +  # a valid CA certificate to verify the client provided certificates
    +  ca-file: /path/to/caFile 
    +  # a valid server certificate
    +  cert-file: /path/to/server-cert
    +  # a valid server key
    +  key-file:  /path/to/server-key
    +

    Fields#

    address#

    Defines the address the gNMI server will listen to.

    This can be a tcp socket in the format <addr:port> or a unix socket starting with unix:///

    skip-verify#

    If true, the server will not verify the client's certificates.

    ca-file#

    Defines the path to the CA certificate file to be used, irrelevant if skip-verify is true

    cert-file#

    Defines the path to the server certificate file to be used.

    key-file#

    Defines the path to the server key file to be used.

    max-subscriptions#

    Defines the maximum number of allowed subscriptions.

    Defaults to 64.

    max-unary-rpc#

    Defines the maximum number of active Get/Set RPCs.

    Defaults to 64.

    min-sample-interval#

    Defines the minimum allowed sample interval, this value is used when the received sample-interval is greater than zero but lower than this minimum value.

    Defaults to 1ms

    default-sample-interval#

    Defines the default sample interval, this value is used when the received sample-interval is zero within a stream/sample subscription.

    Defaults to 1s

    min-heartbeat-interval#

    Defines the minimum heartbeat-interval, this value is used when the received heartbeat-interval is greater than zero but lower than this minimum value.

    Defaults to 1s

    enable-metrics#

    Enables the collection of Prometheus gRPC server metrics.

    debug#

    Enables additional debug logging.

    Caching#

    By default, the gNMI server uses Openconfig's gNMI cache as a backend.

    Distributed caching is supported using any of the other cache types specified here.

    When a distributed cache is used together with the gNMI server feature, a gNMI client can subscribe to any of the gNMI servers to get gNMI updates collected from all the targets.

    On the other hand, if the gNMI client sends a unary RPC (Get, Set), it will have be directed to the gNMI server directly connected to the target.

    gnmi-server:
    +  #
    +  # other gnmi-server attributes
    +  #
    +  cache:
    +    # cache type, defaults to `oc`
    +    type: oc # redis, nats or jetstream
    +    # string, address of the remote cache server,
    +    # irrelevant if type is `oc`
    +    address:
    +    # string, the remote server username.
    +    username:
    +    # string, the remote server password.
    +    password:
    +    # string, expiration period of received messages.
    +    expiration: 60s
    +    # enable extra logging.
    +    debug: false
    +    # int64, default: 1073741824 (1 GiB). 
    +    # Max number of bytes stored in the cache per subscription.
    +    max-bytes:
    +    # int64, default: 1048576. 
    +    # Max number of messages stored per subscription.
    +    max-msgs-per-subscription:
    +    # int, default 100. 
    +    # Batch size used by the JetStream pull subscriber.
    +    fetch-batch-size:
    +    # duration, default 100ms. 
    +    # Wait time used by the JetStream pull subscriber.
    +    fetch-wait-time:
    +
    \ No newline at end of file diff --git a/user_guide/golang_package/examples/capabilities/index.html b/user_guide/golang_package/examples/capabilities/index.html new file mode 100644 index 00000000..cab93779 --- /dev/null +++ b/user_guide/golang_package/examples/capabilities/index.html @@ -0,0 +1,42 @@ + Capabilities - gNMIc

    Capabilities

    The below snippet shows how to create a target, send a Capabilities Request and print the response.

    package main
    +
    +import (
    +    "context"
    +    "fmt"
    +    "log"
    +
    +    "github.com/openconfig/gnmic/pkg/api"
    +    "google.golang.org/protobuf/encoding/prototext"
    +)
    +
    +func main() {
    +    // create a target
    +    tg, err := api.NewTarget(
    +        api.Name("srl1"),
    +        api.Address("10.0.0.1:57400"),
    +        api.Username("admin"),
    +        api.Password("admin"),
    +        api.SkipVerify(true),
    +    )
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +
    +    ctx, cancel := context.WithCancel(context.Background())
    +    defer cancel()
    +
    +    // create a gNMI client
    +    err = tg.CreateGNMIClient(ctx)
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    defer tg.Close()
    +
    +    // send a gNMI capabilities request to the created target
    +    capResp, err := tg.Capabilities(ctx)
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    fmt.Println(prototext.Format(capResp))
    +}
    +
    \ No newline at end of file diff --git a/user_guide/golang_package/examples/get/index.html b/user_guide/golang_package/examples/get/index.html new file mode 100644 index 00000000..cb2719dd --- /dev/null +++ b/user_guide/golang_package/examples/get/index.html @@ -0,0 +1,51 @@ + Get - gNMIc

    Get

    The below snippet shows how to create a target, send a Get Request and print the response.

    package main
    +
    +import (
    +    "context"
    +    "fmt"
    +    "log"
    +
    +    "github.com/openconfig/gnmic/pkg/api"
    +    "google.golang.org/protobuf/encoding/prototext"
    +)
    +
    +func main() {
    +    // create a target
    +    tg, err := api.NewTarget(
    +        api.Name("srl1"),
    +        api.Address("10.0.0.1:57400"),
    +        api.Username("admin"),
    +        api.Password("admin"),
    +        api.SkipVerify(true),
    +    )
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +
    +    ctx, cancel := context.WithCancel(context.Background())
    +    defer cancel()
    +
    +    // create a gNMI client
    +    err = tg.CreateGNMIClient(ctx)
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    defer tg.Close()
    +
    +    // create a GetRequest
    +    getReq, err := api.NewGetRequest(
    +        api.Path("/system/name"),
    +        api.Encoding("json_ietf"))
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    fmt.Println(prototext.Format(getReq))
    +
    +    // send the created gNMI GetRequest to the created target
    +    getResp, err := tg.Get(ctx, getReq)
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    fmt.Println(prototext.Format(getResp))
    +}
    +
    \ No newline at end of file diff --git a/user_guide/golang_package/examples/set/index.html b/user_guide/golang_package/examples/set/index.html new file mode 100644 index 00000000..7f69a46c --- /dev/null +++ b/user_guide/golang_package/examples/set/index.html @@ -0,0 +1,52 @@ + Set - gNMIc

    Set

    The below snippet shows how to create a target, send a Set Request and print the reponse.

    package main
    +
    +import (
    +    "context"
    +    "fmt"
    +    "log"
    +
    +    "github.com/openconfig/gnmic/pkg/api"
    +    "google.golang.org/protobuf/encoding/prototext"
    +)
    +
    +func main() {
    +    // create a target
    +    tg, err := api.NewTarget(
    +        api.Name("srl1"),
    +        api.Address("10.0.0.1:57400"),
    +        api.Username("admin"),
    +        api.Password("admin"),
    +        api.SkipVerify(true),
    +    )
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +
    +    ctx, cancel := context.WithCancel(context.Background())
    +    defer cancel()
    +
    +    err = tg.CreateGNMIClient(ctx)
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    defer tg.Close()
    +
    +    // create a gNMI SetRequest
    +    setReq, err := api.NewSetRequest(
    +        api.Update(
    +            api.Path("/system/name/host-name"),
    +            api.Value("srl2", "json_ietf")),
    +    )
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    fmt.Println(prototext.Format(setReq))
    +
    +    // send the created gNMI SetRequest to the created target
    +    setResp, err := tg.Set(ctx, setReq)
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    fmt.Println(prototext.Format(setResp))
    +}
    +
    \ No newline at end of file diff --git a/user_guide/golang_package/examples/subscribe/index.html b/user_guide/golang_package/examples/subscribe/index.html new file mode 100644 index 00000000..b7ca9e5e --- /dev/null +++ b/user_guide/golang_package/examples/subscribe/index.html @@ -0,0 +1,67 @@ + Subcribe - gNMIc

    Subcribe

    The below snippet shows how to create a target and a Subscribe Request. It then starts a Stream subscription with 10s interval and listens to Responses and errors.

    package main
    +
    +import (
    +    "context"
    +    "fmt"
    +    "log"
    +    "time"
    +
    +    "github.com/openconfig/gnmic/pkg/api"
    +    "google.golang.org/protobuf/encoding/prototext"
    +)
    +
    +func main() {
    +    // create a target
    +    tg, err := api.NewTarget(
    +        api.Name("srl1"),
    +        api.Address("srl1:57400"),
    +        api.Username("admin"),
    +        api.Password("admin"),
    +        api.SkipVerify(true),
    +    )
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +
    +    ctx, cancel := context.WithCancel(context.Background())
    +    defer cancel()
    +    err = tg.CreateGNMIClient(ctx)
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    defer tg.Close()
    +    // create a gNMI subscribeRequest
    +    subReq, err := api.NewSubscribeRequest(
    +        api.Encoding("json_ietf"),
    +        api.SubscriptionListMode("stream"),
    +        api.Subscription(
    +            api.Path("system/name"),
    +            api.SubscriptionMode("sample"),
    +            api.SampleInterval(10*time.Second),
    +        ))
    +    if err != nil {
    +        log.Fatal(err)
    +    }
    +    fmt.Println(prototext.Format(subReq))
    +    // start the subscription
    +    go tg.Subscribe(ctx, subReq, "sub1")
    +    // start a goroutine that will stop the subscription after x seconds
    +    go func() {
    +        select {
    +        case <-ctx.Done():
    +            return
    +        case <-time.After(42 * time.Second):
    +            tg.StopSubscription("sub1")
    +        }
    +    }()
    +    subRspChan, subErrChan := tg.ReadSubscriptions()
    +    for {
    +        select {
    +        case rsp := <-subRspChan:
    +            fmt.Println(prototext.Format(rsp.Response))
    +        case tgErr := <-subErrChan:
    +            log.Fatalf("subscription %q stopped: %v", tgErr.SubscriptionName, tgErr.Err)
    +        }
    +    }
    +}
    +
    \ No newline at end of file diff --git a/user_guide/golang_package/gnmi_options/index.html b/user_guide/golang_package/gnmi_options/index.html new file mode 100644 index 00000000..b047ce49 --- /dev/null +++ b/user_guide/golang_package/gnmi_options/index.html @@ -0,0 +1,129 @@ + gNMI Options - gNMIc

    gNMI Options

    The package github.com/openconfig/gnmic/api exposes a set of api.GNMIOption that can be used with api.NewGetRequest(...api.GNMIOption) GNMIOption, api.NewSetRequest(...api.GNMIOption) GNMIOption or api.NewSubscribeRequest(...api.GNMIOption) GNMIOption to create a gNMI Request.

    // Version sets the provided gNMI version string in a gnmi.CapabilityResponse message.
    +func Version(v string) func(msg proto.Message) error
    +
    // SupportedEncoding creates an GNMIOption that sets the provided encodings as supported encodings in a gnmi.CapabilitiesResponse
    +func SupportedEncoding(encodings ...string) func(msg proto.Message) error
    +
    // SupportedModel creates an GNMIOption that sets the provided name, org and version as a supported model in a gnmi.CapabilitiesResponse.
    +func SupportedModel(name, org, version string) func(msg proto.Message) error
    +
    // Extension creates a GNMIOption that applies the supplied gnmi_ext.Extension to the provided
    +// proto.Message.
    +func Extension(ext *gnmi_ext.Extension) func(msg proto.Message) error
    +
    // Prefix creates a GNMIOption that creates a *gnmi.Path and adds it to the supplied
    +// proto.Message (as a Path Prefix).
    +// The proto.Message can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func Prefix(prefix string) func(msg proto.Message) error
    +
    // Target creates a GNMIOption that set the gnmi Prefix target to the supplied string value.
    +// The proto.Message can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func Target(target string) func(msg proto.Message) error
    +
    // Path creates a GNMIOption that creates a *gnmi.Path and adds it to the supplied proto.Message
    +// which can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.Subscription.
    +func Path(path string) func(msg proto.Message) error
    +
    // Encoding creates a GNMIOption that adds the encoding type to the supplied proto.Message
    +// which can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func Encoding(encoding string) func(msg proto.Message) error
    +
    // EncodingJSON creates a GNMIOption that sets the encoding type to JSON in a gnmi.GetRequest or
    +// gnmi.SubscribeRequest.
    +func EncodingJSON() func(msg proto.Message) error
    +
    // EncodingBytes creates a GNMIOption that sets the encoding type to BYTES in a gnmi.GetRequest or
    +// gnmi.SubscribeRequest.
    +func EncodingBytes() func(msg proto.Message) error
    +
    // EncodingPROTO creates a GNMIOption that sets the encoding type to PROTO in a gnmi.GetRequest or
    +// gnmi.SubscribeRequest.
    +func EncodingPROTO() func(msg proto.Message) error
    +
    // EncodingASCII creates a GNMIOption that sets the encoding type to ASCII in a gnmi.GetRequest or
    +// gnmi.SubscribeRequest.
    +func EncodingASCII() func(msg proto.Message) error
    +
    // EncodingJSON_IETF creates a GNMIOption that sets the encoding type to JSON_IETF in a gnmi.GetRequest or
    +// gnmi.SubscribeRequest.
    +func EncodingJSON_IETF() func(msg proto.Message) error
    +
    // EncodingCustom creates a GNMIOption that adds the encoding type to the supplied proto.Message
    +// which can be a *gnmi.GetRequest, *gnmi.SetRequest or a *gnmi.SubscribeRequest with RequestType Subscribe.
    +// Unlike Encoding, this GNMIOption does not validate if the provided encoding is defined by the gNMI spec.
    +func EncodingCustom(enc int) func(msg proto.Message) error
    +
    // DataType creates a GNMIOption that adds the data type to the supplied proto.Message
    +// which must be a *gnmi.GetRequest.
    +func DataType(datat string) func(msg proto.Message) error
    +
    // DataTypeALL creates a GNMIOption that sets the gnmi.GetRequest data type to ALL
    +func DataTypeALL() func(msg proto.Message) error
    +
    // DataTypeCONFIG creates a GNMIOption that sets the gnmi.GetRequest data type to CONFIG
    +func DataTypeCONFIG() func(msg proto.Message) error
    +
    // DataTypeSTATE creates a GNMIOption that sets the gnmi.GetRequest data type to STATE
    +func DataTypeSTATE() func(msg proto.Message) error
    +
    // DataTypeOPERATIONAL creates a GNMIOption that sets the gnmi.GetRequest data type to OPERATIONAL
    +func DataTypeOPERATIONAL() func(msg proto.Message) error
    +
    // UseModel creates a GNMIOption that add a gnmi.DataModel to a gnmi.GetRequest or gnmi.SubscribeRequest
    +// based on the name, org and version strings provided.
    +func UseModel(name, org, version string) func(msg proto.Message) error
    +
    // Update creates a GNMIOption that creates a *gnmi.Update message and adds it to the supplied proto.Message,
    +// the supplied message must be a *gnmi.SetRequest.
    +func Update(opts ...GNMIOption) func(msg proto.Message) error
    +
    // Replace creates a GNMIOption that creates a *gnmi.Update message and adds it to the supplied proto.Message.
    +// the supplied message must be a *gnmi.SetRequest.
    +func Replace(opts ...GNMIOption) func(msg proto.Message) error
    +
    // Value creates a GNMIOption that creates a *gnmi.TypedValue and adds it to the supplied proto.Message.
    +// the supplied message must be a *gnmi.Update.
    +// If a map is supplied as `data interface{}` it has to be a map[string]interface{}.
    +func Value(data interface{}, encoding string) func(msg proto.Message) error
    +
    // Delete creates a GNMIOption that creates a *gnmi.Path and adds it to the supplied proto.Message.
    +// the supplied message must be a *gnmi.SetRequest. The *gnmi.Path is added the .Delete list.
    +func Delete(path string) func(msg proto.Message) error
    +
    // SubscriptionListMode creates a GNMIOption that sets the SubscribeRequest Mode.
    +// The variable mode must be one of "once", "poll" or "stream".
    +// The supplied proto.Message must be a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func SubscriptionListMode(mode string) func(msg proto.Message) error
    +
    // SubscriptionListModeSTREAM creates a GNMIOption that sets the Subscription List Mode to STREAM
    +func SubscriptionListModeSTREAM() func(msg proto.Message) error
    +
    // SubscriptionListModeONCE creates a GNMIOption that sets the Subscription List Mode to ONCE
    +func SubscriptionListModeONCE() func(msg proto.Message) error
    +
    // SubscriptionListModePOLL creates a GNMIOption that sets the Subscription List Mode to POLL
    +func SubscriptionListModePOLL() func(msg proto.Message) error
    +
    // Qos creates a GNMIOption that sets the QosMarking field in a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func Qos(qos uint32) func(msg proto.Message) error
    +
    // UseAliases creates a GNMIOption that sets the UsesAliases field in a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func UseAliases(b bool) func(msg proto.Message) error
    +
    // AllowAggregation creates a GNMIOption that sets the AllowAggregation field in a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func AllowAggregation(b bool) func(msg proto.Message) error
    +
    // UpdatesOnly creates a GNMIOption that sets the UpdatesOnly field in a *gnmi.SubscribeRequest with RequestType Subscribe.
    +func UpdatesOnly(b bool) func(msg proto.Message) error
    +
    // UpdatesOnly creates a GNMIOption that creates a *gnmi.Subscription based on the supplied GNMIOption(s) and adds it the
    +// supplied proto.Message which must be of type *gnmi.SubscribeRequest with RequestType Subscribe.
    +func Subscription(opts ...GNMIOption) func(msg proto.Message) error
    +
    // SubscriptionMode creates a GNMIOption that sets the Subscription mode in a proto.Message of type *gnmi.Subscription.
    +func SubscriptionMode(mode string) func(msg proto.Message) error
    +
    // SubscriptionModeTARGET_DEFINED creates a GNMIOption that sets the subscription mode to TARGET_DEFINED
    +func SubscriptionModeTARGET_DEFINED() func(msg proto.Message) error
    +
    // SubscriptionModeON_CHANGE creates a GNMIOption that sets the subscription mode to ON_CHANGE
    +func SubscriptionModeON_CHANGE() func(msg proto.Message) error
    +
    // SubscriptionModeSAMPLE creates a GNMIOption that sets the subscription mode to SAMPLE
    +func SubscriptionModeSAMPLE() func(msg proto.Message) error
    +
    // SampleInterval creates a GNMIOption that sets the SampleInterval in a proto.Message of type *gnmi.Subscription.
    +func SampleInterval(d time.Duration) func(msg proto.Message) error
    +
    // HeartbeatInterval creates a GNMIOption that sets the HeartbeatInterval in a proto.Message of type *gnmi.Subscription.
    +func HeartbeatInterval(d time.Duration) func(msg proto.Message) error
    +
    // SuppressRedundant creates a GNMIOption that sets the SuppressRedundant in a proto.Message of type *gnmi.Subscription.
    +func SuppressRedundant(s bool) func(msg proto.Message) error
    +
    // Notification creates a GNMIOption that builds a gnmi.Notification from the supplied GNMIOptions and adds it
    +// to the supplied proto.Message
    +func Notification(opts ...GNMIOption) func(msg proto.Message) error
    +
    // Timestamp sets the supplied timestamp in a gnmi.Notification message
    +func Timestamp(t int64) func(msg proto.Message) error
    +
    // TimestampNow is the same as Timestamp(time.Now().UnixNano())
    +func TimestampNow() func(msg proto.Message) error
    +
    // Alias sets the supplied alias value in a gnmi.Notification message
    +func Alias(alias string) func(msg proto.Message) error
    +
    // Atomic sets the .Atomic field in a gnmi.Notification message
    +func Atomic(b bool) func(msg proto.Message) error
    +
    // UpdateResult creates a GNMIOption that creates a gnmi.UpdateResult and adds it to
    +// a proto.Message of type gnmi.SetResponse.
    +func UpdateResult(opts ...GNMIOption) func(msg proto.Message) error
    +
    // Operation creates a GNMIOption that sets the gnmi.UpdateResult_Operation
    +// value in a gnmi.UpdateResult.
    +func Operation(oper string) func(msg proto.Message) error
    +
    // OperationINVALID creates a GNMIOption that sets the gnmi.SetResponse Operation to INVALID
    +func OperationINVALID() func(msg proto.Message) error
    +
    // OperationDELETE creates a GNMIOption that sets the gnmi.SetResponse Operation to DELETE
    +func OperationDELETE() func(msg proto.Message) error
    +
    // OperationREPLACE creates a GNMIOption that sets the gnmi.SetResponse Operation to REPLACE
    +func OperationREPLACE() func(msg proto.Message) error
    +
    // OperationUPDATE creates a GNMIOption that sets the gnmi.SetResponse Operation to UPDATE
    +func OperationUPDATE() func(msg proto.Message) error
    +
    \ No newline at end of file diff --git a/user_guide/golang_package/intro/index.html b/user_guide/golang_package/intro/index.html new file mode 100644 index 00000000..ccfa5c9e --- /dev/null +++ b/user_guide/golang_package/intro/index.html @@ -0,0 +1,169 @@ + Introduction - gNMIc

    Introduction

    gnmic (github.com/openconfig/gnmic/api) can be imported as a dependency in your Golang programs.

    It acts as a wrapper around the openconfig/gnmi package providing a user friendly API to create a target and easily craft gNMI requests.

    Creating gNMI requests#

    Get Request#

    func NewGetRequest(opts ...GNMIOption) (*gnmi.GetRequest, error)
    +

    The below 2 snippets create a Get Request with 2 paths, json_ietf encoding and data type STATE

    Using github.com/openconfig/gnmic/api

    getReq, err := api.NewGetRequest(
    +    api.Encoding("json_ietf"),
    +    api.DataType("state"),    
    +    api.Path("interface/statistics"),    
    +    api.Path("interface/subinterface/statistics"),
    +)
    +// check error
    +

    Using github.com/openconfig/gnmi

    getReq := &gnmi.GetRequest{
    +        Path: []*gnmi.Path{
    +            {
    +                Elem: []*gnmi.PathElem{
    +                    {Name: "interface"},
    +                    {Name: "statistics"},
    +                },
    +            },
    +            {
    +                Elem: []*gnmi.PathElem{
    +                    {Name: "interface"},
    +                    {Name: "subinterface"},
    +                    {Name: "statistics"},
    +                },
    +            },
    +        },
    +        Type:     gnmi.GetRequest_STATE,
    +        Encoding: gnmi.Encoding_JSON_IETF,
    +    }
    +

    Set Request#

    func NewSetRequest(opts ...GNMIOption) (*gnmi.SetRequest, error)
    +

    The below 2 snippets create a Set Request with one two updates, one replace and one delete messages:

    Using github.com/openconfig/gnmic/api

    setReq, err := api.NewSetRequest(
    +    api.Update(
    +        api.Path("/system/name/host-name"),
    +        api.Value("srl2", "json_ietf"),
    +    ),
    +    api.Update(
    +        api.Path("/system/gnmi-server/unix-socket/admin-state"),
    +        api.Value("enable", "json_ietf"),
    +    ),
    +    api.Replace(
    +        api.Path("/network-instance[name=default]/admin-state"),
    +        api.Value("enable", "json_ietf"),
    +    ),
    +    api.Delete("/interface[name=ethernet-1/1]/admin-state"),
    +)
    +// check error
    +

    Using github.com/openconfig/gnmi

    setReq := &gnmi.SetRequest{
    +    Update: []*gnmi.Update{
    +        {
    +            Path: &gnmi.Path{
    +                Elem: []*gnmi.PathElem{
    +                    {Name: "system"},
    +                    {Name: "name"},
    +                    {Name: "host-name"},
    +                },
    +            },
    +            Val: &gnmi.TypedValue{
    +                Value: &gnmi.TypedValue_JsonIetfVal{
    +                    JsonIetfVal: []byte("\"srl2\""),
    +                },
    +            },
    +        },
    +        {
    +            Path: &gnmi.Path{
    +                Elem: []*gnmi.PathElem{
    +                    {Name: "system"},
    +                    {Name: "gnmi-server"},
    +                    {Name: "unix-socket"},
    +                    {Name: "admin-state"},
    +                },
    +            },
    +            Val: &gnmi.TypedValue{
    +                Value: &gnmi.TypedValue_JsonIetfVal{
    +                    JsonIetfVal: []byte("\"enable\""),
    +                },
    +            },
    +        },
    +    },
    +    Replace: []*gnmi.Update{
    +        {
    +            Path: &gnmi.Path{
    +                Elem: []*gnmi.PathElem{
    +                    {
    +                        Name: "network-instance",
    +                        Key: map[string]string{
    +                            "name": "default",
    +                        },
    +                    },
    +                    {
    +                        Name: "admin-state",
    +                    },
    +                },
    +            },
    +            Val: &gnmi.TypedValue{
    +                Value: &gnmi.TypedValue_JsonIetfVal{
    +                    JsonIetfVal: []byte("\"enable\""),
    +                },
    +            },
    +        },
    +    },
    +    Delete: []*gnmi.Path{
    +        {
    +            Elem: []*gnmi.PathElem{
    +                {
    +                    Name: "interface",
    +                    Key: map[string]string{
    +                        "name": "ethernet-1/1",
    +                    },
    +                },
    +                {
    +                    Name: "admin-state",
    +                },
    +            },
    +        },
    +    },
    +}
    +

    Subscribe Request#

    Create a Subscribe Request

    func NewSubscribeRequest(opts ...GNMIOption) (*gnmi.SubscribeRequest, error)
    +

    Create a Subscribe Poll Request

    func NewSubscribePollRequest(opts ...GNMIOption) *gnmi.SubscribeRequest
    +

    The below 2 snippets create a stream subscribe request with 2 paths, json_ietf encoding and a sample interval of 10 seconds:

    Using github.com/openconfig/gnmic/api

    subReq, err := api.NewSubscribeRequest(
    +    api.Encoding("json_ietf"),
    +    api.SubscriptionListMode("stream"),
    +    api.Subscription(
    +        api.Path("interface/statistics"),
    +        api.SubscriptionMode("sample"),
    +        api.SampleInterval("10s"),
    +    ),
    +    api.Subscription(
    +        api.Path("interface/subinterface/statistics"),
    +        api.SubscriptionMode("sample"),
    +        api.SampleInterval("10s"),
    +    ),
    +)
    +// check error
    +

    Using github.com/openconfig/gnmi

    subReq := &gnmi.SubscribeRequest_Subscribe{
    +    Subscribe: &gnmi.SubscriptionList{
    +        Subscription: []*gnmi.Subscription{
    +            {
    +                Path: &gnmi.Path{
    +                    Elem: []*gnmi.PathElem{
    +                        {Name: "interface"},
    +                        {Name: "statistics"},
    +                    },
    +                },
    +                Mode:           gnmi.SubscriptionMode_SAMPLE,
    +                SampleInterval: uint64(10 * time.Second),
    +            },
    +            {
    +                Path: &gnmi.Path{
    +                    Elem: []*gnmi.PathElem{
    +                        {Name: "interface"},
    +                        {Name: "subinterface"},
    +                        {Name: "statistics"},
    +                    },
    +                },
    +                Mode:           gnmi.SubscriptionMode_SAMPLE,
    +                SampleInterval: uint64(10 * time.Second),
    +            },
    +        },
    +        Mode:     gnmi.SubscriptionList_STREAM,
    +        Encoding: gnmi.Encoding_JSON_IETF,
    +    },
    +}
    +

    Creating Targets#

    A target can be created using func NewTarget(opts ...TargetOption) (*target.Target, error).

    The full list of api.TargetOption can be found here

    tg, err := api.NewTarget(
    +    api.Name("srl1"),
    +    api.Address("10.0.0.1:57400"),
    +    api.Username("admin"),
    +    api.Password("admin"),
    +    api.SkipVerify(true),
    +)
    +// check error
    +

    Once a Target is created, Multiple functions are available to run the desired RPCs, check the examples here

    \ No newline at end of file diff --git a/user_guide/golang_package/target_options/index.html b/user_guide/golang_package/target_options/index.html new file mode 100644 index 00000000..9341b414 --- /dev/null +++ b/user_guide/golang_package/target_options/index.html @@ -0,0 +1,53 @@ + Target Options - gNMIc

    Target Options

    The package github.com/openconfig/gnmic/api exposes a set of api.TargetOption that can be used with api.NewTarget(...api.TargetOption) TargetOption to create target.Target.

    // Name sets the target name.
    +func Name(name string) TargetOption 
    +
    +// Address sets the target address.
    +// This Option can be set multiple times.
    +func Address(addr string) TargetOption
    +
    +// Username sets the target Username.
    +func Username(username string) TargetOption 
    +
    +// Password sets the target Password.
    +func Password(password string) TargetOption 
    +
    +// Timeout sets the gNMI client creation timeout.
    +func Timeout(timeout time.Duration) TargetOption
    +
    +// Insecure sets the option to create a gNMI client with an
    +// insecure gRPC connection
    +func Insecure(i bool) TargetOption 
    +
    +// SkipVerify sets the option to create a gNMI client with a
    +// secure gRPC connection without verifying the target's certificates.
    +func SkipVerify(i bool) TargetOption 
    +
    +// TLSCA sets that path towards the TLS certificate authority file.
    +func TLSCA(tlsca string) TargetOption 
    +
    +// TLSCert sets that path towards the TLS certificate file.
    +func TLSCert(cert string) TargetOption 
    +
    +// TLSKey sets that path towards the TLS key file.
    +func TLSKey(key string) TargetOption 
    +
    +// TLSMinVersion sets the TLS minimum version used during the TLS handshake.
    +func TLSMinVersion(v string) TargetOption 
    +
    +// TLSMaxVersion sets the TLS maximum version used during the TLS handshake.
    +func TLSMaxVersion(v string) TargetOption
    +
    +// TLSVersion sets the desired TLS version used during the TLS handshake.
    +func TLSVersion(v string) TargetOption 
    +
    +// LogTLSSecret, if set to true,
    +// enables logging of the TLS master key.
    +func LogTLSSecret(b bool) TargetOption 
    +
    +// Gzip, if set to true,
    +// adds gzip compression to the gRPC connection.
    +func Gzip(b bool) TargetOption 
    +
    +// Token sets the per RPC credentials for all RPC calls. 
    +func Token(token string) TargetOption
    +
    \ No newline at end of file diff --git a/user_guide/inputs/input_intro/index.html b/user_guide/inputs/input_intro/index.html new file mode 100644 index 00000000..127b5a53 --- /dev/null +++ b/user_guide/inputs/input_intro/index.html @@ -0,0 +1,11 @@ + Introduction - gNMIc

    Introduction

    gnmic supports various Inputs to consume gnmi data, transform it and ultimately export it to one or multiple Outputs.

    The purpose of gnmic's Inputs is to build a gnmi data pipeline by enabling the ingestion and export of gnmi data that was exported by gnmic's outputs upstream.

    Currently supported input types:

    Defining Inputs and matching Outputs#

    To define an Input a user needs to fill in the inputs section in the configuration file.

    Each Input is defined by its name (input1 in the example below), a type field which determines the type of input to be created (nats, stan, kafka) and various other configuration fields which depend on the Input type.

    Note

    Inputs names are case insensitive

    All Input types have an outputs field, under which the user can defined the downstream destination(s) of the consumed data. This way, data consumed once, can be exported multiple times.

    Info

    The same gnmic instance can act as gNMI collector, input and output simultaneously.

    Example:

    # part of gnmic config file
    +inputs:
    +  input1:
    +    type: nats # input type
    +    #
    +    # other config fields depending on the input type
    +    #
    +    outputs:
    +      - output1
    +      - output2
    +

    Inputs use cases#

    Clustering#

    Using gnmic Inputs, the user can aggregate all the collected data into one instance of gnmic that can make it available to a downstream off the shelf tool,typically Prometheus.

    Data reuse#

    Collect data once and use it multiple times. By chaining multiple instances of gnmic the user can process the same stream of data in different ways.

    A different set of event processors can be applied on the data stream before being exported to its intended outputs.

    \ No newline at end of file diff --git a/user_guide/inputs/kafka_input/index.html b/user_guide/inputs/kafka_input/index.html new file mode 100644 index 00000000..d6301184 --- /dev/null +++ b/user_guide/inputs/kafka_input/index.html @@ -0,0 +1,50 @@ + Kafka - gNMIc

    Kafka

    When using Kafka as input, gnmic consumes data from a specific Kafka topic in event or proto format.

    Multiple consumers can be created per gnmic instance (num-workers). All the workers join the same Kafka consumer group (group-id) in order to load share the messages between the workers.

    Multiple instances of gnmic with the same Kafka input can be used to effectively consume the exported messages in parallel

    The Kafka input will export the received messages to the list of outputs configured under its outputs section.

    inputs:
    +  input1:
    +    # string, required, specifies the type of input
    +    type: kafka 
    +    # Kafka subscriber name
    +    # If left empty, it will be populated with the string from flag --instance-name appended with `--kafka-cons`.
    +    # If --instance-name is also empty, a random name is generated in the format `gnmic-$uuid`
    +    # note that each kafka worker (consumer) will get name=$name-$index
    +    name: ""
    +    # Kafka SASL configuration
    +    sasl:
    +      # SASL user name
    +      user:
    +      # SASL password
    +      password:
    +      # SASL mechanism: PLAIN, SCRAM-SHA-256, SCRAM-SHA-512 and OAUTHBEARER are supported
    +      mechanism:
    +      # token url for OAUTHBEARER SASL mechanism
    +      token-url:
    +    # string, comma separated Kafka servers addresses
    +    address: localhost:9092
    +    # string, comma separated topics the Kafka consumer group consumes messages from.
    +    topics: telemetry 
    +    # consumer group all gnmic Kafka input workers join, 
    +    # so that Kafka server can load share the messages between them. Defaults to `gnmic-consumers`
    +    group-id: gnmic-consumers
    +    # duration, the timeout used to detect consumer failures when using Kafka's group management facility.
    +    # If no heartbeats are received by the broker before the expiration of this session timeout,
    +    # then the broker will remove this consumer from the group and initiate a rebalance.
    +    session-timeout: 10s
    +    # duration, the expected time between heartbeats to the consumer coordinator when using Kafka's group
    +      # management facilities.
    +    heartbeat-interval: 3s
    +    # duration, wait time before reconnection attempts after any error
    +    recovery-wait-time: 2s 
    +    # string, kafka version, defaults to 2.5.0
    +    version: 
    +    # string, consumed message expected format, one of: proto, event
    +    format: event 
    +    # bool, enables extra logging
    +    debug: false
    +    # integer, number of kafka consumers to be created
    +    num-workers: 1
    +    # list of processors to apply on the message when received, 
    +    # only applies if format is 'event'
    +    event-processors: 
    +    # []string, list of named outputs to export data to. 
    +    # Must be configured under root level `outputs` section
    +    outputs: 
    +
    \ No newline at end of file diff --git a/user_guide/inputs/nats_input/index.html b/user_guide/inputs/nats_input/index.html new file mode 100644 index 00000000..7c6f2908 --- /dev/null +++ b/user_guide/inputs/nats_input/index.html @@ -0,0 +1,39 @@ + NATS - gNMIc

    NATS

    When using NATS as input, gnmic consumes data from a specific NATS subject in event or proto format.

    Multiple consumers can be created per gnmic instance (num-workers). All the workers join the same NATS queue group (queue) in order to load share the messages between the workers.

    Multiple instances of gnmic with the same NATS input can be used to effectively consume the exported messages in parallel

    The NATS input will export the received messages to the list of outputs configured under its outputs section.

    inputs:
    +  input1:
    +    # string, required, specifies the type of input
    +    type: nats 
    +    # NATS subscriber name
    +    # If left empty, it will be populated with the string from flag --instance-name appended with `--nats-sub`.
    +    # If --instance-name is also empty, a random name is generated in the format `gnmic-$uuid`
    +    # note that each nats worker (subscriber) will get name=$name-$index
    +    name: ""
    +    # string, comma separated NATS servers addresses
    +    address: localhost:4222
    +    # The subject name gnmic NATS consumers subscribe to.
    +    subject: telemetry 
    +    # subscribe queue group all gnmic NATS input workers join, 
    +    # so that NATS server can load share the messages between them.
    +    queue: 
    +    # string, NATS username
    +    username: 
    +    # string, NATS password  
    +    password: 
    +    # duration, wait time before reconnection attempts
    +    connect-time-wait: 2s 
    +    # string, consumed message expected format, one of: proto, event
    +    format: event 
    +    # bool, enables extra logging
    +    debug: false
    +    # integer, number of nats consumers to be created
    +    num-workers: 1
    +    # integer, sets the size of the local buffer where received 
    +    # NATS messages are stored before being sent to outputs.
    +    # This value is set per worker. Defaults to 100 messages
    +    buffer-size: 100
    +    # list of processors to apply on the message when received, 
    +    # only applies if format is 'event'
    +    event-processors: 
    +    # []string, list of named outputs to export data to. 
    +    # Must be configured under root level `outputs` section
    +    outputs: 
    +
    \ No newline at end of file diff --git a/user_guide/inputs/stan_input/index.html b/user_guide/inputs/stan_input/index.html new file mode 100644 index 00000000..4c21f228 --- /dev/null +++ b/user_guide/inputs/stan_input/index.html @@ -0,0 +1,43 @@ + STAN - gNMIc

    STAN

    When using STAN as input, gnmic consumes data from a specific STAN subject in event or proto format.

    Multiple consumers can be created per gnmic instance (num-workers). All the workers join the same STAN queue group (queue) in order to load share the messages between the workers.

    Multiple instances of gnmic with the same STAN input can be used to effectively consume the exported messages in parallel

    The STAN input will export the received messages to the list of outputs configured under its outputs section.

    inputs:
    +  input1:
    +    # string, required, specifies the type of input
    +    type: stan 
    +    # STAN subscriber name
    +    # If left empty, it will be populated with the string from flag --instance-name appended with `--stan-sub`.
    +    # If --instance-name is also empty, a random name is generated in the format `gnmic-$uuid`
    +    # note that each stan worker (subscriber) will get name=$name-$index
    +    name: ""
    +    # string, comma separated STAN servers addresses
    +    address: localhost:4222
    +    # The subject name gnmic STAN consumers subscribe to.
    +    subject: telemetry 
    +    # subscribe queue group all gnmic STAN input workers join, 
    +    # so that STAN server can load share the messages between them.
    +    queue: 
    +    # string, STAN username
    +    username: 
    +    # string, STAN password  
    +    password: 
    +    # duration, wait time before reconnection attempts
    +    connect-time-wait: 2s
    +    # string, the STAN cluster name. defaults to test-cluster
    +    cluster-name: 
    +    # integer, interval (in seconds) at which 
    +    # a connection sends a PING to the server. min=1
    +    ping-interval:
    +    # integer, number of PINGs without a response 
    +    # before the connection is considered lost. min=2
    +    ping-retry:
    +    # string, consumed message expected format, one of: proto, event
    +    format: event 
    +    # bool, enables extra logging
    +    debug: false
    +    # integer, number of stan consumers to be created
    +    num-workers: 1
    +    # list of processors to apply on the message when received, 
    +    # only applies if format is 'event'
    +    event-processors: 
    +    # []string, list of named outputs to export data to. 
    +    # Must be configured under root level `outputs` section
    +    outputs: 
    +
    \ No newline at end of file diff --git a/user_guide/outputs/asciigraph_output/index.html b/user_guide/outputs/asciigraph_output/index.html new file mode 100644 index 00000000..f8e7ac78 --- /dev/null +++ b/user_guide/outputs/asciigraph_output/index.html @@ -0,0 +1,126 @@ + ASCII Graph - gNMIc

    ASCII Graph

    gnmic supports displaying collected metrics as an ASCII graph on the terminal. The graph is generated using the asciigraph package.

    Configuration sample#

    outputs:
    +  output1:
    +    # required
    +    type: asciigraph
    +    # string, the graph caption
    +    caption: 
    +    # integer, the graph height. If unset, defaults to the terminal height
    +    height:
    +    # integer, the graph width. If unset, defaults to the terminal width
    +    width:
    +    # float, the graph minimum value for the vertical axis.
    +    lower-bound:
    +    # float, the graph minimum value for the vertical axis.
    +    upper-bound:
    +    # integer, the graph left offset.
    +    offset:
    +    # integer, the decimal point precision of the label values.
    +    precision:
    +    # string, the caption color. one of ANSI colors.
    +    caption-color:
    +    # string, the axis color. one of ANSI colors.
    +    axis-color:
    +    # string, the label color. one of ANSI colors.
    +    label-color:
    +    # duration, the graph refresh timer.
    +    refresh-timer: 1s
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allows for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +    # bool enable debug
    +    debug: false 
    +

    Example#

    This example shows how to use the asciigraph output.

    gNMIc config

    cat gnmic_asciiout.yaml
    +
    targets:
    +  clab-nfd33-spine1-1:
    +    username: admin
    +    password: NokiaSrl1!
    +    skip-verify: true
    +
    +subscriptions:
    +  sub1:
    +    paths:
    +      - /interface[name=ethernet-1/3]/statistics/out-octets
    +      - /interface[name=ethernet-1/3]/statistics/in-octets
    +    stream-mode: sample
    +    sample-interval: 1s
    +    encoding: ascii
    +
    +outputs:
    +  out1:
    +    type: asciigraph
    +    caption: in/out octets per second
    +    event-processors:
    +      - rate
    +
    +processors:
    +  rate:
    +    event-starlark:
    +      script: rate.star
    +

    Starlark processor

    cat rate.star
    +
    cache = {}
    +
    +values_names = [
    +  '/interface/statistics/out-octets',
    +  '/interface/statistics/in-octets'
    +]
    +
    +N=2
    +
    +def apply(*events):
    +  for e in events:
    +    for value_name in values_names:
    +      v = e.values.get(value_name)
    +      # check if v is not None and is a digit to proceed
    +      if not v:
    +        continue
    +      if not v.isdigit():
    +        continue
    +      # update cache with the latest value
    +      val_key = "_".join([e.tags["source"], e.tags["interface_name"], value_name])
    +      if not cache.get(val_key):
    +        # initialize the cache entry if empty
    +        cache.update({val_key: []})
    +      if len(cache[val_key]) > N:
    +        # remove the oldest entry if the number of entries reached N
    +        cache[val_key] = cache[val_key][1:]
    +      # update cache entry
    +      cache[val_key].append((int(v), e.timestamp))
    +      # get the list of values
    +      val_list = cache[val_key]
    +      # calculate rate
    +      e.values[value_name+"_rate"] = rate(val_list)
    +      e.values.pop(value_name)
    +
    +  return events
    +
    +def rate(vals):
    +  previous_value, previous_timestamp = None, None
    +  for value, timestamp in vals:
    +    if previous_value != None and previous_timestamp != None:
    +      time_diff = (timestamp - previous_timestamp) / 1000000000 # 1 000 000 000
    +      if time_diff > 0:
    +        value_diff = value - previous_value
    +        rate = value_diff / time_diff
    +        return rate
    +
    +    previous_value = value
    +    previous_timestamp = timestamp
    +
    +  return 0
    +
    \ No newline at end of file diff --git a/user_guide/outputs/file_output/index.html b/user_guide/outputs/file_output/index.html new file mode 100644 index 00000000..aeefe36a --- /dev/null +++ b/user_guide/outputs/file_output/index.html @@ -0,0 +1,52 @@ + File - gNMIc

    File

    gnmic supports exporting subscription updates to multiple local files

    A file output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    # required
    +    type: file 
    +    # filename to write telemetry data to.
    +    # will be ignored if `file-type` is set
    +    filename: /path/to/filename
    +    # file-type, stdout or stderr.
    +    # overwrites `filename`
    +    file-type: # stdout or stderr
    +    # string, message formatting, json, protojson, prototext, event
    +    format: 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # boolean, valid only if format is `event`.
    +    # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts.
    +    split-events: false
    +    # string, a GoTemplate that is executed using the received gNMI message as input.
    +    # the template execution is the last step before the data is written to the file,
    +    # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any
    +    # then finally the msg-template is executed.
    +    msg-template:
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: 
    +    # boolean, format the output in indented form with every element on a new line.
    +    multiline: 
    +    # string, indent specifies the set of indentation characters to use in a multiline formatted output
    +    indent: 
    +    # string, separator is the set of characters to write between messages, defaults to new line
    +    separator: 
    +    # integer, specifies the maximum number of allowed concurrent file writes
    +    concurrency-limit: 1000 
    +     # boolean, enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false
    +     # list of processors to apply on the message before writing
    +    event-processors:
    +

    The file output can be used to write to file on the disk, to stdout or to stderr.

    For a disk file, a file name is required.

    For stdout or stderr, only file-type is required.

    \ No newline at end of file diff --git a/user_guide/outputs/gnmi_output/index.html b/user_guide/outputs/gnmi_output/index.html new file mode 100644 index 00000000..787a246f --- /dev/null +++ b/user_guide/outputs/gnmi_output/index.html @@ -0,0 +1,141 @@ + gNMI Server - gNMIc

    gNMI Server

    gnmic supports acting as a gNMI Server to expose the subscribed telemetry data to a gNMI Client using the Subcribe RPC, or to act as a gateway for Get and Set RPCs.

    Configuration#

    outputs:
    +  output1:
    +    # required
    +    type: gnmi 
    +    # gNMI server address, either a TCP socket or UNIX socket. 
    +    # In the latter case, the prefix `unix:///` should be present.
    +    address: ":57400"
    +    # maximum number of active subscriptions.
    +    max-subscriptions: 64
    +    # maximum number of ongoing Get/Set RPCs.
    +    max-unary-rpc: 64
    +    # tls config
    +    tls:
    +      # string, path to the CA certificate file,
    +      # this certificate is used to verify the clients certificates.
    +      ca-file:
    +      # string, server certificate file.
    +      cert-file:
    +      # string, server key file.
    +      key-file:
    +      # string, one of `"", "request", "require", "verify-if-given", or "require-verify" 
    +      #  - request:         The server requests a certificate from the client but does not 
    +      #                     require the client to send a certificate. 
    +      #                     If the client sends a certificate, it is not required to be valid.
    +      #  - require:         The server requires the client to send a certificate and does not 
    +      #                     fail if the client certificate is not valid.
    +      #  - verify-if-given: The server requests a certificate, 
    +      #                     does not fail if no certificate is sent. 
    +      #                     If a certificate is sent it is required to be valid.
    +      #  - require-verify:  The server requires the client to send a valid certificate.
    +      #
    +      # if no ca-file is present, `client-auth` defaults to ""`
    +      # if a ca-file is set, `client-auth` defaults to "require-verify"`
    +      client-auth: ""
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the returned Prefix.Target is empty.
    +    # if left empty, it defaults to:
    +    # `{{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present).
    +    target-template:
    +    # boolean, enables extra logging for the gNMI Server
    +    debug: false
    +    # boolean, enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false 
    +

    Insecure Mode#

    By default, the server runs in insecure mode, as long as skip-verify is false and none of ca-file, cert-file and key-file are set.

    Secure Mode#

    To run this gNMI server in secure mode, there are a few options:

    • Using self signed certificates, without client certificate verification:
    skip-verify: true
    +
    • Using self signed certificates, with client certificate verification:
    # a valid CA certificate to verify the client provided certificates
    +ca-file: /path/to/caFile 
    +
    • Using CA provided certificates, without client certificate verification:
    skip-verify: true
    +# a valid server certificate
    +cert-file: /path/to/server-cert
    +# a valid server key
    +key-file:  /path/to/server-key
    +
    • Using CA provided certificates, with client certificate verification:
    # a valid CA certificate to verify the client provided certificates
    +ca-file: /path/to/caFile 
    +# a valid server certificate
    +cert-file: /path/to/server-cert
    +# a valid server key
    +key-file:  /path/to/server-key
    +

    Supported RPCs#

    This gNMI Server supports Get, Set and Subscribe RPCs.

    gNMI Subscribe RPC#

    The server keeps a cache of gNMI notifications synched with the configured targets based on the configured subscriptions. This means that a client cannot get updates about a leaf that gNMIc did not subscribe to upstream.

    As soon as there is an update to the cache, the added gNMI notification is sent to all the client which subscription matches the new notification.

    Clients can subscribe to specific target using the gNMI Prefix Target field, leaving the Target field empty or setting it to * is equivalent to subscribing to all known targets.

    gNMI Get RPC#

    The server supports the gNMI Get RPC. It relies on the Prefix.Target field to select the target(s) to relay the received GetRequest to.

    If Prefix.Target is empty or is equal to *, a Get RPC is performed for all known targets. The received GetRequest is cloned, enriched with each target name and sent to the corresponding destination.

    Comma separated target names are also supported and allow to select a list of specific targets to send the Get RPC to.

    Once all GetResponses are received back successfully, the notifications contained in each GetResponse are combined into a single GetResponse with their Prefix.Target populated, if empty.

    The resulting GetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client.

    If the Get Request has the origin field set to gnmic, the request is performed against the internal server configuration. Currently only the path targets is supported.

    gnmic -a localhost:57400 --skip-verify get --path gnmic:/targets
    +
    [
    +  {
    +    "timestamp": 1626759382486891218,
    +    "time": "2021-07-20T13:36:22.486891218+08:00",
    +    "prefix": "gnmic:targets[name=clab-gw-srl1:57400]",
    +    "updates": [
    +      {
    +        "Path": "address",
    +        "values": {
    +          "address": "clab-gw-srl1:57400"
    +        }
    +      },
    +      {
    +        "Path": "username",
    +        "values": {
    +          "username": "admin"
    +        }
    +      },
    +      {
    +        "Path": "insecure",
    +        "values": {
    +          "insecure": "false"
    +        }
    +      },
    +      {
    +        "Path": "skip-verify",
    +        "values": {
    +          "skip-verify": "true"
    +        }
    +      },
    +      {
    +        "Path": "timeout",
    +        "values": {
    +          "timeout": "10s"
    +        }
    +      }
    +    ]
    +  },
    +  {
    +    "timestamp": 1626759382486900697,
    +    "time": "2021-07-20T13:36:22.486900697+08:00",
    +    "prefix": "gnmic:targets[name=clab-gw-srl2:57400]",
    +    "updates": [
    +      {
    +        "Path": "address",
    +        "values": {
    +          "address": "clab-gw-srl2:57400"
    +        }
    +      },
    +      {
    +        "Path": "username",
    +        "values": {
    +          "username": "admin"
    +        }
    +      },
    +      {
    +        "Path": "insecure",
    +        "values": {
    +          "insecure": "false"
    +        }
    +      },
    +      {
    +        "Path": "skip-verify",
    +        "values": {
    +          "skip-verify": "true"
    +        }
    +      },
    +      {
    +        "Path": "timeout",
    +        "values": {
    +          "timeout": "10s"
    +        }
    +      }
    +    ]
    +  }
    +]
    +

    gNMI Set RPC#

    The gNMI server supports the gNMI Set RPC. Just like in the case of Get RPC, the server relies on the Prefix.Target field to select the target(s) to relay the received SetRequest to.

    If Prefix.Target is empty or is equal to *, a Set RPC is performed for all known targets. The received SetRequest is cloned, enriched with each target name and sent to the corresponding destination.

    Comma separated target names are also supported and allow to select a list of specific targets to send the Set RPC to.

    Once all SetResponses are received back successfully, the UpdateResults from each response are merged into a single SetResponse, with the addition of the target name set in Path.Target. This is not compliant with the gNMI specification which stipulates that the Target field should only be present in Prefix Paths

    The resulting SetResponse is then returned to the gNMI client. If one of the RPCs fails, an error with status code Internal(13) is returned to the client.

    \ No newline at end of file diff --git a/user_guide/outputs/influxdb_output/index.html b/user_guide/outputs/influxdb_output/index.html new file mode 100644 index 00000000..ae7de22a --- /dev/null +++ b/user_guide/outputs/influxdb_output/index.html @@ -0,0 +1,78 @@ + InfluxDB - gNMIc

    InfluxDB

    gnmic supports exporting subscription updates to influxDB time series database

    Configuration#

    An influxdb output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    # required
    +    type: influxdb 
    +    # influxDB server address
    +    url: http://localhost:8086 
    +    # empty if using influxdb1.8.x
    +    org: myOrg 
    +    # string in the form database/retention-policy. Skip retention policy for the default on
    +    bucket: telemetry
    +    # influxdb 1.8.x use a string in the form: "username:password"
    +    token: 
    +    # number of points to buffer before writing to the server
    +    batch-size: 1000 
    +    # flush period after which the buffer is written to the server whether the batch_size is reached or not
    +    flush-timer: 10s
    +    # if true, the influxdb client will use gzip compression in write requests.
    +    use-gzip: false
    +    # (deprecated, use tls.skip-verify: true) 
    +    #if true, the influxdb client will use a secure connection to the server.
    +    enable-tls: false
    +    # tls config
    +    tls:
    +      # string, path to the CA certificate file,
    +      # this will be used to verify the clients certificates when `skip-verify` is false
    +      ca-file:
    +      # string, client certificate file.
    +      cert-file:
    +      # string, client key file.
    +      key-file:
    +      # boolean, if true, the client will not verify the server
    +      # certificate against the available certificate chain.
    +      skip-verify: false
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false 
    +    # server health check period, used to recover from server connectivity failure.
    +    # health check is disabled by default, can be enabled by setting the below field to any value other that zero.
    +    # with a minimum allowed period of 30s.
    +    health-check-period: 0s 
    +    # defines the write timestamp precision, 
    +    # one of `s` for second, `ms` for millisecond, `us` for microsecond and `ns` for nanoseconds
    +    # any other value defaults to `ns`.
    +    timestamp-precision: ns
    +    # server health check period, used to recover from server connectivity failure
    +    health-check-period: 30s 
    +    # enable debug
    +    debug: false 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # NOT IMPLEMENTED boolean, enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false 
    +    # list of processors to apply on the message before writing
    +    event-processors: []
    +    # cache, if present enables the influxdb output to cache received updates and write them all together 
    +    # at `cache-flush-timer` expiry.
    +    cache:
    +      # duration, if > 0, enables the expiry of values written to the cache.
    +      expiration: 0s
    +      # debug, if true enable extra logging
    +      debug: false
    +    # cache-flush-timer
    +    cache-flush-timer: 5s
    +

    gnmic uses the event format to generate the measurements written to InfluxDB. When an event has been processed through gnmic processors, the final value of the subscription-name tag will be used as an InfluxDB measurement name and the tag will be removed. If the subscription-name tag does not exist in the event, the event's Name will be used as InfluxDB measurement.

    Caching#

    When caching is enabled, the received messages are not written directly to InfluxDB, they are first cached as gNMI updates and written in batch when the cache-flush-timer is reached.

    The below diagram shows how an InfluxDB output works with and without cache enabled:

    When caching is enabled, the cached gNMI updates are periodically retrieved in batch, converted to events.

    If processors are defined under the output, they are applied to the whole list of events at once. This allows augmenting some messages with values from other messages even if they where collected from a different target/subscription.

    \ No newline at end of file diff --git a/user_guide/outputs/jetstream_output/index.html b/user_guide/outputs/jetstream_output/index.html new file mode 100644 index 00000000..29dad4af --- /dev/null +++ b/user_guide/outputs/jetstream_output/index.html @@ -0,0 +1,124 @@ + Jetstream - gNMIc

    Jetstream

    gnmic supports exporting subscription updates NATS Jetstream servers.

    A Jetstream output can be defined using the below format in gnmic config file under outputs section:

    configuration#

    outputs:
    +  output1:
    +    # required
    +    type: jetstream 
    +    # NATS publisher name
    +    # if left empty, this field is populated with the output name used as output ID (output1 in this example).
    +    # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name).
    +    # note that each jetstream worker (publisher) will get a client name=$name-$index
    +    name: ""
    +    # Comma separated NATS servers
    +    address: localhost:4222
    +    # string, stream name to write update to,
    +    # if `create-stream` is set, it will be created
    +    # # may not contain spaces, tabs, period (.), greater than (>) or asterisk (*)
    +    stream: 
    +    # defines stream parameters that gNMIc will create on the target jetstream server(s)
    +    create-stream:
    +      # string, stream description
    +      description: created by gNMIc
    +      # string list, list of subjects allowed on the stream
    +      # defaults to `.create-stream.$name.>`
    +      subjects:
    +      # string, one of `memory`, `file`.
    +      # defines the storage type to use for the stream.
    +      # defaults to `memory`
    +      storage:
    +      # int64, max number of messages in the stream.
    +      max-msgs:
    +      # int64, max bytes the stream may contain.
    +      max-bytes:
    +      # duration, max age of any message in the stream.
    +      max-age:
    +      # int32, maximum message size
    +      max-msg-size:
    +    # string, one of `static`, `subscription.target`, `subscription.target.path` 
    +    # or `subscription.target.pathKeys`.
    +    # Defines the subject format.
    +    # `static`: 
    +    #       all updates will be written to the subject name set under `outputs.$output_name.subject`
    +    # `subscription.target`: 
    +    #       updates from each subscription, target will be written 
    +    #       to subject $subscription_name.$target_name
    +    # `subscription.target.path`: 
    +    #       updates from a certain subscription, target and path 
    +    #       will be written to subject $subscription_name.$target_name.$path.
    +    #       The path is built by joining the gNMI path pathElements with a dot (.).
    +    #       e.g: /interface[name=ethernet-1/1]/statistics/in-octets
    +    #       -->  interface.statistics.in-octets 
    +    # `subscription.target.pathKeys`: 
    +    #       updates from a certain subscription, a certain target and a certain path 
    +    #       will be written to subject $subscription_name.$target_name.$path.
    +    #       The path is built by joining the gNMI path pathElements and Keys with a dot (.).
    +    #       e.g: /interface[name=ethernet-1/1]/statistics/in-octets
    +    #       -->  interface.{name=ethernet-1/1}.statistics.in-octets 
    +    # `target.subscription`:
    +    #       updates from each subscription, target will be written with a prefix of the `subject`
    +    #       to subject $subject.$target_name.$subscription_name if `subject` is present. If not,
    +    #       it will write to $target_name.$subscription_name.
    +    subject-format: static 
    +    # If a subject-format is `static`, gnmic will publish all subscriptions updates 
    +    # to a single subject configured under this field. Defaults to 'telemetry'
    +    # If a subject-format is `target.subscription`, gnmic will publish subscripion
    +    # updates prefixed with this subject.
    +    subject: telemetry
    +    # tls config
    +    tls:
    +      # string, path to the CA certificate file,
    +      # this will be used to verify the clients certificates when `skip-verify` is false
    +      ca-file:
    +      # string, client certificate file.
    +      cert-file:
    +      # string, client key file.
    +      key-file:
    +      # boolean, if true, the client will not verify the server
    +      # certificate against the available certificate chain.
    +      skip-verify: false
    +    # NATS username
    +    username: 
    +    # NATS password  
    +    password: 
    +    # wait time before reconnection attempts
    +    connect-time-wait: 2s 
    +    # Exported message format, one of: proto, prototext, protojson, json, event
    +    format: event 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # boolean, valid only if format is `event`.
    +    # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts.
    +    split-events: false
    +    # string, a GoTemplate that is executed using the received gNMI message as input.
    +    # the template execution is the last step before the data is written to the file.
    +    # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any
    +    # then finally the msg-template is executed.
    +    msg-template:
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false
    +    # integer, number of nats publishers to be created
    +    num-workers: 1 
    +    # duration after which a message waiting to be handled by a worker gets discarded
    +    write-timeout: 5s 
    +    # boolean, enables extra logging for the nats output
    +    debug: false
    +    # boolean, enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false 
    +    # list of processors to apply to the message before writing
    +    event-processors: 
    +

    subject-format#

    The subject-format field is used to control how the received gNMI notifications are written into the configured stream.

    static#

    All notifications will be written to the subject name set under outputs.$output_name.subject

    subscription.target#

    Notifications from each subscription and target pair will be written to subject $subscription_name.$target_name

    subscription.target.path#

    Notifications from a subscription, target and path tuple will be written to subject subscription_name.target_name.$path. The path is built by joining the gNMI path pathElements with a period (.).

    Notifications containing more than one update, will be expanded into multiple notifications with one update each.

    E.g:

    An update from target target1 and subscription sub1 containing path /interface[name=ethernet-1/1]/statistics/in-octets, will be written to subject:

    $stream_name.sub1.target1.interface.statistics.in-octets
    +

    subscription.target.pathKeys#

    Updates from a certain subscription, a certain target and a certain path will be written to subject $subscription_name.$target_name.$path. The path is built by joining the gNMI path pathElements and Keys with a period (.).

    Notifications containing more than one update, will be expanded into multiple notifications with one update each.

    E.g:

    An update from target target1 and subscription sub1 containing path /interface[name=ethernet-1/1]/statistics/in-octets, will be written to subject:

    $stream_name.sub1.target1.interface.{name=ethernet-1/1}.statistics.in-octets
    +
    \ No newline at end of file diff --git a/user_guide/outputs/kafka_output/index.html b/user_guide/outputs/kafka_output/index.html new file mode 100644 index 00000000..f16e3495 --- /dev/null +++ b/user_guide/outputs/kafka_output/index.html @@ -0,0 +1,196 @@ + Kafka - gNMIc

    Kafka

    gnmic supports exporting subscription updates to multiple Apache Kafka brokers/clusters simultaneously

    Configuration sample#

    A Kafka output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    # required
    +    type: kafka 
    +    # kafka client name. 
    +    # if left empty, this field is populated with the output name used as output ID (output1 in this example).
    +    # the full name will be '$(name)-kafka-prod'.
    +    # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name)-kafka-prod.
    +    # note that each kafka worker (producer) will get client name=$name-$index
    +    name: ""
    +    # Comma separated brokers addresses
    +    address: localhost:9092 
    +    # Kafka topic name
    +    topic: telemetry 
    +    # Kafka topic prefix
    +    # If supplied, overrides the `topic` key and outputs to a separate topic per source
    +    # named like `$topic_$subscriptionName_$targetName`. If `source` contains a port number separated with a colon,
    +    # the colon will be replaced with an underscore due to restrictions on the naming of kafka topics.
    +    # ex: telemetry_bgp_neighbor_state_device1_6030
    +    topic-prefix: telemetry
    +    # starts a sync-producer if set to true.
    +    sync-producer: false
    +    # required-acks is used in Produce Requests to tell the broker how many replica acknowledgements
    +    # it must see before responding. One of `no-response`, `wait-for-local`, `wait-for-all`.
    +    required-acks: wait-for-local
    +    # Kafka SASL configuration
    +    sasl:
    +      # SASL user name
    +      user:
    +      # SASL password
    +      password:
    +      # SASL mechanism: PLAIN, SCRAM-SHA-256, SCRAM-SHA-512 and OAUTHBEARER are supported
    +      mechanism:
    +      # token url for OAUTHBEARER SASL mechanism
    +      token-url:
    +    # tls config
    +    tls:
    +      # string, path to the CA certificate file,
    +      # this will be used to verify the clients certificates when `skip-verify` is false
    +      ca-file:
    +      # string, client certificate file.
    +      cert-file:
    +      # string, client key file.
    +      key-file:
    +      # boolean, if true, the client will not verify the server
    +      # certificate against the available certificate chain.
    +      skip-verify: false
    +    # The total number of times to retry sending a message
    +    max-retry: 2 
    +    # Kafka connection timeout
    +    timeout: 5s 
    +    # Wait time to reestablish the kafka producer connection after a failure
    +    recovery-wait-time: 10s 
    +    # Exported msg format, json, protojson, prototext, proto, event
    +    format: event 
    +    # boolean, if true the kafka producer will add a key to 
    +    # the message written to the broker. The key value is ${source}_${subscription-name}.
    +    # this is useful for Kafka topics with multiple partitions, it allows to keep messages from the same source and subscription in sequence.
    +    insert-key: false
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allows for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # boolean, valid only if format is `event`.
    +    # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts.
    +    split-events: false
    +    # string, a GoTemplate that is executed using the received gNMI message as input.
    +    # the template execution is the last step before the data is written to the file,
    +    # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any
    +    # then finally the msg-template is executed.
    +    msg-template:
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false
    +    # Number of kafka producers to be created 
    +    num-workers: 1 
    +    # (bool) enable debug
    +    debug: false 
    +    # (int) number of messages to buffer before being picked up by the workers
    +    buffer-size: 0
    +    # (string) enables compression of produced message. One of gzip, snappy, zstd, lz4
    +    compression-codec: gzip
    +    # (bool) enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false 
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +

    Currently all subscriptions updates (all targets and all subscriptions) are published to the defined topic name unless the topic-prefix configuration option is set.

    Kafka Security protocol#

    Kafka clients can operate with 4 security protocols, their configuration is controlled via both .tls and .sasl fields under the output config.

    Security Protocol Description Configuration
    PLAINTEXT Un-authenticated, non-encrypted channel .tls and .sasl are NOT present
    SASL_PLAINTEXT SASL authenticated, non-encrypted channel only .sasl is present
    SASL_SSL SASL authenticated, SSL channel both .tls and .sasl are present
    SSL SSL channel only .tls is present

    Security Configuration Examples#

    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    # other fields
    +    # no tls and no sasl fields
    +
    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    sasl:
    +      user: admin
    +      password: secret
    +    # other fields
    +    # no tls field
    +

    Example1: Without server certificate verification

    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    sasl:
    +      user: admin
    +      password: secret
    +    tls:
    +      skip-verify: true
    +    # other fields
    +    # ...
    +
    Example2: With server certificate verification
    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    sasl:
    +      user: admin
    +      password: secret
    +    tls:
    +      ca-file: /path/to/ca-file
    +    # other fields
    +    # ...
    +
    Example3: With client certificates
    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    sasl:
    +      user: admin
    +      password: secret
    +    tls:
    +      cert-file: /path/to/cert-file
    +      key-file: /path/to/cert-file
    +    # other fields
    +    # ...
    +
    Example4: With both server certificate verification and client certificates
    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    sasl:
    +      user: admin
    +      password: secret
    +    tls:
    +      cert-file: /path/to/cert-file
    +      key-file: /path/to/cert-file
    +      ca-file: /path/to/ca-file
    +    # other fields
    +    # ...
    +

    Example1: Without server certificate verification

    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    tls:
    +      skip-verify: true
    +    # other fields
    +    # no sasl field
    +
    Example2: With server certificate verification
    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    tls:
    +      ca-file: /path/to/ca-file
    +    # other fields
    +    # no sasl field
    +
    Example3: With client certificates
    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    tls:
    +      cert-file: /path/to/cert-file
    +      key-file: /path/to/cert-file
    +    # other fields
    +    # no sasl field
    +
    Example4: With both server certificate verification and client certificates
    outputs:
    +  output1:
    +    type: kafka
    +    topic: my_kafka_topic
    +    tls:
    +      cert-file: /path/to/cert-file
    +      key-file: /path/to/cert-file
    +      ca-file: /path/to/ca-file
    +    # other fields
    +    # no sasl field
    +

    Kafka Output Metrics#

    When a Prometheus server is enabled, gnmic kafka output exposes 4 prometheus metrics, 3 Counters and 1 Gauge:

    • number_of_kafka_msgs_sent_success_total: Number of msgs successfully sent by gnmic kafka output. This Counter is labeled with the kafka producerID
    • number_of_written_kafka_bytes_total: Number of bytes written by gnmic kafka output. This Counter is labeled with the kafka producerID
    • number_of_kafka_msgs_sent_fail_total: Number of failed msgs sent by gnmic kafka output. This Counter is labeled with the kafka producerID as well as the failure reason
    • msg_send_duration_ns: gnmic kafka output send duration in nanoseconds. This Gauge is labeled with the kafka producerID
    \ No newline at end of file diff --git a/user_guide/outputs/nats_output/index.html b/user_guide/outputs/nats_output/index.html new file mode 100644 index 00000000..80a2998c --- /dev/null +++ b/user_guide/outputs/nats_output/index.html @@ -0,0 +1,74 @@ + NATS - gNMIc

    NATS

    gnmic supports exporting subscription updates to multiple NATS servers/clusters simultaneously

    A NATS output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    # required
    +    type: nats 
    +    # NATS publisher name
    +    # if left empty, this field is populated with the output name used as output ID (output1 in this example).
    +    # the full name will be '$(name)-nats-pub'.
    +    # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name)-nats-pub.
    +    # note that each nats worker (publisher) will get client name=$name-$index
    +    name: ""
    +    # Comma separated NATS servers
    +    address: localhost:4222 
    +    # This prefix is used to to build the subject name for each target/subscription
    +    subject-prefix: telemetry 
    +    # If a subject-prefix is not specified, gnmic will publish all subscriptions updates to a single subject configured under this field. Defaults to 'telemetry'
    +    subject: telemetry 
    +    # NATS username
    +    username: 
    +    # NATS password  
    +    password: 
    +    # wait time before reconnection attempts
    +    connect-time-wait: 2s 
    +    # tls config
    +    tls:
    +      # string, path to the CA certificate file,
    +      # this will be used to verify the clients certificates when `skip-verify` is false
    +      ca-file:
    +      # string, client certificate file.
    +      cert-file:
    +      # string, client key file.
    +      key-file:
    +      # boolean, if true, the client will not verify the server
    +      # certificate against the available certificate chain.
    +      skip-verify: false
    +    # Exported message format, one of: proto, prototext, protojson, json, event
    +    format: json 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # boolean, valid only if format is `event`.
    +    # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts.
    +    split-events: false
    +    # string, a GoTemplate that is executed using the received gNMI message as input.
    +    # the template execution is the last step before the data is written to the file,
    +    # First the received message is formatted according to the `format` field above, then the `event-processors` are applied if any
    +    # then finally the msg-template is executed.
    +    msg-template:
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false
    +    # integer, number of nats publishers to be created
    +    num-workers: 1 
    +    # duration after which a message waiting to be handled by a worker gets discarded
    +    write-timeout: 5s 
    +    # boolean, enables extra logging for the nats output
    +    debug: false
    +    # boolean, enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false 
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +

    Using subject config value, a user can specify the NATS subject to which to send all subscriptions updates for all targets

    If a user wants to separate updates by targets and by subscriptions, subject-prefix can be used. if subject-prefix is specified subject is ignored.

    gnmic takes advantage of NATS subject hierarchy by publishing gNMI subscription updates to a separate subject per target per subscription.

    The NATS subject name is built out of the subject-prefix, name under the target definition and subscription-name resulting in the following format: subject-prefix.name.subscription-name

    e.g: for a target router1, a subscription name port-stats and subject-prefix telemetry the subject name will be telemetry.router1.port-stats

    If the target name is an IP address, or a hostname (meaning potentially contains .), the . characters are replaced with a -

    e.g: for a target 172.17.0.100:57400, the previous subject name becomes telemetry.172-17-0-100:57400.port-stats

    This way a user can subscribe to different subsets of updates by tweaking the subject name:

    • "telemetry.>" gets all updates sent to NATS by all targets, all subscriptions
    • "telemetry.router1.>" gets all NATS updates for target router1
    • "telemetry.*.port-stats" gets all updates from subscription port-stats, for all targets
    \ No newline at end of file diff --git a/user_guide/outputs/output_intro/index.html b/user_guide/outputs/output_intro/index.html new file mode 100644 index 00000000..afd2813f --- /dev/null +++ b/user_guide/outputs/output_intro/index.html @@ -0,0 +1,129 @@ + Introduction - gNMIc

    Introduction

    In the context of gnmi subscriptions (on top of terminal output) gnmic supports multiple output options:

    These outputs can be mixed and matched at will with the different gnmi subscribe targets.

    With multiple outputs defined in the configuration file you can collect once and export the subscriptions updates to multiple locations formatted differently.

    Defining outputs#

    To define an output a user needs to create the outputs section in the configuration file:

    # part of ~/gnmic.yml config file
    +outputs:
    +  output1:
    +    type: file # output type
    +    file-type: stdout # or stderr
    +    format: json
    +  output2:
    +    type: file
    +    filename: /path/to/localFile.log  
    +    format: protojson
    +  output3:
    +    type: nats # output type
    +    address: 127.0.0.1:4222 # comma separated nats servers addresses
    +    subject-prefix: telemetry #
    +    format: event
    +  output4:
    +    type: file
    +    filename: /path/to/localFile.log  
    +    format: json
    +  output5:
    +    type: stan # output type
    +    address: 127.0.0.1:4223 # comma separated nats streaming servers addresses
    +    subject: telemetry #
    +    cluster-name: test-cluster #
    +    format: proto
    +  output6:
    +    type: kafka # output type
    +    address: localhost:9092 # comma separated kafka brokers addresses
    +    topic: telemetry # kafka topic
    +    format: proto
    +  output7:
    +    type: stan # output type
    +    address: 127.0.0.1:4223 # comma separated nats streaming servers addresses
    +    subject: telemetry
    +    cluster-name: test-cluster
    +

    Note

    Outputs names are case insensitive

    Output formats#

    Different formats are supported for all outputs

    Format/output proto protojson prototext json event
    File ❌ ✔ ✔ ✔ ✔
    NATS / STAN ✔ ✔ ❌ ✔ ✔
    Kafka ✔ ✔ ❌ ✔ ✔
    UDP / TCP ✔ ✔ ✔ ✔ ✔
    InfluxDB NA NA NA NA NA
    Prometheus NA NA NA NA NA

    Formats examples#

    {
    +  "update": {
    +  "timestamp": "1595491618677407414",
    +  "prefix": {
    +    "elem": [
    +      {
    +        "name": "configure"
    +      },
    +      {
    +        "name": "system"
    +      }
    +    ]
    +  },
    +  "update": [
    +    {
    +      "path": {
    +        "elem": [
    +          {
    +            "name": "name"
    +          }
    +        ]
    +        },
    +        "val": {
    +          "stringVal": "sr123"
    +        }
    +      }
    +    ]
    +  }
    +}
    +
    update: {
    +  timestamp: 1595491704850352047
    +  prefix: {
    +    elem: {
    +      name: "configure"
    +    }
    +    elem: {
    +      name: "system"
    +    }
    +  }
    +  update: {
    +    path: {
    +      elem: {
    +        name: "name"
    +      }
    +    }
    +    val: {
    +      string_val: "sr123"
    +    }
    +  }
    +}
    +
    {
    +  "source": "172.17.0.100:57400",
    +  "subscription-name": "sub1",
    +  "timestamp": 1595491557144228652,
    +  "time": "2020-07-23T16:05:57.144228652+08:00",
    +  "prefix": "configure/system",
    +  "updates": [
    +    {
    +      "Path": "name",
    +      "values": {
    +        "name": "sr123"
    +      }
    +    }
    +  ]
    +}
    +
    [
    +  {
    +    "name": "sub1",
    +    "timestamp": 1595491586073072000,
    +    "tags": {
    +      "source": "172.17.0.100:57400",
    +      "subscription-name": "sub1"
    +  },
    +    "values": {
    +      "/configure/system/name": "sr123"
    +    }
    +  }
    +]
    +

    Binding outputs#

    Once the outputs are defined, they can be flexibly associated with the targets.

    # part of ~/gnmic.yml config file
    +targets:
    +  router1.lab.com:
    +    username: admin
    +    password: secret
    +    outputs:
    +      - output1
    +      - output3
    +  router2.lab.com:
    +    username: gnmi
    +    password: telemetry
    +    outputs:
    +      - output2
    +      - output3
    +      - output4
    +

    Caching#

    By default, gNMIc outputs write the received gNMI updates as they arrive (i.e without caching).

    Caching messages before writing them to a remote location can yield a few benefits like rate limiting, batch processing, data replication, etc.

    Both influxdb and prometheus outputs support caching messages before exporting. Caching support for other outputs is planned.

    See more details about caching here

    \ No newline at end of file diff --git a/user_guide/outputs/prometheus_output/index.html b/user_guide/outputs/prometheus_output/index.html new file mode 100644 index 00000000..1a7854df --- /dev/null +++ b/user_guide/outputs/prometheus_output/index.html @@ -0,0 +1,168 @@ + Scrape Based (Pull) - gNMIc

    Scrape Based (Pull)

    Introduction#

    gNMIc offers the capability to present gNMI updates on a Prometheus server, allowing a Prometheus system to perform scrapes.

    The Prometheus metric name and its labels are generated according to the subscription name, gNMI path, and the value name.

    To define a gNMIc Prometheus output, use the following format in the gnmic configuration file under the outputs section:

    outputs:
    +  sample-prom-output:
    +    type: prometheus # required
    +    # address to listen on for incoming scrape requests
    +    listen: :9804 
    +    # path to query to get the metrics
    +    path: /metrics 
    +    # maximum lifetime of metrics in the local cache, #
    +    # a zero value defaults to 60s, a negative duration (e.g: -1s) disables the expiration
    +    expiration: 60s 
    +    # a string to be used as the metric namespace
    +    metric-prefix: "" 
    +    # a boolean, if true the subscription name will be appended to the metric name after the prefix
    +    append-subscription-name: false 
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false
    +    # a boolean, enables exporting timestamps received from the gNMI target as part of the metrics
    +    export-timestamps: false 
    +    # a boolean, enables setting string type values as prometheus metric labels.
    +    strings-as-labels: false
    +    # tls config
    +    tls:
    +      # string, path to the CA certificate file,
    +      # this certificate is used to verify the clients certificates.
    +      ca-file:
    +      # string, server certificate file.
    +      cert-file:
    +      # string, server key file.
    +      key-file:
    +      # string, one of `"", "request", "require", "verify-if-given", or "require-verify" 
    +      #  - request:         The server requests a certificate from the client but does not 
    +      #                     require the client to send a certificate. 
    +      #                     If the client sends a certificate, it is not required to be valid.
    +      #  - require:         The server requires the client to send a certificate and does not 
    +      #                     fail if the client certificate is not valid.
    +      #  - verify-if-given: The server requests a certificate, 
    +      #                     does not fail if no certificate is sent. 
    +      #                     If a certificate is sent it is required to be valid.
    +      #  - require-verify:  The server requires the client to send a valid certificate.
    +      #
    +      # if no ca-file is present, `client-auth` defaults to ""`
    +      # if a ca-file is set, `client-auth` defaults to "require-verify"`
    +      client-auth: ""
    +    # see https://gnmic.openconfig.net/user_guide/caching/, 
    +    # if enabled, the received gNMI notifications are stored in a cache.
    +    # the prometheus metrics are generated at the time a prometheus server sends scrape request.
    +    # this behavior allows the processors (if defined) to be run on all the generated events at once.
    +    # this mode uses more resource compared to the default one, but offers more flexibility when it comes 
    +    # to manipulating the data to customize the returned metrics using event-processors.
    +    cache:
    +    # duration, scrape request timeout.
    +    # this timer is started when a scrape request is received, 
    +    # if it is reached, the metrics generation/collection is stopped.
    +    timeout: 10s
    +    # enable debug for prometheus output
    +    debug: false 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +    # an integer, sets the number of worker handling messages to be converted into Prometheus metrics
    +    num-workers: 1
    +    # Enables Consul service registration
    +    service-registration:
    +      # Consul server address, default to localhost:8500
    +      address:
    +      # Consul Data center, defaults to dc1
    +      datacenter: 
    +      # Consul username, to be used as part of HTTP basicAuth
    +      username:
    +      # Consul password, to be used as part of HTTP basicAuth
    +      password:
    +      # Consul Token, is used to provide a per-request ACL token which overrides the agent's default token
    +      token:
    +      # address and port number to be registered as a service address in Consul.
    +      # if the field is empty the address is derived from the listen field.
    +      # if the address does not contain a port number, the port number fmro the listen field is used.
    +      service-address: 
    +      # Prometheus service check interval, for both http and TTL Consul checks,
    +      # defaults to 5s
    +      check-interval:
    +      # Maximum number of failed checks before the service is deleted by Consul
    +      # defaults to 3
    +      max-fail:
    +      # Consul service name
    +      name:
    +      # List of tags to be added to the service registration, 
    +      # if available, the instance-name and cluster-name will be added as tags,
    +      # in the format: gnmic-instance=$instance-name and gnmic-cluster=$cluster-name
    +      tags:
    +      # bool, enables http service check on top of the TTL check
    +      enable-http-check:
    +      # string, if enable-http-check is true, this field can be used to specify the http endpoint to be used to the check
    +      # if provided, this filed with be prepended with 'http://' (if not already present), 
    +      # and appended with the value in 'path' field.
    +      # if not specified, it will be derived from the fields 'listen' and 'path'
    +      http-check-address:
    +      # if set to true, the gnmic instance will try to ac quire a lock before registering the prometheus output in consul.
    +      # this allows to register a single instance of the cluster in consul.
    +      # if the instance which acquired the lock fails, one of the remaining ones will take over.
    +      use-lock: false
    +

    Fields definition#

    type#

    The output type, prometheus in this case.

    listen#

    Address to listen on for incoming scrape requests, defaults to :9804

    path#

    URL Path to query in order to retrieve the metrics, defaults to /metrics

    expiration#

    Maximum lifetime of metrics in the local cache, A zero value defaults to 60s, a negative duration (e.g: -1s) disables the expiration

    metric-prefix#

    A string to be used as the metric namespace

    append-subscription-name#

    A boolean, if true the subscription name will be appended to the metric name after the prefix

    override-timestamps#

    A boolean, if true the message timestamp is changed to current time

    export-timestamps#

    A boolean, enables exporting timestamps received from the gNMI target as part of the metrics

    strings-as-labels#

    A boolean, enables setting string type values as prometheus metric labels.

    tls#

    ca-file#

    A string, path to the CA certificate file. This certificate is used to verify the clients certificates.

    cert-file#

    A string, path to server certificate file.

    key-file#

    A string, server key file.

    client-auth#

    A string, use to control whether the server requests a client certificate or not and how it validates it.

    One of:

    • "":

      The server does not request a certificate from the client.

    • "request":

      The server requests a certificate from the client but does not require the client to send a certificate. If the client sends a certificate, it is not required to be valid.

    • "require":

      The server requires the client to send a certificate and does not fail if the client certificate is not valid.

    • "verify-if-given":

      The server requests a certificate, does not fail if no certificate is sent. If a certificate is sent it is required to be valid.

    • "require-verify":

      The server requires the client to send a valid certificate.

    If the ca-file is not provided, the default value for client-auth is an empty string ("").

    However, if a ca-file is specified, the default value for client-auth becomes "require-verify".

    cache#

    Refer to the cache docs for more information.

    When enabled, gNMI notifications are stored in a cache upon receipt. Prometheus metrics are subsequently generated when a Prometheus system sends a scrape request. This approach allows processors (if defined) to operate on all generated events simultaneously. While this mode consumes more resources compared to the default, it provides increased flexibility for data manipulation and metric customization through the use of event-processors.

    timeout#

    A Duration such as 10s, 1m or 1m30s, defines the scrape request timeout. This timer is started when a scrape request is received from a Prometheus system. If the timer is is reached, the metrics generation/collection is stopped.

    debug#

    A boolean. Enables debug for prometheus output

    add-target#

    A string, one of overwrite, if-not-present or ``. This field allows populating/changing the value of Prefix.Target in the received message.

    If left empty (""), no changes will be made.

    If set to "overwrite", the target value will be replaced with the configuration specified under target-template.

    If set to "if-not-present", the target value will be populated only if it is empty, utilizing the target-template.

    target-template#

    A string, a GoTemplate that allow for the customization of the target field in Prefix.Target. It applies only if the previous field add-target is not empty. If left target-template is left empty, it defaults to:

    {{- if index . "subscription-target" -}}
    +{{ index . "subscription-target" }}
    +{{- else -}}
    +{{ index . "source" | host }}
    +{{- end -}}
    +

    The above template sets the target to the value configured under subscription.$subscription-name.target if any, otherwise it will set it to the target name stripped of the port number (if present)

    event-processors#

    A string list. List of processors to apply on the message before writing

    service-registration#

    Enables Consul service registration

    address#

    Consul server address, default to localhost:8500

    datacenter#

    Consul Data center, defaults to dc1

    username#

    Consul username, to be used as part of HTTP basicAuth

    password#

    Consul password, to be used as part of HTTP basicAuth

    token#

    Consul Token, is used to provide a per-request ACL token which overrides the agent's default token

    service-address#

    Address and port number to be registered as a service address in Consul. if the field is empty the address is derived from the listen field. if the address does not contain a port number, the port number from the listen field is used.

    check-interval#

    Prometheus service check interval, for both http and TTL Consul checks, defaults to 5s

    max-fail#

    Maximum number of failed checks before the service is deleted by Consul defaults to 3

    name#

    Consul service name

    tags#

    List of tags to be added to the service registration, if available, the instance-name and cluster-name will be added as tags, in the format: gnmic-instance=instance-name and gnmic-cluster=cluster-name

    enable-http-check#

    A boolean, enables http service check on top of the TTL check

    http-check-address#

    A string, if enable-http-check is true, this field can be used to specify the http endpoint to be used to the check if provided, this filed with be prepended with 'http://' (if not already present), and appended with the value in 'path' field. if not specified, it will be derived from the fields 'listen' and 'path'

    use-lock#

    A boolean, if set to true, the gnmic instance will try to acquire a lock before registering the prometheus output. This knob allows to register a single instance of the cluster in Consul. if the instance which acquired the lock fails, one of the remaining ones takes over by acquiring the lost lock.

    Metric Generation#

    The below diagram shows an example of a prometheus metric generation from a gnmi update

    Metric Naming#

    The metric name starts with the string configured under metric-prefix.

    Then if append-subscription-name is true, the subscription-name as specified in gnmic configuration file is appended.

    The resulting string is followed by the gNMI path stripped of its keys if there are any.

    All non-alphanumeric characters are replaced with an underscore "_"

    The 3 strings are then joined with an underscore "_"

    If further customization of the metric name is required, the processors can be used to transform the metric name.

    For example, a gNMI update from subscription port-stats with path:

    /interfaces/interface[name=1/1/1]/subinterfaces/subinterface[index=0]/state/counters/in-octets
    +

    is exposed as a metric named:

    gnmic_port_stats_interfaces_interface_subinterfaces_subinterface_state_counters_in_octets
    +

    Metric Labels#

    The metrics labels are generated from the subscription metadata (e.g: subscription-name and source) and the keys present in the gNMI path elements.

    For the previous example the labels would be:

    {interface_name="1/1/1",subinterface_index=0,source="$routerIP:Port",subscription_name="port-stats"}
    +

    Service Registration#

    gnmic supports prometheus_output service registration via Consul.

    It allows prometheus to dynamically discover new instances of gnmic exposing a prometheus server ready for scraping via its service discovery feature.

    If the configuration section service-registration is present under the output definition, gnmic will register the prometheus_output service in Consul.

    Configuration Example#

    The below configuration, registers a service name gnmic-prom-srv with IP=10.1.1.1 and port=9804

    # gnmic.yaml
    +outputs:
    +  output1:
    +    type: prometheus
    +    listen: 10.1.1.1:9804
    +    path: /metrics 
    +    service-registration:
    +      address: consul-agent.local:8500
    +      name: gnmic-prom-srv
    +

    This allows running multiple instances of gnmic with minimal configuration changes by using prometheus service discovery feature.

    Simplified scrape configuration in the prometheus client:

    # prometheus.yaml
    +scrape_configs:
    +  - job_name: 'gnmic'
    +    scrape_interval: 10s
    +    consul_sd_configs:
    +      - server: $CONSUL_ADDRESS
    +        services:
    +          - $service_name
    +

    Service Name and ID#

    The $service_name to be discovered by prometheus is configured under outputs.$output_name.service-registration.name.

    If the service registration name field is not present, the name prometheus-${output_name} will be used.

    In both cases the service ID will be prometheus-${output_name}-${instance_name}.

    Service Checks#

    gnmic registers the service in Consul with a ttl check enabled by default:

    • ttl: gnmic periodically updates the service definition in Consul. The goal is to allow Consul to detect a same instance restarting with a different service name.

    If service-registration.enable-http-check is true, an http check is added:

    • http: Consul periodically scrapes the prometheus server endpoint to check its availability.
    # gnmic.yaml
    +outputs:
    +  output1:
    +    type: prometheus
    +    listen: 10.1.1.1:9804
    +    path: /metrics 
    +    service-registration:
    +      address: consul-agent.local:8500
    +      name: gnmic-prom-srv
    +      enable-http-check: true
    +

    Note that for the http check to work properly, a reachable address ( IP or name ) should be specified under listen.

    Otherwise, a reachable address should be added under service-registration.http-check-address.

    Caching#

    When caching is enabled, the received messages are not immediately converted into metrics, they are cached as gNMI updates. The conversion from gNMI update to Prometheus metrics happens only when a scrape request is received.

    The below diagram shows how a prometheus output works with and without cache enabled:

    When caching is enabled, the received gNMI updates are not processed and converted into metrics immediately, they are rather stored as is in the configured gNMI cache.

    Once a scrape request is received from Prometheus, all the cached gNMI updates are retrieved from the cache, converted to events, the configured processors, if any, are then applied to the whole list of events. Finally, The resulting event are converted into metrics and written back to Prometheus within the scrape response.

    Prometheus Output Metrics#

    When a Prometheus server (gNMI API) is enabled, gnmic prometheus output exposes 2 prometheus Gauges:

    • number_of_prometheus_metrics_total: Number of metrics stored by the prometheus output.
    • number_of_prometheus_cached_metrics_total: Number of metrics cached by the prometheus output.

    Examples#

    A simple Prometheus output#

    A basic Prometheus output utilizing all default values converts each received gNMI update into a Prometheus metric, retaining it in the cache until a scrape request is received from a Prometheus system.

    outputs:
    +  simple-prom:
    +    type: prometheus
    +

    Promote string values to labels#

    A straightforward Prometheus output, utilizing default values for the most part, transforms each incoming gNMI update into a Prometheus metric. In this process, if a value is a string, it is incorporated as a label in the final metric.

    These metrics are retained in the cache, awaiting a scrape request from a Prometheus system.

    outputs:
    +  simple-prom:
    +    type: prometheus
    +    strings-as-labels: true
    +

    Use a gNMI cache#

    A Prometheus output leveraging a gNMI cache stores incoming gNMI updates in their original form, only converting them into Prometheus metrics upon receiving a scrape request from a Prometheus system.

    This mode enables batch processing of all updates simultaneously during their conversion into Prometheus metrics.

    outputs:
    +  simple-prom:
    +    type: prometheus
    +    cache: {}
    +

    ****Register as a Consul service****#

    A Prometheus output that dynamically registers its endpoint within Consul, enabling the Prometheus system to seamlessly discover the associated address and port number.

    outputs:
    +  simple-prom:
    +    type: prometheus
    +    service-registration:
    +      address: consul-server-address:8500
    +
    \ No newline at end of file diff --git a/user_guide/outputs/prometheus_write_output/index.html b/user_guide/outputs/prometheus_write_output/index.html new file mode 100644 index 00000000..66327341 --- /dev/null +++ b/user_guide/outputs/prometheus_write_output/index.html @@ -0,0 +1,94 @@ + Remote Write (Push) - gNMIc

    Remote Write (Push)

    gnmic supports writing metrics to Prometheus using its remote write API.

    gNMIc's prometheus remote write can be used to push metrics to a variety of monitoring systems like Prometheus, Mimir, CortexMetrics, VictoriaMetrics, Thanos...

    A Prometheus write output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    # required
    +    type: prometheus_write
    +    # url to push metrics towards, scheme is required
    +    url: http://<grafana-mimir-addr>:9009/api/v1/push
    +    # a map of string:string, 
    +    # custom HTTP headers to be sent along with each remote write request.
    +    headers:
    +      # header: value
    +    # sets the `Authorization` header on every remote write request with the
    +    # configured username and password.
    +    authentication:
    +      username:
    +      password:
    +    # sets the `Authorization` header with type `.authorization.type` and the token value.
    +    authorization:
    +      type: Bearer
    +      credentials: <token string>
    +    # tls config
    +    tls:
    +      # string, path to the CA certificate file,
    +      # this will be used to verify the clients certificates when `skip-verify` is false
    +      ca-file:
    +      # string, client certificate file.
    +      cert-file:
    +      # string, client key file.
    +      key-file:
    +      # boolean, if true, the client will not verify the server
    +      # certificate against the available certificate chain.
    +      skip-verify: false
    +    # duration, defaults to 10s, time interval between write requests
    +    interval: 10s
    +    # integer, defaults to 1000.
    +    # Buffer size for time series to be sent to the remote system.
    +    # metrics are sent to the remote system every `.interval` or when the buffer is full. Whichever one is reached first.
    +    buffer-size: 1000
    +    # integer, defaults to 500, sets the maximum number of timeSeries per write request to remote.
    +    max-time-series-per-write: 500
    +    # integer, defaults to 0
    +    # number of retries per write, retries will have a back off of 100ms.
    +    max-retries: 0
    +    # metadata configuration
    +    metadata:
    +      # boolean, 
    +      # if true, metrics metadata is sent.
    +      include: false
    +      # duration, defaults to 60s.
    +      # Applies if `metadata.include` is set to true
    +      # Interval after which all metadata entries are sent to the remote write address
    +      interval: 60s
    +      # integer, defaults to 500
    +      # applies if `metadata.include` is set to true
    +      # Max number of metadata entries per write request.
    +      max-entries-per-write: 500
    +    # string, to be used as the metric namespace
    +    metric-prefix: "" 
    +    # boolean, if true the subscription name will be appended to the metric name after the prefix
    +    append-subscription-name: false 
    +    # boolean, enables setting string type values as prometheus metric labels.
    +    strings-as-labels: false
    +    # duration, defaults to 10s
    +    # Push request timeout.
    +    timeout: 10s
    +    # boolean, defaults to false
    +    # Enables debug for prometheus write output.
    +    debug: false 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +    # an integer, sets the number of worker handling messages to be converted into Prometheus metrics
    +    num-workers: 1
    +    # an integer, sets the number of writers draining the buffer and writing to Prometheus
    +    num-writers: 1
    +

    gnmic creates the prometheus metric name and its labels from the subscription name, the gnmic path and the value name.

    Metric Generation#

    The below diagram shows an example of a prometheus metric generation from a gnmi update

    Metric Naming#

    The metric name starts with the string configured under metric-prefix.

    Then if append-subscription-name is true, the subscription-name as specified in gnmic configuration file is appended.

    The resulting string is followed by the gNMI path stripped of its keys if there are any.

    All non-alphanumeric characters are replaced with an underscore "_"

    The 3 strings are then joined with an underscore "_"

    If further customization of the metric name is required, the processors can be used to transform the metric name.

    For example, a gNMI update from subscription port-stats with path:

    /interfaces/interface[name=1/1/1]/subinterfaces/subinterface[index=0]/state/counters/in-octets
    +

    is exposed as a metric named:

    gnmic_port_stats_interfaces_interface_subinterfaces_subinterface_state_counters_in_octets
    +

    Metric Labels#

    The metrics labels are generated from the subscription metadata (e.g: subscription-name and source) and the keys present in the gNMI path elements.

    For the previous example the labels would be:

    {interface_name="1/1/1",subinterface_index=0,source="$routerIP:Port",subscription_name="port-stats"}
    +

    Prometheus Write Metrics#

    When a Prometheus server (gNMI API) is enabled, gnmic prometheus write output exposes 4 prometheus counters and 2 prometheus Gauges:

    • number_of_prometheus_write_msgs_sent_success_total: Number of msgs successfully sent by gnmic prometheus_write output.
    • number_of_prometheus_write_msgs_sent_fail_total: Number of failed msgs sent by gnmic prometheus_write output.
    • msg_send_duration_ns: gnmic prometheus_write output send duration in ns.

    • number_of_prometheus_write_metadata_msgs_sent_success_total: Number of metadata msgs successfully sent by gnmic prometheus_write output.

    • number_of_prometheus_write_metadata_msgs_sent_fail_total: Number of failed metadata msgs sent by gnmic prometheus_write output.
    • metadata_msg_send_duration_ns: gnmic prometheus_write output metadata send duration in ns.
    \ No newline at end of file diff --git a/user_guide/outputs/snmp_output/index.html b/user_guide/outputs/snmp_output/index.html new file mode 100644 index 00000000..106cc25c --- /dev/null +++ b/user_guide/outputs/snmp_output/index.html @@ -0,0 +1,111 @@ + SNMP - gNMIc

    SNMP

    gnmic supports generating SNMP traps based on received gNMI updates.

    This output type is useful when trying to integrate legacy systems that ingest SNMP traps with more modern telemetry/alarms stacks.

    Only SNMPv2c is supported.

    Configuration#

    The SNMP output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  # the output name
    +  snmp_trap: 
    +    # the output type
    +    type: snmp
    +    # the traps destination address
    +    address:
    +    # the trap destination port, defaults to 162
    +    port: 162
    +    # the SNMP trap community
    +    community: public
    +    # duration, wait time before the first trap evaluation.
    +    # defaults to 5s and minimum allowed value is 5s.
    +    start-delay: 5s
    +    # traps definition
    +    traps:
    +        # if true, the SNMP message generated is an inform request, not a trap.
    +      - inform: false
    +        # trap trigger definition,
    +        # the trigger section of the trap defines which received path trigger the trap
    +        # as well as the variable binding to append to it.
    +        trigger:
    +          # xpath, if present in the received event message, the trap is triggered
    +          path:
    +          # a jq script that is executed with the trigger event message as input.
    +          # must return a valid OID.
    +          oid:
    +          # a static string, defining the type of the OID value,
    +          # one of: bool, int, bitString, octetString, null, objectID, objectDescription,
    +          # ipAddress, counter32, gauge32, timeTicks, opaque, nsapAddress, counter64, 
    +          # uint32, opaqueFloat, opaqueDouble
    +          type:
    +          # a jq script that is executed with the trigger event message as input.
    +          # must return a value matching the above configured type.
    +          value:
    +        # trap variable bindings definition,
    +        # the bindings section defines the extra variable bindings to append to the trap.
    +        # multiple bindings can be defined here.
    +        bindings:
    +            # A jq script that is executed with the trigger message as input.
    +            # Must return a valid xpath.
    +            # The local cache is queried using the resulting xpath, the resulting event message is used 
    +            # as input to execute the below oid and value jq scripts
    +          - path:
    +            # A jq script that is executed with the message obtained from the cache as input.
    +            # must return a valid OID. 
    +            oid:
    +            # a static string, defining the type of the OID value,
    +            # one of: bool, int, bitString, octetString, null, objectID, objectDescription,
    +            # ipAddress, counter32, gauge32, timeTicks, opaque, nsapAddress, counter64, 
    +            # uint32, opaqueFloat, opaqueDouble
    +            type:
    +            # A jq script that is executed with the message obtained from the cache as input.
    +            # must return a value matching the above configured type.
    +            value:
    +

    How does it work?#

    The SNMP output stores each received update message in a local cache (1.a), then checks if the message should trigger any of the configured traps (1.b).

    If the received message triggers a trap (2), an SNMP variable binding is generated from the trap trigger configuration section (OID, type and value) based on the triggering event. The OID and value can be jq scripts.

    Then (3) for each configured binding, the configured path (jq script) is rendered based on the triggering event then used to retrieve an event message from the cache, that message is then used to generate the variable binding (OID, type and value).

    Once all bindings are generated, a sysUpTimeInstance (OID=1.3.6.1.2.1.1.3.0) binding is prepended to the PDU list of the trap, its value is the number of seconds since gNMIc SNMP output startup.

    Metrics#

    The SNMP output exposes 4 Prometheus metrics:

    • Number of failed trap generation

    • Number of SNMP trap sending failures

    • SNMP trap generation duration in ns

    gnmic_snmp_output_number_of_snmp_trap_failed_generation{name="snmp_trap",reason="",trap_index="0"} 0
    +gnmic_snmp_output_number_of_snmp_trap_sent_fail_total{name="snmp_trap",reason="",trap_index="0"} 0
    +gnmic_snmp_output_number_of_snmp_traps_sent_total{name="snmp_trap",trap_index="0"} 114
    +gnmic_snmp_output_snmp_trap_generation_duration_ns{name="snmp_trap",trap_index="0"} 380215
    +

    Examples#

    interface operational state trap#

    The below example generates an SNMPV2 trap whenever the operational state of an interface changes (ifOperStatus).

    It adds sysName, ifAdminStatus and ifIndex variable bindings to the trap before sending it out.

    username: admin
    +password: NokiaSrl1!
    +skip-verify: true
    +
    +targets:
    +  clab-snmp-srl1:
    +  clab-snmp-srl2:
    +
    +subscriptions:
    +  sub1:
    +    paths:
    +      - /interface/admin-state
    +      - /interface/oper-state
    +      - /interface/ifindex
    +      - /system/name/host-name
    +    stream-mode: on-change
    +    encoding: ascii
    +
    +outputs:
    +  snmp_trap:
    +    type: snmp
    +    address: snmptrap.server
    +    # port: 162
    +    # community: public
    +    traps:
    +      - trigger:
    +          path: /interface/oper-state # static path
    +          oid: '".1.3.6.1.2.1.2.2.1.8"' # ifOperStatus
    +          type: int
    +          value: if (.values."/interface/oper-state" == "up") 
    +                  then 1 
    +                  else 2 
    +                  end
    +        bindings:         
    +          - path: '"/system/name/host-name"' # jq script
    +            oid: '".1.3.6.1.2.1.1.5"' # sysName
    +            type: octetString
    +            value: '.values."/system/name/host-name"'
    +
    +          - path: '"/interface[name="+.tags.interface_name+"]/admin-state"' # jq script
    +            oid: '".1.3.6.1.2.1.2.2.1.7"' # ifAdminStatus
    +            type: int
    +            value: if (.values."/interface/admin-state" == "enable") 
    +                    then 1 
    +                    else 2 
    +                    end
    +
    +          - path: '"/interface[name="+.tags.interface_name+"]/ifindex"' # jq script
    +            oid: '".1.3.6.1.2.1.2.2.1.1"' # ifIndex
    +            type: int
    +            value: '.values."/interface/ifindex" | tonumber' # jq script
    +
    \ No newline at end of file diff --git a/user_guide/outputs/stan_output/index.html b/user_guide/outputs/stan_output/index.html new file mode 100644 index 00000000..6351b3f6 --- /dev/null +++ b/user_guide/outputs/stan_output/index.html @@ -0,0 +1,59 @@ + STAN - gNMIc

    STAN

    gnmic supports exporting subscription updates to multiple NATS Streaming (STAN) servers/clusters simultaneously

    A STAN output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    type: stan # required
    +    # comma separated STAN servers
    +    address: localhost:4222
    +    # stan subject
    +    subject: telemetry 
    +     # stan subject prefix, the subject prefix is built the same way as for NATS output
    +    subject-prefix: telemetry
    +    # STAN username
    +    username:
    +    # STAN password
    +    password: 
    +    # STAN publisher name
    +    # if left empty, this field is populated with the output name used as output ID (output1 in this example).
    +    # the full name will be '$(name)-stan-pub'.
    +    # If the flag --instance-name is not empty, the full name will be '$(instance-name)-$(name)-stan-pub.
    +    # note that each stan worker (publisher) will get client name=$name-$index
    +    name: ""
    +    # cluster name, mandatory
    +    cluster-name: test-cluster
    +    # STAN ping interval
    +    ping-interval: 5
    +    # STAN ping retry
    +    ping-retry: 2
    +    # string, message marshaling format, one of: proto, prototext, protojson, json, event
    +    format:  event 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false
    +    # duration to wait before re establishing a lost connection to a stan server
    +    recovery-wait-time: 2s
    +    # integer, number of stan publishers to be created
    +    num-workers: 1 
    +    # boolean, enables extra logging for the STAN output
    +    debug: false 
    +    # duration after which a message waiting to be handled by a worker gets discarded
    +    write-timeout: 10s 
    +    # boolean, enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false 
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +

    Using subject config value a user can specify the STAN subject to which to send all subscriptions updates for all targets

    If a user wants to separate updates by targets and by subscriptions, subject-prefix can be used. if subject-prefix is specified subject is ignored.

    \ No newline at end of file diff --git a/user_guide/outputs/tcp_output/index.html b/user_guide/outputs/tcp_output/index.html new file mode 100644 index 00000000..4d2ca0e0 --- /dev/null +++ b/user_guide/outputs/tcp_output/index.html @@ -0,0 +1,46 @@ + TCP - gNMIc

    TCP

    gnmic supports exporting subscription updates to a TCP server

    A TCP output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    # required
    +    type: tcp 
    +    # a UDP server address 
    +    address: IPAddress:Port 
    +    # maximum sending rate, e.g: 1ns, 10ms
    +    rate: 10ms 
    +    # number of messages to buffer in case of sending failure
    +    buffer-size:
    +    # export format. json, protobuf, prototext, protojson, event
    +    format: json 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # boolean, valid only if format is `event`.
    +    # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts.
    +    split-events: false
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false
    +    # string, a delimiter to be sent after each message.
    +    # useful when writing to logstash TCP input.
    +    delimiter:
    +    # enable TCP keepalive and specify the timer, e.g: 1s, 30s
    +    keep-alive: 
    +    # time duration to wait before re-dial in case there is a failure
    +    retry-interval: 
    +    # NOT IMPLEMENTED boolean, enables the collection and export (via prometheus) of output specific metricss
    +    enable-metrics: false 
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +

    A TCP output can be used to export data to an ELK stack, using Logstash TCP input

    \ No newline at end of file diff --git a/user_guide/outputs/udp_output/index.html b/user_guide/outputs/udp_output/index.html new file mode 100644 index 00000000..f2d4174f --- /dev/null +++ b/user_guide/outputs/udp_output/index.html @@ -0,0 +1,41 @@ + UDP - gNMIc

    UDP

    gnmic supports exporting subscription updates to a UDP server

    A UDP output can be defined using the below format in gnmic config file under outputs section:

    outputs:
    +  output1:
    +    # required
    +    type: udp 
    +    # a UDP server address 
    +    address: IPAddress:Port
    +    # maximum sending rate, e.g: 1ns, 10ms
    +    rate: 10ms 
    +    # number of messages to buffer in case of sending failure
    +    buffer-size: 
    +    # export format. json, protobuf, prototext, protojson, event
    +    format: json 
    +    # string, one of `overwrite`, `if-not-present`, ``
    +    # This field allows populating/changing the value of Prefix.Target in the received message.
    +    # if set to ``, nothing changes 
    +    # if set to `overwrite`, the target value is overwritten using the template configured under `target-template`
    +    # if set to `if-not-present`, the target value is populated only if it is empty, still using the `target-template`
    +    add-target: 
    +    # string, a GoTemplate that allow for the customization of the target field in Prefix.Target.
    +    # it applies only if the previous field `add-target` is not empty.
    +    # if left empty, it defaults to:
    +    # {{- if index . "subscription-target" -}}
    +    # {{ index . "subscription-target" }}
    +    # {{- else -}}
    +    # {{ index . "source" | host }}
    +    # {{- end -}}`
    +    # which will set the target to the value configured under `subscription.$subscription-name.target` if any,
    +    # otherwise it will set it to the target name stripped of the port number (if present)
    +    target-template:
    +    # boolean, valid only if format is `event`.
    +    # if true, arrays of events are split and marshaled as JSON objects instead of an array of dicts.
    +    split-events: false
    +    # boolean, if true the message timestamp is changed to current time
    +    override-timestamps: false
    +    # time duration to wait before re-dial in case there is a failure
    +    retry-interval: 
    +    # NOT IMPLEMENTED boolean, enables the collection and export (via prometheus) of output specific metrics
    +    enable-metrics: false 
    +    # list of processors to apply on the message before writing
    +    event-processors: 
    +

    A UDP output can be used to export data to an ELK stack, using Logstash UDP input

    \ No newline at end of file diff --git a/user_guide/prompt_suggestions/index.html b/user_guide/prompt_suggestions/index.html new file mode 100644 index 00000000..7f45ab22 --- /dev/null +++ b/user_guide/prompt_suggestions/index.html @@ -0,0 +1,46 @@ + Prompt mode - gNMIc

    Prompt mode

    Starting with gnmic v0.4.0 release the users can enjoy the interactive prompt mode which can be enabled with the prompt command.

    The prompt mode delivers two major features:

    • simplifies gnmic commands and flags navigation, as every option is suggested and auto-completed
    • provides interactive YANG path auto-suggestions for get, set, subscribe commands effectively making the terminal your YANG browser

    Using the prompt interface#

    Depending on the cursor position in the prompt line, a so-called suggestion box pops up with contextual auto-completions. The user can enter the suggestion box by pressing the TAB key. The and keys can be used to navigate the suggestion list.

    Select the suggested menu item with SPACE key or directly commit your command with ENTER, its that easy!

    The following most-common key bindings will work in the prompt mode:

    Key combination Description
    Option/Control + →/← move cursor a word right/left
    Control + W delete a word to the left
    Control + Z delete a path element in the xpath string (example)
    Control + A move cursor to the beginning of a line
    Control + E move cursor to the end of a line
    Control + C discard the current line
    Control + D exit prompt
    Control + K delete the line after the cursor to the clipboard
    Control + U delete the line before the cursor to the clipboard
    Control + L clear screen

    Commands and flags suggestions#

    To make gnmic configurable and flexible we introduced a considerable amount of flags and sub-commands.
    To help the users navigate the sheer selection of gnmic configuration options, the prompt mode will auto-suggest the global flags, sub-commands and local flags of those sub-commands.

    When the prompt mode is launched, the suggestions will be shown for the top-level commands and all the global flags. Once the sub-command is typed into the terminal, the auto-suggestions will be provided for the commands nested under this command and its local flags.

    In the following demo we show how the command and flag suggestions work. As the prompt starts, the suggestion box immediately hints what commands and global flags are available for input as well as their description.

    The user starts with adding the global flags --address, --insecure, --username and then selects the capabilities command and commits it. This results in gNMI Capability RPC execution against a specified target.

    Mixed mode#

    Its perfectly fine to specify some global flags outside of the prompt command and add more within the prompt mode. For example, the following is a valid invocation:

    gnmic --insecure --username admin --password admin --address 10.1.0.11 prompt
    +

    Here the prompt will start with with the insecure, username, password, address flags set.

    YANG-completions#

    One of the most challenging problems in the network automation field is to process the YANG models and traverse YANG trees to construct the requests used against the network elements.
    Be it gNMI, NETCONF or RESTCONF a users still needs to have a path pointing to specific YANG-defined node which is targeted by a request.

    In gNMI paths can be represented in a human readable XPATH-like form - /a/b/c[key=val]/d - and these paths are based on the underlying YANG models.
    The problem at hand was how to get these paths interactively, or even better - walk the YANG tree from within the CLI and dynamically build the path used in a gNMI RPC?

    With YANG-completions feature embedded in gnmic what used to be a dream is now a reality 🎉

    Let us explain what just happened there.

    In the demonstration above, we called the gnmic with the well-known flags defining the gNMI target (address, username, password). But this time we also added a few YANG specific flags (--file and --dir) that load the full set of Nokia SR OS YANG models and the 3rd party models SR OS rely on.

    gnmic --address 10.1.0.11 --insecure --username admin --password admin \
    +      --file ~/7x50_YangModels/YANG/nokia-combined \
    +      --dir ~/7x50_YangModels/YANG \
    +      prompt
    +

    In the background gnmic processed these YANG models to build the entire schema tree of the Nokia SR OS state and configuration datastores. With that in-mem stored information, gnmic was able to auto-suggest all the possible YANG paths when the user entered the --path flag which accepts gNMI paths.

    By using the auto-suggestion hints, a user navigated the /state tree of a router and drilled down to the version-number leaf that, in the end, was retrieved with the gNMI Get RPC.

    YANG-driven path suggestions

    gnmic is now capable of reading and processing YANG modules to enable live path auto-suggestions

    YANG processing#

    For the YANG-completion feature to work its absolutely imperative for gnmic to successfully parse and compile the YANG models.

    The prompt command leverages the --file and --dir flags to select the YANG models for processing.

    With the --file flag a user specifies a file path to a YANG file or a directory of them that gnmic will read and process. If it points to a directory it will be visited recursively reading in all *.yang files it finds.

    The --dir flag also points to a YANG file or a directory and indicates which additional YANG files might be required. For example, if the YANG modules that a user specified with the --file flag import or include modules that were not part of the path specified with --file, they need to be added with the --dir flag.

    The Examples section provide some good practical examples on how these two flags can be used together to process the YANG models from different vendors.

    Understanding path suggestions#

    When gnmic provides a user with the path suggestions it does it in a smart and intuitive way.

    path suggestions

    First, it understands in what part of the tree a user currently is and suggests only the next possible elements.

    Additionally, the suggested next path elements will be augmented with the information extracted from the YANG model, such as:

    • element description, as given in the YANG description statement for the element
    • element configuration state (rw / ro), as defined in section 4.2.3 of RFC 7950.
    • node type:
      • The containers and lists will be denoted with the [+] marker, which means that a user can type / char after them to receive suggestions for the nested elements.
      • the [⋯] character belongs to a leaf-list element.
      • an empty space will indicate the leaf element.

    Examples#

    The examples in this section will show how to use the --file and --dir flags of the prompt command with the YANG collections from different vendors and standard bodies.

    Nokia SR OS#

    YANG repo: nokia/7x50_YangModels

    Clone the repository with Nokia YANG models and checkout the release of interest:

    git clone https://github.com/nokia/7x50_YangModels
    +cd 7x50_YangModels
    +git checkout sros_20.7.r2
    +

    Start gnmic in prompt mode and read in the nokia-combined YANG modules:

    gnmic --file YANG/nokia-combined \
    +      --dir YANG \
    +      prompt
    +

    This will enable path auto-suggestions for the entire tree of the Nokia SR OS YANG models.

    The full command with the gNMI target specified could look like this:

    gnmic --address 10.1.0.11 --insecure --username admin --password admin \
    +      prompt \
    +      --file ~/7x50_YangModels/YANG/nokia-combined \
    +      --dir ~/7x50_YangModels/YANG
    +

    Openconfig#

    YANG repo: openconfig/public

    Clone the OpenConfig repository:

    git clone https://github.com/openconfig/public
    +cd public
    +

    Start gnmic in prompt mode and read in all the modules:

    gnmic --file release/models \
    +      --dir third_party \
    +      --exclude ietf-interfaces \
    +      prompt
    +

    Note

    With OpenConfig models we have to use --exclude flag to exclude ietf-interfaces module from being clashed with OpenConfig interfaces module.

    Cisco#

    YANG repo: YangModels/yang

    Clone the YangModels/yang repo and change into the main directory of the repo:

    git clone https://github.com/YangModels/yang
    +cd yang/vendor
    +
    IOS-XR#

    The IOS-XR native YANG models are disaggregated and spread all over the place. Although its technically possible to load them all in one go, this approach will produce a lot of top-level modules making the navigation quite hard.

    An easier and cleaner approach would be to find the relevant module(s) and load them separately or in small batches. For example here we load BGP config and operational models together:

    gnmic --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \
    +      --file vendor/cisco/xr/721/Cisco-IOS-XR-ipv4-bgp-oper.yang \
    +      --dir standard/ietf \
    +      prompt
    +

    Note

    We needed to include the ietf/ directory by means of the --dir flag, since the Cisco's native modules rely on the IETF modules and these modules are not in the same directory as the BGP modules.

    The full command that you can against the real Cisco IOS-XR node must have a target defined, the encoding set and origin suggestions enabled. Here is what it can look like:

    gnmic -a 10.10.30.5:57500 --insecure -e json_ietf -u admin -p Cisco123 \
    +      prompt \
    +      --file yang/vendor/cisco/xr/662/Cisco-IOS-XR-ipv4-bgp-cfg.yang \
    +      --file yang/vendor/cisco/xr/662/Cisco-IOS-XR-ipv4-bgp-oper.yang \
    +      --dir yang/standard/ietf \
    +      --suggest-with-origin
    +
    NX-OS#

    Cisco NX-OS native modules, on the other hand, are aggregated in a single file, here is how you can generate the suggestions from it:

    gnmic --file vendor/cisco/xr/721/Cisco-IOS-XR-um-router-bgp-cfg.yang \
    +      --dir standard/ietf \
    +      prompt
    +

    Juniper#

    YANG repo: Juniper/yang

    Clone the Juniper YANG repository and change into the release directory:

    git clone https://github.com/Juniper/yang
    +cd yang/20.3/20.3R1
    +

    Start gnmic and generate path suggestions for the whole configuration tree of Juniper MX:

    gnmic --file junos/conf --dir common prompt
    +

    Note

    1. Juniper models are constructed in a way that a top-level container appears to be /configuration, that will not work with your gNMI Subscribe RPC. Instead, you should omit this top level container. So, for example, the suggested path /configuration/interfaces/interface/state should become /interfaces/interface/state.
    2. Juniper vMX doesn't support gNMI Get RPC, if you plan to test it, use gNMI Subscribe RPC
    3. With gNMI Subscribe, specify -e proto flag to enable protobuf encoding.

    Arista#

    YANG repo: aristanetworks/yang

    Arista uses a subset of OpenConfig modules and does not provide IETF modules inside their repo. So make sure you have IETF models available so you can reference it, a openconfig/public is a good candidate.

    Clone the Arista YANG repo:

    git clone https://github.com/aristanetworks/yang
    +cd yang
    +

    Generate path suggestions for all Arista OpenConfig modules:

    gnmic --file EOS-4.23.2F/openconfig/public/release/models \
    +      --dir ~/public/third_party/ietf \
    +      --exclude ietf-interfaces \
    +      prompt
    +

    Enumeration suggestions#

    gnmic flags that can take pre-defined values (enumerations) will get suggestions as well. For example, no need to keep in mind which subscription modes are available, the prompt will hint you:

    enum suggestion

    File-path completions#

    Whenever a user needs to provide a file path in a prompt mode, the filepath suggestions will make the process interactive:

    \ No newline at end of file diff --git a/user_guide/subscriptions/index.html b/user_guide/subscriptions/index.html new file mode 100644 index 00000000..634c453b --- /dev/null +++ b/user_guide/subscriptions/index.html @@ -0,0 +1,348 @@ + Subscriptions - gNMIc

    Subscriptions

    Defining subscriptions with subscribe command's CLI flags is a quick&easy way to work with gNMI subscriptions.

    A downside of that approach is that commands can get lengthy when defining multiple subscriptions and not all possible flavors and combinations of subscription can be defined.

    With the multiple subscriptions defined in the configuration file we make a complex task of managing multiple subscriptions for multiple targets easy. The idea behind the multiple subscriptions is to define the subscriptions separately and then bind them to the targets.

    Defining subscriptions#

    CLI-based subscription#

    A subscription is configured through a series of command-line interface (CLI) flags. These include, but are not limited to:

    1. --path: This flag is used to set the paths for the subscription.

    2. --mode [once | poll | stream]: Defines the subscription mode. It can be set to once, poll, or stream.

    3. --stream-mode [target-defined | sample | on-change]: Sets the stream subscription mode. The options are target-defined, sample, or on-change.

    4. --sample-interval: Determines the sample interval for a stream/sample subscription.

    A command executed with these flags will generate a single SubscribeRequest that is sent to the target.

    Every path configured with the --path flag leads to a Subscription added to the subscriptionList message.

    There are no constraints when defining a ONCE or POLL subscribe request. However, when a STREAM subscribe request is defined using flags, all subscriptions (paths) will adopt the same mode (target-defined, on-change, or sample) and stream subscription attributes such as sample-interval and heartbeat-interval.

    File-based subscription config#

    To define a subscription a user needs to create the subscriptions container in the configuration file:

    subscriptions:
    +  # a configurable subscription name
    +  subscription-name:
    +    # string, path to be set as the Subscribe Request Prefix
    +    prefix:
    +    # string, value to set as the SubscribeRequest Prefix Target
    +    target:
    +    # boolean, if true, the SubscribeRequest Prefix Target will be set to 
    +    # the configured target name under section `targets`.
    +    # does not apply if the previous field `target` is set.
    +    set-target: # true | false
    +    # list of strings, list of subscription paths for the named subscription
    +    paths: []
    +    # list of strings, schema definition modules
    +    models: []
    +    # string, case insensitive, one of ONCE, STREAM, POLL
    +    mode: STREAM
    +    # string, case insensitive, if `mode` is set to STREAM, this defines the type 
    +    # of streamed subscription,
    +    # one of SAMPLE, TARGET_DEFINED, ON_CHANGE
    +    stream-mode: TARGET_DEFINED
    +    # string, case insensitive, defines the gNMI encoding to be used for the subscription
    +    encoding: JSON
    +    # integer, specifies the packet marking that is to be used for the subscribe responses
    +    qos:
    +    # duration, Golang duration format, e.g: 1s, 1m30s, 1h.
    +    # specifies the sample interval for a STREAM/SAMPLE subscription
    +    sample-interval:
    +    # duration, Golang duration format, e.g: 1s, 1m30s, 1h.
    +    # The heartbeat interval value can be specified along with `ON_CHANGE` or `SAMPLE` 
    +    # stream subscriptions modes and has the following meanings in each case:
    +    # - `ON_CHANGE`: The value of the data item(s) MUST be re-sent once per heartbeat 
    +    #                interval regardless of whether the value has changed or not.
    +    # - `SAMPLE`: The target MUST generate one telemetry update per heartbeat interval, 
    +    #             regardless of whether the `--suppress-redundant` flag is set to true.
    +    heartbeat-interval:
    +    # boolean, if set to true, the target SHOULD NOT generate a telemetry update message unless 
    +    # the value of the path being reported on has changed since the last 
    +    suppress-redundant:
    +    # boolean, if set to true, the target MUST not transmit the current state of the paths 
    +    # that the client has subscribed to, but rather should send only updates to them.
    +    updates-only:
    +    # list of strings, the list of outputs to send updates to. If blank, defaults to all outputs
    +    outputs:
    +      - output1
    +      - output2
    +    # list of subscription definition, this field is used to define multiple stream subscriptions (target-defined, sample or on-change)
    +    # that will be created using a single SubscribeRequest (i.e: share the same gRPC stream).
    +    # This field cannot be defined if `paths`, `stream-mode`, `sample-interval`, `heartbeat-interval` or`suppress-redundant` are set.
    +    # Only fields applicable to STREAM subscriptions can be set in this list of subscriptions: 
    +    # `paths`, `stream-mode`, `sample-interval`, `heartbeat-interval` or`suppress-redundant`
    +    stream-subscriptions:
    +      - paths: []
    +        stream-mode: 
    +        sample-interval:
    +        heartbeat-interval:
    +        suppress-redundant:
    +      - paths: []
    +        stream-mode: 
    +        sample-interval:
    +        heartbeat-interval:
    +        suppress-redundant:
    +    # historical subscription config: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-history.md#1-purpose    
    +    history:
    +      # string, nanoseconds since Unix epoch or RFC3339 format.
    +      # if set, the history extension type will be a Snapshot request
    +      snapshot:
    +      # string, nanoseconds since Unix epoch or RFC3339 format.
    +      # if set, the history extension type will be a Range request
    +      start:
    +      # string, nanoseconds since Unix epoch or RFC3339 format.
    +      # if set, the history extension type will be a Range request
    +      end:
    +

    Subscription config to gNMI SubscribeRequest#

    Each subscription (under subscriptions:) results in a single SubscribeRequest being sent to the target.

    If paths is set, each path results in a separate Subscription message being added to the subscriptionList message.

    If instead of paths, a list of stream-subscriptions is defined:

    subscriptions:
    +  sub1:
    +    stream-subscriptions:
    +      - paths:
    +

    Each path under each stream-subscriptions will result in a separate Subscription message being added to the subscriptionList message.

    Examples#

    A single stream/sample subscription#
    subscriptions:
    +  port_stats:
    +    paths:
    +      - "/state/port[port-id=*]/statistics"
    +    stream-mode: sample
    +    sample-interval: 5s
    +    encoding: bytes
    +
    gnmic sub --path /state/port/statistics \
    +          --stream-mode sample \
    +          --sample-interval 5s \
    +          --encoding bytes
    +
    subscribe: {
    +  subscription: {
    +    path: {
    +      elem: {
    +        name: "state"
    +      }
    +      elem: {
    +        name: "port"
    +      }
    +      elem: {
    +        name: "statistics"
    +      }
    +    }
    +    mode: SAMPLE
    +    sample_interval:  5000000000
    +  }
    +  encoding: BYTES
    +}
    +
    A single stream/on-change subscription#
    subscriptions:
    +  port_stats:
    +    paths:
    +      - "/state/port/oper-state"
    +    stream-mode: on-change
    +    encoding: bytes
    +
    gnmic sub --path /state/port/oper-state \
    +          --stream-mode on-change \
    +          --encoding bytes
    +
    subscribe: {
    +  subscription: {
    +    path: {
    +      elem: {
    +        name: "state"
    +      }
    +      elem: {
    +        name: "port"
    +      }
    +      elem: {
    +        name: "oper-state"
    +      }
    +    }
    +    mode: ON_CHANGE
    +  }
    +  encoding: BYTES
    +}
    +
    A ONCE subscription#
    subscriptions:
    +  system_facts:
    +    paths:
    +      - /configure/system/name
    +      - /state/system/version
    +    mode: once
    +    encoding: bytes
    +
    gnmic sub --path /configure/system/name \
    +          --path /state/system/version \
    +          --mode once \
    +          --encoding bytes
    +
    subscribe: {
    +  subscription: {
    +    path: {
    +      elem: {
    +        name: "configure"
    +      }
    +      elem: {
    +        name: "port"
    +      }
    +      elem: {
    +        name: "name"
    +      }
    +    }
    +  }
    +  subscription: {
    +    path: {
    +      elem: {
    +        name: "state"
    +      }
    +      elem: {
    +        name: "system"
    +      }
    +      elem: {
    +        name: "version"
    +      }
    +    }
    +  }
    +  mode: ONCE
    +  encoding: BYTES
    +}
    +
    Combining multiple stream subscriptions in the same gRPC stream#
    subscriptions:
    +  sub1:
    +    stream-subscriptions:
    +      - paths:
    +        - /configure/system/name
    +        stream-mode: on-change
    +      - paths:
    +        - /state/port/statistics
    +        stream-mode: sample
    +        sample-interval: 10s  
    +    encoding: bytes
    +

    NA

    subscribe: {
    +  subscription: {
    +    path: {
    +      elem: {
    +        name: "configure"
    +      }
    +      elem: {
    +        name: "system"
    +      }
    +      elem: {
    +        name: "name"
    +      }
    +    }
    +    mode: ON_CHANGE
    +  }
    +  subscription: {
    +    path: {
    +      elem: {
    +        name: "state"
    +      }
    +      elem: {
    +        name: "port"
    +      }
    +      elem: {
    +        name: "statistics"
    +      }
    +    }
    +    mode: SAMPLE
    +    sample_interval:  10000000000
    +  }
    +  encoding: BYTES
    +}
    +
    Configure multiple subscriptions#
    # part of ~/gnmic.yml config file
    +subscriptions:  # container for subscriptions
    +  port_stats:     # a named subscription, a key is a name
    +    paths:      # list of subscription paths for that named subscription
    +      - "/state/port[port-id=1/1/c1/1]/statistics/out-octets"
    +      - "/state/port[port-id=1/1/c1/1]/statistics/in-octets"
    +    stream-mode: sample # one of [on-change target-defined sample]
    +    sample-interval: 5s
    +    encoding: bytes
    +  service_state:
    +    paths:
    +      - "/state/service/vpls[service-name=*]/oper-state"
    +      - "/state/service/vprn[service-name=*]/oper-state"
    +    stream-mode: on-change
    +  system_facts:
    +    paths:
    +      - "/configure/system/name"
    +      - "/state/system/version"
    +    mode: once
    +

    Inside that subscriptions container a user defines individual named subscriptions; in the example above two named subscriptions port_stats and service_state were defined.

    These subscriptions can be used on the cli via the [ --name ] flag of subscribe command:

    gnmic subscribe --name service_state --name port_stats
    +

    Or by binding them to different targets, (see next section)

    Binding subscriptions#

    Once the subscriptions are defined, they can be flexibly associated with the targets.

    # part of ~/gnmic.yml config file
    +targets:
    +  router1.lab.com:
    +    username: admin
    +    password: secret
    +    subscriptions:
    +      - port_stats
    +      - service_state
    +  router2.lab.com:
    +    username: gnmi
    +    password: telemetry
    +    subscriptions:
    +      - service_state
    +

    The named subscriptions are put under the subscriptions section of a target container. As shown in the example above, it is allowed to add multiple named subscriptions under a single target; in that case each named subscription will result in a separate Subscription Request towards a target.

    Note

    If a target is not explicitly associated with any subscription, the client will subscribe to all defined subscriptions in the file.

    The full configuration with the subscriptions defined and associated with targets will look like this:

    username: admin
    +password: nokiasr0s
    +insecure: true
    +
    +targets:
    +  router1.lab.com:
    +    subscriptions:
    +      - port_stats
    +      - service_state
    +      - system_facts
    +  router2.lab.com:
    +    subscriptions:
    +      - service_state
    +      - system_facts
    +
    +subscriptions:
    +  port_stats:
    +    paths:
    +      - "/state/port[port-id=1/1/c1/1]/statistics/out-octets"
    +      - "/state/port[port-id=1/1/c1/1]/statistics/in-octets"
    +    stream-mode: sample
    +    sample-interval: 5s
    +    encoding: bytes
    +  service_state:
    +    paths:
    +       - "/state/service/vpls[service-name=*]/oper-state"
    +       - "/state/service/vprn[service-name=*]/oper-state"
    +    stream-mode: on-change
    +  system_facts:
    +    paths:
    +       - "/configure/system/name"
    +       - "/state/system/version"
    +    mode: once
    +

    As a result of such configuration the gnmic will set up three gNMI subscriptions to router1 and two other gNMI subscriptions to router2:

    $ gnmic subscribe
    +gnmic 2020/07/06 22:03:35.579942 target 'router2.lab.com' initialized
    +gnmic 2020/07/06 22:03:35.593082 target 'router1.lab.com' initialized
    +
    {
    +  "source": "router2.lab.com",
    +  "subscription-name": "service_state",
    +  "timestamp": 1594065869313065895,
    +  "time": "2020-07-06T22:04:29.313065895+02:00",
    +  "prefix": "state/service/vpls[service-name=testvpls]",
    +  "updates": [
    +    {
    +      "Path": "oper-state",
    +      "values": {
    +        "oper-state": "down"
    +      }
    +    }
    +  ]
    +}
    +{
    +  "source": "router1.lab.com",
    +  "subscription-name": "service_state",
    +  "timestamp": 1594065868850351364,
    +  "time": "2020-07-06T22:04:28.850351364+02:00",
    +  "prefix": "state/service/vpls[service-name=test]",
    +  "updates": [
    +    {
    +      "Path": "oper-state",
    +      "values": {
    +        "oper-state": "down"
    +      }
    +    }
    +  ]
    +}
    +{
    +  "source": "router1.lab.com",
    +  "subscription-name": "port_stats",
    +  "timestamp": 1594065873938155916,
    +  "time": "2020-07-06T22:04:33.938155916+02:00",
    +  "prefix": "state/port[port-id=1/1/c1/1]/statistics",
    +  "updates": [
    +    {
    +      "Path": "in-octets",
    +      "values": {
    +        "in-octets": "671552"
    +      }
    +    }
    +  ]
    +}
    +{
    +  "source": "router1.lab.com",
    +  "subscription-name": "port_stats",
    +  "timestamp": 1594065873938043848,
    +  "time": "2020-07-06T22:04:33.938043848+02:00",
    +  "prefix": "state/port[port-id=1/1/c1/1]/statistics",
    +  "updates": [
    +    {
    +      "Path": "out-octets",
    +      "values": {
    +        "out-octets": "370930"
    +      }
    +    }
    +  ]
    +}
    +^C
    +received signal 'interrupt'. terminating...
    +
    \ No newline at end of file diff --git a/user_guide/targets/target_discovery/consul_discovery/index.html b/user_guide/targets/target_discovery/consul_discovery/index.html new file mode 100644 index 00000000..a18cbf39 --- /dev/null +++ b/user_guide/targets/target_discovery/consul_discovery/index.html @@ -0,0 +1,43 @@ + Consul Discovery - gNMIc

    Consul Discovery

    The Consul target loader discovers gNMI targets registered as service instances in a Consul Server.

    The loader watches services registered in Consul defined by a service name and optionally a set of tags.

    Services watch#

    When at least one service name is set, gNMIc consul loader will watch the instances registered under that service name and build a target configuration using the service ID as the target name and the registered address and port as the target address.

    The remaining configuration can be set under the service name definition.

    loader:
    +  type: consul
    +  services:
    +    - name: cluster1-gnmi-server
    +      config:
    +        insecure: true
    +        username: admin
    +        password: admin
    +

    Configuration#

    loader:
    +  type: consul
    +  # address of the loader server
    +  address: localhost:8500
    +  # Consul Data center, defaults to dc1
    +  datacenter: dc1
    +  # Consul username, to be used as part of HTTP basicAuth
    +  username:
    +  # Consul password, to be used as part of HTTP basicAuth
    +  password:
    +  # Consul Token, is used to provide a per-request ACL token which overrides the agent's default token
    +  token:
    +  # the key prefix to watch for targets configuration, defaults to "gnmic/config/targets"
    +  key-prefix: gnmic/config/targets
    +  # if true, registers consulLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +  # list of services to watch and derive target configurations from.
    +  services:
    +      # name of the Consul service
    +    - name:
    +      # a list of strings to further filter the service instances
    +      tags: 
    +      # configuration map to apply to target discovered from this service
    +      config:
    +  # list of actions to run on target discovery
    +  on-add:
    +  # list of actions to run on target removal
    +  on-delete:
    +  # variable dict to pass to actions to be run
    +  vars:
    +  # path to variable file, the variables defined will be passed to the actions to be run
    +  # values in this file will be overwritten by the ones defined in `vars`
    +  vars-file:
    +
    \ No newline at end of file diff --git a/user_guide/targets/target_discovery/discovery_intro/index.html b/user_guide/targets/target_discovery/discovery_intro/index.html new file mode 100644 index 00000000..e3bb37da --- /dev/null +++ b/user_guide/targets/target_discovery/discovery_intro/index.html @@ -0,0 +1,57 @@ + Introduction - gNMIc

    Introduction

    Introduction#

    gnmic supports dynamic loading of gNMI targets from external systems. This feature allows adding and deleting gNMI targets without the need to restart gnmic.

    Depending on the discovery method, gnmic will either:

    • Subscribe to changes on the remote system,
    • Or poll the defined targets from the remote systems.

    When a change is detected, the new targets are added and the corresponding subscriptions are immediately established. The removed targets are deleted together with their subscriptions.

    Actions can be run on target discovery (on-add or on-delete), this can be useful to add initial configurations to target ahead of gNMI subscriptions or run checks before subscribing. In the case of on-add actions,

    Notes

    1. Only one discovery type is supported at a time.

    2. Target updates are not supported, delete and re-add is the way to update a target configuration.

    Discovery types#

    Four types of target discovery methods are supported:

    File Loader#

    Watches changes to a local file containing gNMI targets definitions.

    Consul Server Loader#

    Subscribes to Consul KV key prefix changes, the keys and their value represent a target configuration fields.

    Docker Engine Loader#

    Polls containers from a Docker Engine host matching some predefined criteria (docker filters).

    HTTP Loader#

    Queries an HTTP endpoint periodically, expected a well formatted JSON dict of targets configurations.

    Running actions on discovery#

    All actions support fields on-add and on-delete which take a list of predefined action names that will be run sequentially on target discovery or deletion.

    The below configuration example defines 3 actions configure_interfaces, configure_subinterfaces and configure_network_instance which will run when the docker loader discovers a target with label clab-node-kind=srl

    loader:
    +  type: docker
    +  filters:
    +    - containers:
    +      - label: clab-node-kind=srl
    +      config:
    +        skip-verify: true
    +        username: admin
    +        password: NokiaSrl1!
    +  on-add:
    +    - configure_interfaces
    +    - configure_subinterfaces
    +    - configure_network_instances
    +
    +actions:
    +  configure_interfaces:
    +    name: configure_interfaces
    +    type: gnmi
    +    target: '{{ .Input }}'
    +    rpc: set
    +    encoding: json_ietf
    +    debug: true
    +    paths:
    +      - /interface[name=ethernet-1/1]/admin-state
    +      - /interface[name=ethernet-1/2]/admin-state 
    +    values:
    +      - enable
    +      - enable
    +  configure_subinterfaces:
    +    name: configure_subinterfaces
    +    type: gnmi
    +    target: '{{ .Input }}'
    +    rpc: set
    +    encoding: json_ietf
    +    debug: true
    +    paths:
    +      - /interface[name=ethernet-1/1]/subinterface[index=0]/admin-state
    +      - /interface[name=ethernet-1/2]/subinterface[index=0]/admin-state 
    +    values:
    +      - enable
    +      - enable
    +  configure_network_instances:
    +    name: configure_network_instances
    +    type: gnmi
    +    target: '{{ .Input }}'
    +    rpc: set
    +    encoding: json_ietf
    +    debug: true
    +    paths:
    +      - /network-instance[name=default]/admin-state
    +      - /network-instance[name=default]/interface
    +      - /network-instance[name=default]/interface
    +    values:
    +      - enable
    +      - '{"name": "ethernet-1/1.0"}'
    +      - '{"name": "ethernet-1/2.0"}'
    +
    \ No newline at end of file diff --git a/user_guide/targets/target_discovery/docker_discovery/index.html b/user_guide/targets/target_discovery/docker_discovery/index.html new file mode 100644 index 00000000..a468c50e --- /dev/null +++ b/user_guide/targets/target_discovery/docker_discovery/index.html @@ -0,0 +1,98 @@ + Docker Discovery - gNMIc

    Docker Discovery

    The Docker target loader allows discovering gNMI targets from Docker Engine hosts.

    It discovers containers as well as their gNMI address, based on a list of Docker filters

    One gNMI target is added per discovered container.

    Individual Target configurations are derived from the container exposed ports and labels, as well as the global configuration.

    Configuration#

    loader:
    +  # the loader type: docker
    +  type: docker
    +  # string, the docker daemon address,
    +  # leave empty to use the local docker daemon
    +  # possible values:
    +  #  - unix:///var/run/docker.sock
    +  #  - tcp://<docker_host>:port
    +  #  - http://<docker_host>:port
    +  address: ""
    +  # duration, check interval for discovering 
    +  # new docker containers, default: 30s
    +  interval: 30s
    +  # duration, the docker queries timeout, 
    +  # defaults to half of `interval` if left unset or is invalid.
    +  timeout: 15s
    +  # time to wait before the fist docker query
    +  start-delay: 0s
    +  # bool, print loader debug statements.
    +  debug: false
    +  # if true, registers dockerLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +  # containers, network filters: 
    +  # see https://docs.docker.com/engine/reference/commandline/ps/#filtering
    +  # for the possible values.
    +  filters:
    +      # containers filters
    +    - containers:
    +        # containers returned by `docker ps -f "label=clab-node-kind=srl"`
    +        - label: clab-node-kind=srl
    +      # network filters
    +      network:
    +        # networks returned by `docker network ls -f "label=containerlab"`
    +        label: containerlab
    +      # gNMI port value for the containers discovered by this filter.
    +      # It can be a port value or a label name set on the container.
    +      # valid values:
    +      #   `port: "57400"`
    +      #   `port: "label=gnmi-port"`
    +      port: 
    +      # target config for containers discovered by this filter.
    +      # These fields will override the matching global config fields.
    +      config:
    +        username: admin
    +        password: secret1
    +        skip-verify: true
    +  # list of actions to run on target discovery
    +  on-add:
    +  # list of actions to run on target removal
    +  on-delete:
    +  # variable dict to pass to actions to be run
    +  vars:
    +  # path to variable file, the variables defined will be passed to the actions to be run
    +  # values in this file will be overwritten by the ones defined in `vars`
    +  vars-file:
    +
    Filter fields explanation#
    • containers: (Optional)

    A list of lists of docker filters used to select containers from the Docker Engine host.

    The docker filter status=running is implicitly added.

    If not set, all containers with status=running are selected.

    • network: (Optional)

    A set of docker filters used to select the network to connect to the container.

    If not filter is set, all docker networks are considered.

    • port: (Optional)

    This field is used to specify the gNMI port for the discovered containers.

    An integer can be specified in which case it will be used as the gNMI port for all discovered containers.

    Alternatively, a string in the format label=<label_name> can be set, where <label_name> is a docker label containing the gNMI port value.

    If no value is set, the global flag/value port is used.

    • config: (Optional)

    A set of configuration parameters to be applied to all discovered targets by the container filter.

    The target config fields as defined here can be set, except name and address which are discovered by the loader.

    Examples#

    Simple1#

    A simple docker loader with a single docker container filter.

    It loads all containers deployed with containerlab, in lab called lab1.

    loader:
    +  type: docker
    +  filters:
    +    - containers:
    +        - label: containerlab=lab1
    +

    In the above example, gnmic docker loader connects to the local Docker Daemon.

    It will discover containers having label containerlab=lab1 and add them as gNMI targets.

    Default configuration applies to those added targets

    Simple2#

    A simple docker loader with a single docker container filter.

    It loads all containers deployed with containerlab, having kind srl.

    loader:
    +  type: docker
    +  filters:
    +    - containers:
    +        - label: clab-node-kind=srl
    +

    In the above example, gnmic docker loader connects to the local Docker Daemon.

    It will discover containers having label clab-node-kind=srl and add them as gNMI targets.

    Default configuration applies to those added targets

    Advanced Example#

    A more advanced docker loader, with 2 filers, custom networks, ports and target configuration.

    loader:
    +  type: docker
    +  address: unix:///var/run/docker.sock
    +  filters:
    +    # filter 1
    +    - containers:
    +        # containers returned by `docker ps -f "label=clab-node-kind=srl"`
    +        - label: clab-node-kind=srl
    +      network:
    +        # networks returned by `docker network ls -f "label=containerlab"`
    +        label: containerlab
    +      port: "57400"
    +      config:
    +        username: admin
    +        password: secret1
    +        skip-verify: true
    +    # filter 2
    +    - containers:
    +        # containers returned by `docker ps -f "label=clab-node-kind=ceos"`
    +        - label: clab-node-kind=ceos
    +        # containers returned by `docker ps -f "label=clab-node-kind=vr-sros"`
    +        - label: clab-node-kind=vr-sros
    +      network:
    +        # networks returned by `docker network ls -f "name=mgmt"`
    +        name: mgmt
    +      # the value of label=gnmi-port exported by each container`
    +      port: "label=gnmi-port"
    +      config:
    +        username: admin
    +        password: secret2
    +        insecure: true
    +

    In the above example, gnmic docker loader connects to the docker daemon using the local unix socket address.

    It will discover 2 sets of containers matching 2 filters:

    • Filter1:

      • Containers with label clab-node-kind=srl.
      • Use network with label containerlab to connect to them.
      • The port number is the same for all containers and is set to 57400.
      • The config fields username: admin, password: secret1 and skip-verify: true will be applied to all the containers discovered by this filter.
    • Filter2:

      • Containers with labels clab-node-kind-ceos or clab-node-vr-sros
      • Use network with name=mgmt to connect to them. Note that Docker returns all networks with names containing mgmt
      • The port number is discovered from the label gnmi-port set on each container.
      • The config fields username: admin, password: secret2 and insecure: true will be applied to all the containers discovered by this filter.
    \ No newline at end of file diff --git a/user_guide/targets/target_discovery/file_discovery/index.html b/user_guide/targets/target_discovery/file_discovery/index.html new file mode 100644 index 00000000..8087149a --- /dev/null +++ b/user_guide/targets/target_discovery/file_discovery/index.html @@ -0,0 +1,84 @@ + File Discovery - gNMIc

    File Discovery

    gnmic is able to watch changes happening to a file that contains the gNMI targets configuration.

    The file can be located in the local file system or a remote one.

    In case of remote file, ftp, sftp, http(s) protocols are supported. The read timeout of remote files is set to half of the read interval

    Newly added targets are discovered and subscribed to. Deleted targets are moved from gNMIc's list and their subscriptions are terminated.

    Configuration#

    A file target loader can be configured in a couple of ways:

    • using the --targets-file flag:
    gnmic --targets-file ./targets-config.yaml subscribe
    +
    gnmic --targets-file sftp://user:pass@server.com/path/to/targets-file.yaml subscribe
    +
    • using the main configuration file:
    loader:
    +  type: file
    +  # path to the file
    +  path: ./targets-config.yaml
    +  # watch interval at which the file
    +  # is read again to determine if a target was added or deleted.
    +  interval: 30s
    +  # time to wait before the first file read
    +  start-delay: 0s
    +  # if true, registers fileLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +  # list of actions to run on target discovery
    +  on-add:
    +  # list of actions to run on target removal
    +  on-delete:
    +  # variable dict to pass to actions to be run
    +  vars:
    +  # path to variable file, the variables defined will be passed to the actions to be run
    +  # values in this file will be overwritten by the ones defined in `vars`
    +  vars-file:
    +

    The --targets-file flag takes precedence over the loader configuration section.

    The targets file can be either a YAML or a JSON file (identified by its extension json, yaml or yml), and follows the same format as the main configuration file targets section. See here

    Examples#

    Local File#

    loader:
    +  type: file
    +  # path to the file
    +  path: ./targets-config.yaml
    +  # watch interval at which the file
    +  # is read again to determine if a target was added or deleted.
    +  interval: 30s
    +  # if true, registers fileLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +

    Remote File#

    SFTP remote file

    loader:
    +  type: file
    +  # path to the file
    +  path: sftp://user:pass@server.com/path/to/targets-file.yaml
    +  # watch interval at which the file
    +  # is read again to determine if a target was added or deleted.
    +  interval: 30s
    +  # if true, registers fileLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +

    FTP remote file

    loader:
    +  type: file
    +  # path to the file
    +  path: ftp://user:pass@server.com/path/to/targets-file.yaml
    +  # watch interval at which the file
    +  # is read again to determine if a target was added or deleted.
    +  interval: 30s
    +  # if true, registers fileLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +

    HTTP remote file

    loader:
    +  type: file
    +  # path to the file
    +  path: http://user:pass@server.com/path/to/targets-file.yaml
    +  # watch interval at which the file
    +  # is read again to determine if a target was added or deleted.
    +  interval: 30s
    +  # if true, registers fileLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +

    Targets file format#

    10.10.10.10:
    +    username: admin
    +    insecure: true
    +10.10.10.11:
    +    username: admin
    +10.10.10.12:
    +10.10.10.13:
    +10.10.10.14:
    +
    {
    +    "10.10.10.10": {
    +        "username": "admin",
    +        "insecure": true
    +    },
    +     "10.10.10.11": {
    +        "username": "admin",
    +    },
    +     "10.10.10.12": {},
    +     "10.10.10.13": {},
    +     "10.10.10.14": {}
    +}
    +

    Just like the targets in the main configuration file, the missing configuration fields get filled with the global flags, the ENV variables first, the config file main section next and then the default values.

    \ No newline at end of file diff --git a/user_guide/targets/target_discovery/http_discovery/index.html b/user_guide/targets/target_discovery/http_discovery/index.html new file mode 100644 index 00000000..4d50f0bb --- /dev/null +++ b/user_guide/targets/target_discovery/http_discovery/index.html @@ -0,0 +1,48 @@ + HTTP Discovery - gNMIc

    HTTP Discovery

    The HTTP target loader can be used to query targets configurations from a remote HTTP server.

    It expects a well formatted application/json body and a code 200 response.

    It supports secure connections, basic authentication using a username and password and/or Oauth2 token based authentication.

    Configuration#

    loader:
    +  type: http
    +  # resource URL, must include the http(s) schema
    +  url: 
    +  # watch interval at which the HTTP endpoint is queried again
    +  # to determine if a target was added or deleted.
    +  interval: 60s
    +  # HTTP request timeout
    +  timeout: 50s
    +  # time to wait before the fist HTTP query
    +  start-delay: 0s
    +  # tls config
    +  tls:
    +    # string, path to the CA certificate file,
    +    # this will be used to verify the clients certificates when `skip-verify` is false
    +    ca-file:
    +    # string, client certificate file.
    +    cert-file:
    +    # string, client key file.
    +    key-file:
    +    # boolean, if true, the client will not verify the server
    +    # certificate against the available certificate chain.
    +    skip-verify: false
    +  # username to be used with basic authentication
    +  username:
    +  # password to be used with basic authentication
    +  password:
    +  # token to be used with Oauth2 token based authentication
    +  token:
    +  # auth scheme (default is `Bearer`)
    +  auth-scheme:
    +  # text template
    +  template:
    +  # path to a text template file
    +  template-file:
    +  # if true, registers httpLoader prometheus metrics with the provided
    +  # prometheus registry
    +  enable-metrics: false
    +  # list of actions to run on target discovery
    +  on-add:
    +  # list of actions to run on target removal
    +  on-delete:
    +  # variable dict to pass to actions to be run
    +  vars:
    +  # path to variable file, the variables defined will be passed to the actions to be run
    +  # values in this file will be overwritten by the ones defined in `vars`
    +  vars-file:
    +
    \ No newline at end of file diff --git a/user_guide/targets/targets/index.html b/user_guide/targets/targets/index.html new file mode 100644 index 00000000..cb40fc02 --- /dev/null +++ b/user_guide/targets/targets/index.html @@ -0,0 +1,137 @@ + Configuration - gNMIc

    Targets#

    Sometimes it is needed to perform an operation on multiple devices; be it getting the same leaf value from a given set of the network elements or setting a certain configuration element to some value.

    For cases like that gnmic offers support for multiple targets operations which a user can configure both via CLI flags as well as with the file-based configuration.

    CLI configuration#

    Specifying multiple targets in the CLI is as easy as repeating the --address flag.

    ❯ gnmic -a router1.lab.net:57400 \
    +        -a router2.lab.net:57400 \
    +        get --path /configure/system/name
    +

    File-based configuration#

    With the file-based configuration a user has two options to specify multiple targets:

    • using address option
    • using targets option

    address option#

    With address option the user must provide a list of addresses. In the YAML format that would look like that:

    address:
    +  - "router1.lab.net:57400"
    +  - "router2.lab.net:57400"
    +

    The limitation this approach has is that it is impossible to set different credentials for the targets, they will essentially share the credentials specified in a file or via flags.

    target option#

    With the targets option it is possible to set target specific options (such as credentials, subscriptions, TLS config, outputs), and thus this option is recommended to use:

    targets:
    +  router1.lab.net:
    +    timeout: 2s
    +    username: r1
    +    password: gnmi_pass
    +  router2.lab.net:57000:
    +    username: r2
    +    password: gnmi_pass
    +    tls-key: /path/file1
    +    tls-cert: /path/file2
    +

    The target address is defined as the key under the targets section of the configuration file. The default port (57400) can be omitted as demonstrated with router1.lab.net target address. Have a look at the file-based targets configuration example to get a glimpse of what it is capable of.

    The target inherits the globally defined options if the matching options are not set on a target level. For example, if a target doesn't have a username defined, it will use the username value set on a global level.

    secure/insecure connections#

    gnmic supports both secure and insecure gRPC connections to the target.

    insecure connection#

    Using the --insecure flag it is possible to establish an insecure gRPC connection to the target.

    gnmic -a router1:57400 \
    +      --insecure \
    +      get --path /configure/system/name
    +
    secure connection#
    • A one way secure connection without target certificate verification can be established using the --skip-verify flag.
    gnmic -a router1:57400 \
    +      --skip-verify \
    +      get --path /configure/system/name
    +
    • Adding target certificate verification can be done using the --tls-ca flag.
    gnmic -a router1:57400 \
    +      --tls-ca /path/to/ca/file \
    +      get --path /configure/system/name
    +
    • A two way secure connection can be established using the --tls-cert --tls-key flags.
    gnmic -a router1:57400 \
    +      --tls-cert /path/to/certificate/file \
    +      --tls-key /path/to/certificate/file \
    +      get --path /configure/system/name
    +
    • It is also possible to control the negotiated TLS version using the --tls-min-version, --tls-max-version and --tls-version (preferred TLS version) flags.

    target configuration options#

    Target supported options:

    targets:
    +  # target name or an address (IP or DNS name).
    +  # if an address is set it can include a port number or not,
    +  # if a port is not included, the default gRPC port will be added.
    +  target_key:
    +    # target name, will default to the target_key if not specified
    +    name: target_key
    +    # target address, if missing the target_key is used as an address.
    +    # supports comma separated addresses.
    +    # if any of the addresses is missing a port, the default gRPC port will be added.
    +    # if multiple addresses are set, all of them will be tried simultaneously,
    +    # the first established gRPC connection will be used, the other attempts will be canceled.
    +    address:
    +    # target username
    +    username:
    +    # target password
    +    password:
    +    # authentication token, 
    +    # applied only in the case of a secure gRPC connection.
    +    token: 
    +    # target RPC timeout
    +    timeout:
    +    # establish an insecure connection
    +    insecure:
    +    # path to tls ca file
    +    tls-ca:
    +    # path to tls certificate
    +    tls-cert:
    +    # path to tls key
    +    tls-key:
    +    # max tls version to use during negotiation
    +    tls-max-version:
    +    # min tls version to use during negotiation
    +    tls-min-version:
    +    # preferred tls version to use during negotiation
    +    tls-version:
    +    # enable logging of a pre-master TLS secret
    +    log-tls-secret:
    +    # do not verify the target certificate when using tls
    +    skip-verify:
    +    # server name used to verify the hostname on the returned 
    +    # certificates unless skip-verify is true.    
    +    tls-server-name:
    +    # list of subscription names to establish for this target.
    +    # if empty it defaults to all subscriptions defined under
    +    # the main level `subscriptions` field
    +    subscriptions:
    +    # string, case insensitive, defines the gNMI encoding to be used for 
    +    # the subscriptions to be established for this target.
    +    # This encoding value applies only if the subscription configuration does
    +    # NOT explicitly define an encoding.
    +    encoding:
    +    # list of output names to which the gnmi data will be written.
    +    # if empty if defaults to all outputs defined under
    +    # the main level `outputs` field
    +    outputs:
    +    # number of subscribe responses to keep in buffer before writing
    +    # the target outputs
    +    buffer-size:
    +    # target retry period
    +    retry:
    +    # list of tags, relevant when clustering is enabled.
    +    tags:
    +    # a mapping of static tags to add to all events from this target.
    +    # each key/value pair in this mapping will be added to metadata
    +    # on all events
    +    event-tags:
    +    # list of proto file names to decode protoBytes values
    +    proto-files:
    +    # list of directories to look for the proto files
    +    proto-dirs:
    +    # enable grpc gzip compression
    +    gzip: 
    +    # proxy type and address, only SOCKS5 is supported currently
    +    # example: socks5://<address>:<port>
    +    proxy:
    +

    Example#

    Whatever configuration option you choose, the multi-targeted operations will uniformly work across the commands that support them.

    Consider the get command acting on two routers getting their names:

    ❯ gnmic -a router1.lab.net:57400 \
    +        -a router2.lab.net:57400 \
    +        get --path /configure/system/name
    +
    +[router1.lab.net:57400] {
    +[router1.lab.net:57400]   "source": "router1.lab.net:57400",
    +[router1.lab.net:57400]   "timestamp": 1593009759618786781,
    +[router1.lab.net:57400]   "time": "2020-06-24T16:42:39.618786781+02:00",
    +[router1.lab.net:57400]   "updates": [
    +[router1.lab.net:57400]     {
    +[router1.lab.net:57400]       "Path": "configure/system/name",
    +[router1.lab.net:57400]       "values": {
    +[router1.lab.net:57400]         "configure/system/name": "gnmic_r1"
    +[router1.lab.net:57400]       }
    +[router1.lab.net:57400]     }
    +[router1.lab.net:57400]   ]
    +[router1.lab.net:57400] }
    +
    +[router2.lab.net:57400] {
    +[router2.lab.net:57400]   "source": "router2.lab.net:57400",
    +[router2.lab.net:57400]   "timestamp": 1593009759748265232,
    +[router2.lab.net:57400]   "time": "2020-06-24T16:42:39.748265232+02:00",
    +[router2.lab.net:57400]   "updates": [
    +[router2.lab.net:57400]     {
    +[router2.lab.net:57400]       "Path": "configure/system/name",
    +[router2.lab.net:57400]       "values": {
    +[router2.lab.net:57400]         "configure/system/name": "gnmic_r2"
    +[router2.lab.net:57400]       }
    +[router2.lab.net:57400]     }
    +[router2.lab.net:57400]   ]
    +[router2.lab.net:57400] }
    +

    Notice how in the output the different gNMI targets are prefixed with the target address to make the output easy to read. If those prefixes are not needed, you can make them disappear with --no-prefix global flag.

    \ No newline at end of file diff --git a/user_guide/targets/targets_session_sec/index.html b/user_guide/targets/targets_session_sec/index.html new file mode 100644 index 00000000..09475cf6 --- /dev/null +++ b/user_guide/targets/targets_session_sec/index.html @@ -0,0 +1,72 @@ + Session Security - gNMIc

    Targets session security#

    In line with the guidelines detailed in the gNMI Specification, it is mandatory to establish an encrypted TLS session between the client and the server. This measure is essential to ensure secure communication within the gNMI protocol.

    The session between the client and server MUST be encrypted using TLS - 
    +and a target or client MUST NOT fall back to unencrypted sessions. 
    +The target and client SHOULD implement TLS >= 1.2.
    +

    gNMIc provides the ability to tailor and modify the TLS session parameters of the gNMI client according to your specific requirements.

    TLS session types#

    When it comes to establishing a TLS session using gNMIc, various options are available to suit different use cases and environmental requirements. Whether it's a one-way TLS session, a session without certificate validation, or a mutual TLS (mTLS) session, each type caters to specific needs. The selection largely depends on the user's scenario and the degree of security and validation necessary. The upcoming sections will detail each of these session types, offering guidelines to aid in choosing the most appropriate for your specific requirements.

    Simple TLS session w/o server certificate validation#

    For scenarios requiring a simple TLS session without server certificate validation, such as in certain testing or development environments, you can use gNMIc's --skip-verify flag or the skip-verify attribute. This mode bypasses the typical certificate verification process and establishes a secure connection without validating the server's identity. Please exercise caution when using this feature, as it may expose the connection to potential security vulnerabilities. It is recommended primarily for non-production environments or controlled testing situations.

    gnmic -a router1 --skip-verify \
    +             get --path /interface/oper-state
    +
    targets:
    +  router1:
    +    address: router1
    +    skip-verify: true
    +

    Simple TLS session with server certificate validation#

    When establishing a simple TLS session with server certificate validation for enhanced security, gNMIc offers the --tls-ca flag or the tls-ca attribute. These options allow you to point to a Certificate Authority (CA) certificate file. By doing so, the session not only ensures encrypted communication but also verifies the server's identity through its certificate. This validation process greatly enhances the security of the connection, ensuring the client is communicating with the intended server. It's an advisable setting for production environments where data security and integrity are crucial.

    gnmic -a router1 --tls-ca ./ca.pem \
    +                get --path /interface/oper-state
    +
    targets:
    +  router1:
    +    address: router1
    +    tls-ca: ./ca.pem
    +

    Simple TLS session with server certificate validation and server name override#

    There are circumstances where the server's identity, as indicated by its certificate, doesn't match its expected hostname. For such scenarios, gNMIc enables the initiation of a simple TLS session with both server certificate validation and server name override. This functionality can be utilized by employing the --tls-server-name flag or the tls-server-name attribute.

    By overriding the server name in the TLS session, users can specify a different hostname that matches the server's certificate, even if it's not the actual hostname of the server. This allows for successful validation and secure communication even in cases of server name discrepancies due to reasons like load balancing, proxying, etc...

    This feature is particularly beneficial in complex network scenarios or during migrations, where server names might not yet align with their certificates. By ensuring both secure encrypted communication and flexible server name accommodation, it adds an extra layer of adaptability for secure communication, particularly in dynamic or complex network environments.

    gnmic -a router1 --tls-ca ./ca.pem \
    +                --tls-server-name server1 \
    +                get --path /interface/oper-state
    +
    targets:
    +  router1:
    +    address: router1
    +    tls-ca: ./ca.pem
    +    tls-server-name: server1
    +

    Mutual TLS (mTLS) session#

    For heightened security scenarios, gNMIc supports mutual TLS (mTLS) sessions. mTLS not only verifies the server's identity to the client, but also the client's identity to the server. This reciprocal verification is achieved using the --tls-cert and --tls-key flags, or the tls-cert and tls-key attributes. These options allow the user to specify a client certificate and client key, respectively.

    By providing a client certificate (--tls-cert or tls-cert attribute) and a client key (--tls-key or tls-key attribute), gNMIc allows the server to confirm the identity of the client, ensuring that the client is legitimate and authorized to access the server resources.

    Mutual TLS is particularly beneficial in use cases where both ends of a connection need to confirm the other's identity, providing a significantly higher level of trust and security. It reduces the risk of man-in-the-middle attacks and is especially valuable in environments where sensitive data is transmitted or strict access control is required.

    gnmic -a router1 --tls-ca ./ca.pem \
    +                --tls-cert ./router1.cert \
    +                --tls-key ./router.key \
    +                get --path /interface/oper-state
    +
    targets:
    +  router1:
    +    address: router1
    +    tls-ca: ./ca.pem
    +    tls-cert: ./router1.cert
    +    tls-key: ./router1.key
    +

    mTLS session with server name override#

    gnmic -a router1 --tls-ca ./ca.pem \
    +                --tls-server-name server1 \
    +                --tls-cert ./router1.cert \
    +                --tls-key ./router.key \
    +                get --path /interface/oper-state
    +
    targets:
    +  router1:
    +    address: router1
    +    tls-ca: ./ca.pem
    +    tls-server-name: server1
    +    tls-cert: ./router1.cert
    +    tls-key: ./router1.key
    +

    Configuring the client's TLS version#

    By default, gNMIc establishes a TLS session using the Golang's default TLS version (1.2), minimum version (1.2), and maximum version (1.3).

    However, there might be scenarios where users need to control the TLS session negotiation to either test the server behavior or force the session into a specific version. To accommodate these needs, gNMIc provides flexibility by allowing users to explicitly set the TLS version.

    Users can manipulate the negotiated TLS version using the flags (or target attributes) --tls-version, --tls-min-version, and --tls-max-version. These flags give control over the TLS session parameters, facilitating testing and customization of the communication session according to specific requirements.

    Example: Forcing the client and server to use TLS1.3

    gnmic -a router1 --tls-ca ./ca.pem \
    +                --tls-cert ./router1.cert \
    +                --tls-key ./router.key \
    +                --tls-version 1.3 \
    +                --tls-min-version 1.3 \
    +                get --path /interface/oper-state
    +
    targets:
    +  router1:
    +    address: router1
    +    tls-ca: ./ca.pem
    +    tls-cert: ./router1.cert
    +    tls-key: ./router1.key
    +    tls-version: 1.3
    +    tls-min-version: 1.3
    +

    Decrypting gNMI traffic using Wireshark#

    To facilitate advanced debugging or network analysis, gNMIc allows for the decryption of gNMI TLS traffic using the popular network protocol analyzer, Wireshark. The --log-tls-secret flag is instrumental in achieving this, as it stores the session pre-master secret, which can subsequently be used to decrypt TLS traffic.

    When --log-tls-secret is used, the session's pre-master secret will be stored in a file named <target-name>.tlssecret.log. This secret enables Wireshark to decrypt the otherwise secure and encrypted TLS traffic between the client and the server.

    Decryption of TLS traffic is particularly useful for network troubleshooting, performance optimization, or security audits. It allows network administrators or developers to deeply inspect packet data, diagnose network issues, and better understand data flows. However, this practice should be used carefully and ethically, given the sensitive nature of decrypted traffic, especially in production environments.

    gnmic -a router1 --tls-ca ./ca.pem \
    +                --log-tls-secret \
    +                --tls-cert ./router1.cert \
    +                --tls-key ./router.key \
    +                get --path /interface/oper-state
    +
    targets:
    +  router1:
    +    address: router1
    +    tls-ca: ./ca.pem
    +    log-tls-secret: true
    +    tls-cert: ./router1.cert
    +    tls-key: ./router1.key
    +
    \ No newline at end of file diff --git a/user_guide/tunnel_server/index.html b/user_guide/tunnel_server/index.html new file mode 100644 index 00000000..fe768469 --- /dev/null +++ b/user_guide/tunnel_server/index.html @@ -0,0 +1,158 @@ + Tunnel Server - gNMIc

    Tunnel Server#

    Introduction#

    gNMIc supports gNMI Dial-out as defined by openconfig/grpctunnel.

    gNMIc embeds a tunnel server to which the gNMI targets register. Once registered, gNMIc triggers the request gNMI RPC towards the target via the established tunnel.

    This use case is described here

    Server operation#

    When running a Subscribe RPC using gNMIc with the flag --use-tunnel-server,gNMIc starts by running the Tunnel server as defined under tunnel-server.

    The next steps depend on the type of RPC (Unary/Stream) and/or Subscribe Mode (poll/once/stream)

    Unary RPCs#

    gNMIc waits for tunnel-server.target-wait-time for targets to register with the tunnel server, after which it requests a new session from the server for the specified target(s) and runs the RPC through the newly established tunnel.

    Note that if no target is specified, the RPC runs for all registered targets.

    $ cat tunnel_server_config.yaml
    +insecure: true
    +log: true
    +username: admin
    +password: NokiaSrl1!
    +
    +tunnel-server:
    +  address: ":57401"
    +
    $ gnmic --config tunnel_server_config.yaml \
    +      --use-tunnel-server \
    +      get \
    +      --path /configure/system/name
    +2022/03/09 10:12:34.729037 [gnmic] version=dev, commit=none, date=unknown, gitURL=, docs=https://gnmic.openconfig.net
    +2022/03/09 10:12:34.729063 [gnmic] using config file "tunnel_server_config.yaml"
    +2022/03/09 10:12:34.730472 [gnmic] waiting for targets to register with the tunnel server...
    +2022/03/09 10:12:36.435521 [gnmic] tunnel server discovered target {ID:sr1 Type:GNMI_GNOI}
    +2022/03/09 10:12:36.436332 [gnmic] tunnel server discovered target {ID:sr2 Type:GNMI_GNOI}
    +2022/03/09 10:12:36.731125 [gnmic] adding target {"name":"sr1","address":"sr1","username":"admin","password":"NokiaSrl1!","timeout":10000000000,"insecure":true,"skip-verify":false,"subscriptions":["sub1"],"retry-timer":10000000000,"log-tls-secret":false,"gzip":false,"token":""}
    +2022/03/09 10:12:36.731158 [gnmic] adding target {"name":"sr2","address":"sr2","username":"admin","password":"NokiaSrl1!","timeout":10000000000,"insecure":true,"skip-verify":false,"subscriptions":["sub1"],"retry-timer":10000000000,"log-tls-secret":false,"gzip":false,"token":""}
    +2022/03/09 10:12:36.731651 [gnmic] sending gNMI GetRequest: prefix='<nil>', path='[elem:{name:"configure"}  elem:{name:"system"}  elem:{name:"name"}]', type='ALL', encoding='JSON', models='[]', extension='[]' to sr1
    +2022/03/09 10:12:36.731742 [gnmic] sending gNMI GetRequest: prefix='<nil>', path='[elem:{name:"configure"}  elem:{name:"system"}  elem:{name:"name"}]', type='ALL', encoding='JSON', models='[]', extension='[]' to sr2
    +2022/03/09 10:12:36.732337 [gnmic] dialing tunnel connection for tunnel target "sr2"
    +2022/03/09 10:12:36.732572 [gnmic] dialing tunnel connection for tunnel target "sr1"
    +[sr1] [
    +[sr1]   {
    +[sr1]     "source": "sr1",
    +[sr1]     "timestamp": 1646849561604621769,
    +[sr1]     "time": "2022-03-09T10:12:41.604621769-08:00",
    +[sr1]     "updates": [
    +[sr1]       {
    +[sr1]         "Path": "configure/system/name",
    +[sr1]         "values": {
    +[sr1]           "configure/system/name": "sr1"
    +[sr1]         }
    +[sr1]       }
    +[sr1]     ]
    +[sr1]   }
    +[sr1] ]
    +[sr2] [
    +[sr2]   {
    +[sr2]     "source": "sr2",
    +[sr2]     "timestamp": 1646849562004804732,
    +[sr2]     "time": "2022-03-09T10:12:42.004804732-08:00",
    +[sr2]     "updates": [
    +[sr2]       {
    +[sr2]         "Path": "configure/system/name",
    +[sr2]         "values": {
    +[sr2]           "configure/system/name": "sr2"
    +[sr2]         }
    +[sr2]       }
    +[sr2]     ]
    +[sr2]   }
    +[sr2] ]
    +

    Subscribe RPC#

    Poll and Once subscription#

    When a Poll or Once subscription are requested, gNMIc behaves the same way as for a unary RPC, i.e waits for targets to register then runs the RPC.

    Stream subscription#

    In the case of a stream subscription, gNMIc triggers the Subscribe RPC as soon as a target registers. Similarly, a stream subscription will be stopped when a target deregisters from the tunnel server.

    Configuration#

    tunnel-server:
    +  # the address the tunnel server will listen to
    +  address:
    +  # tls config
    +  tls:
    +    # string, path to the CA certificate file,
    +    # this certificate is used to verify the clients certificates.
    +    ca-file:
    +    # string, server certificate file.
    +    cert-file:
    +    # string, server key file.
    +    key-file:
    +    # string, one of `"", "request", "require", "verify-if-given", or "require-verify" 
    +    #  - request:         The server requests a certificate from the client but does not 
    +    #                     require the client to send a certificate. 
    +    #                     If the client sends a certificate, it is not required to be valid.
    +    #  - require:         The server requires the client to send a certificate and does not 
    +    #                     fail if the client certificate is not valid.
    +    #  - verify-if-given: The server requests a certificate, 
    +    #                     does not fail if no certificate is sent. 
    +    #                     If a certificate is sent it is required to be valid.
    +    #  - require-verify:  The server requires the client to send a valid certificate.
    +    #
    +    # if no ca-file is present, `client-auth` defaults to ""`
    +    # if a ca-file is set, `client-auth` defaults to "require-verify"`
    +    client-auth: ""
    +  # the wait time before triggering unary RPCs or subscribe poll/once
    +  target-wait-time: 2s
    +  # enables the collection of Prometheus gRPC server metrics
    +  enable-metrics: false
    +  # enable additional debug logs
    +  debug: false
    +

    Combining Tunnel server with a gNMI server#

    It is possible to start gNMIc with both a gnmi-server and tunnel-server enabled.

    This mode allows to run gNMI RPCs against gNMIc's gNMI server, they will routed to the relevant targets (--target flag) or to all known target (i.e registered targets)

    The configuration file would look like:

    insecure: true
    +username: admin
    +password: NokiaSrl1!
    +
    +subscriptions:
    +  sub1:
    +    paths:
    +      - /state/port
    +    sample-interface: 10s
    +
    +gnmi-server:
    +  address: :57400
    +
    +tunnel-server:
    +  address: :57401
    +  targets:
    +    - id: .*
    +      type: GNMI_GNOI
    +      config:
    +        subscriptions:
    +          - sub1
    +

    Running a Get RPC towards all registered targets

    $ gnmic -a localhost:57400 --insecure get \
    +        --path /configure/system/name
    +[
    +  {
    +    "source": "localhost",
    +    "timestamp": 1646850987401608313,
    +    "time": "2022-03-09T10:36:27.401608313-08:00",
    +    "target": "sr2",
    +    "updates": [
    +      {
    +        "Path": "configure/system/name",
    +        "values": {
    +          "configure/system/name": "sr2"
    +        }
    +      }
    +    ]
    +  },
    +  {
    +    "source": "localhost",
    +    "timestamp": 1646850987205206394,
    +    "time": "2022-03-09T10:36:27.205206394-08:00",
    +    "target": "sr1",
    +    "updates": [
    +      {
    +        "Path": "configure/system/name",
    +        "values": {
    +          "configure/system/name": "sr1"
    +        }
    +      }
    +    ]
    +  }
    +]
    +

    Running a Get RPC towards a single target

    $ gnmic -a localhost:57400 --insecure \
    +        --target sr1 \
    +        get --path /configure/system/name
    +[
    +  {
    +    "source": "localhost",
    +    "timestamp": 1646851044004381267,
    +    "time": "2022-03-09T10:37:24.004381267-08:00",
    +    "target": "sr1",
    +    "updates": [
    +      {
    +        "Path": "configure/system/name",
    +        "values": {
    +          "configure/system/name": "sr1"
    +        }
    +      }
    +    ]
    +  }
    +]
    +

    For detailed configuration of the gnmi-server check this page

    \ No newline at end of file