procon_bypass_man 0.1.17 → 0.1.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +85 -11
  3. data/.rubocop.yml +2 -0
  4. data/CHANGELOG.md +22 -1
  5. data/Gemfile.lock +4 -3
  6. data/README.md +19 -20
  7. data/docs/getting_started.md +88 -0
  8. data/docs/setting/splatoon2_macro_sokuwari_bubble.md +52 -0
  9. data/docs/setting/splatoon2_recommended_setting.md +127 -0
  10. data/docs/upgrade_pbm.md +56 -0
  11. data/lib/ext/module.rb +16 -0
  12. data/lib/procon_bypass_man/background/jobs/report_error_reload_config_job.rb +11 -0
  13. data/lib/procon_bypass_man/background/jobs/report_start_reboot_job.rb +10 -0
  14. data/lib/procon_bypass_man/background.rb +2 -0
  15. data/lib/procon_bypass_man/buttons_setting_configuration/loader.rb +24 -3
  16. data/lib/procon_bypass_man/buttons_setting_configuration/validator.rb +12 -2
  17. data/lib/procon_bypass_man/buttons_setting_configuration.rb +9 -9
  18. data/lib/procon_bypass_man/bypass.rb +9 -8
  19. data/lib/procon_bypass_man/commands/bypass_command.rb +4 -11
  20. data/lib/procon_bypass_man/commands/connect_device_command.rb +23 -11
  21. data/lib/procon_bypass_man/configuration.rb +15 -1
  22. data/lib/procon_bypass_man/device_connector.rb +6 -33
  23. data/lib/procon_bypass_man/device_procon_finder.rb +65 -0
  24. data/lib/procon_bypass_man/never_exit_accidentally.rb +12 -0
  25. data/lib/procon_bypass_man/plugin/splatoon2/macro/sokuwari_for_splash_bomb.rb +22 -0
  26. data/lib/procon_bypass_man/plugins.rb +1 -0
  27. data/lib/procon_bypass_man/procon/button.rb +1 -1
  28. data/lib/procon_bypass_man/procon/macro.rb +89 -0
  29. data/lib/procon_bypass_man/procon/macro_builder.rb +123 -0
  30. data/lib/procon_bypass_man/procon/macro_registry.rb +2 -23
  31. data/lib/procon_bypass_man/procon/user_operation.rb +16 -2
  32. data/lib/procon_bypass_man/procon.rb +2 -0
  33. data/lib/procon_bypass_man/remote_pbm_action/reboot_os_action.rb +1 -0
  34. data/lib/procon_bypass_man/remote_pbm_action/restore_pbm_setting.rb +9 -2
  35. data/lib/procon_bypass_man/runner.rb +7 -7
  36. data/lib/procon_bypass_man/support/yaml_writer.rb +16 -0
  37. data/lib/procon_bypass_man/version.rb +1 -1
  38. data/lib/procon_bypass_man.rb +25 -24
  39. data/procon_bypass_man.gemspec +1 -1
  40. data/project_template/app.rb +5 -2
  41. data/project_template/setting.yml +6 -22
  42. data/sig/main.rbs +5 -0
  43. metadata +17 -4
@@ -6,7 +6,9 @@ require "procon_bypass_man/background/job_performer"
6
6
  require "procon_bypass_man/background/jobs/base_job"
7
7
  require "procon_bypass_man/background/jobs/report_event_base_job"
8
8
  require "procon_bypass_man/background/jobs/report_boot_job"
9
+ require "procon_bypass_man/background/jobs/report_start_reboot_job"
9
10
  require "procon_bypass_man/background/jobs/report_reload_config_job"
11
+ require "procon_bypass_man/background/jobs/report_error_reload_config_job"
10
12
  require "procon_bypass_man/background/jobs/report_load_config_job"
11
13
  require "procon_bypass_man/background/jobs/report_error_job"
12
14
  require "procon_bypass_man/background/jobs/report_pressed_buttons_job"
@@ -5,6 +5,8 @@ module ProconBypassMan
5
5
 
6
6
  # @return [ProconBypassMan::ButtonsSettingConfiguration]
7
7
  def self.load(setting_path: )
