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.

Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.rubocop.yml +8 -0
  4. data/README.rdoc +3 -0
  5. data/app/assets/javascripts/application.js +0 -1
  6. data/app/assets/javascripts/models/source_code.js.coffee +5 -2
  7. data/app/assets/javascripts/smalruby.js.coffee +18 -11
  8. data/app/assets/javascripts/views/main_menu_view.js.coffee +139 -81
  9. data/app/controllers/source_codes_controller.rb +7 -0
  10. data/app/models/concerns/ruby_to_block.rb +391 -0
  11. data/app/models/source_code.rb +3 -2
  12. data/app/views/editor/demo.html.erb +1 -1
  13. data/app/views/editor/index.html.haml +2 -2
  14. data/config/routes.rb +1 -0
  15. data/{app/assets/demos → demos}/default.xml +0 -0
  16. data/{app/assets/demos → demos}/rgb_led_anode.xml +82 -82
  17. data/lib/smalruby_editor/version.rb +3 -3
  18. data/lib/tasks/release.rake +0 -4
  19. data/public/assets/{application-0047adbc0fb7a529ef7a41b5e0e7d097.js → application-8bfffad1222d4e198f3c7ccc1cbd774f.js} +179 -144
  20. data/public/assets/{application-0047adbc0fb7a529ef7a41b5e0e7d097.js.gz → application-8bfffad1222d4e198f3c7ccc1cbd774f.js.gz} +0 -0
  21. data/public/assets/manifest-332a5a1668194028b55103e0ea45c054.json +1 -1
  22. data/smalruby-editor.gemspec +0 -6
  23. data/spec/acceptance/base.feature +119 -0
  24. data/spec/acceptance/block_mode/translate.feature +14 -0
  25. data/spec/acceptance/ruby_mode/translate.feature +65 -0
  26. data/spec/acceptance/standalone/run.feature +14 -1
  27. data/spec/controllers/source_codes_controller_spec.rb +22 -0
  28. data/{public/assets/default-5d1886100d7c8961e9962bbc4bb0c714.xml → spec/fixtures/files/01.rb.xml} +0 -0
  29. data/spec/models/concerns/ruby_to_block_spec.rb +329 -0
  30. data/spec/steps/base_steps.rb +230 -0
  31. data/spec/steps/block_mode_steps.rb +9 -1
  32. data/spec/steps/fix_turnip.rb +28 -0
  33. data/spec/steps/text_editor_steps.rb +52 -193
  34. metadata +19 -97
  35. data/app/assets/javascripts/ruby_mode.js.coffee.erb +0 -12
  36. data/public/assets/rgb_led_anode-91225bef2a8b97f1cefee862a0619b91.xml +0 -83
  37. data/spec/acceptance/ruby_mode/base.feature +0 -33
  38. 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
@@ -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("app/assets/demos/#{@filename}"))) %>';
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 => '#block-tab', :'data-toggle' => 'tab'}<
5
+ %a#block-mode-button{:href => "#block-tab"}<
6
6
  %i.icon-folder-close
7
7
  ブロック
8
8
  %li
9
- %a{:href => '#ruby-tab', :'data-toggle' => 'tab'}<
9
+ %a#ruby-mode-button{:href => "#ruby-tab"}<
10
10
  %i.icon-pencil
11
11
  Ruby
12
12
 
data/config/routes.rb CHANGED
@@ -11,6 +11,7 @@ SmalrubyEditor::Application.routes.draw do
11
11
  post 'source_codes/load'
12
12
  delete 'source_codes/write'
13
13
  post 'source_codes/run'
14
+ post 'source_codes/to_blocks'
14
15
 
15
16
  # The priority is based upon order of creation: first created ->
16
17
  # highest priority.