Skip to content

Commit

Permalink
Feat: Roster list (#19)
Browse files Browse the repository at this point in the history
* Updated Roster List and Item commands

Changed from `<J>` to `<JR>`

* Updated Roster Item parser

Changed from `<j>` to `<jR>`

* Added Roster List Parser

This includes reworking the Roster Item Parser to handle the commands correctly
  • Loading branch information
dcyoung-dev authored Jan 5, 2024
1 parent 73708fb commit b4fc37c
Show file tree
Hide file tree
Showing 14 changed files with 183 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/commands/rosters/rosterCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface RosterCommandParams {
cabId?: number
}

const rosterSendKey = 'J'
const rosterSendKey = 'JR'

export const rosterCommand: (params?: RosterCommandParams) => string = ({ cabId } = {}) => {
return makeCommandFromAttributes([rosterSendKey, cabId])
Expand Down
3 changes: 2 additions & 1 deletion src/parsers/genericParser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ParserResult } from '../types/index.js'
import { rosterItemParser } from './rosters/index.js'
import { rosterItemParser, rosterListParser } from './rosters/index.js'
import { eraseParser, storeParser } from './eeproms/index.js'
import { powerParser } from './powers/index.js'
import { locoParser, throttleParser } from './throttles/index.js'
Expand Down Expand Up @@ -39,6 +39,7 @@ export const genericParser: GenericParser = () => {
locoParser,
powerParser,
rosterItemParser,
rosterListParser,
storeParser,
throttleParser,
decoderAddressParser,
Expand Down
1 change: 1 addition & 0 deletions src/parsers/rosters/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './rosterItemParser.js'
export * from './rosterListParser.js'
16 changes: 10 additions & 6 deletions src/parsers/rosters/rosterItemParser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Command, parseCommand } from '../../utils/index.js'
import { Command, parseCommand, removeDoubleQuotes } from '../../utils/index.js'
import {
FunctionButton,
FunctionButtonKind,
Expand All @@ -7,7 +7,7 @@ import {
ParserResult,
ParserStatus
} from '../../types/index.js'
import { ParserKeyError } from '../errors/index.js'
import { ParserAttributeError, ParserKeyError } from '../errors/index.js'

type RosterFunctionButton = Pick<FunctionButton, 'display' | 'kind'>
type RosterFunctionButtons = FunctionButtons<RosterFunctionButton>
Expand All @@ -18,7 +18,7 @@ interface RosterItemParams {
}

export type RosterItemResult = ParserResult<RosterItemParams>
const rosterItemParserKey = 'j'
const rosterItemParserKey = 'jR'

const functionButtonsParser = (param: string | null = null): RosterFunctionButtons => {
if (param === null) {
Expand All @@ -31,7 +31,7 @@ const functionButtonsParser = (param: string | null = null): RosterFunctionButto
const [display, isPress] = button.split(/(\*)/).reverse()
const kind = (isPress == null && !isPress) ? FunctionButtonKind.TOGGLE : FunctionButtonKind.PRESS
accum[index] = {
display,
display: removeDoubleQuotes(display),
kind
}
return accum
Expand All @@ -41,10 +41,14 @@ const functionButtonsParser = (param: string | null = null): RosterFunctionButto
const parseFromCommand: (params: Command) => RosterItemResult = ({ key, attributes }) => {
const [cabId, display, rawFunctionButtons] = attributes

if (key !== rosterItemParserKey) {
if (key !== rosterItemParserKey || attributes.length > 3) {
throw new ParserKeyError('rosterItemParser', key)
}

if (!display.includes('"')) {
throw new ParserAttributeError('display', display, 'display must be wrapped in double quotes')
}

const functionButtons = functionButtonsParser(rawFunctionButtons)

return {
Expand All @@ -53,7 +57,7 @@ const parseFromCommand: (params: Command) => RosterItemResult = ({ key, attribut
status: ParserStatus.SUCCESS,
params: {
cabId: parseInt(cabId),
display,
display: removeDoubleQuotes(display),
functionButtons
}
}
Expand Down
41 changes: 41 additions & 0 deletions src/parsers/rosters/rosterListParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Command, parseCommand } from '../../utils/index.js'
import { FunctionName, ParserResult, ParserStatus } from '../../types/index.js'
import { ParserKeyError } from '../errors/index.js'

interface RosterListParams {
cabIds: number[]
}

export type RosterListResult = ParserResult<RosterListParams>
const rosterListParserKey = 'jR'

const parseFromCommand: (params: Command) => RosterListResult = ({ key, attributes }) => {
const possibleCabIds = attributes

if (key !== rosterListParserKey) {
throw new ParserKeyError('rosterListParser', key)
}

const cabIds = possibleCabIds.map(cabIdString => {
const cabId = parseInt(cabIdString)
if (!isNaN(cabId) && isFinite(cabId)) {
return cabId
}

throw new Error(`Cab ID ${cabIdString} is not a number`)
})

return {
key: rosterListParserKey,
parser: FunctionName.ROSTER_LIST,
status: ParserStatus.SUCCESS,
params: {
cabIds
}
}
}

export const rosterListParser: (command: string) => RosterListResult = (command) => {
const commandParams = parseCommand(command)
return parseFromCommand(commandParams)
}
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum FunctionName {
LOCO = 'locoParser',
POWER = 'powerParser',
ROSTER_ITEM = 'rosterItemParser',
ROSTER_LIST = 'rosterListParser',
THROTTLE = 'throttleParser',
DECODER_ADDRESS = 'decoderAddress',
TURNOUT = 'turnoutParser',
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './makeCommand.js'
export * from './parseAddress.js'
export * from './parseCommand.js'
export * from './removeQuotes.js'
4 changes: 1 addition & 3 deletions src/utils/parseCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ function splitBySpaceOrQuote (cleanedParams: string): string[] {
}

function getStrings (cleanedParams: string): string[] {
return splitBySpaceOrQuote(cleanedParams).map((part) => {
return part.replaceAll('"', '')
})
return splitBySpaceOrQuote(cleanedParams)
}

export function parseCommand (command: string): Command {
Expand Down
3 changes: 3 additions & 0 deletions src/utils/removeQuotes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function removeDoubleQuotes (str: string): string {
return str.replaceAll('"', '')
}
4 changes: 2 additions & 2 deletions tests/unit/commands/rosters/rosterCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { rosterCommand } from '../../../../src'

describe('rosterCommand()', function () {
it('is valid', function () {
const sendString = '<J>'
const sendString = '<JR>'
const command = rosterCommand()
expect(command).toEqual(sendString)
})

describe('for a specific Cab', () => {
it('is valid', function () {
const sendString = '<J 70>'
const sendString = '<JR 70>'
const command = rosterCommand({ cabId: 70 })
expect(command).toEqual(sendString)
})
Expand Down
62 changes: 57 additions & 5 deletions tests/unit/parsers/genericParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('createParser()', () => {
describe('when no parsers can parse the command', () => {
it('should raise AggregateError', async () => {
const parser = createParser([])
const result = parser.parse('<j 70 "My Loco" "Flash/Ring/*Blast">')
const result = parser.parse('<jR 70 "My Loco" "Flash/Ring/*Blast">')
return await result.catch((e: any) =>
expect(e).toBeInstanceOf(AggregateError)
)
Expand All @@ -31,10 +31,10 @@ describe('createParser()', () => {
rosterItemParser
]
const parser = createParser(parsers)
const result = await parser.parse('<j 70 "My Loco" "Flash/Ring/*Blast">')
const result = await parser.parse('<jR 70 "My Loco" "Flash/Ring/*Blast">')

const expected: RosterItemResult = {
key: 'j',
key: 'jR',
parser: FunctionName.ROSTER_ITEM,
params: {
cabId: 70,
Expand Down Expand Up @@ -123,9 +123,9 @@ describe('genericParser()', () => {
}
},
{
command: '<j 70 "My Loco" "Flash/Ring/*Blast">',
command: '<jR 70 "My Loco" "Flash/Ring/*Blast">',
expectation: {
key: 'j',
key: 'jR',
parser: FunctionName.ROSTER_ITEM,
params: {
cabId: 70,
Expand All @@ -148,6 +148,58 @@ describe('genericParser()', () => {
status: ParserStatus.SUCCESS
}
},
{
command: '<jR 1 22 333 4444>',
expectation: {
key: 'jR',
parser: FunctionName.ROSTER_LIST,
params: {
cabIds: [
1,
22,
333,
4444
]
},
status: ParserStatus.SUCCESS
}
},
{
command: '<jR>',
expectation: {
key: 'jR',
parser: FunctionName.ROSTER_LIST,
params: {
cabIds: []
},
status: ParserStatus.SUCCESS
}
},

{
command: '<jR 200 10>',
expectation: {
key: 'jR',
parser: FunctionName.ROSTER_LIST,
params: {
cabIds: [200, 10]
},
status: ParserStatus.SUCCESS
}
},
{
command: '<jR 200 "10">',
expectation: {
key: 'jR',
parser: FunctionName.ROSTER_ITEM,
params: {
cabId: 200,
display: '10',
functionButtons: {}
},
status: ParserStatus.SUCCESS
}
},
{
command: '<l 10 1 127 43>',
expectation: {
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/parsers/rosters/rosterItemParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import {

describe('rosterItemParser()', function () {
describe('without function descriptions', function () {
it('parses `<j 70 "My Loco">`', () => {
const result = rosterItemParser('<j 70 "My Loco">')
it('parses `<jR 70 "My Loco">`', () => {
const result = rosterItemParser('<jR 70 "My Loco">')

const expected: RosterItemResult = {
key: 'j',
key: 'jR',
parser: FunctionName.ROSTER_ITEM,
params: {
cabId: 70,
Expand All @@ -27,11 +27,11 @@ describe('rosterItemParser()', function () {
})

describe('with function descriptions', function () {
it('parses `<j 70 "My Loco" "Flash/Ring/*Blast">`', () => {
const result = rosterItemParser('<j 70 "My Loco" "Flash/Ring/*Blast">')
it('parses `<jR 70 "My Loco" "Flash/Ring/*Blast">`', () => {
const result = rosterItemParser('<jR 70 "My Loco" "Flash/Ring/*Blast">')

const expected: RosterItemResult = {
key: 'j',
key: 'jR',
parser: FunctionName.ROSTER_ITEM,
params: {
cabId: 70,
Expand Down
54 changes: 54 additions & 0 deletions tests/unit/parsers/rosters/rosterListParser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
FunctionName,
ParserKeyError,
ParserStatus,
rosterListParser,
RosterListResult
} from '../../../../src'

describe('rosterListParser()', function () {
describe('without any Roster Items', function () {
it('parses `<jR>`', () => {
const result = rosterListParser('<jR>')

const expected: RosterListResult = {
key: 'jR',
parser: FunctionName.ROSTER_LIST,
params: {
cabIds: []
},
status: ParserStatus.SUCCESS
}
expect(result).toEqual(expected)
})
})

describe('with Roster Items', function () {
it('parses `<jR 1 22 333 4444>`', () => {
const result = rosterListParser('<jR 1 22 333 4444>')

const expected: RosterListResult = {
key: 'jR',
parser: FunctionName.ROSTER_LIST,
params: {
cabIds: [
1,
22,
333,
4444
]
},
status: ParserStatus.SUCCESS
}
expect(result).toEqual(expected)
})
})

describe('with incorrect key', function () {
it('throws a ParserKeyError', function () {
expect(() => {
rosterListParser('<incorrect-key>')
}).toThrowError(ParserKeyError)
})
})
})
6 changes: 3 additions & 3 deletions tests/unit/utils/parseCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ describe('parseCommand()', () => {

describe('with string values', () => {
it('returns a key and string attributes', () => {
expect(parseCommand('<j 70 "My Loco" "Other Parts">'))
expect(parseCommand('<jR 70 "My Loco" "Other Parts">'))
.toEqual({
key: 'j',
attributes: ['70', 'My Loco', 'Other Parts']
key: 'jR',
attributes: ['70', '"My Loco"', '"Other Parts"']
})
})
})
Expand Down

0 comments on commit b4fc37c

Please sign in to comment.