|
4 | 4 | import fnmatch as _fnmatch |
5 | 5 | import importlib.metadata as _metadata |
6 | 6 | import os as _os |
| 7 | +import shlex as _shlex |
| 8 | +import subprocess as _subprocess |
7 | 9 | import sys as _sys |
| 10 | +import tempfile as _tempfile |
8 | 11 | import time as _time |
9 | 12 |
|
10 | 13 | import mpytool as _mpytool |
|
27 | 30 | # Order of commands in help and completion |
28 | 31 | _CMD_ORDER = [ |
29 | 32 | 'ls', 'tree', 'cat', 'cp', 'mv', 'mkdir', 'rm', 'pwd', 'cd', 'path', |
30 | | - 'stop', 'reset', 'monitor', 'repl', 'exec', 'run', 'info', |
| 33 | + 'stop', 'reset', 'monitor', 'repl', 'exec', 'run', 'edit', 'info', |
31 | 34 | 'flash', 'ota', 'mount', 'ln', 'speedtest', 'sleep', |
32 | 35 | ] |
33 | 36 |
|
@@ -897,6 +900,63 @@ def _dispatch_run(self, commands, is_last_group): |
897 | 900 | self.verbose(f"RUN: {args.file} ({len(code)} bytes)", 1) |
898 | 901 | self.mpy.comm.try_raw_paste(code, timeout=0) |
899 | 902 |
|
| 903 | + def _get_editor(self, editor_arg=None): |
| 904 | + """Get editor from --editor, $VISUAL, or $EDITOR""" |
| 905 | + if editor_arg: |
| 906 | + return editor_arg |
| 907 | + editor = _os.environ.get('VISUAL') or _os.environ.get('EDITOR') |
| 908 | + if not editor: |
| 909 | + raise ParamsError( |
| 910 | + 'No editor configured. ' |
| 911 | + 'Set $VISUAL or $EDITOR, or use --editor') |
| 912 | + return editor |
| 913 | + |
| 914 | + def cmd_edit(self, path, editor=None): |
| 915 | + """Edit file on device using local editor""" |
| 916 | + editor_cmd = self._get_editor(editor) |
| 917 | + self.verbose(f"EDIT: {path}", 1) |
| 918 | + try: |
| 919 | + data = self.mpy.get(path) |
| 920 | + except _mpytool.FileNotFound: |
| 921 | + data = b'' |
| 922 | + self.verbose(" (new file)", 1) |
| 923 | + suffix = '.' + path.rsplit('.', 1)[-1] if '.' in path else '' |
| 924 | + with _tempfile.NamedTemporaryFile( |
| 925 | + mode='wb', suffix=suffix, delete=False) as tmp: |
| 926 | + tmp.write(data) |
| 927 | + tmp_path = tmp.name |
| 928 | + try: |
| 929 | + self.verbose(f" editor: {editor_cmd}", 2) |
| 930 | + result = _subprocess.run(_shlex.split(editor_cmd) + [tmp_path]) |
| 931 | + if result.returncode != 0: |
| 932 | + self.verbose( |
| 933 | + f" editor exited with code {result.returncode}, " |
| 934 | + "file not uploaded", 1, color='yellow') |
| 935 | + return |
| 936 | + with open(tmp_path, 'rb') as f: |
| 937 | + new_data = f.read() |
| 938 | + if new_data == data: |
| 939 | + self.verbose(" (no changes)", 1) |
| 940 | + return |
| 941 | + self.verbose( |
| 942 | + f" uploading {len(new_data)} bytes...", 1, color='cyan') |
| 943 | + self.mpy.put(new_data, path) |
| 944 | + self.verbose(" done", 1, color='green') |
| 945 | + finally: |
| 946 | + try: |
| 947 | + _os.unlink(tmp_path) |
| 948 | + except OSError: |
| 949 | + pass |
| 950 | + |
| 951 | + @command('edit', 'Edit file on device in local editor.') |
| 952 | + @option('--editor', metavar='CMD', help='editor command') |
| 953 | + @argument('path', metavar='remote', help='device file to edit (: prefix)') |
| 954 | + def _dispatch_edit(self, commands, is_last_group): |
| 955 | + args = _make_parser(self._dispatch_edit).parse_args(commands) |
| 956 | + commands.clear() |
| 957 | + path = _parse_device_path(args.path, 'edit') |
| 958 | + self.cmd_edit(path, editor=args.editor) |
| 959 | + |
900 | 960 | @command('info', 'Show device info (platform, memory, filesystem).') |
901 | 961 | def _dispatch_info(self, commands, is_last_group): |
902 | 962 | _, commands[:] = _make_parser(self._dispatch_info).parse_known_args( |
@@ -1213,8 +1273,8 @@ def _dispatch_args(self, commands, is_last_group): |
1213 | 1273 |
|
1214 | 1274 | _COMMANDS = frozenset({ |
1215 | 1275 | 'ls', 'tree', 'cat', 'mkdir', 'rm', 'pwd', 'cd', 'path', |
1216 | | - 'reset', 'stop', 'monitor', 'repl', 'exec', 'run', 'info', 'flash', |
1217 | | - 'ota', 'sleep', 'cp', 'mv', 'mount', 'ln', 'speedtest', |
| 1276 | + 'reset', 'stop', 'monitor', 'repl', 'exec', 'run', 'edit', 'info', |
| 1277 | + 'flash', 'ota', 'sleep', 'cp', 'mv', 'mount', 'ln', 'speedtest', |
1218 | 1278 | '_paths', '_ports', '_commands', '_options', '_args', |
1219 | 1279 | }) |
1220 | 1280 |
|
|
0 commit comments