smalruby-editor 0.1.5-x86-mingw32 → 0.1.6-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of smalruby-editor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +8 -0
- data/README.rdoc +3 -0
- data/app/assets/javascripts/application.js +0 -1
- data/app/assets/javascripts/models/source_code.js.coffee +5 -2
- data/app/assets/javascripts/smalruby.js.coffee +18 -11
- data/app/assets/javascripts/views/main_menu_view.js.coffee +139 -81
- data/app/controllers/source_codes_controller.rb +7 -0
- data/app/models/concerns/ruby_to_block.rb +391 -0
- data/app/models/source_code.rb +3 -2
- data/app/views/editor/demo.html.erb +1 -1
- data/app/views/editor/index.html.haml +2 -2
- data/config/routes.rb +1 -0
- data/{app/assets/demos → demos}/default.xml +0 -0
- data/{app/assets/demos → demos}/rgb_led_anode.xml +82 -82
- data/lib/smalruby_editor/version.rb +3 -3
- data/lib/tasks/release.rake +0 -4
- data/public/assets/{application-0047adbc0fb7a529ef7a41b5e0e7d097.js → application-8bfffad1222d4e198f3c7ccc1cbd774f.js} +179 -144
- data/public/assets/{application-0047adbc0fb7a529ef7a41b5e0e7d097.js.gz → application-8bfffad1222d4e198f3c7ccc1cbd774f.js.gz} +0 -0
- data/public/assets/manifest-332a5a1668194028b55103e0ea45c054.json +1 -1
- data/smalruby-editor.gemspec +0 -6
- data/spec/acceptance/base.feature +119 -0
- data/spec/acceptance/block_mode/translate.feature +14 -0
- data/spec/acceptance/ruby_mode/translate.feature +65 -0
- data/spec/acceptance/standalone/run.feature +14 -1
- data/spec/controllers/source_codes_controller_spec.rb +22 -0
- data/{public/assets/default-5d1886100d7c8961e9962bbc4bb0c714.xml → spec/fixtures/files/01.rb.xml} +0 -0
- data/spec/models/concerns/ruby_to_block_spec.rb +329 -0
- data/spec/steps/base_steps.rb +230 -0
- data/spec/steps/block_mode_steps.rb +9 -1
- data/spec/steps/fix_turnip.rb +28 -0
- data/spec/steps/text_editor_steps.rb +52 -193
- metadata +19 -97
- data/app/assets/javascripts/ruby_mode.js.coffee.erb +0 -12
- data/public/assets/rgb_led_anode-91225bef2a8b97f1cefee862a0619b91.xml +0 -83
- data/spec/acceptance/ruby_mode/base.feature +0 -33
- data/spec/steps/ace_steps.rb +0 -77
@@ -0,0 +1,391 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# rubocop:disable all
|
4
|
+
|
5
|
+
# Rubyのソースコードをブロックに変換するモジュール
|
6
|
+
module RubyToBlock
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
SUCCESS_DATA_MOCK = <<-EOS.strip_heredoc
|
10
|
+
require "smalruby"
|
11
|
+
|
12
|
+
car1 = Character.new(costume: "car1.png", x: 0, y: 0, angle: 0)
|
13
|
+
|
14
|
+
car1.on(:start) do
|
15
|
+
loop do
|
16
|
+
move(10)
|
17
|
+
turn_if_reach_wall
|
18
|
+
end
|
19
|
+
end
|
20
|
+
EOS
|
21
|
+
|
22
|
+
SUCCESS_XML_MOCK = <<-XML.strip_heredoc
|
23
|
+
<xml xmlns="http://www.w3.org/1999/xhtml">
|
24
|
+
<character name="car1" x="0" y="0" angle="0" costumes="car1.png" />
|
25
|
+
<block type="character_new" x="4" y="4">
|
26
|
+
<field name="NAME">car1</field>
|
27
|
+
<statement name="DO">
|
28
|
+
<block type="events_on_start">
|
29
|
+
<statement name="DO">
|
30
|
+
<block type="control_loop">
|
31
|
+
<statement name="DO">
|
32
|
+
<block type="motion_move" inline="true">
|
33
|
+
<value name="STEP">
|
34
|
+
<block type="math_number">
|
35
|
+
<field name="NUM">10</field>
|
36
|
+
</block>
|
37
|
+
</value>
|
38
|
+
<next>
|
39
|
+
<block type="motion_turn_if_reach_wall" />
|
40
|
+
</next>
|
41
|
+
</block>
|
42
|
+
</statement>
|
43
|
+
</block>
|
44
|
+
</statement>
|
45
|
+
</block>
|
46
|
+
</statement>
|
47
|
+
</block>
|
48
|
+
</xml>
|
49
|
+
XML
|
50
|
+
|
51
|
+
# XML形式のブロックに変換する
|
52
|
+
def to_blocks
|
53
|
+
fail if data == '__FAIL__'
|
54
|
+
return SUCCESS_XML_MOCK if data == SUCCESS_DATA_MOCK
|
55
|
+
|
56
|
+
characters = {}
|
57
|
+
character_stack = []
|
58
|
+
receiver_stack = []
|
59
|
+
statement_stack = []
|
60
|
+
blocks = []
|
61
|
+
current_block = nil
|
62
|
+
lines = data.lines
|
63
|
+
while (line = lines.shift)
|
64
|
+
line.chomp!
|
65
|
+
next if line.strip.empty?
|
66
|
+
|
67
|
+
md = STATEMENT_REGEXP.match(line)
|
68
|
+
|
69
|
+
unless md
|
70
|
+
block = Block.new('ruby_statement', fields: { STATEMENT: line })
|
71
|
+
if current_block # TODO: リファクタリング
|
72
|
+
current_block.sibling = block
|
73
|
+
else
|
74
|
+
blocks.push(block)
|
75
|
+
end
|
76
|
+
current_block = block
|
77
|
+
|
78
|
+
next
|
79
|
+
end
|
80
|
+
|
81
|
+
next if md[:require_smalruby]
|
82
|
+
|
83
|
+
if (s = md[:character])
|
84
|
+
md2 = /#{CHARACTER_RE}/.match(s)
|
85
|
+
name = md2[1]
|
86
|
+
|
87
|
+
characters[name] = Character.new(name: name, costumes: [md2[2]],
|
88
|
+
x: md2[3], y: md2[4], angle: md2[5])
|
89
|
+
next
|
90
|
+
end
|
91
|
+
|
92
|
+
if (s = md[:events_on_start])
|
93
|
+
md2 = /#{EVENTS_ON_START_RE}/.match(s)
|
94
|
+
name = md2[1] ? md2[1] : receiver_stack.last.name
|
95
|
+
c = characters[name]
|
96
|
+
character_stack.push(c)
|
97
|
+
|
98
|
+
do_block = Block.new('null')
|
99
|
+
events_on_start_block = Block.new('events_on_start', statements: { DO: do_block })
|
100
|
+
if current_block && current_block.type == 'character_new' &&
|
101
|
+
current_block[:NAME] == name
|
102
|
+
current_block.add_statement(:DO, events_on_start_block)
|
103
|
+
character_new_block = current_block
|
104
|
+
else
|
105
|
+
if c == receiver_stack.last
|
106
|
+
current_block.sibling = events_on_start_block
|
107
|
+
character_new_block = current_block.parent
|
108
|
+
else
|
109
|
+
character_new_block =
|
110
|
+
Block.new('character_new',
|
111
|
+
fields: { NAME: name }, statements: { DO: events_on_start_block })
|
112
|
+
if current_block
|
113
|
+
if current_block.type == 'character_new'
|
114
|
+
blocks.push(character_new_block)
|
115
|
+
else
|
116
|
+
current_block.sibling = character_new_block
|
117
|
+
end
|
118
|
+
else
|
119
|
+
blocks.push(character_new_block)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
statement_stack.push([:events_on_start, character_new_block])
|
124
|
+
|
125
|
+
receiver_stack.push(c)
|
126
|
+
|
127
|
+
current_block = do_block
|
128
|
+
|
129
|
+
next
|
130
|
+
end
|
131
|
+
|
132
|
+
if md[:end]
|
133
|
+
ends_num = lines.select { |l|
|
134
|
+
md2 = STATEMENT_REGEXP.match(l)
|
135
|
+
md2 && md2[:end]
|
136
|
+
}.length + 1
|
137
|
+
ends_num -= lines.select { |l|
|
138
|
+
md2 = STATEMENT_REGEXP.match(l)
|
139
|
+
md2 && (md2[:events_on_start])
|
140
|
+
}.length
|
141
|
+
if (ss = statement_stack.last) &&
|
142
|
+
ends_num <= statement_stack.length
|
143
|
+
case ss.first
|
144
|
+
when :events_on_start
|
145
|
+
current_block = ss[1]
|
146
|
+
|
147
|
+
receiver_stack.pop
|
148
|
+
character_stack.pop
|
149
|
+
statement_stack.pop
|
150
|
+
else
|
151
|
+
# TODO
|
152
|
+
end
|
153
|
+
else
|
154
|
+
block = Block.new('ruby_statement', fields: { STATEMENT: line })
|
155
|
+
if current_block # TODO: リファクタリング
|
156
|
+
current_block.sibling = block
|
157
|
+
else
|
158
|
+
blocks.push(block)
|
159
|
+
end
|
160
|
+
current_block = block
|
161
|
+
end
|
162
|
+
|
163
|
+
next
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
return '' if characters.empty? && blocks.empty?
|
168
|
+
|
169
|
+
xml = REXML::Document.new('<xml xmlns="http://www.w3.org/1999/xhtml" />',
|
170
|
+
attribute_quote: :quote,
|
171
|
+
respect_whitespace: :all)
|
172
|
+
characters.values.each do |c|
|
173
|
+
c.to_xml(xml.root)
|
174
|
+
end
|
175
|
+
blocks.each do |b|
|
176
|
+
b.to_xml(xml.root)
|
177
|
+
end
|
178
|
+
|
179
|
+
output = StringIO.new
|
180
|
+
formatter = REXML::Formatters::Pretty.new(2, true)
|
181
|
+
formatter.compact = true
|
182
|
+
# HACK: 行頭、行末などのスペースが1つになってしまうのを修正している
|
183
|
+
def formatter.write_text( node, output )
|
184
|
+
s = node.to_s()
|
185
|
+
s.gsub!(/\s/,' ')
|
186
|
+
if !node.is_a?(REXML::Text) ||
|
187
|
+
node.is_a?(REXML::Text) && !node.parent.whitespace()
|
188
|
+
s.squeeze!(" ")
|
189
|
+
end
|
190
|
+
s = wrap(s, @width - @level)
|
191
|
+
s = indent_text(s, @level, " ", true)
|
192
|
+
output << (' '*@level + s)
|
193
|
+
end
|
194
|
+
formatter.write(xml, output)
|
195
|
+
|
196
|
+
output.string + "\n"
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
# require "smalruby"
|
202
|
+
REQUIRE_SMALRUBY_RE = '^require\ "smalruby"$'
|
203
|
+
|
204
|
+
# car1 = Character.new(costume: "car1.png", x: 0, y: 0, angle: 0)
|
205
|
+
CHARACTER_RE =
|
206
|
+
'^\s*(\S+)\ =\ Character\.new\(costume:\ "([^"]+)",\ x:\ (\d+),\ y:\ (\d+),\ angle:\ (\d+)\)$'
|
207
|
+
|
208
|
+
EVENTS_ON_START_RE = '^\s*(?:(\S+)\.)?on\(:start\)\ do$'
|
209
|
+
|
210
|
+
END_RE = '^\s*end$'
|
211
|
+
|
212
|
+
STATEMENT_REGEXP = %r{
|
213
|
+
(?<require_smalruby>#{REQUIRE_SMALRUBY_RE})
|
214
|
+
|
|
215
|
+
(?<character>#{CHARACTER_RE})
|
216
|
+
|
|
217
|
+
(?<events_on_start>#{EVENTS_ON_START_RE})
|
218
|
+
|
|
219
|
+
(?<end>#{END_RE})
|
220
|
+
}x
|
221
|
+
|
222
|
+
EXPRESSION_REGEXP = %r{}x
|
223
|
+
LITERAL_REGEXP = %r{}x
|
224
|
+
|
225
|
+
# ソースコードに含まれるキャラクターを表現するクラス
|
226
|
+
class Character
|
227
|
+
attr_accessor :name
|
228
|
+
attr_accessor :costumes
|
229
|
+
attr_accessor :x
|
230
|
+
attr_accessor :y
|
231
|
+
attr_accessor :angle
|
232
|
+
|
233
|
+
def initialize(options)
|
234
|
+
@name = options[:name]
|
235
|
+
@costumes = options[:costumes]
|
236
|
+
@x = options[:x]
|
237
|
+
@y = options[:y]
|
238
|
+
@angle = options[:angle]
|
239
|
+
end
|
240
|
+
|
241
|
+
def to_xml(parent)
|
242
|
+
parent.add_element('character',
|
243
|
+
'name' => @name,
|
244
|
+
'x' => @x, 'y' => @y, 'angle' => @angle,
|
245
|
+
'costumes' => @costumes.join(','))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# ブロック群を表現するモジュール
|
250
|
+
module Block
|
251
|
+
# ブロックのインスタンスを生成する
|
252
|
+
def self.new(type, *args)
|
253
|
+
const_get(type.camelize).new(*args)
|
254
|
+
end
|
255
|
+
|
256
|
+
# すべてのブロックのベースクラス
|
257
|
+
class Base
|
258
|
+
attr_accessor :parent
|
259
|
+
attr_accessor :sibling
|
260
|
+
attr_accessor :fields
|
261
|
+
attr_accessor :values
|
262
|
+
attr_accessor :statements
|
263
|
+
|
264
|
+
def initialize(options = {})
|
265
|
+
@fields = options[:fields] || {}
|
266
|
+
@values = options[:values] || {}
|
267
|
+
@statements = options[:statements] || {}
|
268
|
+
|
269
|
+
if @statements.length > 0
|
270
|
+
@statements.values.each do |s|
|
271
|
+
s.parent = self
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_xml(parent)
|
277
|
+
e = parent.add_element('block', 'type' => type)
|
278
|
+
e.add_attribute('inline', 'true') if inline?
|
279
|
+
fields_to_xml(e)
|
280
|
+
values_to_xml(e)
|
281
|
+
statements_to_xml(e)
|
282
|
+
sibling_to_xml(e)
|
283
|
+
e
|
284
|
+
end
|
285
|
+
|
286
|
+
def type
|
287
|
+
@type ||= self.class.name.sub('RubyToBlock::Block::', '').underscore
|
288
|
+
end
|
289
|
+
|
290
|
+
def inline?
|
291
|
+
false
|
292
|
+
end
|
293
|
+
|
294
|
+
def null?
|
295
|
+
false
|
296
|
+
end
|
297
|
+
|
298
|
+
def [](name)
|
299
|
+
@fields[name]
|
300
|
+
end
|
301
|
+
|
302
|
+
def add_statement(name, block)
|
303
|
+
b = @statements[name]
|
304
|
+
b = b.sibling while b.sibling
|
305
|
+
b.sibling = block
|
306
|
+
end
|
307
|
+
|
308
|
+
def sibling=(block)
|
309
|
+
block.parent = self.parent
|
310
|
+
@sibling = block
|
311
|
+
end
|
312
|
+
|
313
|
+
def indent?
|
314
|
+
false
|
315
|
+
end
|
316
|
+
|
317
|
+
def indent_level
|
318
|
+
b = self
|
319
|
+
level = 0
|
320
|
+
while b.parent
|
321
|
+
level += 1 if b.indent?
|
322
|
+
b = b.parent
|
323
|
+
end
|
324
|
+
level
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
|
329
|
+
def fields_to_xml(parent)
|
330
|
+
@fields.each do |k, v|
|
331
|
+
e = parent.add_element('field', 'name' => k.to_s)
|
332
|
+
if v.is_a?(String)
|
333
|
+
e.text = v
|
334
|
+
else
|
335
|
+
# TODO
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def values_to_xml(parent)
|
341
|
+
@values.each do |k, v|
|
342
|
+
# TODO
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def statements_to_xml(parent)
|
347
|
+
@statements.each do |k, v|
|
348
|
+
next if v.null?
|
349
|
+
e = parent.add_element('statement', 'name' => k.to_s)
|
350
|
+
v.to_xml(e)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def sibling_to_xml(parent)
|
355
|
+
return nil unless @sibling
|
356
|
+
|
357
|
+
e = parent.add_element('next')
|
358
|
+
@sibling.to_xml(e)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
class Null < Base
|
363
|
+
def to_xml(parent)
|
364
|
+
return nil unless @sibling
|
365
|
+
|
366
|
+
@sibling.to_xml(parent)
|
367
|
+
end
|
368
|
+
|
369
|
+
def null?
|
370
|
+
!@sibling
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
class CharacterNew < Base; end
|
375
|
+
|
376
|
+
class RubyStatement < Base
|
377
|
+
def parent=(block)
|
378
|
+
@parent = block
|
379
|
+
@original_statement ||= @fields[:STATEMENT]
|
380
|
+
@fields[:STATEMENT] =
|
381
|
+
@original_statement.sub(/^ {0,#{indent_level * 2}}/, '')
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class EventsOnStart < Base
|
386
|
+
def indent?
|
387
|
+
true
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
data/app/models/source_code.rb
CHANGED
@@ -4,9 +4,12 @@ require 'tempfile'
|
|
4
4
|
require 'open3'
|
5
5
|
require 'digest/sha2'
|
6
6
|
require 'bundler'
|
7
|
+
require_relative 'concerns/ruby_to_block'
|
7
8
|
|
8
9
|
# ソースコードを表現するモデル
|
9
10
|
class SourceCode < ActiveRecord::Base
|
11
|
+
include RubyToBlock
|
12
|
+
|
10
13
|
validates :filename, presence: true
|
11
14
|
validate :validate_filename
|
12
15
|
validates :data, presence: true, allow_blank: true
|
@@ -73,7 +76,6 @@ class SourceCode < ActiveRecord::Base
|
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
76
|
-
# rubocop:disable MethodLength
|
77
79
|
def parse_ruby_error_messages(stderr_str)
|
78
80
|
stderr_str.lines.each.with_object([]) { |line, res|
|
79
81
|
if (md = /^\tfrom .*:(\d+):(in .*)$/.match(line))
|
@@ -88,5 +90,4 @@ class SourceCode < ActiveRecord::Base
|
|
88
90
|
end
|
89
91
|
}
|
90
92
|
end
|
91
|
-
# rubocop:enable MethodLength
|
92
93
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
<script type="text/javascript">
|
4
4
|
$(function() {
|
5
|
-
var data = '<%= escape_javascript(File.read(Rails.root.join("
|
5
|
+
var data = '<%= escape_javascript(File.read(Rails.root.join("demos/#{@filename}"))) %>';
|
6
6
|
data = $('<div>').html(data).text();
|
7
7
|
Smalruby.loadXml(data);
|
8
8
|
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.navbar-inner
|
3
3
|
%ul.nav{:id => 'tabs'}
|
4
4
|
%li.active
|
5
|
-
%a{:href =>
|
5
|
+
%a#block-mode-button{:href => "#block-tab"}<
|
6
6
|
%i.icon-folder-close
|
7
7
|
ブロック
|
8
8
|
%li
|
9
|
-
%a{:href =>
|
9
|
+
%a#ruby-mode-button{:href => "#ruby-tab"}<
|
10
10
|
%i.icon-pencil
|
11
11
|
Ruby
|
12
12
|
|
data/config/routes.rb
CHANGED