rpremote 0.5.0 → 0.7.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.ja.md +30 -46
  4. data/README.md +30 -47
  5. data/RELEASING.md +2 -2
  6. data/THIRD_PARTY_NOTICES.md +1 -1
  7. data/lib/rpremote/bootsel_command.rb +8 -6
  8. data/lib/rpremote/build_command.rb +3 -3
  9. data/lib/rpremote/builder.rb +15 -9
  10. data/lib/rpremote/cli/filesystem_commands.rb +105 -0
  11. data/lib/rpremote/cli.rb +20 -105
  12. data/lib/rpremote/config.rb +6 -5
  13. data/lib/rpremote/config_show.rb +13 -5
  14. data/lib/rpremote/deploy_command.rb +85 -55
  15. data/lib/rpremote/help/dfu_commands.rb +59 -0
  16. data/lib/rpremote/help/filesystem_commands.rb +36 -0
  17. data/lib/rpremote/help/project_commands.rb +89 -0
  18. data/lib/rpremote/help/remote_commands.rb +54 -0
  19. data/lib/rpremote/help/root_text.rb +63 -0
  20. data/lib/rpremote/help.rb +27 -300
  21. data/lib/rpremote/language_source.rb +19 -2
  22. data/lib/rpremote/mrbgems/definition.rb +116 -0
  23. data/lib/rpremote/mrbgems/file_writer.rb +26 -0
  24. data/lib/rpremote/mrbgems/lockfile.rb +131 -0
  25. data/lib/rpremote/mrbgems.rb +95 -155
  26. data/lib/rpremote/mrbgems_command.rb +19 -4
  27. data/lib/rpremote/picoruby_compiler.rb +28 -0
  28. data/lib/rpremote/picoruby_source_patch.rb +0 -4
  29. data/lib/rpremote/target.rb +1 -1
  30. data/lib/rpremote/terminal.rb +0 -2
  31. data/lib/rpremote/version.rb +1 -1
  32. data/lib/rpremote.rb +1 -0
  33. data/patches/picoruby-4.0.4-pwm-sleep-clock.patch +27 -0
  34. data/sig/rpremote.rbs +20 -4
  35. data/tasks/firmware_build.rb +10 -2
  36. metadata +12 -3
  37. data/patches/picoruby-3.4.5-bootsel.patch +0 -71
  38. data/patches/picoruby-4.0.3-bootsel.patch +0 -71
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rpremote
4
+ module Help
5
+ def self.ports_text
6
+ "Usage: #{COMMAND_USAGE.fetch(:ports)}\n\nPrints each detected R2P2 CDC 0 serial path, one per line. It does not connect to a board.\n"
7
+ end
8
+
9
+ def self.run_text
10
+ <<~HELP
11
+ Usage: #{COMMAND_USAGE.fetch(:run)}
12
+
13
+ Uploads FILE to R2P2, or main.rb when FILE is a directory, relays output, then removes the temporary remote file. Defaults are the automatic CDC 0 port,
14
+ 115200 baud, a 20-second idle timeout, and picoruby. Output from the running program resets the idle timeout.
15
+ It exits nonzero when compatible R2P2 firmware reports a Ruby exception.
16
+ --reset-on-timeout resets R2P2 after the run times out and the serial connection has closed.
17
+ HELP
18
+ end
19
+
20
+ def self.exec_text
21
+ <<~HELP
22
+ Usage: #{COMMAND_USAGE.fetch(:exec)}
23
+
24
+ Runs short Ruby CODE through a temporary remote file, then removes it. Defaults are the automatic CDC 0 port,
25
+ 115200 baud, a 20-second idle timeout, and picoruby. Output from the running program resets the idle timeout.
26
+ It exits nonzero when compatible R2P2 firmware reports a Ruby exception.
27
+ HELP
28
+ end
29
+
30
+ def self.monitor_text
31
+ <<~HELP
32
+ Usage: #{COMMAND_USAGE.fetch(:monitor)}
33
+
34
+ Opens a serial monitor for the selected CDC 0 port. Defaults are 115200 baud and 20 seconds; exit with Ctrl-].
35
+ HELP
36
+ end
37
+
38
+ def self.repl_text
39
+ <<~HELP
40
+ Usage: #{COMMAND_USAGE.fetch(:repl)}
41
+
42
+ Opens PicoIRB on the selected CDC 0 port. Defaults are 115200 baud and 20 seconds; exit with Ctrl-].
43
+ HELP
44
+ end
45
+
46
+ def self.reset_text
47
+ <<~HELP
48
+ Usage: #{COMMAND_USAGE.fetch(:reset)}
49
+
50
+ Reboots R2P2 and waits for reconnection. Defaults are the automatic CDC 0 port, 115200 baud, and 20 seconds.
51
+ HELP
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rpremote
4
+ module Help
5
+ TEXT = <<~HELP.freeze
6
+ rpremote - R2P2 remote control for Raspberry Pi Pico 2
7
+
8
+ Usage:
9
+ #{COMMAND_USAGE.fetch(:setup)}
10
+ #{COMMAND_USAGE.fetch(:build)}
11
+ #{COMMAND_USAGE.fetch(:build_clean)}
12
+ #{COMMAND_USAGE.fetch(:bootsel)}
13
+ #{COMMAND_USAGE.fetch(:deploy)}
14
+ #{COMMAND_USAGE.fetch(:dfu_app)}
15
+ #{COMMAND_USAGE.fetch(:dfu_compile)}
16
+ #{COMMAND_USAGE.fetch(:dfu_status)}
17
+ #{COMMAND_USAGE.fetch(:dfu_remove)}
18
+ #{COMMAND_USAGE.fetch(:mrbgems).sub("SUBCOMMAND", "check|list|lock|update")}
19
+ #{COMMAND_USAGE.fetch(:flash)}
20
+ #{COMMAND_USAGE.fetch(:config_show)}
21
+ #{COMMAND_USAGE.fetch(:ports)}
22
+ #{COMMAND_USAGE.fetch(:run)}
23
+ #{COMMAND_USAGE.fetch(:monitor)}
24
+ #{COMMAND_USAGE.fetch(:repl)}
25
+ #{COMMAND_USAGE.fetch(:exec)}
26
+ #{COMMAND_USAGE.fetch(:reset)}
27
+ #{COMMAND_USAGE.fetch(:fs_cp)}
28
+ #{COMMAND_USAGE.fetch(:fs_push)}
29
+ #{COMMAND_USAGE.fetch(:fs_cat)}
30
+ #{COMMAND_USAGE.fetch(:fs_ls)}
31
+ #{COMMAND_USAGE.fetch(:fs_rm)}
32
+ #{COMMAND_USAGE.fetch(:fs_mkdir)}
33
+
34
+ `rpremote build clean` removes only the project's generated `build/` directory.
35
+
36
+ Configuration:
37
+ config/setting.json default project options
38
+
39
+ Options:
40
+ --force download the PicoRuby source again during setup
41
+ --build build the selected firmware before deploy flashes it
42
+ --language-version VERSION
43
+ use a PicoRuby tag or latest (default: latest)
44
+ --cache DIR use another project cache directory
45
+ --lockfile FILE use an explicit Mrbgems.lock for build, deploy, run, or exec
46
+ --no-mrbgems do not build or automatically require locked mrbgems
47
+ --with GROUPS lock common mrbgems and the comma-separated groups
48
+ --without GROUPS exclude the comma-separated groups from the lock
49
+ --board BOARD
50
+ select pico2 or pico2_w (default: pico2)
51
+ --firmware FILE build to, or deploy or flash from, this UF2 path
52
+ --language LANGUAGE
53
+ select the remote language (default: picoruby)
54
+ --config FILE use another configuration file
55
+ --mount DIR use an explicit RP2350 BOOTSEL drive
56
+ --port PORT use an explicit R2P2 CDC 0 device
57
+ --baud RATE serial baud rate (default: 115200)
58
+ --timeout SEC timeout in seconds (default: 20)
59
+ -h, --help show this help
60
+ -V, --version show the version
61
+ HELP
62
+ end
63
+ end
data/lib/rpremote/help.rb CHANGED
@@ -1,33 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ModuleLength, Metrics/CyclomaticComplexity
4
-
5
3
  module Rpremote
