From bc407724403cf826ef086af2fd18095c6a34d7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 5 Oct 2017 19:01:30 -0700 Subject: [PATCH 1/2] Accept callback for formatting tokens Added a formatToken option to compile, getWayName, and tokenize that gives the client an opportunity to manipulate a road name or other token value after any transformations built into this library but before the value is inserted into the overall instruction string. --- .eslintrc.json | 7 ++++++- CHANGELOG.md | 4 ++++ Readme.md | 17 ++++++++++++++++- index.js | 34 ++++++++++++++++++++++++---------- test/index_test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 12 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 009b344cf..7ce88f7c4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -82,7 +82,12 @@ "max-len": "off", "max-lines": "off", "max-nested-callbacks": "error", - "max-params": "error", + "max-params": [ + "error", + { + "max": 4 + } + ], "max-statements": "off", "max-statements-per-line": "off", "multiline-ternary": "off", diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ba17e3ba..7c1c26023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. For change log formatting, see http://keepachangelog.com/ +## master + +- Added a `formatToken` option to `compile`, `getWayName`, and `tokenize` that allows you to manipulate any token value after any grammar or capitalization rules are applied but before the value is inserted into the instruction. [#170](https://github.com/Project-OSRM/osrm-text-instructions/pull/170) + ## 0.9.0 2017-10-05 - Added `getBestMatchingLanguage` for determining the closest available language. Pass a user locale into this method before passing the return value into `compile`. [#168](https://github.com/Project-OSRM/osrm-text-instructions/pull/168) diff --git a/Readme.md b/Readme.md index f00c36d81..f61e4c4f8 100644 --- a/Readme.md +++ b/Readme.md @@ -50,7 +50,22 @@ parameter | required? | values | description ---|----|----|--- `language` | required | `en` `de` `zh-Hans` `fr` `nl` `ru` [and more](https://github.com/Project-OSRM/osrm-text-instructions/tree/master/languages/translations/) | Compiling instructions for the selected language code. `step` | required | [OSRM route step object](https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md#routestep-object) | The RouteStep as it comes out of OSRM -`options` | optional | Object with 2 keys: `legIndex` and `legCount`, both having integer values. Used for giving instructions for arriving at waypoints. +`options` | optional | Object | See [below](#options) + +##### Options + +key | type | description +---|----|----|--- +`legCount` | integer | Number of legs in the route +`legIndex` | integer | Zero-based index of the leg containing the step; together with `legIndex`, this option determines whether an arrival instruction indicates which waypoint the user has arrived at +`formatToken` | function | Function that formats the given token value after grammaticalization and capitalization but before the value is inserted into the instruction string + +`formatToken` takes two parameters: + +* `token`: A string that indicates the kind of token, such as `way_name` or `direction` +* `value`: A grammatical string for this token, capitalized if the token appears at the beginning of the instruction + +and returns a string. ### Development #### Architecture diff --git a/index.js b/index.js index 1d432c359..c97d14345 100644 --- a/index.js +++ b/index.js @@ -98,7 +98,7 @@ module.exports = function(version, _options) { wayName = this.tokenize(language, phrase, { name: name, ref: ref - }); + }, options); } else if (name && ref && wayMotorway && (/\d/).test(ref)) { wayName = ref; } else if (!name && ref) { @@ -204,7 +204,7 @@ module.exports = function(version, _options) { 'nth': nthWaypoint }; - return this.tokenize(language, instruction, replaceTokens); + return this.tokenize(language, instruction, replaceTokens, options); }, grammarize: function(language, name, grammar) { if (!language) throw new Error('No language code provided'); @@ -226,22 +226,36 @@ module.exports = function(version, _options) { return name; }, - tokenize: function(language, instruction, tokens) { + tokenize: function(language, instruction, tokens, options) { if (!language) throw new Error('No language code provided'); // Keep this function context to use in inline function below (no arrow functions in ES4) var that = this; - var output = instruction.replace(/\{(\w+):?(\w+)?\}/g, function(token, tag, grammar) { - var name = tokens[tag]; - if (typeof name !== 'undefined') { - return that.grammarize(language, name, grammar); - } + var startedWithToken = false; + var output = instruction.replace(/\{(\w+)(?::(\w+))?\}/g, function(token, tag, grammar, offset) { + var value = tokens[tag]; // Return unknown token unchanged - return token; + if (typeof value === 'undefined') { + return token; + } + + value = that.grammarize(language, value, grammar); + + // If this token appears at the beginning of the instruction, capitalize it. + if (offset === 0 && instructions[language].meta.capitalizeFirstLetter) { + startedWithToken = true; + value = that.capitalizeFirstLetter(language, value); + } + + if (options && options.formatToken) { + value = options.formatToken(tag, value); + } + + return value; }) .replace(/ {2}/g, ' '); // remove excess spaces - if (instructions[language].meta.capitalizeFirstLetter) { + if (!startedWithToken && instructions[language].meta.capitalizeFirstLetter) { return this.capitalizeFirstLetter(language, output); } diff --git a/test/index_test.js b/test/index_test.js index 414bc9189..1acc1de1b 100644 --- a/test/index_test.js +++ b/test/index_test.js @@ -1,3 +1,4 @@ +/* eslint max-lines: "off" */ var path = require('path'); var fs = require('fs'); var tape = require('tape'); @@ -34,6 +35,47 @@ tape.test('v5 tokenize', function(assert) { }); assert.equal(missingSecond, 'Can osrm {second}', 'does not replace tokens which are not provided'); + var formatsTokens = v5Compiler.tokenize('en', 'Take me {destination}, {way_name}', { + destination: 'home', + 'way_name': 'Country Road' + }, { + formatToken: function (token, value) { + if (token === 'destination') { + return '' + value + ''; + } + if (token === 'name' || token === 'way_name' || token === 'rotary_name') { + return value.replace('Road', 'Road'); + } + + return value; + } + }); + assert.equal(formatsTokens, 'Take me home, Country Road', + 'Formats tokens'); + + var capitalizesTokens = v5Compiler.tokenize('en', '{modifier} turns are prohibited here', { + modifier: 'left' + }, { + formatToken: function (token, value) { + if (token === 'modifier') { + return '' + value + ''; + } + + return value; + } + }); + assert.equal(capitalizesTokens, 'Left turns are prohibited here', + 'Capitalizes tokens before formatting'); + + var formatsGrammaticalTokens = v5Compiler.tokenize('ru', 'Плавно поверните налево на {way_name:accusative}', { + 'way_name': 'Бармалеева улица' + }, { + formatToken: function (token, value) { + return token === 'way_name' ? value.toLocaleUpperCase('ru') : value; + } + }); + assert.equal(formatsGrammaticalTokens, 'Плавно поверните налево на БАРМАЛЕЕВУ УЛИЦУ', + 'Formats tokens after grammaticalization but before insertion'); assert.end(); }); From 2e86fde0efa1c6e8f10d32a4e194762f9125275b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 6 Oct 2017 13:35:01 -0700 Subject: [PATCH 2/2] Removed tokenizedInstruction --- CHANGELOG.md | 1 + Readme.md | 5 ++--- index.js | 10 +--------- test/index_test.js | 19 ------------------- 4 files changed, 4 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1c26023..f587e88c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. For change ## master - Added a `formatToken` option to `compile`, `getWayName`, and `tokenize` that allows you to manipulate any token value after any grammar or capitalization rules are applied but before the value is inserted into the instruction. [#170](https://github.com/Project-OSRM/osrm-text-instructions/pull/170) +- Removed the `options` parameter to this module, including the `tokenizedInstruction` hook. Use the `formatToken` option instead. [#170](https://github.com/Project-OSRM/osrm-text-instructions/pull/170) ## 0.9.0 2017-10-05 diff --git a/Readme.md b/Readme.md index f61e4c4f8..2bc05309b 100644 --- a/Readme.md +++ b/Readme.md @@ -37,12 +37,11 @@ response.legs.forEach(function(leg) { }); ``` -#### Parameters `require('osrm-text-instructions')(version, options)` +#### Parameters `require('osrm-text-instructions')(version)` parameter | required? | values | description ---|----|----|--- `version` | required | `v5` | Major OSRM version -`options.hooks.tokenizedInstruction` | optional | `function(instruction)` | A function to change the raw instruction string before tokens are replaced. Useful to inject custom markup for tokens #### Parameters `compile(language, step, options)` @@ -58,7 +57,7 @@ key | type | description ---|----|----|--- `legCount` | integer | Number of legs in the route `legIndex` | integer | Zero-based index of the leg containing the step; together with `legIndex`, this option determines whether an arrival instruction indicates which waypoint the user has arrived at -`formatToken` | function | Function that formats the given token value after grammaticalization and capitalization but before the value is inserted into the instruction string +`formatToken` | function | Function that formats the given token value after grammaticalization and capitalization but before the value is inserted into the instruction string; useful for wrapping tokens in markup `formatToken` takes two parameters: diff --git a/index.js b/index.js index c97d14345..71cc23303 100644 --- a/index.js +++ b/index.js @@ -2,11 +2,7 @@ var languages = require('./languages'); var instructions = languages.instructions; var grammars = languages.grammars; -module.exports = function(version, _options) { - var opts = {}; - opts.hooks = {}; - opts.hooks.tokenizedInstruction = ((_options || {}).hooks || {}).tokenizedInstruction; - +module.exports = function(version) { Object.keys(instructions).forEach(function(code) { if (!instructions[code][version]) { throw 'invalid version ' + version + ': ' + code + ' not supported'; } }); @@ -184,10 +180,6 @@ module.exports = function(version, _options) { instruction = instructionObject.default; } - if (opts.hooks.tokenizedInstruction) { - instruction = opts.hooks.tokenizedInstruction(instruction); - } - var nthWaypoint = options && options.legIndex >= 0 && options.legIndex !== options.legCount - 1 ? this.ordinalize(language, options.legIndex + 1) : ''; // Replace tokens diff --git a/test/index_test.js b/test/index_test.js index 1acc1de1b..a50b6486b 100644 --- a/test/index_test.js +++ b/test/index_test.js @@ -327,25 +327,6 @@ tape.test('v5 compile', function(t) { t.end(); }); - t.test('respects options.instructionStringHook', function(assert) { - var v5Compiler = compiler('v5', { - hooks: { - tokenizedInstruction: function(instruction) { - return instruction.replace('{way_name}', '{way_name}'); - } - } - }); - - assert.equal(v5Compiler.compile('en', { - maneuver: { - type: 'turn', - modifier: 'left' - }, - name: 'Way Name' - }), 'Turn left onto Way Name'); - assert.end(); - }); - t.test('fixtures match generated instructions', function(assert) { // pre-load instructions var version = 'v5';