diff --git a/format/products-bitmask.js b/format/products-bitmask.js deleted file mode 100644 index b3baab87..00000000 --- a/format/products-bitmask.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const createFormatBitmask = (allProducts) => { - const formatBitmask = (products) => { - if(Object.keys(products).length === 0) throw new Error('products filter must not be empty') - let bitmask = 0 - for (let product in products) { - if (!allProducts[product]) throw new Error('unknown product ' + product) - if (products[product] === true) bitmask += allProducts[product].bitmask - } - return bitmask - } - return formatBitmask -} - -module.exports = createFormatBitmask diff --git a/format/products-filter.js b/format/products-filter.js new file mode 100644 index 00000000..8ee8ffca --- /dev/null +++ b/format/products-filter.js @@ -0,0 +1,35 @@ +'use strict' + +const isObj = require('lodash/isObject') + +const hasProp = (o, k) => Object.prototype.hasOwnProperty.call(o, k) + +const createFormatProductsFilter = (profile) => { + const byProduct = {} + const defaultProducts = {} + for (let product of profile.products) { + byProduct[product.id] = product + defaultProducts[product.id] = product.default + } + + const formatProductsFilter = (filter) => { + if (!isObj(filter)) throw new Error('products filter must be an object') + filter = Object.assign({}, defaultProducts, filter) + + let res = 0 + for (let product in filter) { + if (!hasProp(filter, product) || filter[product] !== true) continue + if (!byProduct[product]) throw new Error('unknown product ' + product) + for (let bitmask of byProduct[product].bitmasks) res += bitmask + } + + return { + type: 'PROD', + mode: 'INC', + value: res + '' + } + } + return formatProductsFilter +} + +module.exports = createFormatProductsFilter diff --git a/index.js b/index.js index 76578b6f..96e8c9a0 100644 --- a/index.js +++ b/index.js @@ -2,16 +2,24 @@ const minBy = require('lodash/minBy') const maxBy = require('lodash/maxBy') +const isObj = require('lodash/isObject') -const validateProfile = require('./lib/validate-profile') const defaultProfile = require('./lib/default-profile') +const createParseBitmask = require('./parse/products-bitmask') +const createFormatProductsFilter = require('./format/products-filter') +const validateProfile = require('./lib/validate-profile') const _request = require('./lib/request') -const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o) const isNonEmptyString = str => 'string' === typeof str && str.length > 0 const createClient = (profile, request = _request) => { profile = Object.assign({}, defaultProfile, profile) + if (!profile.parseProducts) { + profile.parseProducts = createParseBitmask(profile) + } + if (!profile.formatProductsFilter) { + profile.formatProductsFilter = createFormatProductsFilter(profile) + } validateProfile(profile) const departures = (station, opt = {}) => { @@ -24,7 +32,7 @@ const createClient = (profile, request = _request) => { duration: 10 // show departures for the next n minutes }, opt) opt.when = opt.when || new Date() - const products = profile.formatProducts(opt.products || {}) + const products = profile.formatProductsFilter(opt.products || {}) const dir = opt.direction ? profile.formatStation(opt.direction) : null return request(profile, { @@ -90,7 +98,7 @@ const createClient = (profile, request = _request) => { opt.when = opt.when || new Date() const filters = [ - profile.formatProducts(opt.products || {}) + profile.formatProductsFilter(opt.products || {}) ] if ( opt.accessibility && @@ -317,7 +325,7 @@ const createClient = (profile, request = _request) => { perStep: Math.round(durationPerStep), ageOfReport: true, // todo: what is this? jnyFltrL: [ - profile.formatProducts(opt.products || {}) + profile.formatProductsFilter(opt.products || {}) ], trainPosMode: 'CALC' // todo: what is this? what about realtime? } diff --git a/lib/validate-profile.js b/lib/validate-profile.js index 19465c3f..21c86829 100644 --- a/lib/validate-profile.js +++ b/lib/validate-profile.js @@ -47,6 +47,27 @@ const validateProfile = (profile) => { throw new Error(`profile.${key} must not be null.`) } } + + if (!Array.isArray(profile.products)) { + throw new Error('profile.products must be an array.') + } + if (profile.products.length === 0) throw new Error('profile.products is empty.') + for (let product of profile.products) { + if ('string' !== typeof product.id) { + throw new Error('profile.products[].id must be a string.') + } + if ('boolean' !== typeof product.default) { + throw new Error('profile.products[].default must be a boolean.') + } + if (!Array.isArray(product.bitmasks)) { + throw new Error(product.id + '.bitmasks must be an array.') + } + for (let bitmask of product.bitmasks) { + if ('number' !== typeof bitmask) { + throw new Error(product.id + '.bitmasks[] must be a number.') + } + } + } } module.exports = validateProfile diff --git a/p/db/index.js b/p/db/index.js index d8cc5e6f..812cb94d 100644 --- a/p/db/index.js +++ b/p/db/index.js @@ -1,17 +1,12 @@ 'use strict' -const _createParseLine = require('../../parse/line') const _createParseJourney = require('../../parse/journey') const _formatStation = require('../../format/station') -const createParseBitmask = require('../../parse/products-bitmask') -const createFormatBitmask = require('../../format/products-bitmask') const {bike} = require('../../format/filters') -const modes = require('./modes') +const products = require('./products') const formatLoyaltyCard = require('./loyalty-cards').format -const formatBitmask = createFormatBitmask(modes) - const transformReqBody = (body) => { body.client = {id: 'DB', v: '16040000', type: 'IPH', name: 'DB Navigator'} body.ext = 'DB.R15.12.a' @@ -39,26 +34,6 @@ const transformJourneysQuery = (query, opt) => { return query } -const createParseLine = (profile, operators) => { - const parseLine = _createParseLine(profile, operators) - - const parseLineWithMode = (l) => { - const res = parseLine(l) - - res.mode = res.product = null - if ('class' in res) { - const data = modes.bitmasks[parseInt(res.class)] - if (data) { - res.mode = data.mode - res.product = data.product - } - } - - return res - } - return parseLineWithMode -} - const createParseJourney = (profile, stations, lines, remarks) => { const parseJourney = _createParseJourney(profile, stations, lines, remarks) @@ -103,27 +78,6 @@ const formatStation = (id) => { return _formatStation(id) } -const defaultProducts = { - suburban: true, - subway: true, - tram: true, - bus: true, - ferry: true, - national: true, - nationalExp: true, - regional: true, - regionalExp: true, - taxi: false -} -const formatProducts = (products) => { - products = Object.assign(Object.create(null), defaultProducts, products) - return { - type: 'PROD', - mode: 'INC', - value: formatBitmask(products) + '' - } -} - // todo: find option for absolute number of results const dbProfile = { @@ -137,15 +91,12 @@ const dbProfile = { transformReqBody, transformJourneysQuery, - products: modes.allProducts, + products: products, // todo: parseLocation - parseLine: createParseLine, - parseProducts: createParseBitmask(modes.allProducts, defaultProducts), parseJourney: createParseJourney, - formatStation, - formatProducts + formatStation } module.exports = dbProfile diff --git a/p/db/modes.js b/p/db/modes.js deleted file mode 100644 index dcca851f..00000000 --- a/p/db/modes.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict' - -// todo: https://gist.github.com/anonymous/d3323a5d2d6e159ed42b12afd0380434#file-haf_products-properties-L1-L95 -const m = { - nationalExp: { - bitmask: 1, - name: 'InterCityExpress', - short: 'ICE', - mode: 'train', - product: 'nationalExp' - }, - national: { - bitmask: 2, - name: 'InterCity & EuroCity', - short: 'IC/EC', - mode: 'train', - product: 'national' - }, - regionalExp: { - bitmask: 4, - name: 'RegionalExpress & InterRegio', - short: 'RE/IR', - mode: 'train', - product: 'regionalExp' - }, - regional: { - bitmask: 8, - name: 'Regio', - short: 'RB', - mode: 'train', - product: 'regional' - }, - suburban: { - bitmask: 16, - name: 'S-Bahn', - short: 'S', - mode: 'train', - product: 'suburban' - }, - bus: { - bitmask: 32, - name: 'Bus', - short: 'B', - mode: 'bus', - product: 'bus' - }, - ferry: { - bitmask: 64, - name: 'Ferry', - short: 'F', - mode: 'watercraft', - product: 'ferry' - }, - subway: { - bitmask: 128, - name: 'U-Bahn', - short: 'U', - mode: 'train', - product: 'subway' - }, - tram: { - bitmask: 256, - name: 'Tram', - short: 'T', - mode: 'tram', - product: 'tram' - }, - taxi: { - bitmask: 512, - name: 'Group Taxi', - short: 'Taxi', - mode: 'taxi', - product: 'taxi' - }, - unknown: { - bitmask: 0, - name: 'unknown', - short: '?', - product: 'unknown' - } -} - -m.bitmasks = [] -m.bitmasks[1] = m.nationalExp -m.bitmasks[2] = m.national -m.bitmasks[4] = m.regionalExp -m.bitmasks[8] = m.regional -m.bitmasks[16] = m.suburban -m.bitmasks[32] = m.bus -m.bitmasks[64] = m.ferry -m.bitmasks[128] = m.subway -m.bitmasks[256] = m.tram -m.bitmasks[512] = m.taxi - -m.allProducts = [ - m.nationalExp, - m.national, - m.regionalExp, - m.regional, - m.suburban, - m.bus, - m.ferry, - m.subway, - m.tram, - m.taxi -] - -module.exports = m diff --git a/p/db/products.js b/p/db/products.js new file mode 100644 index 00000000..fa683e2b --- /dev/null +++ b/p/db/products.js @@ -0,0 +1,85 @@ +'use strict' + +// todo: https://gist.github.com/anonymous/d3323a5d2d6e159ed42b12afd0380434#file-haf_products-properties-L1-L95 +module.exports = [ + { + id: 'nationalExp', + mode: 'train', + bitmasks: [1], + name: 'InterCityExpress', + short: 'ICE', + default: true + }, + { + id: 'national', + mode: 'train', + bitmasks: [2], + name: 'InterCity & EuroCity', + short: 'IC/EC', + default: true + }, + { + id: 'regionalExp', + mode: 'train', + bitmasks: [4], + name: 'RegionalExpress & InterRegio', + short: 'RE/IR', + default: true + }, + { + id: 'regional', + mode: 'train', + bitmasks: [8], + name: 'Regio', + short: 'RB', + default: true + }, + { + id: 'suburban', + mode: 'train', + bitmasks: [16], + name: 'S-Bahn', + short: 'S', + default: true + }, + { + id: 'bus', + mode: 'bus', + bitmasks: [32], + name: 'Bus', + short: 'B', + default: true + }, + { + id: 'ferry', + mode: 'watercraft', + bitmasks: [64], + name: 'Ferry', + short: 'F', + default: true + }, + { + id: 'subway', + mode: 'train', + bitmasks: [128], + name: 'U-Bahn', + short: 'U', + default: true + }, + { + id: 'tram', + mode: 'tram', + bitmasks: [256], + name: 'Tram', + short: 'T', + default: true + }, + { + id: 'taxi', + mode: 'taxi', + bitmasks: [512], + name: 'Group Taxi', + short: 'Taxi', + default: true + } +] diff --git a/p/insa/index.js b/p/insa/index.js index 8b7bea1b..ed562dee 100644 --- a/p/insa/index.js +++ b/p/insa/index.js @@ -1,20 +1,6 @@ 'use strict' -const _createParseLine = require('../../parse/line') const products = require('./products') -const createParseBitmask = require('../../parse/products-bitmask') -const createFormatBitmask = require('../../format/products-bitmask') - -const defaultProducts = { - nationalExp: true, - national: true, - regional: true, - suburban: true, - bus: true, - tram: true, - tourismTrain: true, -} - const transformReqBody = (body) => { body.client = { @@ -31,49 +17,13 @@ const transformReqBody = (body) => { return body } -const createParseLine = (profile, operators) => { - const parseLine = _createParseLine(profile, operators) - - const parseLineWithMode = (l) => { - const res = parseLine(l) - - res.mode = res.product = null - if ('class' in res) { - const data = products.bitmasks[parseInt(res.class)] - if (data) { - res.mode = data.mode - res.product = data.product - } - } - - return res - } - return parseLineWithMode -} - -const formatProducts = (products) => { - products = Object.assign(Object.create(null), defaultProducts, products) - return { - type: 'PROD', - mode: 'INC', - value: formatBitmask(products) + '' - } -} - -const formatBitmask = createFormatBitmask(products) - - const insaProfile = { locale: 'de-DE', timezone: 'Europe/Berlin', endpoint: 'http://reiseauskunft.insa.de/bin/mgate.exe', transformReqBody, - products: products.allProducts, - parseProducts: createParseBitmask(products.allProducts, defaultProducts), - formatProducts, - - parseLine: createParseLine, + products: products, journeyLeg: true, radar: true diff --git a/p/insa/products.js b/p/insa/products.js index d6b343e5..485c3f23 100644 --- a/p/insa/products.js +++ b/p/insa/products.js @@ -1,82 +1,60 @@ 'use strict' -// TODO Jannis R.: DRY -const p = { - nationalExp: { - bitmask: 1, +module.exports = [ + { + id: 'nationalExp', + mode: 'train', + bitmasks: [1], name: 'InterCityExpress', short: 'ICE', - mode: 'train', - product: 'nationalExp' + default: true }, - national: { - bitmask: 2, + { + id: 'national', + mode: 'train', + bitmasks: [2], name: 'InterCity & EuroCity', short: 'IC/EC', - mode: 'train', - product: 'national' + default: true }, - regional: { - bitmask: 8, + { + id: 'regional', + mode: 'train', + bitmasks: [8], name: 'RegionalExpress & RegionalBahn', short: 'RE/RB', - mode: 'train', - product: 'regional' + default: true }, - suburban: { - bitmask: 16, + { + id: 'suburban', + mode: 'train', + bitmasks: [16], name: 'S-Bahn', short: 'S', - mode: 'train', - product: 'suburban' + default: true }, - tram: { - bitmask: 32, + { + id: 'tram', + mode: 'train', + bitmasks: [32], name: 'Tram', short: 'T', - mode: 'train', - product: 'tram' + default: true }, - bus: { - bitmask: 64+128, + { + id: 'bus', + mode: 'bus', + bitmasks: [64, 128], name: 'Bus', short: 'B', - mode: 'bus', - product: 'bus' + default: true }, - tourismTrain: { - bitmask: 256, + { + id: 'tourismTrain', + mode: 'train', + bitmasks: [256], name: 'Tourism Train', short: 'TT', - mode: 'train', - product: 'tourismTrain' - }, - unknown: { - bitmask: 0, - name: 'unknown', - short: '?', - product: 'unknown' + default: true } -} - -p.bitmasks = [] -p.bitmasks[1] = p.nationalExp -p.bitmasks[2] = p.national -p.bitmasks[8] = p.regional -p.bitmasks[16] = p.suburban -p.bitmasks[32] = p.tram -p.bitmasks[64] = p.bus -p.bitmasks[128] = p.bus -p.bitmasks[256] = p.tourismTrain - -p.allProducts = [ - p.nationalExp, - p.national, - p.regional, - p.suburban, - p.tram, - p.bus, - p.tourismTrain ] - -module.exports = p diff --git a/p/oebb/index.js b/p/oebb/index.js index 5196ab49..99496761 100644 --- a/p/oebb/index.js +++ b/p/oebb/index.js @@ -3,9 +3,6 @@ // todo: https://gist.github.com/anonymous/a5fc856bc80ae7364721943243f934f4#file-haf_config_base-properties-L5 // todo: https://gist.github.com/anonymous/a5fc856bc80ae7364721943243f934f4#file-haf_config_base-properties-L47-L234 -const createParseBitmask = require('../../parse/products-bitmask') -const createFormatBitmask = require('../../format/products-bitmask') -const _createParseLine = require('../../parse/line') const _parseLocation = require('../../parse/location') const _createParseMovement = require('../../parse/movement') @@ -28,26 +25,6 @@ const transformReqBody = (body) => { return body } -const createParseLine = (profile, operators) => { - const parseLine = _createParseLine(profile, operators) - - const parseLineWithMode = (l) => { - const res = parseLine(l) - - res.mode = res.product = null - if ('class' in res) { - const data = products.bitmasks[parseInt(res.class)] - if (data) { - res.mode = data.mode - res.product = data.product - } - } - - return res - } - return parseLineWithMode -} - const parseLocation = (profile, l, lines) => { // ÖBB has some 'stations' **in austria** with no departures/products, // like station entrances, that are actually POIs. @@ -82,28 +59,6 @@ const createParseMovement = (profile, locations, lines, remarks) => { return parseMovement } -const defaultProducts = { - nationalExp: true, - national: true, - interregional: true, - regional: true, - suburban: true, - bus: true, - ferry: true, - subway: true, - tram: true, - onCall: true -} -const formatBitmask = createFormatBitmask(products) -const formatProducts = (products) => { - products = Object.assign(Object.create(null), defaultProducts, products) - return { - type: 'PROD', - mode: 'INC', - value: formatBitmask(products) + '' - } -} - const oebbProfile = { locale: 'de-AT', timezone: 'Europe/Vienna', @@ -111,15 +66,11 @@ const oebbProfile = { endpoint: 'http://fahrplan.oebb.at/bin/mgate.exe', transformReqBody, - products: products.allProducts, + products: products, - parseProducts: createParseBitmask(products.allProducts, defaultProducts), - parseLine: createParseLine, parseLocation, parseMovement: createParseMovement, - formatProducts, - journeyLeg: true, radar: true } diff --git a/p/oebb/products.js b/p/oebb/products.js index 646d4588..91bcf65e 100644 --- a/p/oebb/products.js +++ b/p/oebb/products.js @@ -1,112 +1,84 @@ 'use strict' -const p = { - nationalExp: { - bitmask: 1, +module.exports = [ + { + id: 'nationalExp', + mode: 'train', + bitmasks: [1], name: 'InterCityExpress & RailJet', short: 'ICE/RJ', - mode: 'train', - product: 'nationalExp' + default: true }, - national: { - bitmask: 2 + 4, + { + id: 'national', + mode: 'train', + bitmasks: [2, 4], name: 'InterCity & EuroCity', short: 'IC/EC', - mode: 'train', - product: 'national' + default: true }, - interregional: { - bitmask: 8 + 4096, + { + id: 'interregional', + mode: 'train', + bitmasks: [8, 4096], name: 'Durchgangszug & EuroNight', short: 'D/EN', - mode: 'train', - product: 'interregional' + default: true }, - regional: { - bitmask: 16, + { + id: 'regional', + mode: 'train', + bitmasks: [16], name: 'Regional & RegionalExpress', short: 'R/REX', - mode: 'train', - product: 'regional' + default: true }, - suburban: { - bitmask: 32, + { + id: 'suburban', + mode: 'train', + bitmasks: [32], name: 'S-Bahn', short: 'S', - mode: 'train', - product: 'suburban' + default: true }, - bus: { - bitmask: 64, + { + id: 'bus', + mode: 'bus', + bitmasks: [64], name: 'Bus', short: 'B', - mode: 'bus', - product: 'bus' + default: true }, - ferry: { - bitmask: 128, + { + id: 'ferry', + mode: 'watercraft', + bitmasks: [128], name: 'Ferry', short: 'F', - mode: 'watercraft', - product: 'ferry' + default: true }, - subway: { - bitmask: 256, + { + id: 'subway', + mode: 'train', + bitmasks: [256], name: 'U-Bahn', short: 'U', - mode: 'train', - product: 'subway' + default: true }, - tram: { - bitmask: 512, + { + id: 'tram', + mode: 'train', + bitmasks: [512], name: 'Tram', short: 'T', - mode: 'train', - product: 'tram' + default: true }, - onCall: { - bitmask: 2048, + { + id: 'onCall', + mode: null, // todo + bitmasks: [2048], name: 'On-call transit', short: 'on-call', - mode: null, // todo - product: 'onCall' - }, - unknown: { - bitmask: 0, - name: 'unknown', - short: '?', - product: 'unknown' + default: true } -} - -p.bitmasks = [] -p.bitmasks[1] = p.nationalExp -p.bitmasks[2] = p.national -p.bitmasks[4] = p.national -p.bitmasks[2+4] = p.national -p.bitmasks[8] = p.interregional -p.bitmasks[16] = p.regional -p.bitmasks[32] = p.suburban -p.bitmasks[64] = p.bus -p.bitmasks[128] = p.ferry -p.bitmasks[256] = p.subway -p.bitmasks[512] = p.tram -p.bitmasks[1024] = p.unknown -p.bitmasks[2048] = p.onCall -p.bitmasks[4096] = p.interregional -p.bitmasks[8+4096] = p.interregional - -p.allProducts = [ - p.nationalExp, - p.national, - p.interregional, - p.regional, - p.suburban, - p.bus, - p.ferry, - p.subway, - p.tram, - p.onCall ] - -module.exports = p diff --git a/p/vbb/index.js b/p/vbb/index.js index 3f9f9ecd..534c93d6 100644 --- a/p/vbb/index.js +++ b/p/vbb/index.js @@ -12,12 +12,8 @@ const _createParseJourney = require('../../parse/journey') const _createParseStopover = require('../../parse/stopover') const _createParseDeparture = require('../../parse/departure') const _formatStation = require('../../format/station') -const createParseBitmask = require('../../parse/products-bitmask') -const createFormatBitmask = require('../../format/products-bitmask') -const modes = require('./modes') - -const formatBitmask = createFormatBitmask(modes) +const products = require('./products') const transformReqBody = (body) => { body.client = {type: 'IPA', id: 'VBB', name: 'vbbPROD', v: '4010300'} @@ -31,18 +27,9 @@ const transformReqBody = (body) => { const createParseLine = (profile, operators) => { const parseLine = _createParseLine(profile, operators) - const parseLineWithMode = (l) => { + const parseLineWithMoreDetails = (l) => { const res = parseLine(l) - res.mode = res.product = null - if ('class' in res) { - const data = modes.bitmasks[parseInt(res.class)] - if (data) { - res.mode = data.mode - res.product = data.product - } - } - const details = parseLineName(l.name) res.symbol = details.symbol res.nr = details.nr @@ -52,7 +39,7 @@ const createParseLine = (profile, operators) => { return res } - return parseLineWithMode + return parseLineWithMoreDetails } const parseLocation = (profile, l, lines) => { @@ -146,24 +133,6 @@ const formatStation = (id) => { return _formatStation(id) } -const defaultProducts = { - suburban: true, - subway: true, - tram: true, - bus: true, - ferry: true, - express: true, - regional: true -} -const formatProducts = (products) => { - products = Object.assign(Object.create(null), defaultProducts, products) - return { - type: 'PROD', - mode: 'INC', - value: formatBitmask(products) + '' - } -} - const vbbProfile = { locale: 'de-DE', timezone: 'Europe/Berlin', @@ -176,18 +145,16 @@ const vbbProfile = { transformReqBody, - products: modes.allProducts, + products: products, parseStationName: shorten, parseLocation, parseLine: createParseLine, - parseProducts: createParseBitmask(modes.allProducts, defaultProducts), parseJourney: createParseJourney, parseDeparture: createParseDeparture, parseStopover: createParseStopover, formatStation, - formatProducts, journeysNumF: false, journeyLeg: true, diff --git a/p/vbb/modes.js b/p/vbb/modes.js deleted file mode 100644 index 3e771793..00000000 --- a/p/vbb/modes.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict' - -// todo: remove useless keys -const m = { - suburban: { - category: 0, - bitmask: 1, - name: 'S-Bahn', - mode: 'train', - short: 'S', - product: 'suburban' - }, - - subway: { - category: 1, - bitmask: 2, - name: 'U-Bahn', - mode: 'train', - short: 'U', - product: 'subway' - }, - - tram: { - category: 2, - bitmask: 4, - name: 'Tram', - mode: 'train', - short: 'T', - product: 'tram' - }, - - bus: { - category: 3, - bitmask: 8, - name: 'Bus', - mode: 'bus', - short: 'B', - product: 'bus' - }, - - ferry: { - category: 4, - bitmask: 16, - name: 'Fähre', - mode: 'watercraft', - short: 'F', - product: 'ferry' - }, - - express: { - category: 5, - bitmask: 32, - name: 'IC/ICE', - mode: 'train', - short: 'E', - product: 'express' - }, - - regional: { - category: 6, - bitmask: 64, - name: 'RB/RE', - mode: 'train', - short: 'R', - product: 'regional' - }, - - unknown: { - category: null, - bitmask: 0, - name: 'unknown', - mode: null, - short: '?', - product: 'unknown' - } -} - -m.bitmasks = [] -m.bitmasks[1] = m.suburban -m.bitmasks[2] = m.subway -m.bitmasks[4] = m.tram -m.bitmasks[8] = m.bus -m.bitmasks[16] = m.ferry -m.bitmasks[32] = m.express -m.bitmasks[64] = m.regional - -m.categories = [ - m.suburban, - m.subway, - m.tram, - m.bus, - m.ferry, - m.express, - m.regional, - m.unknown -] - -m.allProducts = [ - m.suburban, - m.subway, - m.tram, - m.bus, - m.ferry, - m.express, - m.regional -] - -// m.parseCategory = (category) => { -// return m.categories[parseInt(category)] || m.unknown -// } - -module.exports = m diff --git a/p/vbb/products.js b/p/vbb/products.js new file mode 100644 index 00000000..6d091247 --- /dev/null +++ b/p/vbb/products.js @@ -0,0 +1,60 @@ +'use strict' + +module.exports = [ + { + id: 'suburban', + mode: 'train', + bitmasks: [1], + name: 'S-Bahn', + short: 'S', + default: true + }, + { + id: 'subway', + mode: 'train', + bitmasks: [2], + name: 'U-Bahn', + short: 'U', + default: true + }, + { + id: 'tram', + mode: 'train', + bitmasks: [4], + name: 'Tram', + short: 'T', + default: true + }, + { + id: 'bus', + mode: 'bus', + bitmasks: [8], + name: 'Bus', + short: 'B', + default: true + }, + { + id: 'ferry', + mode: 'watercraft', + bitmasks: [16], + name: 'Fähre', + short: 'F', + default: true + }, + { + id: 'express', + mode: 'train', + bitmasks: [32], + name: 'IC/ICE', + short: 'E', + default: true + }, + { + id: 'regional', + mode: 'train', + bitmasks: [64], + name: 'RB/RE', + short: 'R', + default: true + } +] diff --git a/parse/line.js b/parse/line.js index 79300c08..fddc66e6 100644 --- a/parse/line.js +++ b/parse/line.js @@ -2,8 +2,14 @@ const slugg = require('slugg') -// todo: are p.number and p.line ever different? const createParseLine = (profile, operators) => { + const byBitmask = [] + for (let product of profile.products) { + for (let bitmask of product.bitmasks) { + byBitmask[bitmask] = product + } + } + const parseLine = (p) => { if (!p) return null // todo: handle this upstream const res = { @@ -13,8 +19,9 @@ const createParseLine = (profile, operators) => { public: true } // todo: what is p.prodCtx && p.prodCtx.num? + // todo: what is p.number? - // This is terrible, but FPTF demands an ID. Let's pray for VBB to expose an ID. + // This is terrible, but FPTF demands an ID. Let's pray for HaCon to expose an ID. // todo: find a better way if (p.line) res.id = slugg(p.line.trim()) else if (p.name) res.id = slugg(p.name.trim()) @@ -24,7 +31,12 @@ const createParseLine = (profile, operators) => { res.productCode = +p.prodCtx.catCode } - // todo: parse mode, remove from profiles + if ('class' in res) { + // todo: what if `res.class` is the sum of two bitmasks? + const product = byBitmask[parseInt(res.class)] + res.mode = product && product.mode || null + res.product = product && product.id || null + } if ('number' === typeof p.oprX) { res.operator = operators[p.oprX] || null diff --git a/parse/products-bitmask.js b/parse/products-bitmask.js index 5dc5e8bc..6b836904 100644 --- a/parse/products-bitmask.js +++ b/parse/products-bitmask.js @@ -1,25 +1,23 @@ 'use strict' -const createParseBitmask = (allProducts, defaultProducts) => { - allProducts = allProducts.sort((p1, p2) => p2.bitmask - p1.bitmask) // desc - if (allProducts.length === 0) throw new Error('allProducts is empty.') - for (let product of allProducts) { - if ('string' !== typeof product.product) { - throw new Error('allProducts[].product must be a string.') - } - if ('number' !== typeof product.bitmask) { - throw new Error(product.product + '.bitmask must be a number.') +const createParseBitmask = (profile) => { + const defaultProducts = {} + let withBitmask = [] + for (let product of profile.products) { + defaultProducts[product.id] = false + for (let bitmask of product.bitmasks) { + withBitmask.push([bitmask, product]) } } + withBitmask.sort((a, b) => b[0] - a[0]) // descending const parseBitmask = (bitmask) => { const res = Object.assign({}, defaultProducts) - for (let product of allProducts) { - if (bitmask === 0) break - if ((product.bitmask & bitmask) > 0) { - res[product.product] = true - bitmask -= product.bitmask + for (let [pBitmask, product] of withBitmask) { + if ((pBitmask & bitmask) > 0) { + res[product.id] = true + bitmask -= pBitmask } } diff --git a/test/db.js b/test/db.js index 62f39074..020ae236 100644 --- a/test/db.js +++ b/test/db.js @@ -8,7 +8,7 @@ const isRoughlyEqual = require('is-roughly-equal') const co = require('./co') const createClient = require('..') const dbProfile = require('../p/db') -const {allProducts} = require('../p/db/modes') +const allProducts = require('../p/db/products') const { assertValidStation, assertValidPoi, @@ -73,7 +73,7 @@ const assertIsJungfernheide = (t, s) => { // todo: DRY with other tests const assertValidProducts = (t, p) => { for (let product of allProducts) { - product = product.product // wat + product = product.id t.equal(typeof p[product], 'boolean', 'product ' + p + ' must be a boolean') } } diff --git a/test/insa.js b/test/insa.js index 61df17fd..3afa29ee 100644 --- a/test/insa.js +++ b/test/insa.js @@ -8,7 +8,7 @@ const validateFptf = require('validate-fptf') const co = require('./co') const createClient = require('..') const insaProfile = require('../p/insa') -const {allProducts} = require('../p/insa/products') +const allProducts = require('../p/insa/products') const { assertValidStation, assertValidPoi, @@ -58,7 +58,7 @@ const assertIsMagdeburgHbf = (t, s) => { // todo: DRY with other tests const assertValidProducts = (t, p) => { for (let product of allProducts) { - product = product.product // wat + product = product.id t.equal(typeof p[product], 'boolean', 'product ' + p + ' must be a boolean') } } diff --git a/test/oebb.js b/test/oebb.js index 80e1d92e..319f79f6 100644 --- a/test/oebb.js +++ b/test/oebb.js @@ -12,7 +12,7 @@ const validateLineWithoutMode = require('./validate-line-without-mode') const co = require('./co') const createClient = require('..') const oebbProfile = require('../p/oebb') -const {allProducts} = require('../p/oebb/products') +const allProducts = require('../p/oebb/products') const { assertValidStation, assertValidPoi, @@ -77,7 +77,7 @@ const assertIsSalzburgHbf = (t, s) => { // todo: DRY with other tests const assertValidProducts = (t, p) => { for (let product of allProducts) { - product = product.product // wat + product = product.id t.equal(typeof p[product], 'boolean', 'product ' + p + ' must be a boolean') } }