6
4
  module Help
7
5
  COMMAND_USAGE = {
8
6
  setup: "rpremote setup [--language LANGUAGE] [--language-version VERSION] [--force] [--cache DIR]",
9
7
  build: "rpremote build [--language LANGUAGE] [--language-version VERSION] [--board BOARD] " \
10
- "[--firmware FILE] [--cache DIR] [--mrbgems FILE|--no-mrbgems]",
8
+ "[--firmware FILE] [--cache DIR] [--lockfile FILE|--no-mrbgems]",
11
9
  build_clean: "rpremote build clean",
12
10
  bootsel: "rpremote bootsel [--reset-flash-memory] [--mount DIR] [--port PORT] [--baud RATE] [--timeout SEC]",
13
11
  deploy: "rpremote deploy PATH [--build] [--language LANGUAGE] [--language-version VERSION] [--board BOARD] " \
14
- "[--firmware FILE] [--cache DIR] [--mrbgems FILE|--no-mrbgems] [--mount DIR] " \
12
+ "[--firmware FILE] [--cache DIR] [--lockfile FILE|--no-mrbgems] [--mount DIR] " \
15
13
  "[--port PORT] [--baud RATE] [--timeout SEC]",
16
14
  dfu_app: "rpremote dfu app FILE [--type ruby|rite] [--port PORT] [--baud RATE] [--timeout SEC]",
17
15
  dfu_compile: "rpremote dfu compile FILE [--output FILE] [--language LANGUAGE] [--language-version VERSION] [--cache DIR]",
18
16
  dfu_status: "rpremote dfu status [--port PORT] [--baud RATE] [--timeout SEC]",
19
17
  dfu_remove: "rpremote dfu remove [--port PORT] [--baud RATE] [--timeout SEC]",
20
- mrbgems: "rpremote mrbgems SUBCOMMAND [--file FILE] [--lockfile FILE]",
18
+ mrbgems: "rpremote mrbgems SUBCOMMAND [--file FILE] [--lockfile FILE] [--with GROUPS] [--without GROUPS]",
21
19
  flash: "rpremote flash [--firmware FILE] [--language LANGUAGE] [--language-version VERSION] " \
22
20
  "[--board BOARD] [--cache DIR] [--mount DIR] [--port PORT] [--timeout SEC]",
23
21
  config_show: "rpremote config show [--language LANGUAGE] [--language-version VERSION] [--board BOARD] " \
24
- "[--cache DIR] [--firmware FILE] [--mrbgems FILE|--no-mrbgems] [--mount DIR] " \
22
+ "[--cache DIR] [--firmware FILE] [--lockfile FILE|--no-mrbgems] [--mount DIR] " \
25
23
  "[--port PORT] [--baud RATE] [--timeout SEC]",
26
24
  ports: "rpremote ports",
27
- run: "rpremote run FILE [--port PORT] [--baud RATE] [--timeout SEC] [--reset-on-timeout] [--language LANGUAGE]",
25
+ run: "rpremote run FILE [--port PORT] [--baud RATE] [--timeout SEC] [--reset-on-timeout] [--language LANGUAGE] " \
26
+ "[--lockfile FILE|--no-mrbgems]",
28
27
  monitor: "rpremote monitor [--port PORT] [--baud RATE] [--timeout SEC]",
29
28
  repl: "rpremote repl [--port PORT] [--baud RATE] [--timeout SEC]",
30
- exec: "rpremote exec CODE [--port PORT] [--baud RATE] [--timeout SEC] [--language LANGUAGE]",
29
+ exec: "rpremote exec CODE [--port PORT] [--baud RATE] [--timeout SEC] [--language LANGUAGE] " \
30
+ "[--lockfile FILE|--no-mrbgems]",
31
31
  reset: "rpremote reset [--port PORT] [--baud RATE] [--timeout SEC]",
32
32
  fs_cp: "rpremote fs cp SOURCE DESTINATION [--recursive] [--port PORT] [--baud RATE] [--timeout SEC]",
33
33
  fs_push: "rpremote fs push LOCAL_DIR :/REMOTE_DIR [--port PORT] [--baud RATE] [--timeout SEC]",
@@ -36,62 +36,12 @@ module Rpremote
36
36
  fs_rm: "rpremote fs rm :/REMOTE/PATH [--port PORT] [--baud RATE] [--timeout SEC]",
37
37
  fs_mkdir: "rpremote fs mkdir :/REMOTE/PATH [--port PORT] [--baud RATE] [--timeout SEC]"
38
38
  }.freeze