8
+ ProconBypassMan::ButtonsSettingConfiguration.instance.setting_path = setting_path
9
+
8
10
  ProconBypassMan::ButtonsSettingConfiguration.switch_new_context(:validation) do |new_instance|
9
11
  yaml = YAML.load_file(setting_path) or raise "読み込みに失敗しました"
10
12
  new_instance.instance_eval(yaml["setting"])
@@ -12,15 +14,19 @@ module ProconBypassMan
12
14
  if validator.valid?
13
15
  next
14
16
  else
15
- raise ProconBypassMan::CouldNotLoadConfigError, validator.errors
17
+ raise ProconBypassMan::CouldNotLoadConfigError, validator.errors_to_s
16
18
  end
17
19
  rescue SyntaxError
18
- raise ProconBypassMan::CouldNotLoadConfigError, "Rubyのシンタックスエラーです"
20
+ fallback_setting_if_has_backup(current_setting_path: setting_path)
21
+ raise ProconBypassMan::CouldNotLoadConfigError, "Rubyスクリプトのシンタックスエラーです"
22
+ rescue NoMethodError
23
+ fallback_setting_if_has_backup(current_setting_path: setting_path)
24
+ raise ProconBypassMan::CouldNotLoadConfigError, "Rubyスクリプトに未定義の定数・変数があります"
19
25
  rescue Psych::SyntaxError
26
+ fallback_setting_if_has_backup(current_setting_path: setting_path)
20
27
  raise ProconBypassMan::CouldNotLoadConfigError, "yamlのシンタックスエラーです"
21
28
  end
22
29
 
23
- ProconBypassMan::ButtonsSettingConfiguration.instance.setting_path = setting_path
24
30
  ProconBypassMan::ButtonsSettingConfiguration.instance.reset!
25
31
  ProconBypassMan.reset!
26
32
 
@@ -36,12 +42,27 @@ module ProconBypassMan
36
42
 
37
43
  File.write(ProconBypassMan.digest_path, Digest::MD5.hexdigest(yaml["setting"]))
38
44
 
45
+ if File.exist?(ProconBypassMan.fallback_setting_path)
46
+ FileUtils.rm_rf(ProconBypassMan.fallback_setting_path)
47
+ end
48
+
39
49
  ProconBypassMan::ButtonsSettingConfiguration.instance
40
50
  end
41
51
 
42
52
  def self.reload_setting
43
53
  self.load(setting_path: ProconBypassMan::ButtonsSettingConfiguration.instance.setting_path)
44
54
  end
55
+
56
+ def self.fallback_setting_if_has_backup(current_setting_path: )
57
+ return unless File.exist?(ProconBypassMan.fallback_setting_path)
58
+ return if current_setting_path.nil?
59
+
60
+ FileUtils.copy(
61
+ ProconBypassMan.fallback_setting_path,
62
+ current_setting_path,
63
+ )
64
+ FileUtils.rm_rf(ProconBypassMan.fallback_setting_path)
65
+ end
45
66
  end
46
67
  end
47
68
  end
@@ -32,6 +32,16 @@ module ProconBypassMan
32
32
  @errors ||= Hash.new {|h,k| h[k] = [] }
33
33
  end
34
34
 
35
+ # @return [Array<String>]
36
+ def errors_to_s
37
+ errors.map { |_x, message|
38
+ value = <<~EOH
39
+ #{message.map { |m| "layer #{m}" }.join("\n")}
40
+ EOH
41
+ value.chomp
42
+ }.join("\n")
43
+ end
44
+
35
45
  private
36
46
 
37
47
  def validate_config_of_button_lonely
@@ -100,7 +110,7 @@ module ProconBypassMan
100
110
  end
101
111
 
102
112
  if(const = Module.const_get(key.to_s))
103
- if not const.respond_to?(:binaries) && mode.call
113
+ if not (const.respond_to?(:binaries) && mode.call)
104
114
  @errors[:mode] << "モード #{key}を読み込めませんでした。"
105
115
  end
106
116
  end
@@ -116,7 +126,7 @@ module ProconBypassMan
116
126
  end
117
127
 
