Skip to content

Commit 631d662

Browse files
authored
Support non-spec message dividers (#57)
Parsing in genmsg leniently considers any line starting with '---' to be a valid message delimiter. Adopts this leniency to rosnodejs parsing. See https://github.com/ros/genmsg/blob/indigo-devel/src/genmsg/msg_loader.py#L459
1 parent f80d172 commit 631d662

3 files changed

Lines changed: 186 additions & 139 deletions

File tree

src/utils/messageGeneration/MessageSpec.js

Lines changed: 157 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,16 @@ class RosMsgSpec {
165165
fileContents = this._loadMessageFile(filePath);
166166
}
167167
if (fileContents !== null) {
168-
this.fileContents = this._extractRelevantMessage(fileContents);
168+
this.fileContents = fileContents;
169169

170-
this._extractFields(this.fileContents);
170+
this._parseMessage(fileContents);
171171
}
172172
}
173173

174+
_parseMessage() {
175+
throw new Error('Unable to parse message file for base class RosMsgSpec');
176+
}
177+
174178
/**
175179
* Generates the file data for this class
176180
*/
@@ -218,7 +222,89 @@ class RosMsgSpec {
218222
}
219223

220224
/**
221-
* TODO: move this to MsgSpec? Really only makes sense there...
225+
* For this message spec, generates the text used to calculate the message's md5 sum
226+
* @returns {string}
227+
*/
228+
getMd5text() {
229+
return '';
230+
}
231+
232+
/**
233+
* Get the md5 sum of this message
234+
* @returns {string}
235+
*/
236+
getMd5sum() {
237+
return md5(this.getMd5text());
238+
}
239+
240+
/**
241+
* Generates a depth-first list of all dependencies of this message in field order.
242+
* @param [deps] {Array}
243+
* @returns {Array}
244+
*/
245+
getFullDependencies(deps = []) {
246+
return [];
247+
}
248+
249+
/**
250+
* Computes the full text of a message/service.
251+
* Necessary for rosbags.
252+
* Mirrors gentools.
253+
* See compute_full_text() in
254+
* https://github.com/ros/ros/blob/kinetic-devel/core/roslib/src/roslib/gentools.py
255+
*/
256+
computeFullText() {
257+
const w = new IndentedWriter();
258+
259+
const deps = this.getFullDependencies();
260+
const sep = '='.repeat(80);
261+
w.write(this.fileContents.trim())
262+
.newline();
263+
264+
deps.forEach((dep) => {
265+
w.write(sep)
266+
.write(`MSG: ${dep.getFullMessageName()}`)
267+
.write(dep.fileContents.trim())
268+
.newline();
269+
});
270+
271+
return w.get().trim();
272+
}
273+
}
274+
275+
/**
276+
* Subclass of RosMsgSpec
277+
* Implements logic for individual ros messages as well as separated parts of services and actions
278+
* (e.g. Request, Response, Goal, ActionResult, ...)
279+
* @class MsgSpec
280+
*/
281+
class MsgSpec extends RosMsgSpec {
282+
constructor(msgCache, packageName, messageName, type, filePath=null, fileContents=null) {
283+
super(msgCache, packageName, messageName, type, filePath);
284+
this.constants = [];
285+
this.fields = [];
286+
287+
this.loadFile(filePath, fileContents);
288+
}
289+
290+
/**
291+
* Parses through message definition for fields and constants
292+
* @param content {string} relevant portion of message definition
293+
* @private
294+
*/
295+
_parseMessage(content) {
296+
let lines = content.split('\n').map((line) => line.trim());
297+
298+
try {
299+
lines.forEach(this._parseLine.bind(this));
300+
}
301+
catch (err) {
302+
console.error('Error while parsing message %s: %s', this.getFullMessageName(), err);
303+
throw err;
304+
}
305+
}
306+
307+
/**
222308
* Given a line from the message file, parse it for useful contents
223309
* @param line {string}
224310
* @private
@@ -284,135 +370,6 @@ class RosMsgSpec {
284370
this.fields.push(f);
285371
}
286372
}
287-
};
288-
289-
/**
290-
* Takes a full definition and pulls out the piece relevant to this specific message spec
291-
* (e.g. only the goal from the action message or only the request from the service message)
292-
* @param fileContents string
293-
* @returns {string}
294-
* @private
295-
*/
296-
_extractRelevantMessage(fileContents) {
297-
let lines = fileContents.split('\n').map((line) => line.trim());
298-
299-
switch (this.type) {
300-
case SRV_REQUEST_TYPE: {
301-
let divider = lines.indexOf(MSG_DIVIDER);
302-
lines = lines.slice(0, divider);
303-
break;
304-
}
305-
case SRV_RESPONSE_TYPE: {
306-
let divider = lines.indexOf(MSG_DIVIDER);
307-
lines = lines.slice(divider + 1);
308-
break;
309-
}
310-
case ACTION_GOAL_TYPE: {
311-
let divider = lines.indexOf(MSG_DIVIDER);
312-
lines = lines.slice(0, divider);
313-
break;
314-
}
315-
case ACTION_RESULT_TYPE: {
316-
const divider1 = lines.indexOf(MSG_DIVIDER) + 1;
317-
const divider2 = lines.indexOf(MSG_DIVIDER, divider1);
318-
lines = lines.slice(divider1, divider2);
319-
break;
320-
}
321-
case ACTION_FEEDBACK_TYPE: {
322-
let divider = lines.indexOf(MSG_DIVIDER);
323-
divider = lines.indexOf(MSG_DIVIDER, divider+1);
324-
lines = lines.slice(divider + 1);
325-
break;
326-
}
327-
default:
328-
break;
329-
}
330-
331-
return lines.join('\n');
332-
}
333-
334-
/**
335-
* Parses through message definition for fields and constants
336-
* Todo: move this to MsgSpec?
337-
* @param content {string} raw message definition
338-
* @private
339-
*/
340-
_extractFields(content) {
341-
let lines = content.split('\n').map((line) => line.trim());
342-
343-
try {
344-
lines.forEach(this._parseLine.bind(this));
345-
}
346-
catch (err) {
347-
console.error('Error while parsing message %s: %s', this.getFullMessageName(), err);
348-
throw err;
349-
}
350-
}
351-
352-
/**
353-
* For this message spec, generates the text used to calculate the message's md5 sum
354-
* @returns {string}
355-
*/
356-
getMd5text() {
357-
return '';
358-
}
359-
360-
/**
361-
* Get the md5 sum of this message
362-
* @returns {string}
363-
*/
364-
getMd5sum() {
365-
return md5(this.getMd5text());
366-
}
367-
368-
/**
369-
* Generates a depth-first list of all dependencies of this message in field order.
370-
* @param [deps] {Array}
371-
* @returns {Array}
372-
*/
373-
getFullDependencies(deps = []) {
374-
return [];
375-
}
376-
377-
/**
378-
* Computes the full text of a message/service.
379-
* Necessary for rosbags.
380-
* Mirrors gentools.
381-
* See compute_full_text() in
382-
* https://github.com/ros/ros/blob/kinetic-devel/core/roslib/src/roslib/gentools.py
383-
*/
384-
computeFullText() {
385-
const w = new IndentedWriter();
386-
387-
const deps = this.getFullDependencies();
388-
const sep = '='.repeat(80);
389-
w.write(this.fileContents.trim())
390-
.newline();
391-
392-
deps.forEach((dep) => {
393-
w.write(sep)
394-
.write(`MSG: ${dep.getFullMessageName()}`)
395-
.write(dep.fileContents.trim())
396-
.newline();
397-
});
398-
399-
return w.get().trim();
400-
}
401-
}
402-
403-
/**
404-
* Subclass of RosMsgSpec
405-
* Implements logic for individual ros messages as well as separated parts of services and actions
406-
* (e.g. Request, Response, Goal, ActionResult, ...)
407-
* @class MsgSpec
408-
*/
409-
class MsgSpec extends RosMsgSpec {
410-
constructor(msgCache, packageName, messageName, type, filePath=null, fileContents=null) {
411-
super(msgCache, packageName, messageName, type, filePath);
412-
this.constants = [];
413-
this.fields = [];
414-
415-
this.loadFile(filePath, fileContents);
416373
}
417374

418375
/**
@@ -558,9 +515,38 @@ class SrvSpec extends RosMsgSpec {
558515
super(msgCache, packageName, messageName, type, filePath);
559516

560517
this.fileContents = this._loadMessageFile(filePath);
518+
const {req, resp} = this._extractMessageSections(this.fileContents);
561519

562-
this.request = new MsgSpec(msgCache, packageName, messageName + 'Request', SRV_REQUEST_TYPE, null, this.fileContents);
563-
this.response = new MsgSpec(msgCache, packageName, messageName + 'Response', SRV_RESPONSE_TYPE, null, this.fileContents);
520+
this.request = new MsgSpec(msgCache, packageName, messageName + 'Request', SRV_REQUEST_TYPE, null, req);
521+
this.response = new MsgSpec(msgCache, packageName, messageName + 'Response', SRV_RESPONSE_TYPE, null, resp);
522+
}
523+
524+
/**
525+
* Takes a full service definition and pulls out the request and response sections
526+
* @param fileContents {string}
527+
* @returns {object}
528+
* @private
529+
*/
530+
_extractMessageSections(fileContents) {
531+
let lines = fileContents.split('\n').map((line) => line.trim());
532+
533+
const sections = {
534+
req: '',
535+
resp: ''
536+
};
537+
538+
let currentSection = 'req';
539+
540+
lines.forEach((line) => {
541+
if (line.startsWith(MSG_DIVIDER)) {
542+
currentSection = 'resp';
543+
}
544+
else {
545+
sections[currentSection] += `\n${line}`;
546+
}
547+
});
548+
549+
return sections;
564550
}
565551

566552
getMd5text() {
@@ -594,14 +580,47 @@ class ActionSpec extends RosMsgSpec {
594580
super(msgCache, packageName, messageName, type, filePath);
595581

596582
this.fileContents = this._loadMessageFile(filePath);
583+
const {goal, result, feedback} = this._extractMessageSections(this.fileContents);
597584

598585
// Parse the action definition into its 3 respective parts
599-
this.goal = new MsgSpec(msgCache, packageName, messageName + 'Goal', ACTION_GOAL_TYPE, null, this.fileContents);
600-
this.result = new MsgSpec(msgCache, packageName, messageName + 'Result', ACTION_RESULT_TYPE, null, this.fileContents);
601-
this.feedback = new MsgSpec(msgCache, packageName, messageName + 'Feedback', ACTION_FEEDBACK_TYPE, null, this.fileContents);
586+
this.goal = new MsgSpec(msgCache, packageName, messageName + 'Goal', ACTION_GOAL_TYPE, null, goal);
587+
this.result = new MsgSpec(msgCache, packageName, messageName + 'Result', ACTION_RESULT_TYPE, null, result);
588+
this.feedback = new MsgSpec(msgCache, packageName, messageName + 'Feedback', ACTION_FEEDBACK_TYPE, null, feedback);
602589
this.generateActionMessages();
603590
}
604591

592+
/**
593+
* Takes a full service definition and pulls out the request and response sections
594+
* @param fileContents {string}
595+
* @returns {object}
596+
* @private
597+
*/
598+
_extractMessageSections(fileContents) {
599+
let lines = fileContents.split('\n').map((line) => line.trim());
600+
601+
const sections = {
602+
goal: '',
603+
result: '',
604+
feedback: ''
605+
};
606+
607+
let currentSection = 'goal';
608+
609+
lines.forEach((line) => {
610+
if (line.startsWith(MSG_DIVIDER)) {
611+
currentSection = {
612+
goal: 'result',
613+
result: 'feedback'
614+
}[currentSection];
615+
}
616+
else {
617+
sections[currentSection] += `\n${line}`;
618+
}
619+
});
620+
621+
return sections;
622+
}
623+
605624
/**
606625
* Get a list of all the message specs created by this ros action
607626
* @returns {MsgSpec[]}
@@ -670,4 +689,4 @@ RosMsgSpec.ACTION_ACTION_FEEDBACK_TYPE = ACTION_ACTION_FEEDBACK_TYPE;
670689
RosMsgSpec.ACTION_ACTION_RESULT_TYPE = ACTION_ACTION_RESULT_TYPE;
671690
RosMsgSpec.ACTION_ACTION_TYPE = ACTION_ACTION_TYPE;
672691

673-
module.exports = RosMsgSpec;
692+
module.exports = RosMsgSpec;

test/gennodejsTest.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,20 @@ describe('gennodejsTests', () => {
735735

736736
done();
737737
});
738+
739+
it('Service with improper divider', (done) => {
740+
// correct message divider is '---'
741+
// we're allowing any line starting with '---' though, parroting genmsg
742+
const HeaderService = msgUtils.getHandlerForSrvType('test_msgs/NonSpecServiceDivider');
743+
744+
const requestMsg = HeaderService.Request.messageDefinition().trim();
745+
expect(requestMsg).to.equal('string data');
746+
747+
const responseMsg = HeaderService.Response.messageDefinition().trim();
748+
expect(responseMsg).to.equal('uint8 response');
749+
750+
done();
751+
});
738752
});
739753

740754
describe('actions', () => {
@@ -996,4 +1010,4 @@ describe('gennodejsTests', () => {
9961010
expect(() => VariableLengthArray.serialize(fullMsg, buf, 0)).to.not.throw(Error);
9971011
});
9981012
});
999-
});
1013+
});

test/onTheFlyMessages.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,20 @@ describe('On The Fly Message Tests', () => {
740740

741741
done();
742742
});
743+
744+
it('Service with improper divider', (done) => {
745+
// correct message divider is '---'
746+
// we're allowing any line starting with '---' though, parroting genmsg
747+
const HeaderService = msgUtils.getHandlerForSrvType('test_msgs/NonSpecServiceDivider');
748+
749+
const requestMsg = HeaderService.Request.messageDefinition().trim();
750+
expect(requestMsg).to.equal('string data');
751+
752+
const responseMsg = HeaderService.Response.messageDefinition().trim();
753+
expect(responseMsg).to.equal('uint8 response');
754+
755+
done();
756+
});
743757
});
744758

745759
describe('actions', () => {

0 commit comments

Comments
 (0)