From 12e25206f37c2b30b1d47b96f573137d7fc70d6b Mon Sep 17 00:00:00 2001 From: Pawan Singh Pal Date: Thu, 29 Nov 2018 17:25:10 +0530 Subject: [PATCH] Add yaml-editor to utils for editing yaml-files on the go Add setup changes to install yaml-editor with spyglass --- MANIFEST.in | 2 + setup.py | 1 + spyglass/data_extractor/custom_exceptions.py | 1 + .../plugins/tugboat/excel_parser.py | 3 +- spyglass/parser/engine.py | 32 +++- spyglass/spyglass.py | 9 +- spyglass/utils/editor/__init__.py | 0 spyglass/utils/editor/editor.py | 157 ++++++++++++++++++ spyglass/utils/editor/static/app.js | 92 ++++++++++ spyglass/utils/editor/static/favicon.ico | Bin 0 -> 1150 bytes .../editor/static/jquery-linedtextarea.css | 68 ++++++++ .../editor/static/jquery-linedtextarea.js | 126 ++++++++++++++ spyglass/utils/editor/static/js-yaml.min.js | 1 + spyglass/utils/editor/templates/yaml.html | 38 +++++ 14 files changed, 526 insertions(+), 4 deletions(-) create mode 100644 MANIFEST.in create mode 100644 spyglass/utils/editor/__init__.py create mode 100644 spyglass/utils/editor/editor.py create mode 100644 spyglass/utils/editor/static/app.js create mode 100644 spyglass/utils/editor/static/favicon.ico create mode 100755 spyglass/utils/editor/static/jquery-linedtextarea.css create mode 100755 spyglass/utils/editor/static/jquery-linedtextarea.js create mode 100644 spyglass/utils/editor/static/js-yaml.min.js create mode 100644 spyglass/utils/editor/templates/yaml.html diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d90a4e1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include spyglass/utils/editor/static * +recursive-include spyglass/utils/editor/templates * diff --git a/setup.py b/setup.py index e21ec83..9d5ccdc 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ setup( entry_points={ 'console_scripts': [ 'spyglass=spyglass.spyglass:main', + 'yaml-editor=spyglass.utils.editor.editor:main', ], 'data_extractor_plugins': ['formation=spyglass.data_extractor.plugins.formation:FormationPlugin', diff --git a/spyglass/data_extractor/custom_exceptions.py b/spyglass/data_extractor/custom_exceptions.py index f43a69f..40f67d6 100644 --- a/spyglass/data_extractor/custom_exceptions.py +++ b/spyglass/data_extractor/custom_exceptions.py @@ -35,6 +35,7 @@ class NoSpecMatched(BaseError): self.specs)) sys.exit(1) + class MissingAttributeError(BaseError): pass diff --git a/spyglass/data_extractor/plugins/tugboat/excel_parser.py b/spyglass/data_extractor/plugins/tugboat/excel_parser.py index 83e4f85..f7b78c1 100644 --- a/spyglass/data_extractor/plugins/tugboat/excel_parser.py +++ b/spyglass/data_extractor/plugins/tugboat/excel_parser.py @@ -19,8 +19,7 @@ import sys import yaml from openpyxl import load_workbook from openpyxl import Workbook -from spyglass.data_extractor.custom_exceptions import - NoSpecMatched, ) +from spyglass.data_extractor.custom_exceptions import NoSpecMatched # from spyglass.data_extractor.custom_exceptions LOG = logging.getLogger(__name__) diff --git a/spyglass/parser/engine.py b/spyglass/parser/engine.py index a330d5f..27b2b05 100644 --- a/spyglass/parser/engine.py +++ b/spyglass/parser/engine.py @@ -15,9 +15,12 @@ import copy import json import logging +import os import pkg_resources import pprint import sys +import tempfile + import jsonschema import netaddr import yaml @@ -81,6 +84,17 @@ class ProcessDataSource(): LOG.debug("Genesis Node Details:\n{}".format( pprint.pformat(self.genesis_node))) + def _get_genesis_node_ip(self): + """ Returns the genesis node ip """ + ip = '0.0.0.0' + LOG.info("Getting Genesis Node IP") + if not self.genesis_node: + self._get_genesis_node_details() + ips = self.genesis_node.get('ip', '') + if ips: + ip = ips.get('oam', '0.0.0.0') + return ip + def _validate_intermediary_data(self, data): """ Validates the intermediary data before generating manifests. @@ -347,11 +361,27 @@ class ProcessDataSource(): f.write(yaml_file) f.close() - def generate_intermediary_yaml(self): + def generate_intermediary_yaml(self, edit_intermediary=False): """ Generating intermediary yaml """ LOG.info("Start: Generate Intermediary") self._apply_design_rules() self._get_genesis_node_details() + # This will validate the extracted data from different sources. self._validate_intermediary_data(self.data) + if edit_intermediary: + self.edit_intermediary_yaml() + # This will check if user edited changes are in order. + self._validate_intermediary_data(self.data) self.intermediary_yaml = self.data return self.intermediary_yaml + + def edit_intermediary_yaml(self): + """ Edit generated data using on browser """ + LOG.info( + "edit_intermediary_yaml: Invoking web server for yaml editing") + with tempfile.NamedTemporaryFile(mode='r+') as file_obj: + yaml.safe_dump(self.data, file_obj, default_flow_style=False) + host = self._get_genesis_node_ip() + os.system('yaml-editor -f {0} -h {1}'.format(file_obj.name, host)) + file_obj.seek(0) + self.data = yaml.safe_load(file_obj) diff --git a/spyglass/spyglass.py b/spyglass/spyglass.py index 0ff92b3..0927eaf 100644 --- a/spyglass/spyglass.py +++ b/spyglass/spyglass.py @@ -57,6 +57,11 @@ LOG = logging.getLogger('spyglass') '-idir', type=click.Path(exists=True), help='The path where intermediary file needs to be generated') +@click.option( + '--edit_intermediary/--no_edit_intermediary', + '-e/-nedit', + default=True, + help='Flag to let user edit intermediary') @click.option( '--generate_manifests', '-m', @@ -96,6 +101,7 @@ def main(*args, **kwargs): # Extract user provided inputs generate_intermediary = kwargs['generate_intermediary'] intermediary_dir = kwargs['intermediary_dir'] + edit_intermediary = kwargs['edit_intermediary'] generate_manifests = kwargs['generate_manifests'] manifest_dir = kwargs['manifest_dir'] intermediary = kwargs['intermediary'] @@ -176,7 +182,8 @@ def main(*args, **kwargs): data_extractor.site_data) LOG.info("Generate intermediary yaml") - intermediary_yaml = process_input_ob.generate_intermediary_yaml() + intermediary_yaml = process_input_ob.generate_intermediary_yaml( + edit_intermediary) else: LOG.info("Loading intermediary from user provided input") with open(intermediary, 'r') as intermediary_file: diff --git a/spyglass/utils/editor/__init__.py b/spyglass/utils/editor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/spyglass/utils/editor/editor.py b/spyglass/utils/editor/editor.py new file mode 100644 index 0000000..689b054 --- /dev/null +++ b/spyglass/utils/editor/editor.py @@ -0,0 +1,157 @@ +# Copyright 2018 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import json +import logging +import os +import sys + +import click +import yaml + +from flask import Flask, request, render_template, send_from_directory +from flask_bootstrap import Bootstrap + + +app_path = os.path.dirname(os.path.abspath(__file__)) +app = Flask('Yaml Editor!', + template_folder=os.path.join(app_path, 'templates'), + static_folder=os.path.join(app_path, 'static')) +Bootstrap(app) +logging.getLogger('werkzeug').setLevel(logging.ERROR) +LOG = app.logger + + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(app.static_folder, 'favicon.ico') + + +@app.route('/', methods=['GET', 'POST']) +def index(): + """Renders index page to edit provided yaml file.""" + LOG.info('Rendering yaml file for editing') + with open(app.config['YAML_FILE']) as file_obj: + data = yaml.safe_load(file_obj) + return render_template('yaml.html', + data=json.dumps(data), + change_str=app.config['STRING_TO_CHANGE']) + + +@app.route('/save', methods=['POST']) +def save(): + """Save current progress on file.""" + LOG.info('Saving edited inputs from user to yaml file') + out = request.json.get('yaml_data') + with open(app.config['YAML_FILE'], 'w') as file_obj: + yaml.safe_dump(out, file_obj, default_flow_style=False) + return "Data saved successfully!" + + +@app.route('/saveExit', methods=['POST']) +def save_exit(): + """Save current progress on file and shuts down the server.""" + LOG.info('Saving edited inputs from user to yaml file and shutting' + ' down server') + out = request.json.get('yaml_data') + with open(app.config['YAML_FILE'], 'w') as file_obj: + yaml.safe_dump(out, file_obj, default_flow_style=False) + func = request.environ.get('werkzeug.server.shutdown') + if func: + func() + return "Saved successfully, Shutting down app! You may close the tab!" + + +@app.errorhandler(404) +def page_not_found(e): + """Serves 404 error.""" + LOG.info('User tried to access unavailable page.') + return '

