diff --git a/index.js b/index.js index 3dfb1177..5b67e14b 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,6 @@ const sortBy = require('lodash/sortBy') const pRetry = require('p-retry') 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') @@ -32,9 +31,6 @@ const defaults = { const createClient = (profile, userAgent, opt = {}) => { profile = Object.assign({}, defaultProfile, profile) - if (!profile.parseProducts) { - profile.parseProducts = createParseBitmask(profile) - } if (!profile.formatProductsFilter) { profile.formatProductsFilter = createFormatProductsFilter(profile) } diff --git a/lib/default-profile.js b/lib/default-profile.js index ad054497..cf2b40ea 100644 --- a/lib/default-profile.js +++ b/lib/default-profile.js @@ -2,6 +2,7 @@ const parseDateTime = require('../parse/date-time') const parsePlatform = require('../parse/platform') +const parseProductsBitmask = require('../parse/products-bitmask') const parseIcon = require('../parse/icon') const parseWhen = require('../parse/when') const parseDeparture = require('../parse/departure') @@ -44,6 +45,7 @@ const defaultProfile = { parseDateTime, parsePlatform, + parseProductsBitmask, parseIcon, parseWhen, parseDeparture, @@ -51,7 +53,7 @@ const defaultProfile = { parseJourneyLeg, parseJourney, parseLine, - parseStationName: id, + parseStationName: (_, name) => name, parseLocation, parseCommon, parsePolyline, diff --git a/parse/arrival-or-departure.js b/parse/arrival-or-departure.js index bbddf771..a7900a07 100644 --- a/parse/arrival-or-departure.js +++ b/parse/arrival-or-departure.js @@ -10,22 +10,26 @@ const DEPARTURE = 'd' // todo: what is d.jny.dirFlg? // todo: d.stbStop.dProgType/d.stbStop.aProgType -const createParseArrOrDep = (profile, opt, data, prefix) => { +const createParseArrOrDep = (prefix) => { if (prefix !== ARRIVAL && prefix !== DEPARTURE) throw new Error('invalid prefix') - const parseArrOrDep = (d) => { + const parseArrOrDep = (ctx, d) => { // d = raw arrival/departure + const {profile, opt} = ctx + const tPlanned = d.stbStop[prefix + 'TimeS'] const tPrognosed = d.stbStop[prefix + 'TimeR'] const tzOffset = d.stbStop[prefix + 'TZOffset'] || null const cancelled = !!d.stbStop[prefix + 'Cncl'] + const plPlanned = d.stbStop[prefix + 'PlatfS'] + const plPrognosed = d.stbStop[prefix + 'PlatfR'] const res = { tripId: d.jid, stop: d.stbStop.location || null, - ...parseWhen(profile, d.date, tPlanned, tPrognosed, tzOffset, cancelled), - ...parsePlatform(profile, d.stbStop[prefix + 'PlatfS'], d.stbStop[prefix + 'PlatfR'], cancelled), + ...profile.parseWhen(ctx, d.date, tPlanned, tPrognosed, tzOffset, cancelled), + ...profile.parsePlatform(ctx, plPlanned, plPrognosed, cancelled), // todo: for arrivals, this is the *origin*, not the *direction* - direction: prefix === DEPARTURE && d.dirTxt && profile.parseStationName(d.dirTxt) || null, + direction: prefix === DEPARTURE && d.dirTxt && profile.parseStationName(ctx, d.dirTxt) || null, line: d.line || null, remarks: [] } @@ -43,9 +47,10 @@ const createParseArrOrDep = (profile, opt, data, prefix) => { } if (opt.stopovers && Array.isArray(d.stopL)) { - const parse = profile.parseStopover(profile, opt, data, d.date) // Filter stations the train passes without stopping, as this doesn't comply with FPTF (yet). - const stopovers = d.stopL.map(parse).filter(st => !st.passBy) + const stopovers = d.stopL + .map(st => profile.parseStopover(ctx, st, d.date)) + .filter(st => !st.passBy) if (prefix === ARRIVAL) res.previousStopovers = stopovers else if (prefix === DEPARTURE) res.nextStopovers = stopovers } diff --git a/parse/arrival.js b/parse/arrival.js index e2c2b554..c21e4261 100644 --- a/parse/arrival.js +++ b/parse/arrival.js @@ -3,8 +3,6 @@ const createParseArrOrDep = require('./arrival-or-departure') const ARRIVAL = 'a' -const createParseArrival = (profile, opt, data) => { - return createParseArrOrDep(profile, opt, data, ARRIVAL) -} +const parseArrival = createParseArrOrDep(ARRIVAL) -module.exports = createParseArrival +module.exports = parseArrival diff --git a/parse/common.js b/parse/common.js index 11c5967f..9d30880d 100644 --- a/parse/common.js +++ b/parse/common.js @@ -3,102 +3,103 @@ const get = require('lodash/get') const findInTree = require('../lib/find-in-tree') -const parseCommonData = (profile, opt, res) => { +const parseCommonData = (_ctx) => { + const {profile, opt, res} = _ctx const c = res.common || {} - res.operators = [] + const common = {} + const ctx = {..._ctx, common} + + common.operators = [] if (Array.isArray(c.opL)) { - res.operators = c.opL.map(op => profile.parseOperator(profile, op)) + common.operators = c.opL.map(op => profile.parseOperator(ctx, op)) findInTree(res, '**.oprX', (idx, parent) => { - if ('number' === typeof idx) parent.operator = res.operators[idx] + if ('number' === typeof idx) parent.operator = common.operators[idx] }) } - res.icons = [] + common.icons = [] if (Array.isArray(c.icoL)) { - res.icons = c.icoL.map(icon => profile.parseIcon(profile, icon)) + common.icons = c.icoL.map(icon => profile.parseIcon(ctx, icon)) findInTree(res, '**.icoX', (idx, parent) => { - if ('number' === typeof idx) parent.icon = res.icons[idx] + if ('number' === typeof idx) parent.icon = common.icons[idx] }) } - res.lines = [] + common.lines = [] if (Array.isArray(c.prodL)) { - const parse = profile.parseLine(profile, opt, res) - res.lines = c.prodL.map(parse) + common.lines = c.prodL.map(l => profile.parseLine(ctx, l)) findInTree(res, '**.prodX', (idx, parent) => { - if ('number' === typeof idx) parent.line = res.lines[idx] + if ('number' === typeof idx) parent.line = common.lines[idx] }) findInTree(res, '**.pRefL', (idxs, parent) => { - parent.lines = idxs.filter(idx => !!res.lines[idx]).map(idx => res.lines[idx]) + parent.lines = idxs.filter(idx => !!common.lines[idx]).map(idx => common.lines[idx]) }) // todo - // **.dep.dProdX: departureLine -> res.lines[idx] - // **.arr.aProdX: arrivalLine -> res.lines[idx] + // **.dep.dProdX: departureLine -> common.lines[idx] + // **.arr.aProdX: arrivalLine -> common.lines[idx] } - res.locations = [] + common.locations = [] if (Array.isArray(c.locL)) { - const parse = loc => profile.parseLocation(profile, opt, res, loc) - res.locations = c.locL.map(parse) + common.locations = c.locL.map(loc => profile.parseLocation(ctx, loc)) - for (let i = 0; i < res.locations.length; i++) { + for (let i = 0; i < common.locations.length; i++) { const raw = c.locL[i] - const loc = res.locations[i] + const loc = common.locations[i] if ('number' === typeof raw.mMastLocX) { - loc.station = Object.assign({}, res.locations[raw.mMastLocX]) + loc.station = Object.assign({}, common.locations[raw.mMastLocX]) loc.station.type = 'station' } else if (raw.isMainMast) loc.type = 'station' } // todo: correct props? findInTree(res, '**.locX', (idx, parent) => { - if ('number' === typeof idx) parent.location = res.locations[idx] + if ('number' === typeof idx) parent.location = common.locations[idx] }) findInTree(res, '**.ani.fLocX', (idxs, parent) => { - parent.fromLocations = idxs.map(idx => res.locations[idx]) + parent.fromLocations = idxs.map(idx => common.locations[idx]) }) findInTree(res, '**.ani.tLocX', (idxs, parent) => { - parent.toLocations = idxs.map(idx => res.locations[idx]) + parent.toLocations = idxs.map(idx => common.locations[idx]) }) findInTree(res, '**.fLocX', (idx, parent) => { - if ('number' === typeof idx) parent.fromLocation = res.locations[idx] + if ('number' === typeof idx) parent.fromLocation = common.locations[idx] }) findInTree(res, '**.tLocX', (idx, parent) => { - if ('number' === typeof idx) parent.toLocation = res.locations[idx] + if ('number' === typeof idx) parent.toLocation = common.locations[idx] }) } - res.hints = [] + common.hints = [] if (opt.remarks && Array.isArray(c.remL)) { - res.hints = c.remL.map(hint => profile.parseHint(profile, hint, {...c, ...res})) + common.hints = c.remL.map(hint => profile.parseHint(ctx, hint)) findInTree(res, '**.remX', (idx, parent) => { - if ('number' === typeof idx) parent.hint = res.hints[idx] + if ('number' === typeof idx) parent.hint = common.hints[idx] }) } - res.warnings = [] + common.warnings = [] if (opt.remarks && Array.isArray(c.himL)) { - res.warnings = c.himL.map(w => profile.parseWarning(profile, w, {...c, ...res})) + common.warnings = c.himL.map(w => profile.parseWarning(ctx, w)) findInTree(res, '**.himX', (idx, parent) => { - if ('number' === typeof idx) parent.warning = res.warnings[idx] + if ('number' === typeof idx) parent.warning = common.warnings[idx] }) } - res.polylines = [] + common.polylines = [] if (opt.polylines && Array.isArray(c.polyL)) { - const parse = profile.parsePolyline(profile, opt, res) - res.polylines = c.polyL.map(parse) + common.polylines = c.polyL.map(p => profile.parsePolyline(ctx, p)) // todo: **.ani.poly -> parsePolyline() findInTree(res, '**.polyG.polyXL', (idxs, _, path) => { - const idx = idxs.find(idx => !!res.polylines[idx]) // find any given polyline + const idx = idxs.find(idx => !!common.polylines[idx]) // find any given polyline const jny = get(res, path.slice(0, -2)) - jny.polyline = res.polylines[idx] + jny.polyline = common.polylines[idx] }) } - return res + return common } module.exports = parseCommonData diff --git a/parse/date-time.js b/parse/date-time.js index 47e77f7c..46639f88 100644 --- a/parse/date-time.js +++ b/parse/date-time.js @@ -4,8 +4,7 @@ const {DateTime, FixedOffsetZone, IANAZone} = require('luxon') const timezones = new WeakMap() -// todo: change to `(profile) => (date, time) => {}` -const parseDateTime = (profile, date, time, tzOffset = null, timestamp = false) => { +const parseDateTime = ({profile}, date, time, tzOffset = null, timestamp = false) => { const pDate = [date.substr(-8, 4), date.substr(-4, 2), date.substr(-2, 2)] if (!pDate[0] || !pDate[1] || !pDate[2]) { throw new Error('invalid date format: ' + date) diff --git a/parse/departure.js b/parse/departure.js index dc86f7a5..254a6310 100644 --- a/parse/departure.js +++ b/parse/departure.js @@ -3,8 +3,6 @@ const createParseArrOrDep = require('./arrival-or-departure') const DEPARTURE = 'd' -const createParseDeparture = (profile, opt, data) => { - return createParseArrOrDep(profile, opt, data, DEPARTURE) -} +const parseDeparture = createParseArrOrDep(DEPARTURE) -module.exports = createParseDeparture +module.exports = parseDeparture diff --git a/parse/hint.js b/parse/hint.js index d3d8aeba..a13259bd 100644 --- a/parse/hint.js +++ b/parse/hint.js @@ -4,7 +4,6 @@ const codesByIcon = Object.assign(Object.create(null), { cancel: 'cancelled' }) -// todo: is passing in profile necessary? // todo: pass in tag list from hint reference, e.g.: // "tagL": [ // "RES_JNY_H3" // H3 = level 3 heading? shown on overview @@ -13,7 +12,7 @@ const codesByIcon = Object.assign(Object.create(null), { // "RES_JNY_DTL" // only shown in journey detail // ] // todo: https://github.com/public-transport/hafas-client/issues/5 -const parseHint = (profile, h, _) => { +const parseHint = (ctx, h) => { // todo: C const text = h.txtN && h.txtN.trim() || '' diff --git a/parse/icon.js b/parse/icon.js index 155437fb..191ef8eb 100644 --- a/parse/icon.js +++ b/parse/icon.js @@ -1,6 +1,6 @@ 'use strict' -const parseIcon = (profile, i) => { +const parseIcon = (ctx, i) => { const res = { type: i.res || null, title: i.text || i.txt || i.txtS || null diff --git a/parse/journey-leg.js b/parse/journey-leg.js index 87a138fd..6e56f4b4 100644 --- a/parse/journey-leg.js +++ b/parse/journey-leg.js @@ -32,120 +32,116 @@ const applyRemarks = (leg, refs) => { } } -const createParseJourneyLeg = (profile, opt, data) => { - // todo: pt.status, pt.isPartCncl - // todo: pt.isRchbl, pt.chRatingRT, pt.chgDurR, pt.minChg - // todo: pt.dep.dProgType, pt.arr.dProgType - // todo: what is pt.jny.dirFlg? - // todo: what is pt.recState? - // todo: what is `sty: 'UNDEF'`? - // todo: pt.prodL - // todo: pt.parJnyL (list of coupled trains) +// todo: pt.status, pt.isPartCncl +// todo: pt.isRchbl, pt.chRatingRT, pt.chgDurR, pt.minChg +// todo: pt.dep.dProgType, pt.arr.dProgType +// todo: what is pt.jny.dirFlg? +// todo: what is pt.recState? +// todo: what is `sty: 'UNDEF'`? +// todo: pt.prodL +// todo: pt.parJnyL (list of coupled trains) +// todo: pt.planrtTS - // j = journey, pt = part - // todo: pt.planrtTS - const parseJourneyLeg = (j, pt, parseStopovers = true) => { - const res = { - origin: clone(pt.dep.location) || null, - destination: clone(pt.arr.location) - } +const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg + const {profile, opt} = ctx - const arr = parseWhen(profile, j.date, pt.arr.aTimeS, pt.arr.aTimeR, pt.arr.aTZOffset, pt.arr.aCncl) - res.arrival = arr.when - res.plannedArrival = arr.plannedWhen - res.arrivalDelay = arr.delay - if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen - - const dep = parseWhen(profile, j.date, pt.dep.dTimeS, pt.dep.dTimeR, pt.dep.dTZOffset, pt.dep.dCncl) - res.departure = dep.when - res.plannedDeparture = dep.plannedWhen - res.departureDelay = dep.delay - if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen - - if (pt.jny) { - res.reachable = !!pt.jny.isRchbl - } - - if (pt.jny && pt.jny.polyline) { - res.polyline = pt.jny.polyline || null - } - - if (pt.type === 'WALK' || pt.type === 'TRSF') { - res.public = true - res.walking = true - res.distance = pt.gis && pt.gis.dist || null - if (pt.type === 'TRSF') res.transfer = true - - // https://gist.github.com/derhuerst/426d4b95aeae701843b1e9c23105b8d4#file-tripsearch-2018-12-05-http-L4207-L4229 - if (opt.remarks && Array.isArray(pt.gis.msgL)) { - applyRemarks(res, pt.gis.msgL) - } - } else if (pt.type === 'JNY') { - // todo: pull `public` value from `profile.products` - res.tripId = pt.jny.jid - res.line = pt.jny.line || null - res.direction = pt.jny.dirTxt && profile.parseStationName(pt.jny.dirTxt) || null - - const arrPl = parsePlatform(profile, pt.arr.aPlatfS, pt.arr.aPlatfR, pt.arr.aCncl) - res.arrivalPlatform = arrPl.platform - res.plannedArrivalPlatform = arrPl.plannedPlatform - if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform - - const depPl = parsePlatform(profile, pt.dep.dPlatfS, pt.dep.dPlatfR, pt.dep.dCncl) - res.departurePlatform = depPl.platform - res.plannedDeparturePlatform = depPl.plannedPlatform - if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform - - if (parseStopovers && pt.jny.stopL) { - const parse = profile.parseStopover(profile, opt, data, j.date) - const stopL = pt.jny.stopL - res.stopovers = stopL.map(parse) - - if (opt.remarks && Array.isArray(pt.jny.msgL)) { - // todo: apply leg-wide remarks if `parseStopovers` is false - applyRemarks(res, pt.jny.msgL) - } - - // filter stations the train passes without stopping, as this doesn't comply with fptf (yet) - res.stopovers = res.stopovers.filter((x) => !x.passBy) - } - - const freq = pt.jny.freq || {} - // todo: expose `res.cycle` even if only one field exists (breaking) - if (freq.minC && freq.maxC) { - res.cycle = { - min: freq.minC * 60, - max: freq.maxC * 60 - } - // nr of connections in this frequency, from now on - if (freq.numC) res.cycle.nr = freq.numC - } - - if (freq.jnyL) { - const parseAlternative = (a) => { - // todo: parse this just like a `leg` (breaking) - // todo: parse `a.stopL`, `a.ctxRecon`, `a.msgL` - const st0 = a.stopL[0] || {} - return { - tripId: a.jid, - line: a.line || null, - direction: a.dirTxt || null, - ...parseWhen(profile, j.date, st0.dTimeS, st0.dTimeR, st0.dTZOffset, st0.dCncl) - } - } - res.alternatives = freq.jnyL.map(parseAlternative) - } - } - - if (pt.arr.aCncl || pt.dep.dCncl) { - res.cancelled = true - Object.defineProperty(res, 'canceled', {value: true}) - } - - return res + const res = { + origin: clone(pt.dep.location) || null, + destination: clone(pt.arr.location) } - return parseJourneyLeg + const arr = profile.parseWhen(ctx, date, pt.arr.aTimeS, pt.arr.aTimeR, pt.arr.aTZOffset, pt.arr.aCncl) + res.arrival = arr.when + res.plannedArrival = arr.plannedWhen + res.arrivalDelay = arr.delay + if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen + + const dep = profile.parseWhen(ctx, date, pt.dep.dTimeS, pt.dep.dTimeR, pt.dep.dTZOffset, pt.dep.dCncl) + res.departure = dep.when + res.plannedDeparture = dep.plannedWhen + res.departureDelay = dep.delay + if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen + + if (pt.jny) { + res.reachable = !!pt.jny.isRchbl + } + + if (pt.jny && pt.jny.polyline) { + res.polyline = pt.jny.polyline || null + } + + if (pt.type === 'WALK' || pt.type === 'TRSF') { + res.public = true + res.walking = true + res.distance = pt.gis && pt.gis.dist || null + if (pt.type === 'TRSF') res.transfer = true + + // https://gist.github.com/derhuerst/426d4b95aeae701843b1e9c23105b8d4#file-tripsearch-2018-12-05-http-L4207-L4229 + if (opt.remarks && Array.isArray(pt.gis.msgL)) { + applyRemarks(res, pt.gis.msgL) + } + } else if (pt.type === 'JNY') { + // todo: pull `public` value from `profile.products` + res.tripId = pt.jny.jid + res.line = pt.jny.line || null + res.direction = pt.jny.dirTxt && profile.parseStationName(ctx, pt.jny.dirTxt) || null + + const arrPl = profile.parsePlatform(ctx, pt.arr.aPlatfS, pt.arr.aPlatfR, pt.arr.aCncl) + res.arrivalPlatform = arrPl.platform + res.plannedArrivalPlatform = arrPl.plannedPlatform + if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform + + const depPl = profile.parsePlatform(ctx, pt.dep.dPlatfS, pt.dep.dPlatfR, pt.dep.dCncl) + res.departurePlatform = depPl.platform + res.plannedDeparturePlatform = depPl.plannedPlatform + if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform + + if (opt.stopovers && pt.jny.stopL) { + const stopL = pt.jny.stopL + res.stopovers = stopL.map(s => profile.parseStopover(ctx, s, date)) + + if (opt.remarks && Array.isArray(pt.jny.msgL)) { + // todo: apply leg-wide remarks if `opt.stopovers` is false + applyRemarks(res, pt.jny.msgL) + } + + // filter stations the train passes without stopping, as this doesn't comply with fptf (yet) + res.stopovers = res.stopovers.filter((x) => !x.passBy) + } + + const freq = pt.jny.freq || {} + // todo: expose `res.cycle` even if only one field exists (breaking) + if (freq.minC && freq.maxC) { + res.cycle = { + min: freq.minC * 60, + max: freq.maxC * 60 + } + // nr of connections in this frequency, from now on + if (freq.numC) res.cycle.nr = freq.numC + } + + if (freq.jnyL) { + const parseAlternative = (a) => { + // todo: parse this just like a `leg` (breaking) + // todo: parse `a.stopL`, `a.ctxRecon`, `a.msgL` + const st0 = a.stopL[0] || {} + return { + tripId: a.jid, + line: a.line || null, + direction: a.dirTxt || null, + ...profile.parseWhen(ctx, date, st0.dTimeS, st0.dTimeR, st0.dTZOffset, st0.dCncl) + } + } + res.alternatives = freq.jnyL.map(parseAlternative) + } + } + + if (pt.arr.aCncl || pt.dep.dCncl) { + res.cancelled = true + Object.defineProperty(res, 'canceled', {value: true}) + } + + return res } -module.exports = createParseJourneyLeg +module.exports = parseJourneyLeg diff --git a/parse/journey.js b/parse/journey.js index 645affa8..ab55c146 100644 --- a/parse/journey.js +++ b/parse/journey.js @@ -22,47 +22,44 @@ const parseScheduledDays = (sDaysB, year, profile) => { return res } -const createParseJourney = (profile, opt, data) => { - const parseLeg = profile.parseJourneyLeg(profile, opt, data) - // todo: c.conSubscr - // todo: c.trfRes x vbb-parse-ticket - // todo: c.sotRating, c.isSotCon, c.sotCtxt - // todo: c.showARSLink - // todo: c.useableTime - // todo: c.cksum - // todo: c.isNotRdbl - // todo: c.badSecRefX - // todo: c.bfATS, c.bfIOSTS - const parseJourney = (j) => { - const legs = j.secL.map(leg => parseLeg(j, leg)) - const res = { - type: 'journey', - legs, - refreshToken: j.ctxRecon || null - } +// todo: c.conSubscr +// todo: c.trfRes x vbb-parse-ticket +// todo: c.sotRating, c.isSotCon, c.sotCtxt +// todo: c.showARSLink +// todo: c.useableTime +// todo: c.cksum +// todo: c.isNotRdbl +// todo: c.badSecRefX +// todo: c.bfATS, c.bfIOSTS +const parseJourney = (ctx, j) => { // j = raw jouney + const {profile, opt} = ctx - const freq = j.freq || {} - if (freq.minC || freq.maxC) { - res.cycle = {} - if (freq.minC) res.cycle.min = freq.minC * 60 - if (freq.maxC) res.cycle.max = freq.maxC * 60 - // nr of connections in this frequency, from now on - if (freq.numC) res.cycle.nr = freq.numC - } - - if (opt.remarks && Array.isArray(j.msgL)) { - res.remarks = findRemarks(j.msgL).map(([remark]) => remark) - } - - if (opt.scheduledDays) { - const year = parseInt(j.date.slice(0, 4)) - res.scheduledDays = parseScheduledDays(j.sDays.sDaysB, year, profile) - } - - return res + const legs = j.secL.map(l => profile.parseJourneyLeg(ctx, l, j.date)) + const res = { + type: 'journey', + legs, + refreshToken: j.ctxRecon || null } - return parseJourney + const freq = j.freq || {} + if (freq.minC || freq.maxC) { + res.cycle = {} + if (freq.minC) res.cycle.min = freq.minC * 60 + if (freq.maxC) res.cycle.max = freq.maxC * 60 + // nr of connections in this frequency, from now on + if (freq.numC) res.cycle.nr = freq.numC + } + + if (opt.remarks && Array.isArray(j.msgL)) { + res.remarks = findRemarks(j.msgL).map(([remark]) => remark) + } + + if (opt.scheduledDays) { + const year = parseInt(j.date.slice(0, 4)) + res.scheduledDays = parseScheduledDays(j.sDays.sDaysB, year, profile) + } + + return res } -module.exports = createParseJourney +module.exports = parseJourney diff --git a/parse/line.js b/parse/line.js index 83f206d8..0aa1610f 100644 --- a/parse/line.js +++ b/parse/line.js @@ -2,45 +2,42 @@ const slugg = require('slugg') -const createParseLine = (profile, opt, _) => { - const byBitmask = [] - for (let product of profile.products) { - for (let bitmask of product.bitmasks) { - byBitmask[bitmask] = product +const parseLine = ({profile}, p) => { + if (!p) return null // todo: handle this upstream + const name = p.line || p.addName || p.name || null // wtf + const res = { + type: 'line', + // This is terrible, but FPTF demands an ID. Let's pray for HAFAS. + id: ( + p.prodCtx && p.prodCtx.lineId && slugg(p.prodCtx.lineId.trim()) + || name && slugg(name.trim()) + || null + ), + // todo: what is p.prodCtx.matchId? use as `id`? expose it. + fahrtNr: p.prodCtx && p.prodCtx.num || null, + name, + public: true + } + // todo: what is p.number? + // todo: what is p.prodCtx.catCode? + + if ('cls' in p) { + // todo: use profile.products.find() for this + const byBitmask = [] + for (let product of profile.products) { + for (let bitmask of product.bitmasks) { + byBitmask[bitmask] = product + } } + + // todo: what if `p.cls` is the sum of two bitmasks? + const product = byBitmask[parseInt(p.cls)] + res.mode = product && product.mode || null + res.product = product && product.id || null } - // todo: p.himIdL - const parseLine = (p) => { - if (!p) return null // todo: handle this upstream - const name = p.line || p.addName || p.name || null // wtf - const res = { - type: 'line', - // This is terrible, but FPTF demands an ID. Let's pray for HAFAS. - id: ( - p.prodCtx && p.prodCtx.lineId && slugg(p.prodCtx.lineId.trim()) - || name && slugg(name.trim()) - || null - ), - // todo: what is p.prodCtx.matchId? use as `id`? expose it. - fahrtNr: p.prodCtx && p.prodCtx.num || null, - name, - public: true - } - // todo: what is p.number? - // todo: what is p.prodCtx.catCode? - - if ('cls' in p) { - // todo: what if `p.cls` is the sum of two bitmasks? - const product = byBitmask[parseInt(p.cls)] - res.mode = product && product.mode || null - res.product = product && product.id || null - } - - if (p.operator) res.operator = p.operator // todo: move up - return res - } - return parseLine + if (p.operator) res.operator = p.operator // todo: move up + return res } -module.exports = createParseLine +module.exports = parseLine diff --git a/parse/location.js b/parse/location.js index 80891e58..0d755b70 100644 --- a/parse/location.js +++ b/parse/location.js @@ -9,7 +9,9 @@ const ADDRESS = 'A' const leadingZeros = /^0+/ // todo: what is s.rRefL? -const parseLocation = (profile, opt, _, l) => { +const parseLocation = (ctx, l) => { + const {profile, opt} = ctx + const lid = parse(l.lid, {delimiter: '@'}) const res = { type: 'location', @@ -25,11 +27,11 @@ const parseLocation = (profile, opt, _, l) => { const stop = { type: l.isMainMast ? 'station' : 'stop', id: res.id, - name: l.name || id.O ? profile.parseStationName(l.name || id.O) : null, + name: l.name || id.O ? profile.parseStationName(ctx, l.name || id.O) : null, location: 'number' === typeof res.latitude ? res : null // todo: remove `.id` } - if ('pCls' in l) stop.products = profile.parseProducts(l.pCls) + if ('pCls' in l) stop.products = profile.parseProductsBitmask(ctx, l.pCls) if ('meta' in l) stop.isMeta = !!l.meta if (opt.linesOfStops && Array.isArray(l.lines)) { diff --git a/parse/movement.js b/parse/movement.js index fa61c48a..7bff0ecf 100644 --- a/parse/movement.js +++ b/parse/movement.js @@ -1,53 +1,49 @@ 'use strict' -const createParseMovement = (profile, opt, data) => { - // todo: what is m.dirGeo? maybe the speed? - // todo: what is m.stopL? - // todo: what is m.proc? wut? - // todo: what is m.pos? - // todo: what is m.ani.dirGeo[n]? maybe the speed? - // todo: what is m.ani.proc[n]? wut? - const parseMovement = (m) => { - const pStopover = profile.parseStopover(profile, opt, data, m.date) +// todo: what is m.dirGeo? maybe the speed? +// todo: what is m.stopL? +// todo: what is m.proc? wut? +// todo: what is m.pos? +// todo: what is m.ani.dirGeo[n]? maybe the speed? +// todo: what is m.ani.proc[n]? wut? +const parseMovement = (ctx, m) => { // m = raw movement + const {profile, opt} = ctx - const res = { - direction: m.dirTxt ? profile.parseStationName(m.dirTxt) : null, - tripId: m.jid || null, - line: m.line || null, - location: m.pos ? { - type: 'location', - latitude: m.pos.y / 1000000, - longitude: m.pos.x / 1000000 - } : null, - // todo: stopL[0] is the first of the trip! -> filter out - nextStopovers: m.stopL.map(pStopover), - frames: [] - } - - if (m.ani) { - if (Array.isArray(m.ani.mSec)) { - for (let i = 0; i < m.ani.mSec.length; i++) { - res.frames.push({ - origin: m.ani.fromLocations[i] || null, - destination: m.ani.toLocations[i] || null, - t: m.ani.mSec[i] - }) - } - } - - if (opt.polylines) { - if (m.ani.poly) { - const parse = profile.parsePolyline(profile, opt, data) - res.polyline = parse(m.ani.poly) - } else if (m.ani.polyline) { - res.polyline = m.ani.polyline - } - } - } - - return res + const res = { + direction: m.dirTxt ? profile.parseStationName(ctx, m.dirTxt) : null, + tripId: m.jid || null, + line: m.line || null, + location: m.pos ? { + type: 'location', + latitude: m.pos.y / 1000000, + longitude: m.pos.x / 1000000 + } : null, + // todo: stopL[0] is the first of the trip! -> filter out + nextStopovers: m.stopL.map(s => profile.parseStopover(ctx, s, m.date)), + frames: [] } - return parseMovement + + if (m.ani) { + if (Array.isArray(m.ani.mSec)) { + for (let i = 0; i < m.ani.mSec.length; i++) { + res.frames.push({ + origin: m.ani.fromLocations[i] || null, + destination: m.ani.toLocations[i] || null, + t: m.ani.mSec[i] + }) + } + } + + if (opt.polylines) { + if (m.ani.poly) { + res.polyline = profile.parsePolyline(ctx, m.ani.poly) + } else if (m.ani.polyline) { + res.polyline = m.ani.polyline + } + } + } + + return res } -module.exports = createParseMovement +module.exports = parseMovement diff --git a/parse/nearby.js b/parse/nearby.js index 1bc31897..11cd6d4c 100644 --- a/parse/nearby.js +++ b/parse/nearby.js @@ -6,9 +6,8 @@ // todo: what is s.wt? // todo: what is s.dur? -// todo: [breaking] change to createParseNearby(profile, data) => (n) => nearby -const parseNearby = (profile, opt, data, n) => { - const res = profile.parseLocation(profile, opt, data, n) +const parseNearby = (ctx, n) => { // n = raw nearby location + const res = ctx.profile.parseLocation(ctx, n) res.distance = n.dist return res } diff --git a/parse/operator.js b/parse/operator.js index cac2db21..f8e54d49 100644 --- a/parse/operator.js +++ b/parse/operator.js @@ -2,7 +2,7 @@ const slugg = require('slugg') -const parseOperator = (profile, a) => { +const parseOperator = (ctx, a) => { const name = a.name && a.name.trim() if (!name) return null return { diff --git a/parse/platform.js b/parse/platform.js index 4922cf36..4276b3ed 100644 --- a/parse/platform.js +++ b/parse/platform.js @@ -1,6 +1,6 @@ 'use strict' -const parsePlatform = (profile, platfS, platfR, cncl = false) => { +const parsePlatform = (ctx, platfS, platfR, cncl = false) => { let planned = platfS || null let prognosed = platfR || null diff --git a/parse/polyline.js b/parse/polyline.js index 367dac9e..593934d2 100644 --- a/parse/polyline.js +++ b/parse/polyline.js @@ -3,50 +3,47 @@ const {toGeoJSON} = require('@mapbox/polyline') const distance = require('gps-distance') -const createParsePolyline = (profile, opt, _) => { - // todo: what is p.delta? - // todo: what is p.type? - // todo: what is p.crdEncS? - // todo: what is p.crdEncF? - const parsePolyline = (p) => { - const shape = toGeoJSON(p.crdEncYX) - if (shape.coordinates.length === 0) return null +// todo: what is p.delta? +// todo: what is p.type? +// todo: what is p.crdEncS? +// todo: what is p.crdEncF? +const parsePolyline = (ctx, p) => { // p = raw polyline + const shape = toGeoJSON(p.crdEncYX) + if (shape.coordinates.length === 0) return null - const res = shape.coordinates.map(crd => ({ - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: crd - } - })) + const res = shape.coordinates.map(crd => ({ + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: crd + } + })) - if (Array.isArray(p.ppLocRefL)) { - for (let ref of p.ppLocRefL) { - const p = res[ref.ppIdx] - if (p && ref.location) p.properties = ref.location - } - - // Often there is one more point right next to each point at a station. - // We filter them here if they are < 5m from each other. - for (let i = 1; i < res.length; i++) { - const p1 = res[i - 1].geometry.coordinates - const p2 = res[i].geometry.coordinates - const d = distance(p1[1], p1[0], p2[1], p2[0]) - if (d >= .005) continue - const l1 = Object.keys(res[i - 1].properties).length - const l2 = Object.keys(res[i].properties).length - if (l1 === 0 && l2 > 0) res.splice(i - 1, 1) - else if (l2 === 0 && l1 > 0) res.splice(i, 1) - } + if (Array.isArray(p.ppLocRefL)) { + for (let ref of p.ppLocRefL) { + const p = res[ref.ppIdx] + if (p && ref.location) p.properties = ref.location } - return { - type: 'FeatureCollection', - features: res + // Often there is one more point right next to each point at a station. + // We filter them here if they are < 5m from each other. + for (let i = 1; i < res.length; i++) { + const p1 = res[i - 1].geometry.coordinates + const p2 = res[i].geometry.coordinates + const d = distance(p1[1], p1[0], p2[1], p2[0]) + if (d >= .005) continue + const l1 = Object.keys(res[i - 1].properties).length + const l2 = Object.keys(res[i].properties).length + if (l1 === 0 && l2 > 0) res.splice(i - 1, 1) + else if (l2 === 0 && l1 > 0) res.splice(i, 1) } } - return parsePolyline + + return { + type: 'FeatureCollection', + features: res + } } -module.exports = createParsePolyline +module.exports = parsePolyline diff --git a/parse/products-bitmask.js b/parse/products-bitmask.js index 087e21a9..e854a116 100644 --- a/parse/products-bitmask.js +++ b/parse/products-bitmask.js @@ -1,22 +1,17 @@ 'use strict' -const createParseBitmask = (profile) => { - const defaultProducts = {} - for (let product of profile.products) defaultProducts[product.id] = false +const parseBitmask = ({profile}, bitmask) => { + const res = {} + for (let product of profile.products) res[product.id] = false - const parseBitmask = (bitmask) => { - const res = Object.assign({}, defaultProducts) + const bits = bitmask.toString(2).split('').map(i => parseInt(i)).reverse() + for (let i = 0; i < bits.length; i++) { + if (!bits[i]) continue // ignore `0` - const bits = bitmask.toString(2).split('').map(i => parseInt(i)).reverse() - for (let i = 0; i < bits.length; i++) { - if (!bits[i]) continue // ignore `0` - - const product = profile.products.find(p => p.bitmasks.includes(Math.pow(2, i))) - if (product) res[product.id] = true - } - return res + const product = profile.products.find(p => p.bitmasks.includes(Math.pow(2, i))) + if (product) res[product.id] = true } - return parseBitmask + return res } -module.exports = createParseBitmask +module.exports = parseBitmask diff --git a/parse/stopover.js b/parse/stopover.js index eb501510..883c3102 100644 --- a/parse/stopover.js +++ b/parse/stopover.js @@ -4,48 +4,46 @@ const parseWhen = require('./when') const parsePlatform = require('./platform') const findRemarks = require('./find-remarks') -const createParseStopover = (profile, opt, data, date) => { - const parseStopover = (st) => { - const arr = parseWhen(profile, date, st.aTimeS, st.aTimeR, st.aTZOffset, st.aCncl) - const arrPl = parsePlatform(profile, st.aPlatfS, st.aPlatfR, st.aCncl) - const dep = parseWhen(profile, date, st.dTimeS, st.dTimeR, st.dTZOffset, st.dCncl) - const depPl = parsePlatform(profile, st.dPlatfS, st.dPlatfR, st.dCncl) +const parseStopover = (ctx, st, date) => { // st = raw stopover + const {profile, opt} = ctx - const res = { - stop: st.location || null, - arrival: arr.when, - plannedArrival: arr.plannedWhen, - arrivalDelay: arr.delay, - arrivalPlatform: arrPl.platform, - plannedArrivalPlatform: arrPl.plannedPlatform, - departure: dep.when, - plannedDeparture: dep.plannedWhen, - departureDelay: dep.delay, - departurePlatform: depPl.platform, - plannedDeparturePlatform: depPl.plannedPlatform - } + const arr = profile.parseWhen(ctx, date, st.aTimeS, st.aTimeR, st.aTZOffset, st.aCncl) + const arrPl = profile.parsePlatform(ctx, st.aPlatfS, st.aPlatfR, st.aCncl) + const dep = profile.parseWhen(ctx, date, st.dTimeS, st.dTimeR, st.dTZOffset, st.dCncl) + const depPl = profile.parsePlatform(ctx, st.dPlatfS, st.dPlatfR, st.dCncl) - if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen - if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform - if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen - if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform - - // mark stations the train passes without stopping - if(st.dInS === false && st.aOutS === false) res.passBy = true - - if (st.aCncl || st.dCncl) { - res.cancelled = true - Object.defineProperty(res, 'canceled', {value: true}) - } - - if (opt.remarks && Array.isArray(st.msgL)) { - res.remarks = findRemarks(st.msgL).map(([remark]) => remark) - } - - return res + const res = { + stop: st.location || null, + arrival: arr.when, + plannedArrival: arr.plannedWhen, + arrivalDelay: arr.delay, + arrivalPlatform: arrPl.platform, + plannedArrivalPlatform: arrPl.plannedPlatform, + departure: dep.when, + plannedDeparture: dep.plannedWhen, + departureDelay: dep.delay, + departurePlatform: depPl.platform, + plannedDeparturePlatform: depPl.plannedPlatform } - return parseStopover + if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen + if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform + if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen + if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform + + // mark stations the train passes without stopping + if(st.dInS === false && st.aOutS === false) res.passBy = true + + if (st.aCncl || st.dCncl) { + res.cancelled = true + Object.defineProperty(res, 'canceled', {value: true}) + } + + if (opt.remarks && Array.isArray(st.msgL)) { + res.remarks = findRemarks(st.msgL).map(([remark]) => remark) + } + + return res } -module.exports = createParseStopover +module.exports = parseStopover diff --git a/parse/warning.js b/parse/warning.js index 55aebe18..04ea3b28 100644 --- a/parse/warning.js +++ b/parse/warning.js @@ -7,7 +7,7 @@ const typesByIcon = Object.assign(Object.create(null), { HimWarn: 'status' }) -const parseMsgEdge = (profile) => (e) => { +const parseMsgEdge = (ctx) => (e) => { const res = omit(e, [ 'icoX', 'fLocX', 'fromLocation', @@ -19,18 +19,20 @@ const parseMsgEdge = (profile) => (e) => { res.toLoc = e.toLocation || null return res } -const parseMsgEvent = (profile) => (e) => { +const parseMsgEvent = ({profile}) => (e) => { return { // todo: rename `Loc` -> `Location` [breaking] fromLoc: e.fromLocation || null, toLoc: e.toLocation || null, - start: profile.parseDateTime(profile, e.fDate, e.fTime, null), - end: profile.parseDateTime(profile, e.tDate, e.tTime, null), + start: profile.parseDateTime(ctx, e.fDate, e.fTime, null), + end: profile.parseDateTime(ctx, e.tDate, e.tTime, null), sections: e.sectionNums || [] // todo: parse } } -const parseWarning = (profile, w, data) => { +const parseWarning = (ctx, w) => { + const {profile, res: resp} = ctx + // todo: act, pub, lead, tckr, prod, comp, // todo: cat (1, 2), pubChL, rRefL, impactL // pubChL: @@ -62,24 +64,24 @@ const parseWarning = (profile, w, data) => { priority: w.prio, category: w.cat || null // todo: parse to sth meaningful } - if ('prod' in w) res.products = profile.parseProducts(w.prod) + if ('prod' in w) res.products = profile.parseProductsBitmask(ctx, w.prod) - if (w.edgeRefL && data.himMsgEdgeL) { + if (w.edgeRefL && resp.common && resp.common.himMsgEdgeL) { res.edges = w.edgeRefL - .map(i => data.himMsgEdgeL[i]) + .map(i => resp.common.himMsgEdgeL[i]) .filter(e => !!e) - .map(parseMsgEdge(profile)) + .map(parseMsgEdge(ctx)) } - if (w.eventRefL && data.himMsgEventL) { + if (w.eventRefL && resp.common && resp.common.himMsgEventL) { res.events = w.eventRefL - .map(i => data.himMsgEventL[i]) + .map(i => resp.common.himMsgEventL[i]) .filter(e => !!e) - .map(parseMsgEvent(profile)) + .map(parseMsgEvent(ctx)) } - if (w.sDate && w.sTime) res.validFrom = profile.parseDateTime(profile, w.sDate, w.sTime, null) - if (w.eDate && w.eTime) res.validUntil = profile.parseDateTime(profile, w.eDate, w.eTime, null) - if (w.lModDate && w.lModTime) res.modified = profile.parseDateTime(profile, w.lModDate, w.lModTime, null) + if (w.sDate && w.sTime) res.validFrom = profile.parseDateTime(ctx, w.sDate, w.sTime, null) + if (w.eDate && w.eTime) res.validUntil = profile.parseDateTime(ctx, w.eDate, w.eTime, null) + if (w.lModDate && w.lModTime) res.modified = profile.parseDateTime(ctx, w.lModDate, w.lModTime, null) return res } diff --git a/parse/when.js b/parse/when.js index 1251f61b..8425813e 100644 --- a/parse/when.js +++ b/parse/when.js @@ -1,15 +1,15 @@ 'use strict' -const parseWhen = (profile, date, timeS, timeR, tzOffset, cncl = false) => { - const parse = profile.parseDateTime +const parseWhen = (ctx, date, timeS, timeR, tzOffset, cncl = false) => { + const parse = ctx.profile.parseDateTime - let planned = timeS ? parse(profile, date, timeS, tzOffset, false) : null - let prognosed = timeR ? parse(profile, date, timeR, tzOffset, false) : null + let planned = timeS ? parse(ctx, date, timeS, tzOffset, false) : null + let prognosed = timeR ? parse(ctx, date, timeR, tzOffset, false) : null let delay = null if (planned && prognosed) { - const tPlanned = parse(profile, date, timeS, tzOffset, true) - const tPrognosed = parse(profile, date, timeR, tzOffset, true) + const tPlanned = parse(ctx, date, timeS, tzOffset, true) + const tPrognosed = parse(ctx, date, timeR, tzOffset, true) delay = Math.round((tPrognosed - tPlanned) / 1000) } diff --git a/test/parse/date-time.js b/test/parse/date-time.js index e5fa181a..4ed2ad4a 100644 --- a/test/parse/date-time.js +++ b/test/parse/date-time.js @@ -3,48 +3,53 @@ const test = require('tape') const parse = require('../../parse/date-time') -const profile = { - timezone: 'Europe/Berlin', - locale: 'de-DE' +const ctx = { + common: {}, + opt: {}, + profile: { + timezone: 'Europe/Berlin', + locale: 'de-DE' + } } -test('date & time parsing uses profile.timezone', (t) => { - const iso = parse({ - ...profile, timezone: 'Europe/Moscow' - }, '20190819', '203000', undefined, false) - t.equal(iso, '2019-08-19T20:30:00+03:00') - t.end() -}) - test('date & time parsing returns a timestamp', (t) => { - const iso = parse(profile, '20190819', '203000', undefined, false) - const ts = parse(profile, '20190819', '203000', undefined, true) + const iso = parse(ctx, '20190819', '203000', undefined, false) + const ts = parse(ctx, '20190819', '203000', undefined, true) t.equal(ts, +new Date(iso)) t.equal(ts, 1566239400 * 1000) t.end() }) test('date & time parsing uses tzOffset', (t) => { - const iso = parse(profile, '20190819', '203000', -120, false) + const iso = parse(ctx, '20190819', '203000', -120, false) t.equal(iso, '2019-08-19T20:30:00-02:00') t.end() }) test('date & time parsing works with day "overflow"', (t) => { - const iso = parse(profile, '20190819', '02203000', undefined, false) + const iso = parse(ctx, '20190819', '02203000', undefined, false) t.equal(iso, '2019-08-21T20:30:00+02:00') t.end() }) // #106 test('date & time parsing works with day "overflow" & tzOffset', (t) => { - const iso = parse(profile, '20190819', '02203000', -120, false) + const iso = parse(ctx, '20190819', '02203000', -120, false) t.equal(iso, '2019-08-21T20:30:00-02:00') t.end() }) test('date & time parsing works with summer & winter time', (t) => { - const iso = parse(profile, '20190219', '203000', undefined, false) + const iso = parse(ctx, '20190219', '203000', undefined, false) t.equal(iso, '2019-02-19T20:30:00+01:00') t.end() }) + +test('date & time parsing uses profile.timezone', (t) => { + const iso = parse({ + ...ctx, + profile: {...ctx.profile, timezone: 'Europe/Moscow'} + }, '20190819', '203000', undefined, false) + t.equal(iso, '2019-08-19T20:30:00+03:00') + t.end() +}) diff --git a/test/parse/hint.js b/test/parse/hint.js index 3dff77c8..69ba0313 100644 --- a/test/parse/hint.js +++ b/test/parse/hint.js @@ -3,7 +3,11 @@ const test = require('tape') const parse = require('../../parse/hint') -const profile = {} +const ctx = { + data: {}, + opt: {}, + profile: {} +} test('parses hints correctly', (t) => { const input = { @@ -18,20 +22,20 @@ test('parses hints correctly', (t) => { text: 'some text' } - t.deepEqual(parse(profile, input), expected) - t.deepEqual(parse(profile, { + t.deepEqual(parse(ctx, input), expected) + t.deepEqual(parse(ctx, { ...input, type: 'I' }), expected) // alternative trip - t.deepEqual(parse(profile, { + t.deepEqual(parse(ctx, { ...input, type: 'L', jid: 'trip id' }), { ...expected, type: 'status', code: 'alternative-trip', tripId: 'trip id' }) // type: M - t.deepEqual(parse(profile, { + t.deepEqual(parse(ctx, { ...input, type: 'M', txtS: 'some summary' }), { ...expected, type: 'status', summary: 'some summary' @@ -39,18 +43,18 @@ test('parses hints correctly', (t) => { // type: D for (const type of ['D', 'U', 'R', 'N', 'Y']) { - t.deepEqual(parse(profile, {...input, type}), { + t.deepEqual(parse(ctx, {...input, type}), { ...expected, type: 'status' }) } // .code via .icon - t.deepEqual(parse(profile, { + t.deepEqual(parse(ctx, { ...input, code: null, icon: {type: 'cancel'} }), {...expected, code: 'cancelled'}) // invalid - t.equal(parse(profile, {...input, type: 'X'}), null) + t.equal(parse(ctx, {...input, type: 'X'}), null) t.end() }) diff --git a/test/parse/icon.js b/test/parse/icon.js index 940b2f97..634f5570 100644 --- a/test/parse/icon.js +++ b/test/parse/icon.js @@ -3,14 +3,18 @@ const test = require('tape') const parse = require('../../parse/icon') -test('parses icons correctly', (t) => { - const profile = {} +const ctx = { + data: {}, + opt: {}, + profile: {} +} +test('parses icons correctly', (t) => { const text = { "res": "BVG", "text": "Berliner Verkehrsbetriebe" } - t.deepEqual(parse(profile, text), { + t.deepEqual(parse(ctx, text), { type: 'BVG', title: 'Berliner Verkehrsbetriebe' }) @@ -19,7 +23,7 @@ test('parses icons correctly', (t) => { "res": "PROD_BUS", "txtS": "18" } - t.deepEqual(parse(profile, txtS), { + t.deepEqual(parse(ctx, txtS), { type: 'PROD_BUS', title: '18' }) @@ -28,7 +32,7 @@ test('parses icons correctly', (t) => { "res": "RBB", "txt": "Regionalbus Braunschweig GmbH" } - t.deepEqual(parse(profile, txt), { + t.deepEqual(parse(ctx, txt), { type: 'RBB', title: 'Regionalbus Braunschweig GmbH' }) @@ -36,7 +40,7 @@ test('parses icons correctly', (t) => { const noText = { "res": "attr_bike_r" } - t.deepEqual(parse(profile, noText), { + t.deepEqual(parse(ctx, noText), { type: 'attr_bike_r', title: null }) @@ -56,7 +60,7 @@ test('parses icons correctly', (t) => { "a": 255 } } - t.deepEqual(parse(profile, withColor), { + t.deepEqual(parse(ctx, withColor), { type: 'prod_sub_t', title: null, fgColor: {r: 255, g: 255, b: 255, a: 255}, diff --git a/test/parse/line.js b/test/parse/line.js index 1dcbe88c..7a7826f6 100644 --- a/test/parse/line.js +++ b/test/parse/line.js @@ -1,7 +1,7 @@ 'use strict' const test = require('tape') -const parser = require('../../parse/line') +const parse = require('../../parse/line') const profile = { products: [ @@ -10,8 +10,11 @@ const profile = { {id: 'bus', bitmasks: [4, 8]} ] } -const opt = {} -const parse = parser(profile, opt, {}) +const ctx = { + data: {}, + opt: {}, + profile +} test('parses lines correctly', (t) => { const input = { @@ -29,23 +32,23 @@ test('parses lines correctly', (t) => { public: true } - t.deepEqual(parse(input), expected) + t.deepEqual(parse(ctx, input), expected) - t.deepEqual(parse({ + t.deepEqual(parse(ctx, { ...input, line: null, addName: input.line }), expected) - t.deepEqual(parse({ + t.deepEqual(parse(ctx, { ...input, line: null, name: input.line }), expected) // no prodCtx.lineId - t.deepEqual(parse({ + t.deepEqual(parse(ctx, { ...input, prodCtx: {...input.prodCtx, lineId: null} }), { ...expected, id: 'foo-line' }) // no prodCtx - t.deepEqual(parse({ + t.deepEqual(parse(ctx, { ...input, prodCtx: undefined }), { ...expected, id: 'foo-line', fahrtNr: null diff --git a/test/parse/location.js b/test/parse/location.js index 8e9fa0af..1b452f0a 100644 --- a/test/parse/location.js +++ b/test/parse/location.js @@ -5,11 +5,16 @@ const omit = require('lodash/omit') const parse = require('../../parse/location') const profile = { - parseStationName: name => name.toLowerCase(), - parseProducts: bitmask => [bitmask] + parseStationName: (_, name) => name.toLowerCase(), + parseProductsBitmask: (_, bitmask) => [bitmask] } -const opt = { - linesOfStops: false + +const ctx = { + data: {}, + opt: { + linesOfStops: false + }, + profile } test('parses an address correctly', (t) => { @@ -20,7 +25,7 @@ test('parses an address correctly', (t) => { crd: {x: 13418027, y: 52515503} } - const address = parse(profile, opt, null, input) + const address = parse(ctx, input) t.deepEqual(address, { type: 'location', id: 'some id', @@ -40,7 +45,7 @@ test('parses a POI correctly', (t) => { crd: {x: 13418027, y: 52515503} } - const poi = parse(profile, opt, null, input) + const poi = parse(ctx, input) t.deepEqual(poi, { type: 'location', poi: true, @@ -50,10 +55,10 @@ test('parses a POI correctly', (t) => { longitude: 13.418027 }) - const withExtId = parse(profile, opt, null, {...input, extId: 'some ext id'}) + const withExtId = parse(ctx, {...input, extId: 'some ext id'}) t.equal(withExtId.id, 'some ext id') - const withLeadingZero = parse(profile, opt, null, {...input, extId: '00some ext id'}) + const withLeadingZero = parse(ctx, {...input, extId: '00some ext id'}) t.equal(withLeadingZero.id, 'some ext id') t.end() @@ -68,7 +73,7 @@ test('parses a stop correctly', (t) => { pCls: 123 } - const stop = parse(profile, opt, null, input) + const stop = parse(ctx, input) t.deepEqual(stop, { type: 'stop', id: 'foo stop', @@ -82,17 +87,20 @@ test('parses a stop correctly', (t) => { products: [123] }) - const withoutLoc = parse(profile, opt, null, omit(input, ['crd'])) + const withoutLoc = parse(ctx, omit(input, ['crd'])) t.equal(withoutLoc.location, null) - const mainMast = parse(profile, opt, null, {...input, isMainMast: true}) + const mainMast = parse(ctx, {...input, isMainMast: true}) t.equal(mainMast.type, 'station') - const meta = parse(profile, opt, null, {...input, meta: 1}) + const meta = parse(ctx, {...input, meta: 1}) t.equal(meta.isMeta, true) const lineA = {id: 'a'} - const withLines = parse(profile, {...opt, linesOfStops: true}, null, { + const withLines = parse({ + ...ctx, + opt: {...ctx.opt, linesOfStops: true} + }, { ...input, lines: [lineA] }) t.deepEqual(withLines.lines, [lineA]) diff --git a/test/parse/operator.js b/test/parse/operator.js index 6f73f329..90dc24ad 100644 --- a/test/parse/operator.js +++ b/test/parse/operator.js @@ -3,16 +3,19 @@ const test = require('tape') const parse = require('../../parse/operator') +const ctx = { + data: {}, + opt: {}, + profile: {} +} test('parses an operator correctly', (t) => { - const profile = {} - const op = { "name": "Berliner Verkehrsbetriebe", "icoX": 1, "id": "Berliner Verkehrsbetriebe" } - t.deepEqual(parse(profile, op), { + t.deepEqual(parse(ctx, op), { type: 'operator', id: 'berliner-verkehrsbetriebe', name: 'Berliner Verkehrsbetriebe' diff --git a/test/parse/warning.js b/test/parse/warning.js index 4bce1b16..22335e00 100644 --- a/test/parse/warning.js +++ b/test/parse/warning.js @@ -4,9 +4,14 @@ const test = require('tape') const parse = require('../../parse/warning') const profile = { - parseProducts: bitmask => [bitmask], + parseProductsBitmask: (_, bitmask) => [bitmask], parseDateTime: (_, date, time) => date + ':' + time } +const ctx = { + data: {}, + opt: {}, + profile +} test('parses warnings correctly', (t) => { const input = { @@ -27,26 +32,26 @@ test('parses warnings correctly', (t) => { category: 1 } - t.deepEqual(parse(profile, input), expected) + t.deepEqual(parse(ctx, input), expected) // without basic fields - t.deepEqual(parse(profile, {...input, hid: null}), {...expected, id: null}) - t.deepEqual(parse(profile, {...input, head: null}), {...expected, summary: null}) - t.deepEqual(parse(profile, {...input, text: null}), {...expected, text: null}) - t.deepEqual(parse(profile, {...input, cat: null}), {...expected, category: null}) + t.deepEqual(parse(ctx, {...input, hid: null}), {...expected, id: null}) + t.deepEqual(parse(ctx, {...input, head: null}), {...expected, summary: null}) + t.deepEqual(parse(ctx, {...input, text: null}), {...expected, text: null}) + t.deepEqual(parse(ctx, {...input, cat: null}), {...expected, category: null}) // without icon - t.deepEqual(parse(profile, {...input, icon: null}), { + t.deepEqual(parse(ctx, {...input, icon: null}), { ...expected, type: 'warning', icon: null }) // with products - t.deepEqual(parse(profile, {...input, prod: 123}), { + t.deepEqual(parse(ctx, {...input, prod: 123}), { ...expected, products: [123] }) // validFrom, validUntil, modified - t.deepEqual(parse(profile, { + t.deepEqual(parse(ctx, { ...input, sDate: '20190101', sTime: '094020', eDate: '20190101', eTime: '114020', diff --git a/test/parse/when.js b/test/parse/when.js index c5160d9a..40934426 100644 --- a/test/parse/when.js +++ b/test/parse/when.js @@ -4,11 +4,16 @@ const test = require('tape') const parse = require('../../parse/when') const profile = { - parseDateTime: (profile, date, time, tzOffset, timestamp = false) => { + parseDateTime: ({profile}, date, time, tzOffset, timestamp = false) => { if (timestamp) return ((date + '' + time) - tzOffset * 60) * 1000 return date + ':' + time } } +const ctx = { + data: {}, + opt: {}, + profile +} test('parseWhen works correctly', (t) => { const date = '20190606' @@ -21,15 +26,15 @@ test('parseWhen works correctly', (t) => { delay: 130 // seconds } - t.deepEqual(parse(profile, date, timeS, timeR, tzOffset), expected) + t.deepEqual(parse(ctx, date, timeS, timeR, tzOffset), expected) // no realtime data - t.deepEqual(parse(profile, date, timeS, null, tzOffset), { + t.deepEqual(parse(ctx, date, timeS, null, tzOffset), { ...expected, when: expected.plannedWhen, delay: null }) // cancelled - t.deepEqual(parse(profile, date, timeS, timeR, tzOffset, true), { + t.deepEqual(parse(ctx, date, timeS, timeR, tzOffset, true), { ...expected, when: null, prognosedWhen: expected.when