Skip to content

Commit 89096a4

Browse files
authored
Merge pull request #528 from smalruby/feature/issue-527-print-puts-p
feat: use new meta-comment format @ruby:method: for print/puts/p
2 parents de0fa4c + c76d860 commit 89096a4

File tree

8 files changed

+251
-4
lines changed

8 files changed

+251
-4
lines changed

src/containers/blocks.jsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,29 @@ class Blocks extends React.Component {
470470
if (fromRuby) {
471471
this.workspace.cleanUp();
472472

473+
// Re-calculate the position of the comments.
474+
this.workspace.getTopComments(false).forEach(comment => {
475+
if (comment.blockId) {
476+
const block = this.workspace.getBlockById(comment.blockId);
477+
if (block) {
478+
const blockXY = block.getRelativeToSurfaceXY();
479+
const blockHW = block.getHeightWidth();
480+
const rtl = this.workspace.RTL;
481+
const x = rtl ?
482+
blockXY.x - blockHW.width - 20 - comment.getWidth() :
483+
blockXY.x + blockHW.width + 20;
484+
const y = blockXY.y;
485+
comment.moveTo(x, y);
486+
487+
const targetComments = this.props.vm.editingTarget.comments;
488+
if (targetComments && targetComments[comment.id]) {
489+
targetComments[comment.id].x = x;
490+
targetComments[comment.id].y = y;
491+
}
492+
}
493+
}
494+
});
495+
473496
this.workspace.getTopBlocks(false).forEach(wsTopBlock => {
474497
const topBlock = blocks.getBlock(wsTopBlock.id);
475498
if (topBlock) {

src/lib/ruby-generator/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ RubyGenerator.scrub_ = function (block, code) {
356356
let commentCode = '';
357357
if (!this.isConnectedValue(block)) {
358358
let comment = this.getCommentText(block);
359-
if (comment) {
359+
if (comment && !comment.startsWith('@ruby:')) {
360360
commentCode += `${this.prefixLines(comment, '# ')}\n`;
361361
}
362362
const inputs = this.getInputs(block);
@@ -366,7 +366,12 @@ RubyGenerator.scrub_ = function (block, code) {
366366
if (childBlock) {
367367
comment = this.allNestedComments(childBlock);
368368
if (comment) {
369-
commentCode += this.prefixLines(comment, '# ');
369+
const filteredComment = comment.split('\n')
370+
.filter(line => !line.startsWith('@ruby:'))
371+
.join('\n');
372+
if (filteredComment.trim().length > 0) {
373+
commentCode += this.prefixLines(filteredComment, '# ');
374+
}
370375
}
371376
}
372377
}

src/lib/ruby-generator/looks.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export default function (Generator) {
1212

1313
Generator.looks_say = function (block) {
1414
const message = Generator.valueToCode(block, 'MESSAGE', Generator.ORDER_NONE) || Generator.quote_('');
15+
const comment = Generator.getCommentText(block);
16+
if (comment) {
17+
if (comment.startsWith('@ruby:method:')) {
18+
const methodName = comment.substring(13);
19+
if (['print', 'puts', 'p'].includes(methodName)) {
20+
return `${methodName}(${message})\n`;
21+
}
22+
}
23+
}
1524
return `say(${message})\n`;
1625
};
1726

src/lib/ruby-to-blocks-converter/index.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ class RubyToBlocksConverter {
162162
extensionIDs: new Set(),
163163

164164
blocks: {},
165+
comments: {},
165166
blockTypes: {},
166167
localVariables: {},
167168
variables: {},
@@ -283,11 +284,20 @@ class RubyToBlocksConverter {
283284
Object.keys(target.blocks._blocks).forEach(blockId => {
284285
target.blocks.deleteBlock(blockId);
285286
});
287+
target.comments = {};
286288

287289
Object.keys(this._context.blocks).forEach(blockId => {
288290
target.blocks.createBlock(this._context.blocks[blockId]);
289291
});
290292

293+
Object.keys(this._context.comments).forEach(commentId => {
294+
const comment = this._context.comments[commentId];
295+
target.createComment(
296+
comment.id, comment.blockId, comment.text,
297+
comment.x, comment.y, comment.width, comment.height, comment.minimized
298+
);
299+
});
300+
291301
this.vm.emitWorkspaceUpdate();
292302
});
293303
}
@@ -878,6 +888,25 @@ class RubyToBlocksConverter {
878888
return null;
879889
}
880890

891+
createComment (text, blockId, x = 0, y = 0, minimized = true) {
892+
return this._createComment(text, blockId, x, y, minimized);
893+
}
894+
895+
_createComment (text, blockId, x = 0, y = 0, minimized = true) {
896+
const id = Blockly.utils.genUid();
897+
this._context.comments[id] = {
898+
id: id,
899+
text: text,
900+
blockId: blockId,
901+
x: x,
902+
y: y,
903+
width: 200,
904+
height: 200,
905+
minimized: minimized
906+
};
907+
return id;
908+
}
909+
881910
createBlock (opcode, type, attributes = {}) {
882911
return this._createBlock(opcode, type, attributes);
883912
}
@@ -895,6 +924,9 @@ class RubyToBlocksConverter {
895924
x: void 0,
896925
y: void 0
897926
}, attributes);
927+
if (attributes.comment) {
928+
block.comment = this._createComment(attributes.comment, block.id);
929+
}
898930
this._context.blocks[block.id] = block;
899931
this._context.blockTypes[block.id] = type;
900932
return block;
@@ -1425,6 +1457,7 @@ class RubyToBlocksConverter {
14251457
} else {
14261458
result.push(block);
14271459
}
1460+
14281461
if (block.next) {
14291462
const b = this._lastBlock(block);
14301463
if (this._getBlockType(b) === 'statement') {

src/lib/ruby-to-blocks-converter/looks.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ const validateBackdrop = function (backdropName, args) {
7979
*/
8080
const LooksConverter = {
8181
register: function (converter) {
82+
['print', 'puts', 'p'].forEach(methodName => {
83+
converter.registerOnSend('sprite', methodName, 1, params => {
84+
const {args} = params;
85+
if (!converter._isNumberOrStringOrBlock(args[0])) return null;
86+
87+
const block = createBlockWithMessage.call(converter, 'looks_say', args[0], 'Hello!');
88+
block.comment = converter.createComment(`@ruby:method:${methodName}`, block.id, 200, 0);
89+
return block;
90+
});
91+
});
92+
8293
['say', 'think'].forEach(methodName => {
8394
converter.registerOnSend('sprite', methodName, 1, params => {
8495
const {args} = params;

test/helpers/expect-to-equal-blocks.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,12 @@ const expectToEqualBlock = function (context, parent, actualBlock, expectedBlock
182182
expect(blocks.getOpcode(block)).toEqual(expected.opcode);
183183
expect(block.parent).toEqual(parent);
184184
expect(block.shadow).toEqual(expected.shadow === true);
185-
expect(block.x).toEqual(void 0);
186-
expect(block.y).toEqual(void 0);
185+
if (expected.x !== void 0) {
186+
expect(block.x).toEqual(expected.x);
187+
}
188+
if (expected.y !== void 0) {
189+
expect(block.y).toEqual(expected.y);
190+
}
187191

188192
expectToEqualFields(context, blocks.getFields(block), expected.fields);
189193

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import RubyGenerator from '../../../../src/lib/ruby-generator';
2+
import LooksBlocks from '../../../../src/lib/ruby-generator/looks';
3+
4+
describe('RubyGenerator/Looks', () => {
5+
beforeEach(() => {
6+
RubyGenerator.cache_ = {
7+
comments: {},
8+
targetCommentTexts: []
9+
};
10+
RubyGenerator.definitions_ = {};
11+
RubyGenerator.functionNames_ = {};
12+
RubyGenerator.currentTarget = null;
13+
LooksBlocks(RubyGenerator);
14+
});
15+
16+
describe('looks_say', () => {
17+
test('normal say', () => {
18+
const block = {
19+
id: 'block-id',
20+
opcode: 'looks_say',
21+
inputs: {
22+
MESSAGE: {}
23+
}
24+
};
25+
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
26+
const expected = 'say("Hello!")\n';
27+
expect(RubyGenerator.looks_say(block)).toEqual(expected);
28+
});
29+
30+
test('with @ruby:method:print', () => {
31+
const block = {
32+
id: 'block-id',
33+
opcode: 'looks_say',
34+
inputs: {
35+
MESSAGE: {}
36+
}
37+
};
38+
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:print' };
39+
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
40+
const expected = 'print("Hello!")\n';
41+
expect(RubyGenerator.looks_say(block)).toEqual(expected);
42+
});
43+
44+
test('with @ruby:method:puts', () => {
45+
const block = {
46+
id: 'block-id',
47+
opcode: 'looks_say',
48+
inputs: {
49+
MESSAGE: {}
50+
}
51+
};
52+
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:puts' };
53+
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
54+
const expected = 'puts("Hello!")\n';
55+
expect(RubyGenerator.looks_say(block)).toEqual(expected);
56+
});
57+
58+
test('with @ruby:method:p', () => {
59+
const block = {
60+
id: 'block-id',
61+
opcode: 'looks_say',
62+
inputs: {
63+
MESSAGE: {}
64+
}
65+
};
66+
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:p' };
67+
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
68+
const expected = 'p("Hello!")\n';
69+
expect(RubyGenerator.looks_say(block)).toEqual(expected);
70+
});
71+
72+
test('with unknown @ruby: tag defaults to say', () => {
73+
const block = {
74+
id: 'block-id',
75+
opcode: 'looks_say',
76+
inputs: {
77+
MESSAGE: {}
78+
}
79+
};
80+
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:unknown' };
81+
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
82+
const expected = 'say("Hello!")\n';
83+
expect(RubyGenerator.looks_say(block)).toEqual(expected);
84+
});
85+
});
86+
87+
describe('scrub_ (meta-comment filtering)', () => {
88+
test('should filter out @ruby: comments', () => {
89+
const block = {
90+
id: 'block-id',
91+
opcode: 'looks_say',
92+
inputs: {},
93+
next: null
94+
};
95+
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:print' };
96+
RubyGenerator.getInputs = jest.fn().mockReturnValue({});
97+
RubyGenerator.isConnectedValue = jest.fn().mockReturnValue(false);
98+
RubyGenerator.getBlock = jest.fn().mockReturnValue(null);
99+
RubyGenerator.blockToCode = jest.fn().mockReturnValue('');
100+
101+
const code = 'print("Hello!")\n';
102+
const result = RubyGenerator.scrub_(block, code);
103+
104+
// Should NOT contain the comment since it starts with @ruby:
105+
expect(result).toEqual('print("Hello!")\n');
106+
});
107+
108+
test('should keep normal comments', () => {
109+
const block = {
110+
id: 'block-id',
111+
opcode: 'looks_say',
112+
inputs: {},
113+
next: null
114+
};
115+
RubyGenerator.cache_.comments['block-id'] = { text: 'normal comment' };
116+
RubyGenerator.getInputs = jest.fn().mockReturnValue({});
117+
RubyGenerator.isConnectedValue = jest.fn().mockReturnValue(false);
118+
RubyGenerator.getBlock = jest.fn().mockReturnValue(null);
119+
RubyGenerator.blockToCode = jest.fn().mockReturnValue('');
120+
121+
const code = 'say("Hello!")\n';
122+
const result = RubyGenerator.scrub_(block, code);
123+
124+
expect(result).toEqual('# normal comment\nsay("Hello!")\n');
125+
});
126+
});
127+
});

test/unit/lib/ruby-to-blocks-converter/looks.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,4 +1182,39 @@ describe('RubyToBlocksConverter/Looks', () => {
11821182
});
11831183
});
11841184
});
1185+
1186+
describe('print, puts, p', () => {
1187+
['print', 'puts', 'p'].forEach(method => {
1188+
test(`${method}("Hello") should become looks_say with comment`, () => {
1189+
code = `${method}("Hello")`;
1190+
expected = [
1191+
{
1192+
opcode: 'looks_say',
1193+
inputs: [
1194+
{
1195+
name: 'MESSAGE',
1196+
block: expectedInfo.makeText('Hello')
1197+
}
1198+
]
1199+
}
1200+
];
1201+
1202+
// First verify blocks structure
1203+
convertAndExpectToEqualBlocks(converter, target, code, expected);
1204+
1205+
// Then verify comment
1206+
// We need to find the block that is 'looks_say' (it should be the first/only top level block)
1207+
const blockId = Object.keys(converter.blocks).find(id => converter.blocks[id].opcode === 'looks_say');
1208+
const block = converter.blocks[blockId];
1209+
expect(block.comment).toBeDefined();
1210+
1211+
const commentId = block.comment;
1212+
expect(converter._context.comments[commentId]).toBeDefined();
1213+
expect(converter._context.comments[commentId].text).toEqual(`@ruby:method:${method}`);
1214+
expect(converter._context.comments[commentId].x).toEqual(200);
1215+
expect(converter._context.comments[commentId].y).toEqual(0);
1216+
expect(converter._context.comments[commentId].minimized).toBe(true);
1217+
});
1218+
});
1219+
});
11851220
});

0 commit comments

Comments
 (0)