39
-
40
- TEXT = <<~HELP.freeze
41
- rpremote - R2P2 remote control for Raspberry Pi Pico 2
42
-
43
- Usage:
44
- #{COMMAND_USAGE.fetch(:setup)}
45
- #{COMMAND_USAGE.fetch(:build)}
46
- #{COMMAND_USAGE.fetch(:build_clean)}
47
- #{COMMAND_USAGE.fetch(:bootsel)}
48
- #{COMMAND_USAGE.fetch(:deploy)}
49
- #{COMMAND_USAGE.fetch(:dfu_app)}
50
- #{COMMAND_USAGE.fetch(:dfu_compile)}
51
- #{COMMAND_USAGE.fetch(:dfu_status)}
52
- #{COMMAND_USAGE.fetch(:dfu_remove)}
53
- #{COMMAND_USAGE.fetch(:mrbgems).sub("SUBCOMMAND", "check|list|lock|update")}
54
- #{COMMAND_USAGE.fetch(:flash)}
55
- #{COMMAND_USAGE.fetch(:config_show)}
56
- #{COMMAND_USAGE.fetch(:ports)}
57
- #{COMMAND_USAGE.fetch(:run)}
58
- #{COMMAND_USAGE.fetch(:monitor)}
59
- #{COMMAND_USAGE.fetch(:repl)}
60
- #{COMMAND_USAGE.fetch(:exec)}
61
- #{COMMAND_USAGE.fetch(:reset)}
62
- #{COMMAND_USAGE.fetch(:fs_cp)}
63
- #{COMMAND_USAGE.fetch(:fs_push)}
64
- #{COMMAND_USAGE.fetch(:fs_cat)}
65
- #{COMMAND_USAGE.fetch(:fs_ls)}
66
- #{COMMAND_USAGE.fetch(:fs_rm)}
67
- #{COMMAND_USAGE.fetch(:fs_mkdir)}
68
-
69
- `rpremote build clean` removes only the project's generated `build/` directory.
70
-
71
- Configuration:
72
- config/setting.json default project options
73
-
74
- Options:
75
- --force download the PicoRuby source again during setup
76
- --build build the selected firmware before deploy flashes it
77
- --language-version VERSION
78
- use R2P2/PicoRuby 4.0.3 or 3.4.2 (default: 4.0.3)
79
- --cache DIR use another project cache directory
80
- --mrbgems FILE use an explicit Mrbgems definition during build or deploy
81
- --no-mrbgems build or deploy without the automatically detected Mrbgems
82
- --board BOARD
83
- select pico2 or pico2_w (default: pico2)
84
- --firmware FILE build to, or deploy or flash from, this UF2 path
85
- --language LANGUAGE
86
- select the remote language (default: picoruby)
87
- --config FILE use another configuration file
88
- --mount DIR use an explicit RP2350 BOOTSEL drive
89
- --port PORT use an explicit R2P2 CDC 0 device
90
- --baud RATE serial baud rate (default: 115200)
91
- --timeout SEC timeout in seconds (default: 20)
92
- -h, --help show this help
93
- -V, --version show the version
94
- HELP
39
+ COMMAND_TEXT_METHODS = {
40
+ "setup" => :setup_text, "bootsel" => :bootsel_text, "deploy" => :deploy_text,
41
+ "flash" => :flash_text, "ports" => :ports_text, "run" => :run_text,
42
+ "exec" => :exec_text, "monitor" => :monitor_text, "repl" => :repl_text,
43
+ "reset" => :reset_text
44
+ }.freeze
95
45
 
