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 2011afc..c022d7b 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/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 0000000..d3dca27
Binary files /dev/null and b/spyglass/utils/editor/static/favicon.ico differ
diff --git a/spyglass/utils/editor/static/jquery-linedtextarea.css b/spyglass/utils/editor/static/jquery-linedtextarea.css
new file mode 100755
index 0000000..82ad0bd
--- /dev/null
+++ b/spyglass/utils/editor/static/jquery-linedtextarea.css
@@ -0,0 +1,68 @@
+/**
+ * jQuery Lined Textarea Plugin
+ * http://alan.blog-city.com/jquerylinedtextarea.htm
+ *
+ * Copyright (c) 2010 Alan Williamson
+ *
+ * Released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Usage:
+ * Displays a line number count column to the left of the textarea
+ *
+ * Class up your textarea with a given class, or target it directly
+ * with JQuery Selectors
+ *
+ * $(".lined").linedtextarea({
+ * selectedLine: 10,
+ * selectedClass: 'lineselect'
+ * });
+ *
+ */
+
+.linedwrap {
+ border: 1px solid #c0c0c0;
+ padding: 3px;
+}
+
+.linedtextarea {
+ padding: 0px;
+ margin: 0px;
+}
+
+.linedtextarea textarea, .linedwrap .codelines .lineno {
+ font-size: 10pt;
+ font-family: monospace;
+ line-height: normal !important;
+}
+
+.linedtextarea textarea {
+ padding-right:0.3em;
+ padding-top:0.3em;
+ border: 0;
+}
+
+.linedwrap .lines {
+ margin-top: 0px;
+ width: 50px;
+ float: left;
+ overflow: hidden;
+ border-right: 1px solid #c0c0c0;
+ margin-right: 10px;
+}
+
+.linedwrap .codelines {
+ padding-top: 5px;
+}
+
+.linedwrap .codelines .lineno {
+ color:#AAAAAA;
+ padding-right: 0.5em;
+ padding-top: 0.0em;
+ text-align: right;
+ white-space: nowrap;
+}
+
+.linedwrap .codelines .lineselect {
+ color: red;
+}
diff --git a/spyglass/utils/editor/static/jquery-linedtextarea.js b/spyglass/utils/editor/static/jquery-linedtextarea.js
new file mode 100755
index 0000000..b3699d6
--- /dev/null
+++ b/spyglass/utils/editor/static/jquery-linedtextarea.js
@@ -0,0 +1,126 @@
+/**
+ * jQuery Lined Textarea Plugin
+ * http://alan.blog-city.com/jquerylinedtextarea.htm
+ *
+ * Copyright (c) 2010 Alan Williamson
+ *
+ * Version:
+ * $Id: jquery-linedtextarea.js 464 2010-01-08 10:36:33Z alan $
+ *
+ * Released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Usage:
+ * Displays a line number count column to the left of the textarea
+ *
+ * Class up your textarea with a given class, or target it directly
+ * with JQuery Selectors
+ *
+ * $(".lined").linedtextarea({
+ * selectedLine: 10,
+ * selectedClass: 'lineselect'
+ * });
+ *
+ * History:
+ * - 2010.01.08: Fixed a Google Chrome layout problem
+ * - 2010.01.07: Refactored code for speed/readability; Fixed horizontal sizing
+ * - 2010.01.06: Initial Release
+ *
+ */
+(function($) {
+
+ $.fn.linedtextarea = function(options) {
+
+ // Get the Options
+ var opts = $.extend({}, $.fn.linedtextarea.defaults, options);
+
+
+ /*
+ * Helper function to make sure the line numbers are always
+ * kept up to the current system
+ */
+ var fillOutLines = function(codeLines, h, lineNo){
+ while ( (codeLines.height() - h ) <= 0 ){
+ if ( lineNo == opts.selectedLine )
+ codeLines.append("" + 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 %}
+
+
+
+
+
+
+
+
+
+{% endblock %}