def do_ls(self, line): """ls [-a] [-l] [FILE|DIRECTORY|PATTERN]... PATTERN supports * ? [seq] [!seq] Unix filename matching List directory contents. """ args = self.line_to_args(line) if len(args.filenames) == 0: args.filenames = ['.'] for idx, fn in enumerate(args.filenames): if not is_pattern(fn): filename = resolve_path(self.cur_dir, fn) stat = auto(self.boards, get_stat, filename) mode = stat_mode(stat) if not mode_exists(mode): err = "Cannot access '{}': No such file or directory" eprint(err.format(filename)) continue if not mode_isdir(mode): if args.long: print_long(filename, stat, oprint) else: oprint(filename) continue if len(args.filenames) > 1: if idx > 0: oprint('') oprint("%s:" % filename) pattern = '*' else: # A pattern was specified filename, pattern = validate_pattern(self.boards, self.cur_dir, fn) if filename is None: # An error was printed continue files = [] ldir_stat = auto(self.boards, listdir_stat, filename) if ldir_stat is None: err = "Cannot access '{}': No such file or directory" eprint(err.format(filename)) else: for filename, stat in sorted(ldir_stat, key=lambda entry: entry[0]): if is_visible(filename) or args.all: if fnmatch(filename, pattern): if args.long: print_long(filename, stat, oprint) else: files.append(decorated_filename(filename, stat)) if len(files) > 0: print_cols(sorted(files), oprint, shutil.get_terminal_size().columns)
def do_cd(self, line): """cd DIRECTORY Changes the current directory. ~ expansion is supported, and cd - goes to the previous directory. """ args = self.line_to_args(line) if len(args) == 0: dirname = '~' else: if args[0] == '-': dirname = self.prev_dir else: dirname = args[0] dirname = resolve_path(self.cur_dir, dirname) mode = auto(self.boards, get_mode, dirname) if mode_isdir(mode): self.prev_dir = self.cur_dir self.cur_dir = dirname auto(self.boards, chdir, dirname) else: eprint("Directory '%s' does not exist" % dirname)
def do_cat(self, line): """cat FILENAME... Concatenates files and sends to stdout. """ # note: when we get around to supporting cat from stdin, we'll need # to write stdin to a temp file, and then copy the file # since we need to know the filesize when copying to the pyboard. args = self.line_to_args(line) for filename in args: filename = resolve_path(self.cur_dir, filename) mode = auto(self.boards, get_mode, filename) if not mode_exists(mode): eprint("Cannot access '%s': No such file" % filename) continue if not mode_isfile(mode): eprint("'%s': is not a file" % filename) continue cat(self.boards, filename, self.stdout)
def do_edit(self, line): """edit FILE Copies the file locally, launches an editor to edit the file. When the editor exits, if the file was modified then its copied back. You can specify the editor used with the --editor command line option when you start shell49, or by using the SHELL49_EDITOR or VISUAL or EDITOR environment variable. If none of those are set, then vi will be used. """ if len(line) == 0: eprint("Must provide a filename") return filename = resolve_path(self.cur_dir, line) dev, dev_filename = self.boards.get_dev_and_path(filename) mode = auto(self.boards, get_mode, filename) if mode_exists(mode) and mode_isdir(mode): eprint("Unable to edit directory '{}'".format(filename)) return if dev is None: # File is local os.system("{} '{}'".format(self.editor, filename)) else: # File is remote with tempfile.TemporaryDirectory() as temp_dir: local_filename = os.path.join(temp_dir, os.path.basename(filename)) if mode_exists(mode): print('Retrieving {} ...'.format(filename)) cp(filename, local_filename) old_stat = get_stat(local_filename) os.system("{} '{}'".format(self.editor, local_filename)) new_stat = get_stat(local_filename) if old_stat != new_stat: print('Updating {} ...'.format(filename)) cp(local_filename, filename)
def do_cp(self, line): """cp SOURCE DEST Copy a single SOURCE file to DEST file. cp SOURCE... DIRECTORY Copy multiple SOURCE files to a directory. cp [-r] PATTERN DIRECTORY Copy matching files to DIRECTORY. cp [-r|--recursive] [SOURCE|SOURCE_DIR]... DIRECTORY The destination must be a directory except in the case of copying a single file. To copy directories -r must be specified. This will cause directories and their contents to be recursively copied. """ args = self.line_to_args(line) if len(args.filenames) < 2: eprint('Missing destination file') return dst_dirname = resolve_path(self.cur_dir, args.filenames[-1]) dst_mode = auto(self.boards, get_mode, dst_dirname) d_dst = {} # Destination directory: lookup stat by basename if args.recursive: dst_files = auto(self.boards, listdir_stat, dst_dirname) if dst_files is None: err = "cp: target {} is not a directory" eprint(err.format(dst_dirname)) return for name, stat in dst_files: d_dst[name] = stat src_filenames = args.filenames[:-1] # Process PATTERN sfn = src_filenames[0] if is_pattern(sfn): if len(src_filenames) > 1: eprint("Usage: cp [-r] PATTERN DIRECTORY") return src_filenames = process_pattern(self.boards, self.cur_dir, sfn) if src_filenames is None: return for src_filename in src_filenames: if is_pattern(src_filename): eprint("Only one pattern permitted.") return src_filename = resolve_path(self.cur_dir, src_filename) src_mode = auto(self.boards, get_mode, src_filename) if not mode_exists(src_mode): eprint("File '{}' doesn't exist".format(src_filename)) return if mode_isdir(src_mode): if args.recursive: # Copying a directory src_basename = os.path.basename(src_filename) dst_filename = os.path.join(dst_dirname, src_basename) if src_basename in d_dst: dst_stat = d_dst[src_basename] dst_mode = stat_mode(dst_stat) if not mode_isdir(dst_mode): err = "Destination {} is not a directory" eprint(err.format(dst_filename)) return else: if not mkdir(dst_filename): err = "Unable to create directory {}" eprint(err.format(dst_filename)) return rsync(src_filename, dst_filename, mirror=False, dry_run=False, print_func=lambda *args: None, recursed=False) else: eprint("Omitting directory {}".format(src_filename)) continue if mode_isdir(dst_mode): dst_filename = os.path.join( dst_dirname, os.path.basename(src_filename)) else: dst_filename = dst_dirname if not cp(self.boards, src_filename, dst_filename): err = "Unable to copy '{}' to '{}'" eprint(err.format(src_filename, dst_filename)) break
def real_filename_complete(self, text, line, begidx, endidx): """Figure out what filenames match the completion.""" # line contains the full command line that's been entered so far. # text contains the portion of the line that readline is trying to complete # text should correspond to line[begidx:endidx] # # The way the completer works text will start after one of the characters # in DELIMS. So if the filename entered so far was "embedded\ sp" and # then text will point to the s in sp. # # The following bit of logic backs up to find the real beginning of the # filename. for before_match in range(begidx, 0, -1): if line[before_match] in self.DELIMS and before_match >= 1 and line[ before_match - 1] != '\\': break # We set fixed to be the portion of the filename which is before text # and match is the full portion of the filename that's been entered so # far (that's that part we use for matching files). # # When we return a list of completions, the bit that we return should # just be the portion that we replace 'text' with. # fixed portion of the match fixed = unescape(line[before_match + 1:begidx]) # portion to match filenames against match = unescape(line[before_match + 1:endidx]) # We do the following to cover the case that the current directory # is / and the path being entered is relative. if match[0] == '/': abs_match = match elif self.cur_dir == '/': abs_match = self.cur_dir + match else: abs_match = self.cur_dir + '/' + match completions = [] prepend = '' if abs_match.rfind('/') == 0: # match is in the root directory # This means that we're looking for matches in the root directory # (i.e. abs_match is /foo and the user hit TAB). # So we'll supply the matching board names as possible completions. # Since they're all treated as directories we leave the trailing slash. if match[0] == '/': completions += [ dev.name_path for dev in self.boards.boards() if dev.name_path.startswith(abs_match) ] else: completions += [ dev.name_path[1:] for dev in self.boards.boards() if dev.name_path.startswith(abs_match) ] try: # Add root directories of the default device def_dev = self.boards.default if match[0] == '/': completions += [ root_dir for root_dir in def_dev.root_dirs if root_dir.startswith(match) ] else: completions += [ root_dir[1:] for root_dir in def_dev.root_dirs if root_dir[1:].startswith(match) ] except BoardError: pass else: # This means that there are at least 2 slashes in abs_match. If one # of them matches a board name then we need to remove the board # name from fixed. Since the results from listdir_matches won't # contain the board name, we need to prepend each of the completions. for dev in self.boards.boards(): if abs_match.startswith(dev.name_path): prepend = dev.name_path[:-1] paths = sorted(auto(self.boards, listdir_matches, match)) for path in paths: path = prepend + path completions.append(escape(path.replace(fixed, '', 1))) return completions