118
128
  if(const = Module.const_get(key.to_s))
119
- if not const.respond_to?(:steps) && macro.call
129
+ if not (const.respond_to?(:steps) && macro.call)
120
130
  @errors[:macro] << "マクロ #{key}を読み込めませんでした。"
121
131
  end
122
132
  end
@@ -10,8 +10,6 @@ module ProconBypassMan
10
10
  :setting_path,
11
11
  :mode_plugins,
12
12
  :macro_plugins,
13
- :context,
14
- :current_context_key,
15
13
  :neutral_position
16
14
 
17
15
  def self.instance
@@ -27,22 +25,22 @@ module ProconBypassMan
27
25
  @@context[current_context_key] = val
28
26
  end
29
27
 
30
- def self.switch_new_context(key)
31
- @@context[key] = new
28
+ def self.switch_new_context(new_context_key)
29
+ @@context[new_context_key] = new
32
30
  previous_key = current_context_key
33
31
  if block_given?
34
- @@current_context_key = key
35
- value = yield(@@context[key])
36
- @@current_context_key = previous_key
32
+ @@current_context_key = new_context_key
33
+ value = yield(@@context[new_context_key])
37
34
  return value
38
35
  else
39
- @@current_context_key = key
36
+ @@current_context_key = new_context_key
40
37
  end
38
+ ensure
39
+ @@current_context_key = previous_key
41
40
  end
42
41
 
43
42
  def initialize
44
43
  reset!
45
- self.class.instance = self
46
44
  end
47
45
 
48
46
  module ManualMode
@@ -103,6 +101,8 @@ module ProconBypassMan
103
101
  def reset!
104
102
  @prefix_keys_for_changing_layer = []
105
103
  self.mode_plugins = {}
104
+ # プロセスを一度起動するとsetting_pathは変わらない、という想定なので適当に扱う. resetでは初期化しない
105
+ # self.setting_path = nil
106
106
  self.macro_plugins = {}
