mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
RISON.js, Added state service, added write-only URL state persistence to discover, add last-state aware context to app tabs
This commit is contained in:
parent
7de0bf3730
commit
9b43fcc0d7
8 changed files with 585 additions and 29 deletions
|
@ -7,7 +7,8 @@
|
|||
"define": true,
|
||||
"require": true,
|
||||
"console": true,
|
||||
"jsonPath": false
|
||||
"jsonPath": false,
|
||||
"rison": false
|
||||
},
|
||||
|
||||
"camelcase": false,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div class="container-fluid">
|
||||
<ul class="nav navbar-nav">
|
||||
<li ng-repeat="app in apps" ng-class="{active: activeApp == app.id}">
|
||||
<a href="#/{{app.id}}" bo-text="app.name"></a>
|
||||
<a ng-href="#/{{app.lastPath || app.id}}" bo-text="app.name"></a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
|
|
|
@ -6,6 +6,8 @@ define(function (require) {
|
|||
|
||||
var app = require('modules').get('app/discover');
|
||||
|
||||
require('services/state');
|
||||
|
||||
var intervals = [
|
||||
{ display: '', val: null },
|
||||
{ display: 'Hourly', val: 'hourly' },
|
||||
|
@ -15,7 +17,7 @@ define(function (require) {
|
|||
{ display: 'Yearly', val: 'yearly' }
|
||||
];
|
||||
|
||||
app.controller('discover', function ($scope, config, $q, $route, savedSearches, courier, createNotifier, $location) {
|
||||
app.controller('discover', function ($scope, config, $q, $route, savedSearches, courier, createNotifier, $location, state) {
|
||||
var notify = createNotifier({
|
||||
location: 'Discover'
|
||||
});
|
||||
|
@ -23,12 +25,21 @@ define(function (require) {
|
|||
var search = $route.current.locals.search;
|
||||
if (!search) return notify.fatal('search failed to load');
|
||||
|
||||
$scope.state = {};
|
||||
|
||||
// Will need some watchers here to see if anything that implies a refresh changes
|
||||
$scope.$on('$routeUpdate', function () {
|
||||
$scope.state = $location.search();
|
||||
});
|
||||
/* Manage state & url state */
|
||||
|
||||
var initialQuery = search.get('query');
|
||||
|
||||
function loadState() {
|
||||
$scope.state = state.get();
|
||||
$scope.state = _.defaults($scope.state, {
|
||||
query: initialQuery ? initialQuery.query_string.query : '',
|
||||
columns: ['_source'],
|
||||
sort: ['_score', 'desc']
|
||||
});
|
||||
}
|
||||
|
||||
loadState();
|
||||
|
||||
$scope.opts = {
|
||||
// number of records to fetch, then paginate through
|
||||
|
@ -57,17 +68,10 @@ define(function (require) {
|
|||
// stores the complete list of fields
|
||||
$scope.fields = null;
|
||||
|
||||
// stores the fields we want to fetch
|
||||
$scope.state.columns = null;
|
||||
|
||||
// index pattern interval options
|
||||
$scope.intervals = intervals;
|
||||
$scope.interval = intervals[0];
|
||||
|
||||
// TODO: URL arg should override this
|
||||
var initialQuery = search.get('query');
|
||||
$scope.state.query = initialQuery ? initialQuery.query_string.query : '';
|
||||
|
||||
// the index to use when they don't specify one
|
||||
config.$watch('discover.defaultIndex', function (val) {
|
||||
if (!val) return config.set('discover.defaultIndex', '_all');
|
||||
|
@ -86,7 +90,6 @@ define(function (require) {
|
|||
$scope.rows = res.hits.hits;
|
||||
});
|
||||
|
||||
$scope.state.sort = ['_score', 'desc'];
|
||||
|
||||
$scope.getSort = function () {
|
||||
return $scope.state.sort;
|
||||
|
@ -128,7 +131,7 @@ define(function (require) {
|
|||
// set the index on the savedSearch
|
||||
search.index($scope.opts.index);
|
||||
// clear the columns and fields, then refetch when we do a search
|
||||
$scope.state.columns = $scope.fields = null;
|
||||
//$scope.state.columns = $scope.fields = null;
|
||||
}
|
||||
|
||||
if (!$scope.fields) getFields();
|
||||
|
@ -145,13 +148,23 @@ define(function (require) {
|
|||
$scope.fetch = function () {
|
||||
updateDataSource();
|
||||
// fetch just this savedSearch
|
||||
$scope.updateState();
|
||||
search.fetch();
|
||||
};
|
||||
|
||||
$scope.updateState = function () {
|
||||
//$location.search($scope.state);
|
||||
state.set($scope.state);
|
||||
};
|
||||
|
||||
// This is a hacky optimization for comparing the contents of a large array to a short one.
|
||||
function arrayToKeys(array, value) {
|
||||
var obj = {};
|
||||
_.each(array, function (key) {
|
||||
obj[key] = value || true;
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
var activeGetFields;
|
||||
function getFields() {
|
||||
var defer = $q.defer();
|
||||
|
@ -174,6 +187,9 @@ define(function (require) {
|
|||
.then(function (fields) {
|
||||
if (!fields) return;
|
||||
|
||||
|
||||
var columnObjects = arrayToKeys($scope.state.columns);
|
||||
|
||||
$scope.fields = [];
|
||||
$scope.state.columns = $scope.state.columns || [];
|
||||
|
||||
|
@ -188,11 +204,11 @@ define(function (require) {
|
|||
field.name = name;
|
||||
|
||||
_.defaults(field, currentState[name]);
|
||||
$scope.fields.push(_.defaults(field, {display: false}));
|
||||
$scope.fields.push(_.defaults(field, {display: columnObjects[name] || false}));
|
||||
});
|
||||
|
||||
|
||||
refreshColumns();
|
||||
|
||||
defer.resolve();
|
||||
}, defer.reject);
|
||||
|
||||
|
@ -214,8 +230,6 @@ define(function (require) {
|
|||
};
|
||||
|
||||
$scope.toggleField = function (name) {
|
||||
console.log('toggling', name);
|
||||
|
||||
var field = _.find($scope.fields, { name: name });
|
||||
|
||||
// toggle the display property
|
||||
|
@ -235,13 +249,14 @@ define(function (require) {
|
|||
|
||||
$scope.refreshFieldList = function () {
|
||||
search.clearFieldCache(function () {
|
||||
getFields(function () {
|
||||
getFields().then(function () {
|
||||
$scope.fetch();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function refreshColumns() {
|
||||
|
||||
// Get all displayed field names;
|
||||
var fields = _.pluck(_.filter($scope.fields, function (field) {
|
||||
return field.display;
|
||||
|
|
|
@ -32,7 +32,7 @@ define(function (require) {
|
|||
data: $scope.data,
|
||||
field: field.name,
|
||||
count: 5,
|
||||
grouped: true
|
||||
grouped: false
|
||||
});
|
||||
} else {
|
||||
delete field.details;
|
||||
|
|
|
@ -18,16 +18,32 @@ define(function (require) {
|
|||
appendToBody: false
|
||||
});
|
||||
})
|
||||
.controller('kibana', function ($rootScope, $scope, courier, config, configFile, createNotifier, $timeout) {
|
||||
.controller('kibana', function ($rootScope, $scope, courier, config, configFile, createNotifier, $timeout, $location) {
|
||||
var notify = createNotifier({
|
||||
location: 'Kibana Controller'
|
||||
});
|
||||
$scope.apps = configFile.apps;
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function (event, uri) {
|
||||
if (!uri) return;
|
||||
var route = uri.match(/#\/([^\/]*)/);
|
||||
|
||||
function updateAppData() {
|
||||
var route = $location.path().split(/\//);
|
||||
var app = _.find($scope.apps, {id: route[1]});
|
||||
|
||||
// Record the last URL w/ state of the app, use for tab.
|
||||
app.lastPath = $location.url().substring(1);
|
||||
|
||||
// Set class of container to application-<whateverApp>
|
||||
$scope.activeApp = route ? route[1] : null;
|
||||
}
|
||||
|
||||
$scope.$on('$routeChangeSuccess', function (event, data) {
|
||||
if (!data) return;
|
||||
updateAppData();
|
||||
});
|
||||
|
||||
$scope.$on('$routeUpdate', function (event, data) {
|
||||
if (!data) return;
|
||||
updateAppData();
|
||||
});
|
||||
|
||||
$rootScope.rootDataSource = courier.createSource('search')
|
||||
|
|
|
@ -11,6 +11,7 @@ define(function (require) {
|
|||
var modules = require('modules');
|
||||
var notify = require('notify/notify');
|
||||
|
||||
require('utils/rison');
|
||||
require('elasticsearch');
|
||||
require('angular-route');
|
||||
require('angular-bindonce');
|
||||
|
@ -29,7 +30,7 @@ define(function (require) {
|
|||
setup(function (err) {
|
||||
kibana
|
||||
// setup default routes
|
||||
.config(function ($routeProvider) {
|
||||
.config(function ($routeProvider, $provide) {
|
||||
$routeProvider
|
||||
.otherwise({
|
||||
redirectTo: '/' + configFile.defaultAppId
|
||||
|
|
19
src/kibana/services/state.js
Normal file
19
src/kibana/services/state.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
require('modules')
|
||||
.get('kibana/services')
|
||||
.service('state', function ($location) {
|
||||
this.set = function (state) {
|
||||
var search = $location.search();
|
||||
search._r = rison.encode(state);
|
||||
$location.search(search);
|
||||
return search;
|
||||
};
|
||||
|
||||
this.get = function () {
|
||||
var search = $location.search();
|
||||
return _.isUndefined(search._r) ? {} : rison.decode(search._r);
|
||||
};
|
||||
});
|
||||
});
|
504
src/kibana/utils/rison.js
Normal file
504
src/kibana/utils/rison.js
Normal file
|
@ -0,0 +1,504 @@
|
|||
/* jshint ignore:start */
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// the stringifier is based on
|
||||
// http://json.org/json.js as of 2006-04-28 from json.org
|
||||
// the parser is based on
|
||||
// http://osteele.com/sources/openlaszlo/json
|
||||
//
|
||||
|
||||
if (typeof rison == 'undefined')
|
||||
window.rison = {};
|
||||
|
||||
/**
|
||||
* rules for an uri encoder that is more tolerant than encodeURIComponent
|
||||
*
|
||||
* encodeURIComponent passes ~!*()-_.'
|
||||
*
|
||||
* we also allow ,:@$/
|
||||
*
|
||||
*/
|
||||
rison.uri_ok = { // ok in url paths and in form query args
|
||||
'~': true, '!': true, '*': true, '(': true, ')': true,
|
||||
'-': true, '_': true, '.': true, ',': true,
|
||||
':': true, '@': true, '$': true,
|
||||
"'": true, '/': true
|
||||
};
|
||||
|
||||
/*
|
||||
* we divide the uri-safe glyphs into three sets
|
||||
* <rison> - used by rison ' ! : ( ) ,
|
||||
* <reserved> - not common in strings, reserved * @ $ & ; =
|
||||
*
|
||||
* we define <identifier> as anything that's not forbidden
|
||||
*/
|
||||
|
||||
/**
|
||||
* punctuation characters that are legal inside ids.
|
||||
*/
|
||||
// this var isn't actually used
|
||||
//rison.idchar_punctuation = "_-./~";
|
||||
|
||||
(function () {
|
||||
var l = [];
|
||||
for (var hi = 0; hi < 16; hi++) {
|
||||
for (var lo = 0; lo < 16; lo++) {
|
||||
if (hi+lo == 0) continue;
|
||||
var c = String.fromCharCode(hi*16 + lo);
|
||||
if (! /\w|[-_.\/~]/.test(c))
|
||||
l.push('\\u00' + hi.toString(16) + lo.toString(16));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* characters that are illegal inside ids.
|
||||
* <rison> and <reserved> classes are illegal in ids.
|
||||
*
|
||||
*/
|
||||
rison.not_idchar = l.join('')
|
||||
//idcrx = new RegExp('[' + rison.not_idchar + ']');
|
||||
//console.log('NOT', (idcrx.test(' ')) );
|
||||
})();
|
||||
//rison.not_idchar = " \t\r\n\"<>[]{}'!=:(),*@$;&";
|
||||
rison.not_idchar = " '!:(),*@$";
|
||||
|
||||
|
||||
/**
|
||||
* characters that are illegal as the start of an id
|
||||
* this is so ids can't look like numbers.
|
||||
*/
|
||||
rison.not_idstart = "-0123456789";
|
||||
|
||||
|
||||
(function () {
|
||||
var idrx = '[^' + rison.not_idstart + rison.not_idchar +
|
||||
'][^' + rison.not_idchar + ']*';
|
||||
|
||||
rison.id_ok = new RegExp('^' + idrx + '$');
|
||||
|
||||
// regexp to find the end of an id when parsing
|
||||
// g flag on the regexp is necessary for iterative regexp.exec()
|
||||
rison.next_id = new RegExp(idrx, 'g');
|
||||
})();
|
||||
|
||||
/**
|
||||
* this is like encodeURIComponent() but quotes fewer characters.
|
||||
*
|
||||
* @see rison.uri_ok
|
||||
*
|
||||
* encodeURIComponent passes ~!*()-_.'
|
||||
* rison.quote also passes ,:@$/
|
||||
* and quotes " " as "+" instead of "%20"
|
||||
*/
|
||||
rison.quote = function(x) {
|
||||
if (/^[-A-Za-z0-9~!*()_.',:@$\/]*$/.test(x))
|
||||
return x;
|
||||
|
||||
return encodeURIComponent(x)
|
||||
.replace('%2C', ',', 'g')
|
||||
.replace('%3A', ':', 'g')
|
||||
.replace('%40', '@', 'g')
|
||||
.replace('%24', '$', 'g')
|
||||
.replace('%2F', '/', 'g')
|
||||
.replace('%20', '+', 'g');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// based on json.js 2006-04-28 from json.org
|
||||
// license: http://www.json.org/license.html
|
||||
//
|
||||
// hacked by nix for use in uris.
|
||||
//
|
||||
|
||||
(function () {
|
||||
var sq = { // url-ok but quoted in strings
|
||||
"'": true, '!': true
|
||||
},
|
||||
s = {
|
||||
array: function (x) {
|
||||
var a = ['!('], b, f, i, l = x.length, v;
|
||||
for (i = 0; i < l; i += 1) {
|
||||
v = x[i];
|
||||
f = s[typeof v];
|
||||
if (f) {
|
||||
v = f(v);
|
||||
if (typeof v == 'string') {
|
||||
if (b) {
|
||||
a[a.length] = ',';
|
||||
}
|
||||
a[a.length] = v;
|
||||
b = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
a[a.length] = ')';
|
||||
return a.join('');
|
||||
},
|
||||
'boolean': function (x) {
|
||||
if (x)
|
||||
return '!t';
|
||||
return '!f'
|
||||
},
|
||||
'null': function (x) {
|
||||
return "!n";
|
||||
},
|
||||
number: function (x) {
|
||||
if (!isFinite(x))
|
||||
return '!n';
|
||||
// strip '+' out of exponent, '-' is ok though
|
||||
return String(x).replace(/\+/,'');
|
||||
},
|
||||
object: function (x) {
|
||||
if (x) {
|
||||
if (x instanceof Array) {
|
||||
return s.array(x);
|
||||
}
|
||||
// WILL: will this work on non-Firefox browsers?
|
||||
if (typeof x.__prototype__ === 'object' && typeof x.__prototype__.encode_rison !== 'undefined')
|
||||
return x.encode_rison();
|
||||
|
||||
var a = ['('], b, f, i, v, ki, ks=[];
|
||||
for (i in x)
|
||||
ks[ks.length] = i;
|
||||
ks.sort();
|
||||
for (ki = 0; ki < ks.length; ki++) {
|
||||
i = ks[ki];
|
||||
v = x[i];
|
||||
f = s[typeof v];
|
||||
if (f) {
|
||||
v = f(v);
|
||||
if (typeof v == 'string') {
|
||||
if (b) {
|
||||
a[a.length] = ',';
|
||||
}
|
||||
a.push(s.string(i), ':', v);
|
||||
b = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
a[a.length] = ')';
|
||||
return a.join('');
|
||||
}
|
||||
return '!n';
|
||||
},
|
||||
string: function (x) {
|
||||
if (x == '')
|
||||
return "''";
|
||||
|
||||
if (rison.id_ok.test(x))
|
||||
return x;
|
||||
|
||||
x = x.replace(/(['!])/g, function(a, b) {
|
||||
if (sq[b]) return '!'+b;
|
||||
return b;
|
||||
});
|
||||
return "'" + x + "'";
|
||||
},
|
||||
undefined: function (x) {
|
||||
throw new Error("rison can't encode the undefined value");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* rison-encode a javascript structure
|
||||
*
|
||||
* implemementation based on Douglas Crockford's json.js:
|
||||
* http://json.org/json.js as of 2006-04-28 from json.org
|
||||
*
|
||||
*/
|
||||
rison.encode = function (v) {
|
||||
return s[typeof v](v);
|
||||
};
|
||||
|
||||
/**
|
||||
* rison-encode a javascript object without surrounding parens
|
||||
*
|
||||
*/
|
||||
rison.encode_object = function (v) {
|
||||
if (typeof v != 'object' || v === null || v instanceof Array)
|
||||
throw new Error("rison.encode_object expects an object argument");
|
||||
var r = s[typeof v](v);
|
||||
return r.substring(1, r.length-1);
|
||||
};
|
||||
|
||||
/**
|
||||
* rison-encode a javascript array without surrounding parens
|
||||
*
|
||||
*/
|
||||
rison.encode_array = function (v) {
|
||||
if (!(v instanceof Array))
|
||||
throw new Error("rison.encode_array expects an array argument");
|
||||
var r = s[typeof v](v);
|
||||
return r.substring(2, r.length-1);
|
||||
};
|
||||
|
||||
/**
|
||||
* rison-encode and uri-encode a javascript structure
|
||||
*
|
||||
*/
|
||||
rison.encode_uri = function (v) {
|
||||
return rison.quote(s[typeof v](v));
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// based on openlaszlo-json and hacked by nix for use in uris.
|
||||
//
|
||||
// Author: Oliver Steele
|
||||
// Copyright: Copyright 2006 Oliver Steele. All rights reserved.
|
||||
// Homepage: http://osteele.com/sources/openlaszlo/json
|
||||
// License: MIT License.
|
||||
// Version: 1.0
|
||||
|
||||
|
||||
/**
|
||||
* parse a rison string into a javascript structure.
|
||||
*
|
||||
* this is the simplest decoder entry point.
|
||||
*
|
||||
* based on Oliver Steele's OpenLaszlo-JSON
|
||||
* http://osteele.com/sources/openlaszlo/json
|
||||
*/
|
||||
rison.decode = function(r) {
|
||||
var errcb = function(e) { throw Error('rison decoder error: ' + e); };
|
||||
var p = new rison.parser(errcb);
|
||||
return p.parse(r);
|
||||
};
|
||||
|
||||
/**
|
||||
* parse an o-rison string into a javascript structure.
|
||||
*
|
||||
* this simply adds parentheses around the string before parsing.
|
||||
*/
|
||||
rison.decode_object = function(r) {
|
||||
return rison.decode('('+r+')');
|
||||
};
|
||||
|
||||
/**
|
||||
* parse an a-rison string into a javascript structure.
|
||||
*
|
||||
* this simply adds array markup around the string before parsing.
|
||||
*/
|
||||
rison.decode_array = function(r) {
|
||||
return rison.decode('!('+r+')');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* construct a new parser object for reuse.
|
||||
*
|
||||
* @constructor
|
||||
* @class A Rison parser class. You should probably
|
||||
* use rison.decode instead.
|
||||
* @see rison.decode
|
||||
*/
|
||||
rison.parser = function (errcb) {
|
||||
this.errorHandler = errcb;
|
||||
};
|
||||
|
||||
/**
|
||||
* a string containing acceptable whitespace characters.
|
||||
* by default the rison decoder tolerates no whitespace.
|
||||
* to accept whitespace set rison.parser.WHITESPACE = " \t\n\r\f";
|
||||
*/
|
||||
rison.parser.WHITESPACE = "";
|
||||
|
||||
// expose this as-is?
|
||||
rison.parser.prototype.setOptions = function (options) {
|
||||
if (options['errorHandler'])
|
||||
this.errorHandler = options.errorHandler;
|
||||
};
|
||||
|
||||
/**
|
||||
* parse a rison string into a javascript structure.
|
||||
*/
|
||||
rison.parser.prototype.parse = function (str) {
|
||||
this.string = str;
|
||||
this.index = 0;
|
||||
this.message = null;
|
||||
var value = this.readValue();
|
||||
if (!this.message && this.next())
|
||||
value = this.error("unable to parse string as rison: '" + rison.encode(str) + "'");
|
||||
if (this.message && this.errorHandler)
|
||||
this.errorHandler(this.message, this.index);
|
||||
return value;
|
||||
};
|
||||
|
||||
rison.parser.prototype.error = function (message) {
|
||||
if (typeof(console) != 'undefined')
|
||||
console.log('rison parser error: ', message);
|
||||
this.message = message;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
rison.parser.prototype.readValue = function () {
|
||||
var c = this.next();
|
||||
var fn = c && this.table[c];
|
||||
|
||||
if (fn)
|
||||
return fn.apply(this);
|
||||
|
||||
// fell through table, parse as an id
|
||||
|
||||
var s = this.string;
|
||||
var i = this.index-1;
|
||||
|
||||
// Regexp.lastIndex may not work right in IE before 5.5?
|
||||
// g flag on the regexp is also necessary
|
||||
rison.next_id.lastIndex = i;
|
||||
var m = rison.next_id.exec(s);
|
||||
|
||||
// console.log('matched id', i, r.lastIndex);
|
||||
|
||||
if (m.length > 0) {
|
||||
var id = m[0];
|
||||
this.index = i+id.length;
|
||||
return id; // a string
|
||||
}
|
||||
|
||||
if (c) return this.error("invalid character: '" + c + "'");
|
||||
return this.error("empty expression");
|
||||
}
|
||||
|
||||
rison.parser.parse_array = function (parser) {
|
||||
var ar = [];
|
||||
var c;
|
||||
while ((c = parser.next()) != ')') {
|
||||
if (!c) return parser.error("unmatched '!('");
|
||||
if (ar.length) {
|
||||
if (c != ',')
|
||||
parser.error("missing ','");
|
||||
} else if (c == ',') {
|
||||
return parser.error("extra ','");
|
||||
} else
|
||||
--parser.index;
|
||||
var n = parser.readValue();
|
||||
if (typeof n == "undefined") return undefined;
|
||||
ar.push(n);
|
||||
}
|
||||
return ar;
|
||||
};
|
||||
|
||||
rison.parser.bangs = {
|
||||
t: true,
|
||||
f: false,
|
||||
n: null,
|
||||
'(': rison.parser.parse_array
|
||||
}
|
||||
|
||||
rison.parser.prototype.table = {
|
||||
'!': function () {
|
||||
var s = this.string;
|
||||
var c = s.charAt(this.index++);
|
||||
if (!c) return this.error('"!" at end of input');
|
||||
var x = rison.parser.bangs[c];
|
||||
if (typeof(x) == 'function') {
|
||||
return x.call(null, this);
|
||||
} else if (typeof(x) == 'undefined') {
|
||||
return this.error('unknown literal: "!' + c + '"');
|
||||
}
|
||||
return x;
|
||||
},
|
||||
'(': function () {
|
||||
var o = {};
|
||||
var c;
|
||||
var count = 0;
|
||||
while ((c = this.next()) != ')') {
|
||||
if (count) {
|
||||
if (c != ',')
|
||||
this.error("missing ','");
|
||||
} else if (c == ',') {
|
||||
return this.error("extra ','");
|
||||
} else
|
||||
--this.index;
|
||||
var k = this.readValue();
|
||||
if (typeof k == "undefined") return undefined;
|
||||
if (this.next() != ':') return this.error("missing ':'");
|
||||
var v = this.readValue();
|
||||
if (typeof v == "undefined") return undefined;
|
||||
o[k] = v;
|
||||
count++;
|
||||
}
|
||||
return o;
|
||||
},
|
||||
"'": function () {
|
||||
var s = this.string;
|
||||
var i = this.index;
|
||||
var start = i;
|
||||
var segments = [];
|
||||
var c;
|
||||
while ((c = s.charAt(i++)) != "'") {
|
||||
//if (i == s.length) return this.error('unmatched "\'"');
|
||||
if (!c) return this.error('unmatched "\'"');
|
||||
if (c == '!') {
|
||||
if (start < i-1)
|
||||
segments.push(s.slice(start, i-1));
|
||||
c = s.charAt(i++);
|
||||
if ("!'".indexOf(c) >= 0) {
|
||||
segments.push(c);
|
||||
} else {
|
||||
return this.error('invalid string escape: "!'+c+'"');
|
||||
}
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
if (start < i-1)
|
||||
segments.push(s.slice(start, i-1));
|
||||
this.index = i;
|
||||
return segments.length == 1 ? segments[0] : segments.join('');
|
||||
},
|
||||
// Also any digit. The statement that follows this table
|
||||
// definition fills in the digits.
|
||||
'-': function () {
|
||||
var s = this.string;
|
||||
var i = this.index;
|
||||
var start = i-1;
|
||||
var state = 'int';
|
||||
var permittedSigns = '-';
|
||||
var transitions = {
|
||||
'int+.': 'frac',
|
||||
'int+e': 'exp',
|
||||
'frac+e': 'exp'
|
||||
};
|
||||
do {
|
||||
var c = s.charAt(i++);
|
||||
if (!c) break;
|
||||
if ('0' <= c && c <= '9') continue;
|
||||
if (permittedSigns.indexOf(c) >= 0) {
|
||||
permittedSigns = '';
|
||||
continue;
|
||||
}
|
||||
state = transitions[state+'+'+c.toLowerCase()];
|
||||
if (state == 'exp') permittedSigns = '-';
|
||||
} while (state);
|
||||
this.index = --i;
|
||||
s = s.slice(start, i)
|
||||
if (s == '-') return this.error("invalid number");
|
||||
return Number(s);
|
||||
}
|
||||
};
|
||||
// copy table['-'] to each of table[i] | i <- '0'..'9':
|
||||
(function (table) {
|
||||
for (var i = 0; i <= 9; i++)
|
||||
table[String(i)] = table['-'];
|
||||
})(rison.parser.prototype.table);
|
||||
|
||||
// return the next non-whitespace character, or undefined
|
||||
rison.parser.prototype.next = function () {
|
||||
var s = this.string;
|
||||
var i = this.index;
|
||||
do {
|
||||
if (i == s.length) return undefined;
|
||||
var c = s.charAt(i++);
|
||||
} while (rison.parser.WHITESPACE.indexOf(c) >= 0);
|
||||
this.index = i;
|
||||
return c;
|
||||
};
|
||||
|
||||
/* jshint ignore:end */
|
Loading…
Add table
Add a link
Reference in a new issue