Move parse/unparse from narrow to Filter.

I'm trying to move well-isolated methods out of narrow.js, so that
narrow.js is more strongly focused on UI/ajax interactions and
big, heavy lifting stuff.  The logical home for parse/unparse
seemed to be Filter, and they brought along two private methods
with them.  The big code moves involved trivial follow ups
like s/exports/Filter/.

(imported from commit ace0fe5aa1c7abce0334d079ba9eb8d9a57bd10f)
This commit is contained in:
Steve Howell
2013-08-21 19:29:28 -04:00
parent fece7ce70e
commit c4d598b36e
7 changed files with 97 additions and 97 deletions

View File

@@ -47,6 +47,81 @@ Filter.canonicalize_tuple = function (tuple) {
return [operator, operand]; return [operator, operand];
}; };
/* We use a variant of URI encoding which looks reasonably
nice and still handles unambiguously cases such as
spaces in operands.
This is just for the search bar, not for saving the
narrow in the URL fragment. There we do use full
URI encoding to avoid problematic characters. */
function encodeOperand(operand) {
return operand.replace(/%/g, '%25')
.replace(/\+/g, '%2B')
.replace(/ /g, '+');
}
function decodeOperand(encoded) {
return util.robust_uri_decode(encoded.replace(/\+/g, ' '));
}
// Parse a string into a list of operators (see below).
Filter.parse = function (str) {
var operators = [];
var search_term = [];
var matches = str.match(/"[^"]+"|\S+/g);
if (matches === null) {
return operators;
}
_.each(matches, function (token) {
var parts, operator;
if (token.length === 0) {
return;
}
parts = token.split(':');
if (token[0] === '"' || parts.length === 1) {
// Looks like a normal search term.
search_term.push(token);
} else {
// Looks like an operator.
// FIXME: Should we skip unknown operator names here?
operator = parts.shift();
operators.push([operator, decodeOperand(parts.join(':'))]);
}
});
// NB: Callers of 'parse' can assume that the 'search' operator is last.
if (search_term.length > 0) {
operators.push(['search', search_term.join(' ')]);
}
return operators;
};
/* Convert a list of operators to a string.
Each operator is a key-value pair like
['subject', 'my amazing subject']
These are not keys in a JavaScript object, because we
might need to support multiple operators of the same type.
*/
Filter.unparse = function (operators) {
var parts = _.map(operators, function (elem) {
var operator = elem[0];
if (operator === 'search') {
// Search terms are the catch-all case.
// All tokens that don't start with a known operator and
// a colon are glued together to form a search term.
return elem[1];
} else {
return elem[0] + ':' + encodeOperand(elem[1]);
}
});
return parts.join(' ');
};
Filter.prototype = { Filter.prototype = {
predicate: function Filter_predicate() { predicate: function Filter_predicate() {
if (this._predicate === undefined) { if (this._predicate === undefined) {

View File

@@ -39,48 +39,8 @@ exports.public_operators = function () {
return current_filter.public_operators(); return current_filter.public_operators();
}; };
/* We use a variant of URI encoding which looks reasonably
nice and still handles unambiguously cases such as
spaces in operands.
This is just for the search bar, not for saving the
narrow in the URL fragment. There we do use full
URI encoding to avoid problematic characters. */
function encodeOperand(operand) {
return operand.replace(/%/g, '%25')
.replace(/\+/g, '%2B')
.replace(/ /g, '+');
}
function decodeOperand(encoded) {
return util.robust_uri_decode(encoded.replace(/\+/g, ' '));
}
/* Convert a list of operators to a string.
Each operator is a key-value pair like
['subject', 'my amazing subject']
These are not keys in a JavaScript object, because we
might need to support multiple operators of the same type.
*/
exports.unparse = function (operators) {
var parts = _.map(operators, function (elem) {
var operator = elem[0];
if (operator === 'search') {
// Search terms are the catch-all case.
// All tokens that don't start with a known operator and
// a colon are glued together to form a search term.
return elem[1];
} else {
return elem[0] + ':' + encodeOperand(elem[1]);
}
});
return parts.join(' ');
};
exports.search_string = function () { exports.search_string = function () {
return exports.unparse(exports.operators()); return Filter.unparse(exports.operators());
}; };
// Collect operators which appear only once into an object, // Collect operators which appear only once into an object,
@@ -125,37 +85,6 @@ exports.set_compose_defaults = function (opts) {
} }
}; };
// Parse a string into a list of operators (see below).
exports.parse = function (str) {
var operators = [];
var search_term = [];
var matches = str.match(/"[^"]+"|\S+/g);
if (matches === null) {
return operators;
}
_.each(matches, function (token) {
var parts, operator;
if (token.length === 0) {
return;
}
parts = token.split(':');
if (token[0] === '"' || parts.length === 1) {
// Looks like a normal search term.
search_term.push(token);
} else {
// Looks like an operator.
// FIXME: Should we skip unknown operator names here?
operator = parts.shift();
operators.push([operator, decodeOperand(parts.join(':'))]);
}
});
// NB: Callers of 'parse' can assume that the 'search' operator is last.
if (search_term.length > 0) {
operators.push(['search', search_term.join(' ')]);
}
return operators;
};
exports.stream = function () { exports.stream = function () {
if (current_filter === undefined) { if (current_filter === undefined) {
return undefined; return undefined;
@@ -305,7 +234,7 @@ exports.activate = function (operators, opts) {
} }
// Put the narrow operators in the search bar. // Put the narrow operators in the search bar.
$('#search_query').val(exports.unparse(operators)); $('#search_query').val(Filter.unparse(operators));
search.update_button_visibility(); search.update_button_visibility();
compose.update_recipient_on_narrow(); compose.update_recipient_on_narrow();
compose_fade.update_message_list(); compose_fade.update_message_list();

View File

@@ -5,7 +5,7 @@ var exports = {};
function narrow_or_search_for_term(search_string) { function narrow_or_search_for_term(search_string) {
var search_query_box = $("#search_query"); var search_query_box = $("#search_query");
ui.change_tab_to('#home'); ui.change_tab_to('#home');
var operators = narrow.parse(search_string); var operators = Filter.parse(search_string);
narrow.activate(operators, {trigger: 'search'}); narrow.activate(operators, {trigger: 'search'});
// It's sort of annoying that this is not in a position to // It's sort of annoying that this is not in a position to
@@ -90,7 +90,7 @@ exports.initialize = function () {
// operators. (The reason the other actions don't call // operators. (The reason the other actions don't call
// this codepath is that they first all blur the box to // this codepath is that they first all blur the box to
// indicate that they've done what they need to do) // indicate that they've done what they need to do)
narrow.activate(narrow.parse(search_query_box.val())); narrow.activate(Filter.parse(search_query_box.val()));
search_query_box.blur(); search_query_box.blur();
update_buttons_with_focus(false); update_buttons_with_focus(false);
} }

View File

@@ -124,7 +124,7 @@ function get_stream_suggestions(operators) {
var prefix = 'Narrow to stream'; var prefix = 'Narrow to stream';
var highlighted_stream = typeahead_helper.highlight_query_in_phrase(query, stream); var highlighted_stream = typeahead_helper.highlight_query_in_phrase(query, stream);
var description = prefix + ' ' + highlighted_stream; var description = prefix + ' ' + highlighted_stream;
var search_string = narrow.unparse([['stream', stream]]); var search_string = Filter.unparse([['stream', stream]]);
return {description: description, search_string: search_string}; return {description: description, search_string: search_string};
}); });
@@ -178,7 +178,7 @@ function get_private_suggestions(all_people, operators) {
var suggestions = _.map(people, function (person) { var suggestions = _.map(people, function (person) {
var name = highlight_person(query, person); var name = highlight_person(query, person);
var description = 'Narrow to private messages with ' + name; var description = 'Narrow to private messages with ' + name;
var search_string = narrow.unparse([['pm-with', person.email]]); var search_string = Filter.unparse([['pm-with', person.email]]);
return {description: description, search_string: search_string}; return {description: description, search_string: search_string};
}); });
@@ -214,7 +214,7 @@ function get_person_suggestions(all_people, query, prefix, operator) {
function get_default_suggestion(operators) { function get_default_suggestion(operators) {
// Here we return the canonical suggestion for the full query that the // Here we return the canonical suggestion for the full query that the
// user typed. (The caller passes us the parsed query as "operators".) // user typed. (The caller passes us the parsed query as "operators".)
var search_string = narrow.unparse(operators); var search_string = Filter.unparse(operators);
var description = describe(operators); var description = describe(operators);
description = Handlebars.Utils.escapeExpression(description); description = Handlebars.Utils.escapeExpression(description);
return {description: description, search_string: search_string}; return {description: description, search_string: search_string};
@@ -244,7 +244,7 @@ function get_topic_suggestions(query_operators) {
// If somebody explicitly types search:, then we might // If somebody explicitly types search:, then we might
// not want to suggest topics, but I feel this is a very // not want to suggest topics, but I feel this is a very
// minor issue, and narrow.parse() is currently lossy // minor issue, and Filter.parse() is currently lossy
// in terms of telling us whether they provided the operator, // in terms of telling us whether they provided the operator,
// i.e. "foo" and "search:foo" both become [['search', 'foo']]. // i.e. "foo" and "search:foo" both become [['search', 'foo']].
switch (operator) { switch (operator) {
@@ -312,7 +312,7 @@ function get_topic_suggestions(query_operators) {
return _.map(topics, function (topic) { return _.map(topics, function (topic) {
var topic_operator = ['topic', topic]; var topic_operator = ['topic', topic];
var operators = query_operators.concat([topic_operator]); var operators = query_operators.concat([topic_operator]);
var search_string = narrow.unparse(operators); var search_string = Filter.unparse(operators);
var description = describe(operators); var description = describe(operators);
return {description: description, search_string: search_string}; return {description: description, search_string: search_string};
}); });
@@ -331,7 +331,7 @@ function get_operator_subset_suggestions(query, operators) {
for (i = operators.length - 1; i >= 1; --i) { for (i = operators.length - 1; i >= 1; --i) {
var subset = operators.slice(0, i); var subset = operators.slice(0, i);
var search_string = narrow.unparse(subset); var search_string = Filter.unparse(subset);
var description = describe(subset); var description = describe(subset);
var suggestion = {description: description, search_string: search_string}; var suggestion = {description: description, search_string: search_string};
suggestions.push(suggestion); suggestions.push(suggestion);
@@ -400,7 +400,7 @@ exports.get_suggestions = function (query) {
var suggestions; var suggestions;
// Add an entry for narrow by operators. // Add an entry for narrow by operators.
var operators = narrow.parse(query); var operators = Filter.parse(query);
suggestion = get_default_suggestion(operators); suggestion = get_default_suggestion(operators);
result = [suggestion]; result = [suggestion];

View File

@@ -53,5 +53,15 @@ var Filter = require('js/filter.js');
assert(!predicate({type: 'stream', stream: 'foo', subject: 'whatever'})); assert(!predicate({type: 'stream', stream: 'foo', subject: 'whatever'}));
}()); }());
(function test_parse_and_unparse() {
var string ='stream:Foo topic:Bar yo';
var operators = [['stream', 'Foo'], ['topic', 'Bar'], ['search', 'yo']];
assert.deepEqual(Filter.parse(string), operators);
string = 'stream:Foo topic:Bar yo';
assert.deepEqual(Filter.unparse(operators), string);
}());

View File

@@ -12,16 +12,6 @@ var narrow = require('js/narrow.js');
var Filter = global.Filter; var Filter = global.Filter;
var stream_data = global.stream_data; var stream_data = global.stream_data;
(function test_parse_and_unparse() {
var string ='stream:Foo topic:Bar yo';
var operators = [['stream', 'Foo'], ['topic', 'Bar'], ['search', 'yo']];
assert.deepEqual(narrow.parse(string), operators);
string = 'stream:Foo topic:Bar yo';
assert.deepEqual(narrow.unparse(operators), string);
}());
(function test_stream() { (function test_stream() {
var operators = [['stream', 'Foo'], ['topic', 'Bar'], ['search', 'yo']]; var operators = [['stream', 'Foo'], ['topic', 'Bar'], ['search', 'yo']];
narrow._set_current_filter(new Filter(operators)); narrow._set_current_filter(new Filter(operators));

View File

@@ -30,11 +30,7 @@ set_global('stream_data', {
get_name: stream_data.get_name get_name: stream_data.get_name
}); });
var narrow = require('js/narrow.js'); set_global('narrow', {});
set_global('narrow', {
parse: narrow.parse,
unparse: narrow.unparse
});
(function test_basic_get_suggestions() { (function test_basic_get_suggestions() {
var query = 'fred'; var query = 'fred';