107
107
  self.layers = {
108
108
  up: Layer.new,
@@ -5,6 +5,7 @@ class ProconBypassMan::Bypass
5
5
 
6
6
  class BypassValue < Struct.new(:binary, :sent)
7
7
  def to_text
8
+ return unless binary
8
9
  "#{binary.unpack.first} #{'x' unless sent}"
9
10
  end
10
11
  end
@@ -31,16 +32,16 @@ class ProconBypassMan::Bypass
31
32
  self.bypass_value.binary = ProconBypassMan::Domains::InboundProconBinary.new(binary: input)
32
33
  rescue IO::EAGAINWaitReadable
33
34
  monitor.record(:eagain_wait_readable_on_read)
34
- sleep(0.001)
35
- retry
36
35
  end
37
36
 
38
- begin
39
- self.procon.write_nonblock(input)
40
- self.bypass_value.sent = true
41
- rescue IO::EAGAINWaitReadable
42
- monitor.record(:eagain_wait_readable_on_write)
43
- break
37
+ if input
38
+ begin
39
+ self.procon.write_nonblock(input)
40
+ self.bypass_value.sent = true
41
+ rescue IO::EAGAINWaitReadable
42
+ monitor.record(:eagain_wait_readable_on_write)
43
+ break
44
+ end
44
45
  end
45
46
  end
46
47
 
@@ -28,19 +28,12 @@ class ProconBypassMan::BypassCommand
28
28
  monitor2 = ProconBypassMan::IOMonitor.new(label: "procon -> switch")
29
29
  ProconBypassMan.logger.info "Thread1を起動します"
30
30
  t1 = Thread.new do
31
- timer = ProconBypassMan::SafeTimeout.new(timeout: Time.now + 10)
32
31
  bypass = ProconBypassMan::Bypass.new(gadget: @gadget, procon: @procon, monitor: monitor1)
33
32
  loop do
34
33
  break if $will_terminate_token
35
- timer.throw_if_timeout!
36
34
  bypass.send_gadget_to_procon!
37
- sleep(0.005)
38
- rescue ProconBypassMan::SafeTimeout::Timeout
39
- ProconBypassMan.logger.info "10秒経過したのでThread1を終了します"
40
- monitor1.shutdown
41
- puts "10秒経過したのでThread1を終了します"
42
- break
43
- rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError => e
35
+ sleep(0.02)
36
+ rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError, Errno::ESHUTDOWN => e
44
37
  ProconBypassMan::SendErrorCommand.execute(error: "Switchとの切断されました.終了処理を開始します. #{e.full_message}")
45
38
  Process.kill "TERM", Process.ppid
46
39
  rescue Errno::ETIMEDOUT => e
@@ -62,7 +55,7 @@ class ProconBypassMan::BypassCommand
62
55
  rescue EOFError => e
63
56
  ProconBypassMan::SendErrorCommand.execute(error: "Proconが切断されました。終了処理を開始します. #{e.full_message}")
64
57
  Process.kill "TERM", Process.ppid
65
- rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError => e
58
+ rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError, Errno::ESHUTDOWN => e
66
59
  ProconBypassMan::SendErrorCommand.execute(error: "Proconが切断されました。終了処理を開始します2. #{e.full_message}")
67
60
  Process.kill "TERM", Process.ppid
68
61
  end
@@ -80,7 +73,7 @@ class ProconBypassMan::BypassCommand
80
73
  [t1, t2].each(&:join)
81
74
  @gadget&.close
82
75
  @procon&.close
83
- exit 1
76
+ exit 1 # child processなのでexitしていい
84
77
  end
85
78
  end
86
79
  end
@@ -1,16 +1,28 @@
1
1
  class ProconBypassMan::ConnectDeviceCommand
2
+ class NotFoundProconError < StandardError; end
3
+
2
4
  # @return [void]
3
5
  def self.execute!
4
- gadget, procon = ProconBypassMan::DeviceConnector.connect
5
- rescue ProconBypassMan::DeviceConnector::NotFoundProconError => e
6
- ProconBypassMan.logger.error e
7
- gadget&.close
8
- procon&.close
9
- raise ProconBypassMan::NotFoundProconError
10
- rescue ProconBypassMan::SafeTimeout::Timeout
11
- ProconBypassMan.logger.error "デバイスとの通信でタイムアウトが起きて接続ができませんでした。"
12
- gadget&.close
13
- procon&.close
14
- raise ::ProconBypassMan::EternalConnectionError
6
+ unless has_required_files?
7
+ raise ProconBypassMan::NotFoundRequiredFilesError, "there is not /sys/kernel/config/usb_gadget/procon"
8
+ end
9
+
10
+ begin
11
+ gadget, procon = ProconBypassMan::DeviceConnector.connect
12
+ rescue ProconBypassMan::DeviceConnector::NotFoundProconError => e
13
+ ProconBypassMan.logger.error e
14
+ gadget&.close
15
+ procon&.close
16
+ raise ProconBypassMan::ConnectDeviceCommand::NotFoundProconError
17
+ rescue ProconBypassMan::SafeTimeout::Timeout
18
+ ProconBypassMan.logger.error "デバイスとの通信でタイムアウトが起きて接続ができませんでした。"
19
+ gadget&.close
20
+ procon&.close
21
+ raise ProconBypassMan::EternalConnectionError
22
+ end
23
+ end
24
+
25
+ def self.has_required_files?
26
+ Dir.exist?("/sys/kernel/config/usb_gadget/procon")
15
27
  end
16
28
  end
@@ -38,10 +38,19 @@ class ProconBypassMan::Configuration
38
38
  def device_id
39
39
  ENV["DEBUG_DEVICE_ID"] || ProconBypassMan::WriteDeviceIdCommand.execute
40
40
  end
41
+
42
+ # @return [Boolean]
43
+ def never_exit_accidentally
44
+ config.never_exit_accidentally
45
+ end
46
+
47
+ def fallback_setting_path
48
+ "/tmp/procon_bypass_man_fallback_setting.yaml"
49
+ end
41
50
  end
42
51
 
43
52
  attr_accessor :enable_critical_error_logging
44
- attr_writer :verbose_bypass_log, :raw_setting, :enable_reporting_pressed_buttons
53
+ attr_writer :verbose_bypass_log, :raw_setting, :enable_reporting_pressed_buttons, :never_exit_accidentally
45
54
 
46
55
  def root=(path)
47
56
  @root = path
@@ -158,4 +167,9 @@ class ProconBypassMan::Configuration
158
167
  def enable_reporting_pressed_buttons
159
168
  @enable_reporting_pressed_buttons ||= false
160
169
  end
170
+
171
+ # @return [Boolean]
172
+ def never_exit_accidentally
173
+ @never_exit_accidentally || false
174
+ end
161
175
  end
@@ -263,46 +263,19 @@ class ProconBypassMan::DeviceConnector
263
263
  @procon
264
264
  end
265
265
 
266
- def is_available_device?(path)
267
- return false if !File.exist?(path)
268
-
269
- system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
270
- system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
271
- sleep 0.5
272
-
273
- file = File.open(path, "w+")
274
- begin
275
- file.read_nonblock(64)
276
- rescue EOFError
277
- file.close
278
- return false
279
- rescue IO::EAGAINWaitReadable
280
- file.close
281
- return true
282
- end
283
- end
284
-
285
- def to_bin(string)
286
- string.unpack "H*"
287
- end
288
-
289
266
  def init_devices
290
267
  if @initialized_devices
291
268
  return
292
269
  end
293
270
 
294
- case
295
- when is_available_device?(PROCON_PATH)
296
- ProconBypassMan.logger.info "proconのデバイスファイルは#{PROCON_PATH}を使います"
297
- @procon = File.open(PROCON_PATH, "w+b")
298
- @gadget = File.open('/dev/hidg0', "w+b")
299
- when is_available_device?(PROCON2_PATH)
300
- ProconBypassMan.logger.info "proconのデバイスファイルは#{PROCON2_PATH}を使います"
301
- @procon = File.open(PROCON2_PATH, "w+b")
302
- @gadget = File.open('/dev/hidg0', "w+b")
271
+ if path = ProconBypassMan::DeviceProconFinder.find
272
+ @procon = File.open(path, "w+b")
273
+ ProconBypassMan.logger.info "proconのデバイスファイルは#{path}を使います"
303
274
  else
304
- raise NotFoundProconError, "/dev/hidraw0, /dev/hidraw1の両方見つかりませんでした"
275
+ raise(ProconBypassMan::DeviceConnector::NotFoundProconError)
305
276
  end
277
+ @gadget = File.open('/dev/hidg0', "w+b")
278
+
306
279
  system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
307
280
  system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
308
281
  sleep 0.5
@@ -0,0 +1,65 @@
1
+ class ProconBypassMan::DeviceProconFinder
2
+ HID_NAME = "Nintendo Co., Ltd. Pro Controller"
3
+
4
+ def self.find
5
+ new.find
6
+ end
7
+
8
+ # @return [String, NilClass]
9
+ def find
10
+ find_device_path
11
+ end
12
+
13
+ private
14
+
15
+ # @return [String , NilClass]
16
+ def find_device_path
17
+ if(line = device_from_shell) && (hidraw_name = line.match(/(hidraw\d+)\s+/)[1])
18
+ "/dev/#{hidraw_name}"
19
+ end
20
+ end
21
+
22
+ # @return [String , NilClass]
23
+ def device_from_shell
24
+ shell_output.split("\n").detect { |o| o.include?(HID_NAME) }
25
+ end
26
+
27
+ # @return [String]
28
+ def shell_output
29
+ `bash -c '#{get_list_shell}'`
30
+ end
31
+
32
+ def get_list_shell
33
+ <<~SHELL
34
+ #!/bin/bash
35
+
36
+ FILES=/dev/hidraw*
37
+ for f in $FILES
38
+ do
39
+ FILE=${f##*/}
40
+ DEVICE="$(cat /sys/class/hidraw/${FILE}/device/uevent | grep HID_NAME | cut -d '=' -f2)"
41
+ printf "%s %s\n" $FILE "$DEVICE"
42
+ done
43
+ SHELL
44
+ end
45
+
46
+ # これいる?
47
+ def is_available_device?(path)
48
+ return false if !File.exist?(path)
49
+
50
+ system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
51
+ system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
52
+ sleep 0.5
53
+
54
+ file = File.open(path, "w+")
55
+ begin
56
+ file.read_nonblock(64)
57
+ rescue EOFError
58
+ file.close
59
+ return false
60
+ rescue IO::EAGAINWaitReadable
61
+ file.close
62
+ return true
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,12 @@
1
+ module ProconBypassMan
2
+ module NeverExitAccidentally
3
+ def exit_if_allow(status)
4
+ if ProconBypassMan.never_exit_accidentally
5
+ eternal_sleep
6
+ else
7
+ yield if block_given?
8
+ exit(status)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ module ProconBypassMan
2
+ module Plugin
3
+ module Splatoon2
4
+ module Macro
5
+ module SokuwariForSplashBomb
6
+ def self.display_name
7
+ :sokuwari_for_splash_bomb
8
+ end
9
+
10
+ # procon_bypass_man: 0.1.18以上が必要
11
+ def self.steps
12
+ [ :toggle_r_for_0_2sec,
13
+ :toggle_thumbr_for_0_14sec,
14
+ :toggle_thumbr_and_toggle_zr_for_0_34sec,
15
+ :toggle_r_for_1sec,
16
+ ].freeze
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,6 +3,7 @@ require_relative "plugin/splatoon2/macro/fast_return"
3
3
  require_relative "plugin/splatoon2/macro/jump_to_right_key"
4
4
  require_relative "plugin/splatoon2/macro/jump_to_up_key"
5
5
  require_relative "plugin/splatoon2/macro/jump_to_left_key"
6
+ require_relative "plugin/splatoon2/macro/sokuwari_for_splash_bomb"
6
7
  require_relative "plugin/splatoon2/mode/guruguru"
7
8
 
8
9
  module ProconBypassMan
@@ -4,7 +4,7 @@ class ProconBypassMan::Procon::Button
4
4
  attr_accessor :byte_position, :bit_position
5
5
 
6
6
  def initialize(key)
7
- b = ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP[key] or raise(UnknownButtonFoundError, '定義にないボタンです')
7
+ b = ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP[key] or raise(UnknownButtonFoundError, "#{key}は定義にないボタンです")
8
8
  self.byte_position = b[:byte_position]
9
9
  self.bit_position = b[:bit_position]
10
10
  end
@@ -0,0 +1,89 @@
1
+ class ProconBypassMan::Procon::Macro
2
+ class NestedStep
3
+ def initialize(value)
4
+ @hash = value
5
+ unless @hash[:end_at]
6
+ @hash[:end_at] = (Time.now + @hash[:continue_for]).round(4)
7
+ end
8
+ end
9
+
10
+ def over_end_at?
11
+ (@hash[:end_at] < Time.now).tap do |result|
12
+ if result
13
+ ProconBypassMan.logger.debug { "[Macro] nested step is finished(#{@hash})" }
14
+ end
15
+ end
16
+ end
17
+
18
+ def next_step
19
+ incr_step_index!
20
+
21
+ debug_incr_called_count!
22
+ if step = current_step
23
+ return step
24
+ else
25
+ reset_step_index!
26
+ return current_step
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def current_step
33
+ @hash[:steps][step_index]
34
+ end
35
+
36
+ def step_index
37
+ @hash[:step_index]
38
+ end
39
+
40
+ def incr_step_index!
41
+ if step_index
42
+ @hash[:step_index] += 1
43
+ else
44
+ @hash[:step_index] = 0
45
+ end
46
+ end
47
+
48
+ def reset_step_index!
49
+ @hash[:step_index] = 0
50
+ end
51
+
52
+ def debug_incr_called_count!
53
+ @hash[:debug_called_count] ||= 0
54
+ @hash[:debug_called_count] += 1
55
+ end
56
+ end
57
+
58
+ attr_accessor :name, :steps
59
+
60
+ def initialize(name: , steps: )
61
+ self.name = name
62
+ self.steps = steps
63
+ end
64
+
65
+ def next_step
66
+ step = steps.first
67
+ if step.is_a?(Symbol)
68
+ return steps.shift
69
+ end
70
+
71
+ if step.is_a?(Hash)
72
+ nested_step = NestedStep.new(step)
73
+ if nested_step.over_end_at?
74
+ steps.shift # NestedStepを破棄する
75
+ return next_step
76
+ else
77
+ return nested_step.next_step
78
+ end
79
+ end
80
+ end
81
+
82
+ def finished?
83
+ steps.empty?
84
+ end
85
+
86
+ def ongoing?
87
+ !finished?
88
+ end
89
+ end
@@ -0,0 +1,123 @@
1
+ class ProconBypassMan::Procon::MacroBuilder
2
+ class SubjectMerger
3
+ def self.merge(subjects)
4
+ if subjects.size == 1
5
+ return subjects.first.to_steps
6
+ end
7
+
8
+ base = subjects.first
9
+ remain = subjects[1..-1]
10
+ remain.map { |x| base.to_steps.zip(x.to_steps) }.first
11
+ end
12
+ end
13
+
14
+ class Subject
15
+ def initialize(value)
16
+ @button =
17
+ if match = value.match(/_(\w+)\z/)
18
+ match[1]
19
+ else
20
+ :unknown
21
+ end
22
+ @type =
23
+ if value.start_with?("toggle_")
24
+ :toggle
25
+ else
26
+ :pressing
27
+ end
28
+ end
29
+
30
+ def toggle?
31
+ @type == :toggle
32
+ end
33
+
34
+ def pressing?
35
+ not toggle?
36
+ end
37
+
38
+ def to_steps
39
+ case @type
40
+ when :toggle
41
+ [@button.to_sym, :none]
42
+ when :pressing
43
+ [@button.to_sym, @button.to_sym]
44
+ end
45
+ end
46
+ end
47
+
48
+ RESERVED_WORD_NONE = :none
49
+ RESERVED_WORDS = {
50
+ RESERVED_WORD_NONE => true,
51
+ }
52
+
53
+ def initialize(steps)
54
+ @steps = steps.map(&:to_s)
55
+ end
56
+
57
+ # @return [Arary<Symbol>]
58
+ def build
59
+ steps = @steps.map { |step|
60
+ if is_reserved?(step: step) || v1_format?(step: step)
61
+ step.to_sym
62
+ elsif value = build_if_v2_format?(step: step)
63
+ value
64
+ else
65
+ nil
66
+ end
67
+ }
68
+ steps.compact.flatten
69
+ end
70
+
71
+ private
72
+
73
+ def is_reserved?(step: )
74
+ RESERVED_WORDS[step.to_sym]
75
+ end
76
+
77
+ def v1_format?(step: )
78
+ if is_button(step)
79
+ step
80
+ end
81
+ end
82
+
83
+ def build_if_v2_format?(step: )
84
+ # 時間指定なし
85
+ if(match = step.match(%r!\Atoggle_(\w+)\z!)) && (button_candidate = match[1]) && is_button(button_candidate)
86
+ button = button_candidate
87
+ return [button.to_sym, :none]
88
+ end
89
+
90
+ # 時間指定あり
91
+ if %r!^(pressing_|toggle_)! =~ step && (subjects = step.scan(%r!pressing_[^_]+|toggle_[^_]+!)) && (match = step.match(%r!_for_([\d_]+)(sec)?\z!))
92
+ sec = match[1]
93
+ return [
94
+ { continue_for: to_num(sec),
95
+ steps: SubjectMerger.merge(subjects.map { |x| Subject.new(x) }),
96
+ }
97
+ ]
98
+ end
99
+
100
+ # no-op command
101
+ if(match = step.match(%r!wait_for_([\d_]+)(sec)?\z!))
102
+ sec = match[1]
103
+ return [
104
+ { continue_for: to_num(sec),
105
+ steps: [:none],
106
+ }
107
+ ]
108
+ end
109
+ end
110
+
111
+ # @return [Boolean]
112
+ def is_button(step)
113
+ !!ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP[step.to_sym]
114
+ end
115
+
116
+ def to_num(value)
117
+ if value.include?("_")
118
+ value.sub("_", ".").to_f
119
+ else
120
+ value.to_i
121
+ end
122
+ end
123
+ end