basic working network settings

NetworkSettingsSheet for wifi password still not working
This commit is contained in:
Marco Martin 2014-12-02 16:23:55 +01:00
parent 40fc34f4ae
commit 01e7a29c0a
4 changed files with 837 additions and 154 deletions

View file

@ -0,0 +1,152 @@
/*
* Copyright (C) 2013 Robin Burchell <robin+mer@viroteck.net>
* Copyright (C) 2012 Jolla Ltd. <dmitry.rozhkov@jollamobile.com>
*
* You may use this file under the terms of the BSD license as follows:
*
* "Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Nemo Mobile nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
*/
import QtQuick 2.0
import QtQuick.Controls 1.2
import MeeGo.Connman 0.2
import "mustache.js" as M
Item {
id: networkPage
property variant mustacheView
property UserAgent userAgent
property Timer scanTimer
property var netfields: {}
function handleInput(key, value) {
var dict = {};
var isDoneEnabled = false;
console.log("Received from TextField " + key + " " + value);
dict[key] = value;
networkPage.netfields = dict;
for (var id in networkPage.netfields) {
console.log(id + "-> " + networkPage.netfields[id]);
isDoneEnabled = isDoneEnabled || networkPage.netfields[id].length;
}
networkPage.acceptButtonEnabled = isDoneEnabled;
}
Connections {
target: userAgent
onUserInputCanceled: {
console.log("qmlsettings: UserAgent cancelled user input request");
networkPage.reject()
}
}
Button {
text: "Reject"
onClicked: {
userAgent.sendUserReply({});
scanTimer.running = true;
}
}
Button {
text: "Accept"
onClicked: {
console.log('clicked Done ' + 'x:' + x + ' y:' + y);
var fields = networkPage.netfields;
for (var key in fields) {
console.log(key + " --> " + fields[key]);
}
scanTimer.running = true;
userAgent.sendUserReply(fields);
}
}
Column {
spacing: 10
anchors.fill: parent
Label {
anchors { left: parent.left; leftMargin: 10 }
text: "Sign in to secure Wi-Fi network"
}
Label {
id: networkName
anchors { left: parent.left; leftMargin: 10 }
}
Item {
height: 30
}
Item {
id: dynFields
width: parent.width
height: 200
property string form_tpl: "
import QtQuick 2.0
import com.nokia.meego 2.0
Item {
id: form
anchors { fill: parent; margins: 10 }
Column {
spacing: 5
anchors { fill: parent }
{{#fields}}
Text {
text: '{{name}}'
color: 'white'
font.pointSize: 14
}
TextField {
id: {{id}}
signal send (string key, string value)
anchors { left: parent.left; right: parent.right }
placeholderText: 'enter {{name}}'
Component.onCompleted: {
{{id}}.send.connect(handleInput);
}
onTextChanged: {
console.log('Sending from TextField {{id}}' + {{id}}.text);
{{id}}.send('{{name}}', {{id}}.text);
}
}
{{/fields}}
}
}
"
Component.onCompleted: {
console.log(mustacheView)
console.log(form_tpl)
// TODO: can we replace mustache with just regular old bindings?
var output = M.Mustache.render(form_tpl, mustacheView);
console.log("Creating " + output)
var form = Qt.createQmlObject(output, dynFields, "dynamicForm1");
console.log("Created " + form)
}
}
}
}

View file

@ -33,6 +33,7 @@
import QtQuick 2.0 import QtQuick 2.0
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import org.kde.plasma.components 2.0 import org.kde.plasma.components 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.extras 2.0 as PlasmaExtras
import MeeGo.Connman 0.2 import MeeGo.Connman 0.2
@ -135,7 +136,7 @@ Item {
} }
} }
stackView.pop();
} }
} }
Button { Button {
@ -171,28 +172,40 @@ Item {
anchors.right: parent.right anchors.right: parent.right
spacing: 10 spacing: 10
Rectangle { Row {
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 80
color: "transparent"
Image { PlasmaCore.IconItem {
anchors { left: parent.left; verticalCenter: parent.verticalCenter } height: units.iconSizes.large
source: "image://theme/icon-m-common-wlan-strength5" width: height
width: 60 source: {
height: 60 var strength = form.strength;
var str_id = 0;
if (strength >= 100) {
str_id = 100;
} else if (strength >= 80) {
str_id = 80;
} else if (strength >= 60) {
str_id = 60;
} else if (strength >= 40) {
str_id = 40;
} else if (strength >= 20) {
str_id = 20;
}
return "network-wireless-" + str_id;
}
} }
Text { PlasmaExtras.Heading {
anchors { left: parent.left; verticalCenter: parent.verticalCenter; leftMargin: 80 }
text: form.networkName text: form.networkName
} }
} }
Rectangle { Item {
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 100 height: 100
color: "transparent"
Button { Button {
id: disconnectButton id: disconnectButton
anchors { anchors {
@ -203,21 +216,20 @@ Item {
onClicked: { onClicked: {
console.log("Disconnect clicked"); console.log("Disconnect clicked");
network.requestDisconnect(); network.requestDisconnect();
sheet.close(); stackView.pop();
} }
} }
} }
Item { Column {
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 100
Text { Text {
anchors { left: parent.left; leftMargin: 20 } anchors { left: parent.left; leftMargin: 20 }
text: "Method" text: "Method"
} }
ButtonRow { ButtonRow {
id: method id: method
anchors { left: parent.left; right: parent.right; top: parent.top; topMargin: 30; leftMargin: 10; rightMargin: 10 } anchors { left: parent.left; right: parent.right; leftMargin: 10; rightMargin: 10 }
state: form.ipv4.Method state: form.ipv4.Method
states: [ states: [
@ -250,81 +262,72 @@ Item {
} }
} }
Item { Column {
id: networkInfo id: networkInfo
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 340 spacing: 50
Column { Column {
spacing: 50 anchors { left: parent.left; right: parent.right }
Item {
height: 40
anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20; }
text: "IP address" text: "IP address"
}
Text {
anchors { left: parent.left; leftMargin: 20; top:parent.top; topMargin: 30 }
text: form.ipv4.Address
}
} }
Item { Text {
height: 40 anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
anchors { left: parent.left; right: parent.right } text: form.ipv4.Address
Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top }
text: "Subnet mask"
}
Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 }
text: form.ipv4.Netmask
}
}
Item {
height: 40
anchors { left: parent.left; right: parent.right }
Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top }
text: "Router"
}
Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 }
text: form.ipv4.Gateway
}
}
Item {
height: 40
anchors { left: parent.left; right: parent.right }
Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top }
text: "DNS"
}
Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 }
text: form.nameservers.join()
}
} }
} }
Column {
anchors { left: parent.left; right: parent.right }
Text {
anchors { left: parent.left; leftMargin: 20 }
text: "Subnet mask"
}
Text {
anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
text: form.ipv4.Netmask
}
}
Column {
anchors { left: parent.left; right: parent.right }
Text {
anchors { left: parent.left; leftMargin: 20 }
text: "Router"
}
Text {
anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
text: form.ipv4.Gateway
}
}
Column {
anchors { left: parent.left; right: parent.right }
Text {
anchors { left: parent.left; leftMargin: 20 }
text: "DNS"
}
Text {
anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
text: form.nameservers.join()
}
}
} }
Item { Item {
id: networkFields id: networkFields
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 360
Column { Column {
spacing: 50 spacing: 50
Item { Column {
height: 40
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "IP address" text: "IP address"
} }
TextField { TextField {
@ -334,47 +337,44 @@ Item {
text: form.ipv4.Address text: form.ipv4.Address
} }
} }
Item { Column {
height: 40
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "Subnet mask" text: "Subnet mask"
} }
TextField { TextField {
id: netmask id: netmask
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 } anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
width: 440 width: 440
text: form.ipv4.Netmask text: form.ipv4.Netmask
} }
} }
Item { Column {
height: 40
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "Router" text: "Router"
} }
TextField { TextField {
id: gateway id: gateway
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 } anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
width: 440 width: 440
text: form.ipv4.Gateway text: form.ipv4.Gateway
} }
} }
Item { Column {
height: 40
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "DNS" text: "DNS"
} }
TextField { TextField {
id: nameserversField id: nameserversField
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 } anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
width: 440 width: 440
text: { text: {
var nservs = ""; var nservs = "";
@ -390,32 +390,30 @@ Item {
} }
} }
Item { Column {
height: 100
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "Search domains" text: "Search domains"
} }
TextField { TextField {
id: domainsField id: domainsField
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 } anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
width: 440 width: 440
text: form.domains.join() text: form.domains.join()
} }
} }
Item { Column {
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 100
Text { Text {
anchors { left: parent.left; leftMargin: 20 } anchors { left: parent.left; leftMargin: 20 }
text: "HTTP Proxy" text: "HTTP Proxy"
} }
ButtonRow { ButtonRow {
id: proxy id: proxy
anchors { left: parent.left; right: parent.right; top: parent.top; topMargin: 30; leftMargin: 10; rightMargin: 10 } anchors { left: parent.left; right: parent.right; topMargin: 30; leftMargin: 10; rightMargin: 10 }
state: form.proxy.Method state: form.proxy.Method
states: [ states: [
@ -469,75 +467,66 @@ Item {
} }
} }
Item { Column {
id: proxyManualFields id: proxyManualFields
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 440
spacing: 50
Column { Column {
spacing: 50 anchors { left: parent.left; right: parent.right }
Item {
height: 40
anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "Server" text: "Server"
}
TextField {
id: proxyserver
anchors { left: parent.left; leftMargin: 20; top:parent.top; topMargin: 30 }
width: 440
text: form.proxyConfig.Servers ? form.proxyConfig.Servers[0].split(":")[0] : ""
}
} }
Item { TextField {
height: 40 id: proxyserver
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; leftMargin: 20; top:parent.top; topMargin: 30 }
width: 440
text: form.proxyConfig.Servers ? form.proxyConfig.Servers[0].split(":")[0] : ""
}
}
Column {
anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "Port" text: "Port"
} }
TextField { TextField {
id: proxyport id: proxyport
anchors { left: parent.left; leftMargin: 20; top: parent.top; topMargin: 30 } anchors { left: parent.left; leftMargin: 20; topMargin: 30 }
width: 440 width: 440
text: form.proxyConfig.Servers ? form.proxyConfig.Servers[0].split(":")[1] : "" text: form.proxyConfig.Servers ? form.proxyConfig.Servers[0].split(":")[1] : ""
// TODO: validator // TODO: validator
}
} }
} }
} }
Item { Column {
id: proxyAutoFields id: proxyAutoFields
anchors { left: parent.left; right: parent.right } anchors { left: parent.left; right: parent.right }
height: 440
spacing: 50
CheckBox {
id: proxyAutoUrl
text: "Use URL provided by DHCP server"
}
Column { Column {
spacing: 50 visible: !proxyAutoUrl.checked
CheckBox { anchors { left: parent.left; right: parent.right }
id: proxyAutoUrl
text: "Use URL provided by DHCP server"
}
Item {
height: 40
visible: !proxyAutoUrl.checked
anchors { left: parent.left; right: parent.right }
Text { Text {
anchors { left: parent.left; leftMargin: 20; top: parent.top } anchors { left: parent.left; leftMargin: 20 }
text: "URL" text: "URL"
} }
TextField { TextField {
id: proxyurl id: proxyurl
anchors { left: parent.left; leftMargin: 20; top:parent.top; topMargin: 30 } anchors { left: parent.left; leftMargin: 20; top:parent.top; topMargin: 30 }
width: 440 width: 440
readOnly: proxyAutoUrl.checked readOnly: proxyAutoUrl.checked
text: form.proxy.URL text: form.proxy.URL
// TODO: validator // TODO: validator
}
} }
} }
} }

