From 69e060375c136467af8b5fd69e8da2508aa8f1b2 Mon Sep 17 00:00:00 2001 From: Tyler Stewart Date: Tue, 4 Aug 2020 13:20:29 -0600 Subject: [PATCH] feat: expose array-like methods for searching data (#4) * feat: expose array-like methods for searching data Instead of limiting an end-user to a string search, enable `predicate` functions to `find` and `filter` values. BREAKING CHANGE: exposed api changed from string search to predicate methods * docs: update readme * feat: data no longer lowercased or characters removed * fix: correct function declaration name Co-authored-by: forstermatth Co-authored-by: forstermatth --- package.json | 7 +++++-- readme.md | 18 ++++++++++++------ src/generate.js | 7 +++---- src/index.d.ts | 8 ++++++-- src/index.js | 37 +++++++++++++++---------------------- src/util.js | 9 --------- test/util.spec.js | 10 ---------- 7 files changed, 41 insertions(+), 55 deletions(-) delete mode 100644 src/util.js delete mode 100644 test/util.spec.js diff --git a/package.json b/package.json index 2be155b..0996ca5 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,12 @@ { "name": "canadian-city-timezones", - "version": "1.0.0", + "version": "2.0.0", "description": "Searchable timezones for all Canadian cities, towns, townships, villages, hamlets, and municipalities.", "main": "src/index.js", "types": "src/index.d.ts", "files": [ "src/index.js", "src/index.d.ts", - "src/util.js", "src/data.csv" ], "scripts": { @@ -20,6 +19,10 @@ "timezones" ], "author": "trs", + "repository": { + "type": "git", + "url": "https://github.com/autovance/canadian-city-timezones" + }, "license": "MIT", "devDependencies": { "csv-parser": "^2.3.3", diff --git a/readme.md b/readme.md index ba26e67..841c575 100644 --- a/readme.md +++ b/readme.md @@ -11,9 +11,9 @@ `npm install canadian-city-timezones` ```ts -import {find, findAll} from 'canadian-city-timezones'; +import {find} from 'canadian-city-timezones'; -const result = await find('Lethbridge Alberta'); +const result = await find((city, province) => city === 'Lethbridge' && province === 'Alberta'); result.city // Lethbridge result.province // Alberta result.timezone // America/Edmonton @@ -24,16 +24,22 @@ result.timezone // America/Edmonton ### Methods ```ts -find(query: string): Promise +find(predicate: (value: TimezoneResult) => boolean): Promise ``` -Returns the first matching result for the given string. +Returns the first matching result for the given predicate. ```ts -findAll(query: string): Promise +filter(predicate: (value: TimezoneResult) => boolean): AsyncGenerator ``` -Returns all matching results for the given string. +Yields all matching results for the given predicate. + +```ts +values(): AsyncGenerator +``` + +Yields all values. ### Interfaces diff --git a/src/generate.js b/src/generate.js index 9c301b0..f827363 100644 --- a/src/generate.js +++ b/src/generate.js @@ -8,7 +8,6 @@ const fetch = require('node-fetch'); const csv = require('csv-parser'); const geoTz = require('geo-tz'); const removeAccents = require('remove-accents'); -const {removeSpecialCharacters} = require('./util'); const mkdirAsync = promisify(mkdir); const pipelineAsync = promisify(pipeline); @@ -31,7 +30,7 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function * getCityData() { const req = await fetch(CAN_CITY_LIST); - // Download file first as the csv parser would prematurely end being piped it directly + // Download file first as the csv parser would prematurely end being piped in directly await pipelineAsync( req.body, createWriteStream('gc.csv') @@ -90,8 +89,8 @@ async function * generateData() { } yield [ - removeSpecialCharacters(cityData.name), - removeSpecialCharacters(cityData.province), + cityData.name, + cityData.province, timezone ].join(','); diff --git a/src/index.d.ts b/src/index.d.ts index 6675ab6..5943df2 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -6,6 +6,10 @@ declare interface TimezoneResult { timezone: string; } -declare function find(query: string): Promise; +declare type Predicate = (data: TimezoneResult) => boolean; -declare function findAll(query: string): Promise; +declare function values(): AsyncGenerator; + +declare function find(predicate: Predicate): Promise; + +declare function filter(predicate: Predicate): AsyncGenerator; diff --git a/src/index.js b/src/index.js index 6557d26..864dc72 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,6 @@ const path = require('path'); const {createReadStream} = require('fs'); const {createInterface} = require('readline'); -const {removeSpecialCharacters} = require('./util'); const CITY_MAP_DATA = path.join(__dirname, 'data.csv'); @@ -28,43 +27,37 @@ async function * getDataIterator() { } } -const isPartialMatchFactory = (query) => { - const searchItems = query.split(' ').map((item) => removeSpecialCharacters(item)); - - return (data) => { - const values = [ - data.city, - data.province - ]; - - return searchItems.every((item) => values.join().includes(item)); - } +async function* values() { + yield* getDataIterator(); } -async function find(query) { - const isPartialMatch = isPartialMatchFactory(query); +async function find(predicate) { + if (typeof predicate !== 'function') { + throw new TypeError(`${String(predicate)} is not a function`); + } for await (const data of getDataIterator()) { - if (isPartialMatch(data)) { + if (predicate(data)) { return data; } } return null; } -async function findAll(query) { - const isPartialMatch = isPartialMatchFactory(query); +async function* filter(predicate) { + if (typeof predicate !== 'function') { + throw new TypeError(`${String(predicate)} is not a function`); + } - const results = []; for await (const data of getDataIterator()) { - if (isPartialMatch(data)) { - results.push(data); + if (predicate(data)) { + yield data; } } - return results; } module.exports = { + values, find, - findAll + filter }; diff --git a/src/util.js b/src/util.js deleted file mode 100644 index c118d7c..0000000 --- a/src/util.js +++ /dev/null @@ -1,9 +0,0 @@ -const VALID_CHARACTERS_REGEX = /[^a-zA-Z0-9 ]/ig; - -function removeSpecialCharacters(value = '') { - return value.replace(VALID_CHARACTERS_REGEX, '').toLocaleLowerCase(); -} - -module.exports = { - removeSpecialCharacters -}; diff --git a/test/util.spec.js b/test/util.spec.js deleted file mode 100644 index ccb4dc2..0000000 --- a/test/util.spec.js +++ /dev/null @@ -1,10 +0,0 @@ -const {removeSpecialCharacters} = require('../src/util'); - -describe('util', () => { - describe('removeSpecialCharacters', () => { - it('retains letters, numbers, and spaces', () => { - const result = removeSpecialCharacters('hello world! 123\'s-!@#$'); - expect(result).toBe('hello world 123s'); - }); - }); -});