|
21 | 21 | # SOFTWARE. |
22 | 22 | from __future__ import print_function |
23 | 23 | import os |
| 24 | +import pathlib |
24 | 25 | import platform |
25 | 26 | import posixpath |
26 | 27 | import re |
@@ -70,7 +71,7 @@ def windows_full_port_name(portname): |
70 | 71 | return "\\\\.\\{0}".format(portname) |
71 | 72 |
|
72 | 73 |
|
73 | | -@click.group(cls=AmypGroup) |
| 74 | +@click.group(cls=AmypGroup, context_settings={"auto_envvar_prefix": "AMPY"}) |
74 | 75 | @click.option( |
75 | 76 | "--port", |
76 | 77 | "-p", |
@@ -115,35 +116,54 @@ def cli(port, baud, delay): |
115 | 116 |
|
116 | 117 |
|
117 | 118 | @cli.command() |
118 | | -@click.argument("remote_file") |
119 | | -@click.argument("local_file", type=click.File("wb"), required=False) |
120 | | -def get(remote_file, local_file): |
| 119 | +@click.argument("remote_files", nargs=-1, required=True) |
| 120 | +@click.option( |
| 121 | + "--output", |
| 122 | + "-o", |
| 123 | + type=click.Path(), |
| 124 | + default=None, |
| 125 | + help="Local file or directory to save to. Defaults to printing to stdout.", |
| 126 | +) |
| 127 | +def get(remote_files, output): |
121 | 128 | """ |
122 | 129 | Retrieve a file from the board. |
123 | 130 |
|
124 | 131 | Get will download a file from the board and print its contents or save it |
125 | 132 | locally. You must pass at least one argument which is the path to the file |
126 | | - to download from the board. If you don't specify a second argument then |
127 | | - the file contents will be printed to standard output. However if you pass |
128 | | - a file name as the second argument then the contents of the downloaded file |
129 | | - will be saved to that file (overwriting anything inside it!). |
| 133 | + to download from the board. If you don't specify --output then the file |
| 134 | + contents will be printed to standard output. If you pass --output with a |
| 135 | + file path the downloaded contents will be saved there. When downloading |
| 136 | + multiple files, --output must point to an existing local directory. |
130 | 137 |
|
131 | 138 | For example to retrieve the boot.py and print it out run: |
132 | 139 |
|
133 | 140 | ampy --port /board/serial/port get boot.py |
134 | 141 |
|
135 | 142 | Or to get main.py and save it as main.py locally run: |
136 | 143 |
|
137 | | - ampy --port /board/serial/port get main.py main.py |
| 144 | + ampy --port /board/serial/port get main.py --output main.py |
| 145 | +
|
| 146 | + Or to download multiple files into a local directory: |
| 147 | +
|
| 148 | + ampy --port /board/serial/port get boot.py main.py --output ./local_dir/ |
138 | 149 | """ |
139 | | - # Get the file contents. |
140 | 150 | board_files = files.Files(_board) |
141 | | - contents = board_files.get(remote_file) |
142 | | - # Print the file out if no local file was provided, otherwise save it. |
143 | | - if local_file is None: |
144 | | - print(contents.decode("utf-8")) |
| 151 | + if len(remote_files) > 1: |
| 152 | + if output is None or not pathlib.Path(output).is_dir(): |
| 153 | + raise click.UsageError( |
| 154 | + "A local directory (--output) must be provided when downloading multiple files." |
| 155 | + ) |
| 156 | + dest_dir = pathlib.Path(output) |
| 157 | + for remote_file in remote_files: |
| 158 | + contents = board_files.get(remote_file) |
| 159 | + dest = dest_dir / pathlib.PurePosixPath(remote_file).name |
| 160 | + dest.write_bytes(contents) |
145 | 161 | else: |
146 | | - local_file.write(contents) |
| 162 | + contents = board_files.get(remote_files[0]) |
| 163 | + if output is None: |
| 164 | + print(contents.decode("utf-8")) |
| 165 | + else: |
| 166 | + pathlib.Path(output).write_bytes(contents) |
147 | 167 |
|
148 | 168 |
|
149 | 169 | @cli.command() |
@@ -228,7 +248,13 @@ def ls(directory, long_format, recursive): |
228 | 248 | @cli.command() |
229 | 249 | @click.argument("local", type=click.Path(exists=True)) |
230 | 250 | @click.argument("remote", required=False) |
231 | | -def put(local, remote): |
| 251 | +@click.option( |
| 252 | + "--no-progress", |
| 253 | + is_flag=True, |
| 254 | + default=False, |
| 255 | + help="Disable progress bar during file upload.", |
| 256 | +) |
| 257 | +def put(local, remote, no_progress): |
232 | 258 | """Put a file or folder and its contents on the board. |
233 | 259 |
|
234 | 260 | Put will upload a local file or folder to the board. If the file already |
@@ -281,36 +307,58 @@ def put(local, remote): |
281 | 307 | pass |
282 | 308 | # Loop through all the files and put them on the board too. |
283 | 309 | for filename in child_files: |
284 | | - with open(os.path.join(parent, filename), "rb") as infile: |
285 | | - remote_filename = posixpath.join(remote_parent, filename) |
286 | | - board_files.put(remote_filename, infile.read()) |
287 | | - |
| 310 | + local_path = os.path.join(parent, filename) |
| 311 | + remote_filename = posixpath.join(remote_parent, filename) |
| 312 | + with open(local_path, "rb") as infile: |
| 313 | + data = infile.read() |
| 314 | + if no_progress: |
| 315 | + board_files.put(remote_filename, data) |
| 316 | + else: |
| 317 | + label = remote_filename |
| 318 | + with click.progressbar( |
| 319 | + length=len(data), label=label, width=0 |
| 320 | + ) as bar: |
| 321 | + def _cb(written, total, bar=bar, prev=[0]): |
| 322 | + bar.update(written - prev[0]) |
| 323 | + prev[0] = written |
| 324 | + board_files.put(remote_filename, data, progress_callback=_cb) |
288 | 325 |
|
289 | 326 | else: |
290 | 327 | # File copy, open the file and copy its contents to the board. |
291 | | - # Put the file on the board. |
292 | 328 | with open(local, "rb") as infile: |
293 | | - board_files = files.Files(_board) |
294 | | - board_files.put(remote, infile.read()) |
| 329 | + data = infile.read() |
| 330 | + board_files = files.Files(_board) |
| 331 | + if no_progress: |
| 332 | + board_files.put(remote, data) |
| 333 | + else: |
| 334 | + with click.progressbar(length=len(data), label=remote, width=0) as bar: |
| 335 | + def _cb(written, total, bar=bar, prev=[0]): |
| 336 | + bar.update(written - prev[0]) |
| 337 | + prev[0] = written |
| 338 | + board_files.put(remote, data, progress_callback=_cb) |
295 | 339 |
|
296 | 340 |
|
297 | 341 | @cli.command() |
298 | | -@click.argument("remote_file") |
299 | | -def rm(remote_file): |
| 342 | +@click.argument("remote_files", nargs=-1, required=True) |
| 343 | +def rm(remote_files): |
300 | 344 | """Remove a file from the board. |
301 | 345 |
|
302 | | - Remove the specified file from the board's filesystem. Must specify one |
303 | | - argument which is the path to the file to delete. Note that this can't |
304 | | - delete directories which have files inside them, but can delete empty |
| 346 | + Remove the specified file(s) from the board's filesystem. Must specify at |
| 347 | + least one argument which is the path to the file to delete. Note that this |
| 348 | + can't delete directories which have files inside them, but can delete empty |
305 | 349 | directories. |
306 | 350 |
|
307 | 351 | For example to delete main.py from the root of a board run: |
308 | 352 |
|
309 | 353 | ampy --port /board/serial/port rm main.py |
| 354 | +
|
| 355 | + Or to delete multiple files at once: |
| 356 | +
|
| 357 | + ampy --port /board/serial/port rm main.py boot.py |
310 | 358 | """ |
311 | | - # Delete the provided file/directory on the board. |
312 | 359 | board_files = files.Files(_board) |
313 | | - board_files.rm(remote_file) |
| 360 | + for remote_file in remote_files: |
| 361 | + board_files.rm(remote_file) |
314 | 362 |
|
315 | 363 |
|
316 | 364 | @cli.command() |
|
0 commit comments