View file

@ -42,12 +42,14 @@ import QtQuick.Controls 1.2
StackView { StackView {
id: stackView id: stackView
initialItem: mainView initialItem: mainView
width: 1080
height: 1815
Component { Component {
id: mainView id: mainView
Item { Item {
id: mainWindow id: mainWindow
property Item tools: commonTools property Item tools//: commonTools
Timer { Timer {
id: scanTimer id: scanTimer
@ -96,15 +98,16 @@ StackView {
} }
if (!networkingModel.sheetOpened) { if (!networkingModel.sheetOpened) {
networkingModel.sheetOpened = true networkingModel.sheetOpened = true
var sheet = pageStack.openSheet(Qt.resolvedUrl("NetworkSettingsSheet.qml"), { var sheet = stackView.push({item: Qt.resolvedUrl("NetworkSettingsSheet.qml"), properties:{
mustacheView: view, mustacheView: view,
networkName: networkingModel.networkName, networkName: networkingModel.networkName,
userAgent: userAgent, userAgent: userAgent,
scanTimer: scanTimer scanTimer: scanTimer
}) }})
sheet.accepted.connect(function() { networkingModel.sheetOpened = false })
sheet.rejected.connect(function() { networkingModel.sheetOpened = false }) // sheet.accepted.connect(function() { networkingModel.sheetOpened = false })
// TODO: there was code that checked for pageStack.busy and //sheet.rejected.connect(function() { networkingModel.sheetOpened = false })
// TODO: there was code that checked for stackView.busy and
// didn't open if it was true. What was that about? // didn't open if it was true. What was that about?
} }
} }
@ -130,17 +133,19 @@ StackView {
ListView { ListView {
id: networkList id: networkList
//header: WirelessApplet { } //header: WirelessApplet { }
anchors.margins: UiConstants.DefaultMargin anchors.margins: 4//UiConstants.DefaultMargin
anchors.fill: parent anchors.fill: parent
model: networkingModel model: networkingModel
delegate: PlasmaComponents.ListItem { delegate: PlasmaComponents.ListItem {
enabled: true
PlasmaCore.IconItem { PlasmaCore.IconItem {
id: icon
anchors { anchors {
left: parent.left left: parent.left
top: parent.top top: parent.top
bottom: parent.bototm bottom: parent.bototm
} }
height: units.iconSizes.large
width: height width: height
source: { source: {
var strength = modelData.strength; var strength = modelData.strength;
@ -166,6 +171,7 @@ StackView {
top: parent.top top: parent.top
right: parent.right right: parent.right
bottom: parent.bottom bottom: parent.bottom
leftMargin: units.smallSpacing
} }
PlasmaExtras.Heading { PlasmaExtras.Heading {
level: 2 level: 2

536
settingsmodules/mustache.js Normal file
View file

@ -0,0 +1,536 @@
/*!
* mustache.js - Logic-less {{mustache}} templates with JavaScript
* http://github.com/janl/mustache.js
*/
var Mustache = (typeof module !== "undefined" && module.exports) || {};
(function (exports) {
exports.name = "mustache.js";
exports.version = "0.5.0-dev";
exports.tags = ["{{", "}}"];
exports.parse = parse;
exports.compile = compile;
exports.render = render;
exports.clearCache = clearCache;
// This is here for backwards compatibility with 0.4.x.
exports.to_html = function (template, view, partials, send) {
var result = render(template, view, partials);
if (typeof send === "function") {
send(result);
} else {
return result;
}
};
var _toString = Object.prototype.toString;
var _isArray = Array.isArray;
var _forEach = Array.prototype.forEach;
var _trim = String.prototype.trim;
var isArray;
if (_isArray) {
isArray = _isArray;
} else {
isArray = function (obj) {
return _toString.call(obj) === "[object Array]";
};
}
var forEach;
if (_forEach) {
forEach = function (obj, callback, scope) {
return _forEach.call(obj, callback, scope);
};
} else {
forEach = function (obj, callback, scope) {
for (var i = 0, len = obj.length; i < len; ++i) {
callback.call(scope, obj[i], i, obj);
}
};
}
var spaceRe = /^\s*$/;
function isWhitespace(string) {
return spaceRe.test(string);
}
var trim;
if (_trim) {
trim = function (string) {
return string == null ? "" : _trim.call(string);
};
} else {
var trimLeft, trimRight;
if (isWhitespace("\xA0")) {
trimLeft = /^\s+/;
trimRight = /\s+$/;
} else {
// IE doesn't match non-breaking spaces with \s, thanks jQuery.
trimLeft = /^[\s\xA0]+/;
trimRight = /[\s\xA0]+$/;
}
trim = function (string) {
return string == null ? "" :
String(string).replace(trimLeft, "").replace(trimRight, "");
};
}
var escapeMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;'
};
function escapeHTML(string) {
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
return escapeMap[s] || s;
});
}
/**
* Adds the `template`, `line`, and `file` properties to the given error
* object and alters the message to provide more useful debugging information.
*/
function debug(e, template, line, file) {
file = file || "<template>";
var lines = template.split("\n"),
start = Math.max(line - 3, 0),
end = Math.min(lines.length, line + 3),
context = lines.slice(start, end);
var c;
for (var i = 0, len = context.length; i < len; ++i) {
c = i + start + 1;
context[i] = (c === line ? " >> " : " ") + context[i];
}
e.template = template;
e.line = line;
e.file = file;
e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
return e;
}
/**
* Looks up the value of the given `name` in the given context `stack`.
*/
function lookup(name, stack, defaultValue) {
if (name === ".") {
return stack[stack.length - 1];
}
var names = name.split(".");
var lastIndex = names.length - 1;
var target = names[lastIndex];
var value, context, i = stack.length, j, localStack;
while (i) {
localStack = stack.slice(0);
context = stack[--i];
j = 0;
while (j < lastIndex) {
context = context[names[j++]];
if (context == null) {
break;
}
localStack.push(context);
}
if (context && typeof context === "object" && target in context) {
value = context[target];
break;
}
}
// If the value is a function, call it in the current context.
if (typeof value === "function") {
value = value.call(localStack[localStack.length - 1]);
}
if (value == null) {
return defaultValue;
}
return value;
}
function renderSection(name, stack, callback, inverted) {
var buffer = "";
var value = lookup(name, stack);
if (inverted) {
// From the spec: inverted sections may render text once based on the
// inverse value of the key. That is, they will be rendered if the key
// doesn't exist, is false, or is an empty list.
if (value == null || value === false || (isArray(value) && value.length === 0)) {
buffer += callback();
}
} else if (isArray(value)) {
forEach(value, function (value) {
stack.push(value);
buffer += callback();
stack.pop();
});
} else if (typeof value === "object") {
stack.push(value);
buffer += callback();
stack.pop();
} else if (typeof value === "function") {
var scope = stack[stack.length - 1];
var scopedRender = function (template) {
return render(template, scope);
};
buffer += value.call(scope, callback(), scopedRender) || "";
} else if (value) {
buffer += callback();
}
return buffer;
}
/**
* Parses the given `template` and returns the source of a function that,
* with the proper arguments, will render the template. Recognized options
* include the following:
*
* - file The name of the file the template comes from (displayed in
* error messages)
* - tags An array of open and close tags the `template` uses. Defaults
* to the value of Mustache.tags
* - debug Set `true` to log the body of the generated function to the
* console
* - space Set `true` to preserve whitespace from lines that otherwise
* contain only a {{tag}}. Defaults to `false`
*/
function parse(template, options) {
options = options || {};
var tags = options.tags || exports.tags,
openTag = tags[0],
closeTag = tags[tags.length - 1];
var code = [
'var buffer = "";', // output buffer
"\nvar line = 1;", // keep track of source line number
"\ntry {",
'\nbuffer += "'
];
var spaces = [], // indices of whitespace in code on the current line
hasTag = false, // is there a {{tag}} on the current line?
nonSpace = false; // is there a non-space char on the current line?
// Strips all space characters from the code array for the current line
// if there was a {{tag}} on it and otherwise only spaces.
var stripSpace = function () {
if (hasTag && !nonSpace && !options.space) {
while (spaces.length) {
code.splice(spaces.pop(), 1);
}
} else {
spaces = [];
}
hasTag = false;
nonSpace = false;
};
var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
var setTags = function (source) {
tags = trim(source).split(/\s+/);
nextOpenTag = tags[0];
nextCloseTag = tags[tags.length - 1];
};
var includePartial = function (source) {
code.push(
'";',
updateLine,
'\nvar partial = partials["' + trim(source) + '"];',
'\nif (partial) {',
'\n buffer += render(partial,stack[stack.length - 1],partials);',
'\n}',
'\nbuffer += "'
);
};
var openSection = function (source, inverted) {
var name = trim(source);
if (name === "") {
throw debug(new Error("Section name may not be empty"), template, line, options.file);
}
sectionStack.push({name: name, inverted: inverted});
code.push(
'";',
updateLine,
'\nvar name = "' + name + '";',
'\nvar callback = (function () {',
'\n return function () {',
'\n var buffer = "";',
'\nbuffer += "'
);
};
var openInvertedSection = function (source) {
openSection(source, true);
};
var closeSection = function (source) {
var name = trim(source);
var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
if (!openName || name != openName) {
throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
}
var section = sectionStack.pop();
code.push(
'";',
'\n return buffer;',
'\n };',
'\n})();'
);
if (section.inverted) {
code.push("\nbuffer += renderSection(name,stack,callback,true);");
} else {
code.push("\nbuffer += renderSection(name,stack,callback);");
}
code.push('\nbuffer += "');
};
var sendPlain = function (source) {
code.push(
'";',
updateLine,
'\nbuffer += lookup("' + trim(source) + '",stack,"");',
'\nbuffer += "'
);
};
var sendEscaped = function (source) {
code.push(
'";',
updateLine,
'\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
'\nbuffer += "'
);
};
var line = 1, c, callback;
for (var i = 0, len = template.length; i < len; ++i) {
if (template.slice(i, i + openTag.length) === openTag) {
i += openTag.length;
c = template.substr(i, 1);
updateLine = '\nline = ' + line + ';';
nextOpenTag = openTag;
nextCloseTag = closeTag;
hasTag = true;
switch (c) {
case "!": // comment
i++;
callback = null;
break;
case "=": // change open/close tags, e.g. {{=<% %>=}}
i++;
closeTag = "=" + closeTag;
callback = setTags;
break;
case ">": // include partial
i++;
callback = includePartial;
break;
case "#": // start section
i++;
callback = openSection;
break;
case "^": // start inverted section
i++;
callback = openInvertedSection;
break;
case "/": // end section
i++;
callback = closeSection;
break;
case "{": // plain variable
closeTag = "}" + closeTag;
// fall through
case "&": // plain variable
i++;
nonSpace = true;
callback = sendPlain;
break;
default: // escaped variable
nonSpace = true;
callback = sendEscaped;
}
var end = template.indexOf(closeTag, i);
if (end === -1) {
throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
}
var source = template.substring(i, end);
if (callback) {
callback(source);
}
// Maintain line count for \n in source.
var n = 0;
while (~(n = source.indexOf("\n", n))) {
line++;
n++;
}
i = end + closeTag.length - 1;
openTag = nextOpenTag;
closeTag = nextCloseTag;
} else {
c = template.substr(i, 1);
switch (c) {
case '"':
case "\\":
nonSpace = true;
code.push("\\" + c);
break;
case "\r":
// Ignore carriage returns.
break;
case "\n":
spaces.push(code.length);
code.push("\\n");
stripSpace(); // Check for whitespace on the current line.
line++;
break;
default:
if (isWhitespace(c)) {
spaces.push(code.length);
} else {
nonSpace = true;
}
code.push(c);
}
}
}
if (sectionStack.length != 0) {
throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
}
// Clean up any whitespace from a closing {{tag}} that was at the end
// of the template without a trailing \n.
stripSpace();
code.push(
'";',
"\nreturn buffer;",
"\n} catch (e) { throw {error: e, line: line}; }"
);
// Ignore `buffer += "";` statements.
var body = code.join("").replace(/buffer \+= "";\n/g, "");
if (options.debug) {
if (typeof console != "undefined" && console.log) {
console.log(body);
} else if (typeof print === "function") {
print(body);
}
}
return body;
}
/**
* Used by `compile` to generate a reusable function for the given `template`.
*/
function _compile(template, options) {
var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
var body = parse(template, options);
var fn = new Function(args, body);
// This anonymous function wraps the generated function so we can do
// argument coercion, setup some variables, and handle any errors
// encountered while executing it.
return function (view, partials) {
partials = partials || {};
var stack = [view]; // context stack
try {
return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
} catch (e) {
throw debug(e.error, template, e.line, options.file);
}
};
}
// Cache of pre-compiled templates.
var _cache = {};
/**
* Clear the cache of compiled templates.
*/
function clearCache() {
_cache = {};
}
/**
* Compiles the given `template` into a reusable function using the given
* `options`. In addition to the options accepted by Mustache.parse,
* recognized options include the following:
*
* - cache Set `false` to bypass any pre-compiled version of the given
* template. Otherwise, a given `template` string will be cached
* the first time it is parsed
*/
function compile(template, options) {
options = options || {};
// Use a pre-compiled version from the cache if we have one.
if (options.cache !== false) {
if (!_cache[template]) {
_cache[template] = _compile(template, options);
}
return _cache[template];
}
return _compile(template, options);
}
/**
* High-level function that renders the given `template` using the given
* `view` and `partials`. If you need to use any of the template options (see
* `compile` above), you must compile in a separate step, and then call that
* compiled function.
*/
function render(template, view, partials) {
return compile(template)(view, partials);
}
})(Mustache);