smalruby-editor 0.1.5-x86-mingw32 → 0.1.6-x86-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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