404: Page not Found!

' + + +def run(*args, **kwargs): + """Starts the server.""" + LOG.info('Initiating web server for yaml editing') + port = kwargs.get('port', None) + if not port: + port = 8161 + app.run(host='0.0.0.0', port=port, debug=False) + + +@click.command() +@click.option( + '--file', + '-f', + required=True, + type=click.File(), + multiple=False, + help="Path with file name to the intermediary yaml file." +) +@click.option( + '--host', + '-h', + default='0.0.0.0', + type=click.STRING, + multiple=False, + help="Optional port parameter to run Flask on." +) +@click.option( + '--port', + '-p', + default=8161, + type=click.INT, + multiple=False, + help="Optional port parameter to run Flask on." +) +@click.option( + '--string', + '-s', + default='#CHANGE_ME', + type=click.STRING, + multiple=False, + help="Text which is required to be changed on yaml file." +) +def main(*args, **kwargs): + LOG.setLevel(logging.INFO) + LOG.info('Initiating yaml-editor') + try: + yaml.safe_load(kwargs['file']) + except yaml.YAMLError as e: + LOG.error('EXITTING - Please provide a valid yaml file.') + if hasattr(e, 'problem_mark'): + mark = e.problem_mark + LOG.error("Error position: ({0}:{1})".format( + mark.line + 1, mark.column + 1)) + sys.exit(2) + except Exception: + LOG.error('EXITTING - Please provide a valid yaml file.') + sys.exit(2) + LOG.info(""" + +############################################################################## + +Please go to http://{0}:{1}/ to edit your yaml file. + +############################################################################## + + """.format(kwargs['host'], kwargs['port'])) + app.config['YAML_FILE'] = kwargs['file'].name + app.config['STRING_TO_CHANGE'] = kwargs['string'] + run(*args, **kwargs) + + +if __name__ == '__main__': + """Invoked when used as a script.""" + main() diff --git a/spyglass/utils/editor/static/app.js b/spyglass/utils/editor/static/app.js new file mode 100644 index 0000000..b0e834a --- /dev/null +++ b/spyglass/utils/editor/static/app.js @@ -0,0 +1,92 @@ +// Copyright 2018 AT&T Intellectual Property. All other rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// This file includes all the frond-end functionality being used for the +// yaml editor application. + + +/** + * Calls /save URL to save edit progress. + * @param {String} data Stringified JSON data. + */ +function save(data) { + $.ajax({ + type: 'POST', + url: '/save', + data: data, + success: function(res) { + setTimeout(function() { alert(res); }, 3); + }, + contentType: 'application/json;charset=UTF-8' + }); +} + +/** + * Calls /saveExit URL to save edit progress and shut down web server. + * @param {String} data Stringified JSON data. + */ +function saveAndExit(data) { + $.ajax({ + type: 'POST', + url: '/saveExit', + data: data, + success: function(res) { + setTimeout(function() { alert(res); }, 3); + }, + contentType: 'application/json;charset=UTF-8' + }); +} + +/** + * Collects and validates data from textarea. + * @returns {String} Stringified JSON data. + */ +function getSimpleData() { + var data = $("#yaml_data").val(); + try { + var index = data.indexOf(changeStr) + if (index != -1) { + var lineNum = data.substring(0, index).split('\n').length; + alert('Please change value on line '+ lineNum + '!') + return null + } + data = jsyaml.load(data) + } + catch(err) { + alert(err) + return null + } + return JSON.stringify({yaml_data : data}) +} + +/** + * Function to save edit progress. + */ +function saveSimple() { + var data = getSimpleData() + if (data) { + save(data) + } +} + +/** + * Function to save edit progress and shut down web server. + */ +function saveExitSimple() { + var data = getSimpleData() + if (data) { + saveAndExit(data) + } +} diff --git a/spyglass/utils/editor/static/favicon.ico b/spyglass/utils/editor/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d3dca27a02a14c2554c5ae693af87100e1d5359f GIT binary patch literal 1150 zcmb7?TS!xJ9LN9ZqM#`0A*7djtf!!tpeM1H=qaM7vLJ~POGcTOAkBr9SjI}`=G?_i zk8bD8UEEEVx4Frh#&pwkn_8x(cGJA9h>CCjbEMG1C=Q?h@BjZDKEMB!kWKVUNg=e| zN3ukO>?VZlr_dD5tmbJTg#PpGSVb8BM^kgLoYb7ZzFC!{xuLr1SyGFc22FO!o_{H! zIa@-c>0%LEAQc;n4U5YB2`KYo3BCsJYqIYX`HAa9m07x7d8Q~;d3p^Q#i^>ZC8;_| zt=L%OT=dm)u-tP&ng0^1t1Sz>uf0-1PSR~MlG?H+jc44fYmfhgj^oT-X4K#gCz$9m z>Y94LchefiqQ-gE742kP!XUV-{(ccF_??13{NgO<%)qU7!Nat{=js#IF&Lc%cUPZl zud;6Fk4(&?FAzh}Ka4;}1kZZM5FVOF|G% zO?(vQ|BLTx^ddI7fU&oq){wv$y^Ha8pAn{Y*-~9@b7j-I`T6s+FkU#Uy=XB9&}t2? z#}*XM#wz9d>+@x4jW?C+eD<+LIDdz;9}agI4%ctcoYmm}rYn$N;_ey9g~P|z_y" + lineNo + ""); + else + codeLines.append("
" + lineNo + "
"); + + lineNo++; + } + return lineNo; + }; + + + /* + * Iterate through each of the elements are to be applied to + */ + return this.each(function() { + var lineNo = 1; + var textarea = $(this); + + /* Turn off the wrapping of as we don't want to screw up the line numbers */ + textarea.attr("wrap", "off"); + textarea.css({resize:'none'}); + var originalTextAreaWidth = textarea.outerWidth(); + + /* Wrap the text area in the elements we need */ + textarea.wrap("
"); + var linedTextAreaDiv = textarea.parent().wrap("
"); + var linedWrapDiv = linedTextAreaDiv.parent(); + + linedWrapDiv.prepend("
"); + + var linesDiv = linedWrapDiv.find(".lines"); + linesDiv.height( textarea.height() + 6 ); + + + /* Draw the number bar; filling it out where necessary */ + linesDiv.append( "
" ); + var codeLinesDiv = linesDiv.find(".codelines"); + lineNo = fillOutLines( codeLinesDiv, linesDiv.height(), 1 ); + + /* Move the textarea to the selected line */ + if ( opts.selectedLine != -1 && !isNaN(opts.selectedLine) ){ + var fontSize = parseInt( textarea.height() / (lineNo-2) ); + var position = parseInt( fontSize * opts.selectedLine ) - (textarea.height()/2); + textarea[0].scrollTop = position; + } + + + /* Set the width */ + var sidebarWidth = linesDiv.outerWidth(); + var paddingHorizontal = parseInt( linedWrapDiv.css("border-left-width") ) + parseInt( linedWrapDiv.css("border-right-width") ) + parseInt( linedWrapDiv.css("padding-left") ) + parseInt( linedWrapDiv.css("padding-right") ); + var linedWrapDivNewWidth = originalTextAreaWidth - paddingHorizontal; + var textareaNewWidth = originalTextAreaWidth - sidebarWidth - paddingHorizontal - 20; + + textarea.width( textareaNewWidth ); + linedWrapDiv.width( linedWrapDivNewWidth ); + + + + /* React to the scroll event */ + textarea.scroll( function(tn){ + var domTextArea = $(this)[0]; + var scrollTop = domTextArea.scrollTop; + var clientHeight = domTextArea.clientHeight; + codeLinesDiv.css( {'margin-top': (-1*scrollTop) + "px"} ); + lineNo = fillOutLines( codeLinesDiv, scrollTop + clientHeight, lineNo ); + }); + + + /* Should the textarea get resized outside of our control */ + textarea.resize( function(tn){ + var domTextArea = $(this)[0]; + linesDiv.height( domTextArea.clientHeight + 6 ); + }); + + }); + }; + + // default options + $.fn.linedtextarea.defaults = { + selectedLine: -1, + selectedClass: 'lineselect' + }; +})(jQuery); diff --git a/spyglass/utils/editor/static/js-yaml.min.js b/spyglass/utils/editor/static/js-yaml.min.js new file mode 100644 index 0000000..078bf73 --- /dev/null +++ b/spyglass/utils/editor/static/js-yaml.min.js @@ -0,0 +1 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).jsyaml=e()}}(function(){return function o(a,s,c){function u(t,e){if(!s[t]){if(!a[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);var i=new Error("Cannot find module '"+t+"'");throw i.code="MODULE_NOT_FOUND",i}var r=s[t]={exports:{}};a[t][0].call(r.exports,function(e){return u(a[t][1][e]||e)},r,r.exports,o,a,s,c)}return s[t].exports}for(var l="function"==typeof require&&require,e=0;e=i.flowLevel;switch(H(r,n,i.indent,t,function(e){return function(e,t){var n,i;for(n=0,i=e.implicitTypes.length;n"+V(r,i.indent)+Z(L(function(e,t){var n,i,r=/(\n+)([^\n]*)/g,o=(s=e.indexOf("\n"),s=-1!==s?s:e.length,r.lastIndex=s,z(e.slice(0,s),t)),a="\n"===e[0]||" "===e[0];var s;for(;i=r.exec(e);){var c=i[1],u=i[2];n=" "===u[0],o+=c+(a||n||""===u?"":"\n")+z(u,t),a=n}return o}(r,t),e));case $:return'"'+function(e){for(var t,n,i,r="",o=0;ot&&o tag resolver accepts not "'+c+'" style');i=s.represent[c](t,c)}e.dump=i}return!0}return!1}function Q(e,t,n,i,r,o){e.tag=null,e.dump=n,J(e,n,!1)||J(e,n,!0);var a=l.call(e.dump);i&&(i=e.flowLevel<0||e.flowLevel>t);var s,c,u="[object Object]"===a||"[object Array]"===a;if(u&&(c=-1!==(s=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||c||2!==e.indent&&0 "+e.dump)}return!0}function X(e,t){var n,i,r=[],o=[];for(function e(t,n,i){var r,o,a;if(null!==t&&"object"==typeof t)if(-1!==(o=n.indexOf(t)))-1===i.indexOf(o)&&i.push(o);else if(n.push(t),Array.isArray(t))for(o=0,a=t.length;ot)&&0!==i)_(e,"bad indentation of a sequence entry");else if(e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndentt)&&(K(e,t,b,!0,r)&&(m?d=e.result:h=e.result),m||(D(e,l,p,f,d,h,o,a),f=d=h=null),q(e,!0,-1),s=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==s)_(e,"bad indentation of a mapping entry");else if(e.lineIndentl&&(l=e.lineIndent),j(o))p++;else{if(e.lineIndent>10),56320+(c-65536&1023)),e.position++}else _(e,"unknown escape sequence");n=i=e.position}else j(s)?(T(e,n,i,!0),R(e,q(e,!1,t)),n=i=e.position):e.position===e.lineStart&&Y(e)?_(e,"unexpected end of the document within a double quoted scalar"):(e.position++,i=e.position)}_(e,"unexpected end of the stream within a double quoted scalar")}(e,p)?m=!0:!function(e){var t,n,i;if(42!==(i=e.input.charCodeAt(e.position)))return!1;for(i=e.input.charCodeAt(++e.position),t=e.position;0!==i&&!I(i)&&!E(i);)i=e.input.charCodeAt(++e.position);return e.position===t&&_(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),e.anchorMap.hasOwnProperty(n)||_(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],q(e,!0,-1),!0}(e)?function(e,t,n){var i,r,o,a,s,c,u,l,p=e.kind,f=e.result;if(I(l=e.input.charCodeAt(e.position))||E(l)||35===l||38===l||42===l||33===l||124===l||62===l||39===l||34===l||37===l||64===l||96===l)return!1;if((63===l||45===l)&&(I(i=e.input.charCodeAt(e.position+1))||n&&E(i)))return!1;for(e.kind="scalar",e.result="",r=o=e.position,a=!1;0!==l;){if(58===l){if(I(i=e.input.charCodeAt(e.position+1))||n&&E(i))break}else if(35===l){if(I(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&Y(e)||n&&E(l))break;if(j(l)){if(s=e.line,c=e.lineStart,u=e.lineIndent,q(e,!1,-1),e.lineIndent>=t){a=!0,l=e.input.charCodeAt(e.position);continue}e.position=o,e.line=s,e.lineStart=c,e.lineIndent=u;break}}a&&(T(e,r,o,!1),R(e,e.line-s),r=o=e.position,a=!1),S(l)||(o=e.position+1),l=e.input.charCodeAt(++e.position)}return T(e,r,o,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,p,x===n)&&(m=!0,null===e.tag&&(e.tag="?")):(m=!0,null===e.tag&&null===e.anchor||_(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===d&&(m=s&&B(e,f))),null!==e.tag&&"!"!==e.tag)if("?"===e.tag){for(c=0,u=e.implicitTypes.length;c tag; it should be "'+l.kind+'", not "'+e.kind+'"'),l.resolve(e.result)?(e.result=l.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):_(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):_(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||m}function $(e){var t,n,i,r,o=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(r=e.input.charCodeAt(e.position))&&(q(e,!0,-1),r=e.input.charCodeAt(e.position),!(0t/2-1){n=" ... ",i+=5;break}for(r="",o=this.position;ot/2-1){r=" ... ",o-=5;break}return a=this.buffer.slice(i,o),s.repeat(" ",e)+n+a+r+"\n"+s.repeat(" ",e+this.position-i+n.length)+"^"},i.prototype.toString=function(e){var t,n="";return this.name&&(n+='in "'+this.name+'" '),n+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(n+=":\n"+t),n},t.exports=i},{"./common":2}],7:[function(e,t,n){"use strict";var i=e("./common"),r=e("./exception"),o=e("./type");function a(e,t,i){var r=[];return e.include.forEach(function(e){i=a(e,t,i)}),e[t].forEach(function(n){i.forEach(function(e,t){e.tag===n.tag&&e.kind===n.kind&&r.push(t)}),i.push(n)}),i.filter(function(e,t){return-1===r.indexOf(t)})}function s(e){this.include=e.include||[],this.implicit=e.implicit||[],this.explicit=e.explicit||[],this.implicit.forEach(function(e){if(e.loadKind&&"scalar"!==e.loadKind)throw new r("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.")}),this.compiledImplicit=a(this,"implicit",[]),this.compiledExplicit=a(this,"explicit",[]),this.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{}};function i(e){n[e.kind][e.tag]=n.fallback[e.tag]=e}for(e=0,t=arguments.length;e>16&255),s.push(a>>8&255),s.push(255&a)),a=a<<6|o.indexOf(i.charAt(t));return 0==(n=r%4*6)?(s.push(a>>16&255),s.push(a>>8&255),s.push(255&a)):18===n?(s.push(a>>10&255),s.push(a>>2&255)):12===n&&s.push(a>>4&255),c?c.from?c.from(s):new c(s):s},predicate:function(e){return c&&c.isBuffer(e)},represent:function(e){var t,n,i="",r=0,o=e.length,a=u;for(t=0;t>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]),r=(r<<8)+e[t];return 0==(n=o%3)?(i+=a[r>>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]):2===n?(i+=a[r>>10&63],i+=a[r>>4&63],i+=a[r<<2&63],i+=a[64]):1===n&&(i+=a[r>>2&63],i+=a[r<<4&63],i+=a[64],i+=a[64]),i}})},{"../type":13}],15:[function(e,t,n){"use strict";var i=e("../type");t.exports=new i("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},{"../type":13}],16:[function(e,t,n){"use strict";var i=e("../common"),r=e("../type"),o=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var a=/^[-+]?[0-9]+e/;t.exports=new r("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!o.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n,i,r;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,r=[],0<="+-".indexOf(t[0])&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:0<=t.indexOf(":")?(t.split(":").forEach(function(e){r.unshift(parseFloat(e,10))}),t=0,i=1,r.forEach(function(e){t+=e*i,i*=60}),n*t):n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||i.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(i.isNegativeZero(e))return"-0.0";return n=e.toString(10),a.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},{"../common":2,"../type":13}],17:[function(e,t,n){"use strict";var i=e("../common"),r=e("../type");t.exports=new r("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,i,r,o=e.length,a=0,s=!1;if(!o)return!1;if("-"!==(t=e[a])&&"+"!==t||(t=e[++a]),"0"===t){if(a+1===o)return!0;if("b"===(t=e[++a])){for(a++;a +{% endblock %} + +{% block scripts %} +{{super()}} + + + + +{% endblock %} + +{% block content %} + +
+
+
Edit your YAML (Update corresponding fields with {{ change_str }} text):
+
+ +
+
+
+ + +
+
+ +{% endblock %}