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.

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.