parseProductsBitmask via profile, pass ctx into parse fns 💥

This commit is contained in:
Jannis R 2019-10-20 00:19:11 +02:00
parent 29d7bd4299
commit fb7a5653e3
No known key found for this signature in database
GPG key ID: 0FE83946296A88A5
30 changed files with 501 additions and 487 deletions

View file

@ -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)
}

View file

@ -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,

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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() || ''

View file

@ -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

View file

@ -32,31 +32,31 @@ 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
const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
const {profile, opt} = ctx
// 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 arr = parseWhen(profile, j.date, pt.arr.aTimeS, pt.arr.aTimeR, pt.arr.aTZOffset, pt.arr.aCncl)
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 = parseWhen(profile, j.date, pt.dep.dTimeS, pt.dep.dTimeR, pt.dep.dTZOffset, pt.dep.dCncl)
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
@ -84,25 +84,24 @@ const createParseJourneyLeg = (profile, opt, data) => {
// 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
res.direction = pt.jny.dirTxt && profile.parseStationName(ctx, pt.jny.dirTxt) || null
const arrPl = parsePlatform(profile, pt.arr.aPlatfS, pt.arr.aPlatfR, pt.arr.aCncl)
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 = parsePlatform(profile, pt.dep.dPlatfS, pt.dep.dPlatfR, pt.dep.dCncl)
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 (parseStopovers && pt.jny.stopL) {
const parse = profile.parseStopover(profile, opt, data, j.date)
if (opt.stopovers && pt.jny.stopL) {
const stopL = pt.jny.stopL
res.stopovers = stopL.map(parse)
res.stopovers = stopL.map(s => profile.parseStopover(ctx, s, date))
if (opt.remarks && Array.isArray(pt.jny.msgL)) {
// todo: apply leg-wide remarks if `parseStopovers` is false
// todo: apply leg-wide remarks if `opt.stopovers` is false
applyRemarks(res, pt.jny.msgL)
}
@ -130,7 +129,7 @@ const createParseJourneyLeg = (profile, opt, data) => {
tripId: a.jid,
line: a.line || null,
direction: a.dirTxt || null,
...parseWhen(profile, j.date, st0.dTimeS, st0.dTimeR, st0.dTZOffset, st0.dCncl)
...profile.parseWhen(ctx, date, st0.dTimeS, st0.dTimeR, st0.dTZOffset, st0.dCncl)
}
}
res.alternatives = freq.jnyL.map(parseAlternative)
@ -143,9 +142,6 @@ const createParseJourneyLeg = (profile, opt, data) => {
}
return res
}
return parseJourneyLeg
}
module.exports = createParseJourneyLeg
module.exports = parseJourneyLeg

View file

@ -22,19 +22,19 @@ 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))
// 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 legs = j.secL.map(l => profile.parseJourneyLeg(ctx, l, j.date))
const res = {
type: 'journey',
legs,
@ -60,9 +60,6 @@ const createParseJourney = (profile, opt, data) => {
}
return res
}
return parseJourney
}
module.exports = createParseJourney
module.exports = parseJourney

View file

@ -2,16 +2,7 @@
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
}
}
// todo: p.himIdL
const parseLine = (p) => {
const parseLine = ({profile}, p) => {
if (!p) return null // todo: handle this upstream
const name = p.line || p.addName || p.name || null // wtf
const res = {
@ -31,6 +22,14 @@ const createParseLine = (profile, opt, _) => {
// 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
@ -39,8 +38,6 @@ const createParseLine = (profile, opt, _) => {
if (p.operator) res.operator = p.operator // todo: move up
return res
}
return parseLine
}
module.exports = createParseLine
module.exports = parseLine

View file

@ -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)) {

View file

@ -1,17 +1,16 @@
'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,
direction: m.dirTxt ? profile.parseStationName(ctx, m.dirTxt) : null,
tripId: m.jid || null,
line: m.line || null,
location: m.pos ? {
@ -20,7 +19,7 @@ const createParseMovement = (profile, opt, data) => {
longitude: m.pos.x / 1000000
} : null,
// todo: stopL[0] is the first of the trip! -> filter out
nextStopovers: m.stopL.map(pStopover),
nextStopovers: m.stopL.map(s => profile.parseStopover(ctx, s, m.date)),
frames: []
}
@ -37,8 +36,7 @@ const createParseMovement = (profile, opt, data) => {
if (opt.polylines) {
if (m.ani.poly) {
const parse = profile.parsePolyline(profile, opt, data)
res.polyline = parse(m.ani.poly)
res.polyline = profile.parsePolyline(ctx, m.ani.poly)
} else if (m.ani.polyline) {
res.polyline = m.ani.polyline
}
@ -46,8 +44,6 @@ const createParseMovement = (profile, opt, data) => {
}
return res
}
return parseMovement
}
module.exports = createParseMovement
module.exports = parseMovement

View file

@ -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
}

View file

@ -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 {

View file

@ -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

View file

@ -3,12 +3,11 @@
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) => {
// 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
@ -45,8 +44,6 @@ const createParsePolyline = (profile, opt, _) => {
type: 'FeatureCollection',
features: res
}
}
return parsePolyline
}
module.exports = createParsePolyline
module.exports = parsePolyline

View file

@ -1,11 +1,8 @@
'use strict'
const createParseBitmask = (profile) => {
const defaultProducts = {}
for (let product of profile.products) defaultProducts[product.id] = false
const parseBitmask = (bitmask) => {
const res = Object.assign({}, defaultProducts)
const parseBitmask = ({profile}, bitmask) => {
const res = {}
for (let product of profile.products) res[product.id] = false
const bits = bitmask.toString(2).split('').map(i => parseInt(i)).reverse()
for (let i = 0; i < bits.length; i++) {
@ -15,8 +12,6 @@ const createParseBitmask = (profile) => {
if (product) res[product.id] = true
}
return res
}
return parseBitmask
}
module.exports = createParseBitmask
module.exports = parseBitmask

View file

@ -4,12 +4,13 @@ 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 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)
const res = {
stop: st.location || null,
@ -43,9 +44,6 @@ const createParseStopover = (profile, opt, data, date) => {
}
return res
}
return parseStopover
}
module.exports = createParseStopover
module.exports = parseStopover

View file

@ -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
}

View file

@ -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)
}

View file

@ -3,48 +3,53 @@
const test = require('tape')
const parse = require('../../parse/date-time')
const profile = {
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()
})

View file

@ -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()
})

View file

@ -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},

View file

@ -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

View file

@ -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 = {
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])

View file

@ -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'

View file

@ -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',

View file

@ -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