96
46
  def self.requested?(command, args)
97
47
  %w[help --help -h].include?(command) || args.include?("--help") || args.include?("-h")
@@ -100,243 +50,20 @@ module Rpremote
100
50
  def self.command_text(command, args)
101
51
  command = args.shift if command == "help"
102
52
  return TEXT if command.nil? || %w[help --help -h].include?(command)
53
+ return args.first == "clean" ? build_clean_text : build_text if command == "build"
54
+ return dfu_text(args.first) if command == "dfu"
55
+ return mrbgems_text(args.first) if command == "mrbgems"
56
+ return config_text(args.first) if command == "config"
57
+ return fs_text(args.first) if command == "fs"
103
58
 
104
- case command
105
- when "setup" then setup_text
106
- when "build" then args.first == "clean" ? build_clean_text : build_text
107
- when "bootsel" then bootsel_text
108
- when "deploy" then deploy_text
109
- when "dfu" then dfu_text(args.first)
110
- when "mrbgems" then mrbgems_text(args.first)
111
- when "flash" then flash_text
112
- when "config" then config_text(args.first)
113
- when "ports" then ports_text
114
- when "run" then run_text
115
- when "exec" then exec_text
116
- when "monitor" then monitor_text
117
- when "repl" then repl_text
118
- when "reset" then reset_text
119
- when "fs" then fs_text(args.first)
120
- else TEXT
121
- end
122
- end
123
-
124
- def self.setup_text
125
- <<~HELP
126
- Usage: #{COMMAND_USAGE.fetch(:setup)}
127
-
128
- Creates config/setting.json when it does not exist, then downloads and prepares PicoRuby source and the official Raspberry Pi nuke_universal.uf2 firmware.
129
- Options: --language LANGUAGE (picoruby), --language-version VERSION (4.0.3), --cache DIR (firmware), --force.
130
- This changes the project configuration and source cache but does not connect to a board.
131
- HELP
132
- end
133
-
134
- def self.build_text
135
- <<~HELP
136
- Usage: #{COMMAND_USAGE.fetch(:build)}
137
-
138
- Builds a custom UF2 for the selected target. It writes generated files under build/ and the selected firmware path.
139
- Options: --language LANGUAGE, --language-version VERSION, --board BOARD (pico2), --cache DIR (firmware),
140
- --firmware FILE, --mrbgems FILE, --no-mrbgems. A project Mrbgems file is used automatically by default.
141
- HELP
142
- end
143
-
144
- def self.build_clean_text
145
- <<~HELP
146
- Usage: #{COMMAND_USAGE.fetch(:build_clean)}
147
-
148
- Removes only the project's generated build/ directory. It does not remove firmware/, Mrbgems, or Mrbgems.lock.
149
- HELP
150
- end
151
-
152
- def self.deploy_text
153
- <<~HELP
154
- Usage: #{COMMAND_USAGE.fetch(:deploy)}
155
-
156
- Enters BOOTSEL when needed, flashes the existing selected UF2, and waits for the R2P2 Shell to become ready.
157
- With --build, builds the selected custom UF2 before entering BOOTSEL and flashing it.
158
- If PATH/lib/NAME exists, it copies it to :/lib/NAME, where NAME is the final component of PATH. It then runs PATH/main.rb and preserves its Shell job, so hardware output remains active until the next command.
159
- The stages run in order and stop at the first failure. Flashing replaces persistent board firmware.
160
- deploy requires PicoRuby 4.x firmware (currently 4.0.3).
161
- Options combine build, BOOTSEL, flash, library-copy, and run settings. The first install of firmware with automatic BOOTSEL still requires holding BOOTSEL.
162
- HELP
163
- end
164
-
165
- def self.bootsel_text
166
- <<~HELP
167
- Usage: #{COMMAND_USAGE.fetch(:bootsel)}
168
-
169
- Asks the running R2P2 firmware to restart as an RP2350 BOOTSEL USB volume, then waits for that volume to appear.
170
- By default it does not write firmware. This requires firmware built by a current rpremote; use physical BOOTSEL once to install it first.
171
- --reset-flash-memory copies the official Raspberry Pi nuke_universal.uf2 in BOOTSEL mode and erases all external flash memory.
172
- After resetting flash memory, wait for BOOTSEL to reappear and run `rpremote flash` to install R2P2 again.
173
- HELP
174
- end
175
-
176
- def self.dfu_text(subcommand)
177
- return dfu_app_text if subcommand == "app"
178
- return dfu_compile_text if subcommand == "compile"
179
- return dfu_status_text if subcommand == "status"
180
- return dfu_remove_text if subcommand == "remove"
181
-
182
- <<~HELP
183
- Usage: #{COMMAND_USAGE.fetch(:dfu_app)}
184
- #{COMMAND_USAGE.fetch(:dfu_compile)}
185
- #{COMMAND_USAGE.fetch(:dfu_status)}
186
- #{COMMAND_USAGE.fetch(:dfu_remove)}
187
-
188
- Stages and inspects PicoModem DFU applications. Run `rpremote dfu SUBCOMMAND --help` for details.
189
- HELP
190
- end
191
-
192
- def self.dfu_app_text
193
- <<~HELP
194
- Usage: #{COMMAND_USAGE.fetch(:dfu_app)}
195
-
196
- Transfers a Ruby source or matching RITE bytecode application to the inactive DFU slot. The next R2P2 restart tries it.
197
- Options default to the configured or automatically selected CDC 0 port, 115200 baud, and 20 seconds.
198
- The transfer changes the staged boot application; use `rpremote reset` to restart and require DFU.confirm after a successful boot.
199
- HELP
200
- end
201
-
202
- def self.dfu_compile_text
203
- <<~HELP
204
- Usage: #{COMMAND_USAGE.fetch(:dfu_compile)}
205
-
206
- Compiles Ruby source to bytecode that matches the selected PicoRuby version. The output defaults beside FILE.
207
- It requires prepared PicoRuby source and does not connect to a board.
208
- HELP
209
- end
210
-
211
- def self.dfu_status_text
212
- <<~HELP
213
- Usage: #{COMMAND_USAGE.fetch(:dfu_status)}
214
-
215
- Prints the active and candidate DFU A/B slots. Defaults are the configured or automatically selected CDC 0 port,
216
- 115200 baud, and 20 seconds. This command reads board state without changing it.
217
- HELP
218
- end
219
-
220
- def self.dfu_remove_text
221
- <<~HELP
222
- Usage: #{COMMAND_USAGE.fetch(:dfu_remove)}
223
-
224
- Permanently removes the Ruby source and bytecode applications from both DFU A/B slots and resets their metadata.
225
- Run `rpremote reset` afterward to stop the application already running in RAM. It does not remove /home/app.rb,
226
- /home/app.mrb, other files, embedded mrbgems, or R2P2 firmware.
227
- HELP
228
- end
229
-
230
- def self.mrbgems_text(subcommand)
231
- action = subcommand || "check|list|lock|update"
232
- <<~HELP
233
- Usage: #{COMMAND_USAGE.fetch(:mrbgems).sub("SUBCOMMAND", action)}
234
-
235
- Checks or lists build dependencies, writes a reproducible lock file, or updates locked GitHub commits.
236
- `check` and `list` are read-only. `lock` writes Mrbgems.lock; `update` resolves new commits and rewrites it.
237
- HELP
238
- end
239
-
240
- def self.flash_text
241
- <<~HELP
242
- Usage: #{COMMAND_USAGE.fetch(:flash)}
243
-
244
- Copies the selected UF2 to an RP2350 BOOTSEL volume and waits for R2P2 to reconnect. It replaces persistent board firmware.
245
- Defaults select pico2, firmware/picoruby-4.0.3-pico2.uf2, an automatic BOOTSEL mount and CDC 0 port, and 20 seconds.
246
- HELP
247
- end
248
-
249
- def self.config_text(subcommand)
250
- unless subcommand == "show"
251
- return "Usage: #{COMMAND_USAGE.fetch(:config_show)}\n\nUse `rpremote config show --help` for the supported overrides.\n"
252
- end
253
-
254
- <<~HELP
255
- Usage: #{COMMAND_USAGE.fetch(:config_show)}
256
-
257
- Prints the configuration file, defaults, and command-line overrides after resolution. It does not connect to a board or change state.
258
- HELP
259
- end
260
-
261
- def self.ports_text
262
- "Usage: #{COMMAND_USAGE.fetch(:ports)}\n\nPrints each detected R2P2 CDC 0 serial path, one per line. It does not connect to a board.\n"
263
- end
264
-
265
- def self.run_text
266
- <<~HELP
267
- Usage: #{COMMAND_USAGE.fetch(:run)}
268
-
269
- Uploads FILE to R2P2, or main.rb when FILE is a directory, relays output, then removes the temporary remote file. Defaults are the automatic CDC 0 port,
270
- 115200 baud, a 20-second idle timeout, and picoruby. Output from the running program resets the idle timeout.
271
- It exits nonzero when compatible R2P2 firmware reports a Ruby exception.
272
- --reset-on-timeout resets R2P2 after the run times out and the serial connection has closed.
273
- HELP
274
- end
275
-
276
- def self.exec_text
277
- <<~HELP
278
- Usage: #{COMMAND_USAGE.fetch(:exec)}
279
-
280
- Runs short Ruby CODE through a temporary remote file, then removes it. Defaults are the automatic CDC 0 port,
281
- 115200 baud, a 20-second idle timeout, and picoruby. Output from the running program resets the idle timeout.
282
- It exits nonzero when compatible R2P2 firmware reports a Ruby exception.
283
- HELP
284
- end
285
-
286
- def self.monitor_text
287
- <<~HELP
288
- Usage: #{COMMAND_USAGE.fetch(:monitor)}
289
-
290
- Opens a serial monitor for the selected CDC 0 port. Defaults are 115200 baud and 20 seconds; exit with Ctrl-].
291
- HELP
292
- end
293
-
294
- def self.repl_text
295
- <<~HELP
296
- Usage: #{COMMAND_USAGE.fetch(:repl)}
297
-
298
- Opens PicoIRB on the selected CDC 0 port. Defaults are 115200 baud and 20 seconds; exit with Ctrl-].
299
- HELP
300
- end
301
-
302
- def self.reset_text
303
- <<~HELP
304
- Usage: #{COMMAND_USAGE.fetch(:reset)}
305
-
306
- Reboots R2P2 and waits for reconnection. Defaults are the automatic CDC 0 port, 115200 baud, and 20 seconds.
307
- HELP
308
- end
309
-
310
- def self.fs_text(subcommand)
311
- return fs_subcommand_text(subcommand) if %w[cp push cat ls rm mkdir].include?(subcommand)
312
-
313
- <<~HELP
314
- Usage: #{COMMAND_USAGE.fetch(:fs_cp)}
315
- #{COMMAND_USAGE.fetch(:fs_push)}
316
- #{COMMAND_USAGE.fetch(:fs_cat)}
317
- #{COMMAND_USAGE.fetch(:fs_ls)}
318
- #{COMMAND_USAGE.fetch(:fs_rm)}
319
- #{COMMAND_USAGE.fetch(:fs_mkdir)}
320
-
321
- Uses :/REMOTE/PATH for R2P2 paths. Run `rpremote fs SUBCOMMAND --help` for details.
322
- HELP
323
- end
324
-
325
- def self.fs_subcommand_text(subcommand)
326
- usage = COMMAND_USAGE.fetch(:"fs_#{subcommand}")
327
- effect = case subcommand
328
- when "rm" then "Deletes the remote path permanently."
329
- when "mkdir" then "Creates a remote directory."
330
- when "cp" then "Transfers one file, or recursively uploads a local directory with --recursive."
331
- when "push" then "Creates missing remote directories and recursively uploads a local directory."
332
- else "Reads remote files."
333
- end
334
- <<~HELP
335
- Usage: #{usage}
336
-
337
- #{effect} Defaults are the automatic CDC 0 port, 115200 baud, and 20 seconds.
338
- HELP
59
+ method_name = COMMAND_TEXT_METHODS[command]
60
+ method_name ? public_send(method_name) : TEXT
339
61
  end
340
62
  end
341
63
  end
342
- # rubocop:enable Metrics/ModuleLength, Metrics/CyclomaticComplexity
64
+
65
+ require_relative "help/root_text"
66
+ require_relative "help/project_commands"
67
+ require_relative "help/dfu_commands"
68
+ require_relative "help/remote_commands"
69
+ require_relative "help/filesystem_commands"
@@ -36,6 +36,7 @@ module Rpremote
36
36
  unless File.directory?(source_dir) && !force
37
37
  FileUtils.rm_rf(source_dir) if force
38
38
  extractor.call(archive_path, cache_dir)
39
+ normalize_extracted_source
39
40
  end
40
41
  raise ExtractError, "PicoRuby source was not extracted: #{source_dir}" unless File.directory?(source_dir)
41
42
 
@@ -45,7 +46,7 @@ module Rpremote
45
46
  end
46
47
 
47
48
  def archive_url
48
- "https://github.com/picoruby/picoruby/archive/refs/tags/#{version}.zip"
49
+ "https://github.com/picoruby/picoruby/archive/refs/#{source_ref}.zip"
49
50
  end
50
51
 
51
52
  def archive_path
@@ -60,6 +61,22 @@ module Rpremote
60
61
 
61
62
  attr_reader :fetcher, :extractor, :preparer, :patcher
62
63
 
64
+ def source_ref
65
+ version == "latest" ? "heads/master" : "tags/#{version}"
66
+ end
67
+
68
+ def extracted_source_dir
69
+ return source_dir unless version == "latest"
70
+
71
+ File.join(cache_dir, "picoruby-master")
72
+ end
73
+
74
+ def normalize_extracted_source
75
+ return if extracted_source_dir == source_dir || !File.directory?(extracted_source_dir)
76
+
77
+ FileUtils.mv(extracted_source_dir, source_dir)
78
+ end
79
+
63
80
  def fetch_archive
64
81
  temporary = "#{archive_path}.part"
65
82
  File.binwrite(temporary, fetcher.call(archive_url))
@@ -88,7 +105,7 @@ module Rpremote
88
105
  commands << ["git", "remote", "add", "origin", "https://github.com/picoruby/picoruby.git"]
89
106
  end
90
107
  commands.push(
91
- ["git", "fetch", "--depth", "1", "origin", "refs/tags/#{version}"],
108
+ ["git", "fetch", "--depth", "1", "origin", source_ref],
92
109
  ["git", "checkout", "--force", "FETCH_HEAD"],
93
110
  ["git", "submodule", "update", "--init", "--recursive", "--depth", "1"]
94
111
  )
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rpremote
4
+ class Mrbgems
5
+ class Definition
6
+ GEM_OPTIONS = { commit: nil, require: nil, auto_require: true, group: nil }.freeze
7
+
8
+ attr_reader :dependencies, :groups, :vm_name
9
+
10
+ def initialize(filename)
11
+ @directory = File.dirname(filename)
12
+ @dependencies = []
13
+ @groups = []
14
+ @group_stack = []
15
+ end
16
+
17
+ def vm(name)
18
+ value = name.to_sym
19
+ raise DefinitionError, "unsupported mrbgems VM: #{name.inspect}" unless VMS.include?(value)
20
+ raise DefinitionError, "mrbgems VM specified more than once" if vm_name
21
+
22
+ @vm_name = value
23
+ end
24
+
25
+ def gem(github: nil, path: nil, branch: "main", **options)
26
+ settings = gem_settings(options)
27
+ sources = [github, path].compact
28
+ raise DefinitionError, "gem requires exactly one of github or path" unless sources.length == 1
29
+
30
+ groups = dependency_groups(settings[:group])
31
+ dependency = if github
32
+ github_dependency(
33
+ github, branch, settings[:commit], settings[:require], settings[:auto_require], groups
34
+ )
35
+ else
36
+ path_dependency(
37
+ path, branch, settings[:commit], settings[:require], settings[:auto_require], groups
38
+ )
39
+ end
40
+ key = [dependency.type, dependency.source]
41
+ raise DefinitionError, "duplicate mrbgem: #{dependency.source}" if dependencies.any? do |item|
42
+ [item.type, item.source] == key
43
+ end
44
+
45
+ dependencies << dependency
46
+ end
47
+
48
+ def group(*names, &block)
49
+ raise DefinitionError, "mrbgem group requires at least one name" if names.empty?
50
+ raise DefinitionError, "mrbgem group requires a block" unless block
51
+
52
+ normalized = Mrbgems.normalize_groups(names)
53
+ @groups |= normalized
54
+ @group_stack << normalized
55
+ instance_eval(&block)
56
+ ensure
57
+ @group_stack.pop if normalized
58
+ end
59
+
60
+ private
61
+
62
+ attr_reader :directory
63
+
64
+ def gem_settings(options)
65
+ unknown = options.keys - GEM_OPTIONS.keys
66
+ raise DefinitionError, "unknown mrbgem option: #{unknown.join(", ")}" unless unknown.empty?
67
+
68
+ settings = GEM_OPTIONS.merge(options)
69
+ require_name = settings[:require]
70
+ unless require_name.nil? || (require_name.is_a?(String) && !require_name.empty? &&
71
+ !require_name.match?(/[\x00-\x1f\x7f]/))
72
+ raise DefinitionError, "invalid mrbgem require name: #{require_name.inspect}"
73
+ end
74
+ unless [true, false].include?(settings[:auto_require])
75
+ raise DefinitionError, "mrbgem auto_require must be true or false: #{settings[:auto_require].inspect}"
76
+ end
77
+
78
+ settings
79
+ end
80
+
81
+ def current_groups
82
+ @group_stack.flatten.uniq.freeze
83
+ end
84
+
85
+ def dependency_groups(group)
86
+ return current_groups if group.nil?
87
+
88
+ names = Array(group)
89
+ raise DefinitionError, "mrbgem group requires at least one name" if names.empty?
90
+
91
+ normalized = Mrbgems.normalize_groups(names)
92
+ @groups |= normalized
93
+ (current_groups + normalized).uniq.freeze
94
+ end
95
+
96
+ def github_dependency(source, branch, commit, require_name, auto_require, groups)
97
+ raise DefinitionError, "invalid GitHub mrbgem: #{source.inspect}" unless GITHUB_PATTERN.match?(source.to_s)
98
+ raise DefinitionError, "GitHub mrbgem branch must not be empty" if branch.to_s.empty?
99
+ raise DefinitionError, "invalid Git commit: #{commit.inspect}" if commit && !COMMIT_PATTERN.match?(commit.to_s)
100
+
101
+ Dependency.new(type: :github, source: source, branch: branch,
102
+ commit: commit&.downcase, path: nil, require_name: require_name,
103
+ auto_require: auto_require, groups: groups)
104
+ end
105
+
106
+ def path_dependency(source, branch, commit, require_name, auto_require, groups)
107
+ raise DefinitionError, "local mrbgem path must not be empty" if source.to_s.empty?
108
+ raise DefinitionError, "local mrbgem does not accept branch or commit" if branch != "main" || commit
109
+
110
+ Dependency.new(type: :path, source: source, branch: nil, commit: nil,
111
+ path: File.expand_path(source, directory), require_name: require_name,
112
+ auto_require: auto_require, groups: groups)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rpremote
4
+ class Mrbgems
5
+ module FileWriter
6
+ private
7
+
8
+ def write_json(filename, contents)
9
+ write_file(filename, "#{JSON.pretty_generate(contents)}\n")
10
+ end
11
+
12
+ def write_file(filename, contents)
13
+ return if File.file?(filename) && File.binread(filename) == contents
14
+
15
+ FileUtils.mkdir_p(File.dirname(filename))
16
+ temporary = "#{filename}.tmp-#{Process.pid}"
17
+ File.binwrite(temporary, contents)
18
+ File.rename(temporary, filename)
19
+ ensure
20
+ FileUtils.rm_f(temporary) if temporary
21
+ end
22
+ end
23
+
24
+ include FileWriter
25
+ end
26
+ end