diff --git a/extfslib.py b/extfslib.py index ffa871c..bad33a6 100644 --- a/extfslib.py +++ b/extfslib.py @@ -2,9 +2,10 @@ extfslib is a library which contains Archive class to support writing extfs plugins for Midnight Commander. -Tested against python 2.7 and mc 4.8.7 +Tested against python 3.6 and mc 4.8.22 Changelog: + 2.0 Switch to python3 1.1 Added item pattern, and common git/uid attrs 1.0 Initial release @@ -13,6 +14,7 @@ Date: 2013-05-12 Version: 1.1 Licence: BSD """ +import argparse import os import sys import re @@ -33,8 +35,8 @@ class Archive(object): "read": "r", "write": "w", "delete": "d"} - ITEM = ("%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(datetime)s " - "%(display_name)s\n") + ITEM = (b"%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(datetime)s " + b"%(display_name)s\n") def __init__(self, fname): """Prepare archive content for operations""" @@ -43,13 +45,14 @@ class Archive(object): self._uid = os.getuid() self._gid = os.getgid() self._arch = fname + self.name_map = {} self._contents = self._get_dir() def _map_name(self, name): """MC still have a bug in extfs subsystem, in case of filepaths with leading space. This is workaround to this bug, which replaces leading space with tilda.""" - if name.startswith(" "): + if name.startswith(b" "): new_name = "".join(["~", name[1:]]) return new_name return name @@ -58,8 +61,9 @@ class Archive(object): """Get real filepath of the file. See _map_name docstring for details.""" for item in self._contents: - if item['display_name'] == name: - return item['fpath'] + if item[b'display_name'] == name.encode('utf-8', + 'surrogateescape'): + return item[b'fpath'] return None def _get_dir(self): @@ -72,7 +76,7 @@ class Archive(object): if not out: return - for line in out.split("\n"): + for line in out.split(b"\n"): match = self.LINE_PAT.match(line) if not match: continue @@ -147,6 +151,61 @@ def usage(): {"prg": sys.argv[0]}) +def _parse_args(arch_class): + """Use ArgumentParser to check for script arguments and execute.""" + + CALL_MAP = {'list': lambda a: arch_class(a.arch).list(), + 'copyin': lambda a: arch_class(a.arch).copyin(a.src, a.dst), + 'copyout': lambda a: arch_class(a.arch).copyout(a.src, a.dst), + 'mkdir': lambda a: arch_class(a.arch).mkdir(a.dst), + 'rm': lambda a: arch_class(a.arch).rm(a.dst), + 'run': lambda a: arch_class(a.arch).run(a.dst)} + + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(help='supported commands') + parser_list = subparsers.add_parser('list', help="List contents of " + "archive") + parser_copyin = subparsers.add_parser('copyin', help="Copy file into " + "archive") + parser_copyout = subparsers.add_parser('copyout', help="Copy file out of " + "archive") + parser_rm = subparsers.add_parser('rm', help="Delete file from archive") + parser_mkdir = subparsers.add_parser('mkdir', help="Create directory in " + "archive") + parser_run = subparsers.add_parser('run', help="Execute archived file") + + parser_list.add_argument('arch', help="Archive filename") + parser_list.set_defaults(func=CALL_MAP['list']) + + parser_copyin.add_argument('arch', help="Archive filename") + parser_copyin.add_argument('src', help="Source filename") + parser_copyin.add_argument('dst', help="Destination filename (to be " + "written into archive)") + parser_copyin.set_defaults(func=CALL_MAP['copyin']) + + parser_copyout.add_argument('arch', help="D64 Image filename") + parser_copyout.add_argument('src', help="Source filename (to be read from" + " archive") + parser_copyout.add_argument('dst', help="Destination filename") + parser_copyout.set_defaults(func=CALL_MAP['copyout']) + + parser_rm.add_argument('arch', help="D64 Image filename") + parser_rm.add_argument('dst', help="File inside archive to be deleted") + parser_rm.set_defaults(func=CALL_MAP['rm']) + + parser_mkdir.add_argument('arch', help="archive filename") + parser_mkdir.add_argument('dst', help="Directory name inside archive to " + "be created") + parser_mkdir.set_defaults(func=CALL_MAP['mkdir']) + + parser_run.add_argument('arch', help="archive filename") + parser_run.add_argument('dst', help="File to be executed") + parser_run.set_defaults(func=CALL_MAP['run']) + + args = parser.parse_args() + return args.func(args) + + def parse_args(arch_class): """Retrive and parse arguments from commandline and apply them into passed arch_class class object.""" diff --git a/ulha b/ulha index 067bcbf..343d2dd 100755 --- a/ulha +++ b/ulha @@ -1,12 +1,13 @@ -#! /usr/bin/env python +#!/usr/bin/env python3 """ Lha Virtual filesystem executive for Midnight Commander. -Tested against python 2.7, lha[1] 1.14 and mc 4.8.7 +Tested against python 3.6, lha[1] 1.14 and mc 4.8.22 [1] http://lha.sourceforge.jp Changelog: + 2.0 Switch to python3 1.2 Moved item pattern to extfslib module 1.1 Moved common code into extfslib library 1.0 Initial release @@ -29,22 +30,22 @@ from extfslib import Archive, parse_args class ULha(Archive): """Archive handle. Provides interface to MC's extfs subsystem""" - LINE_PAT = re.compile("^((?P[d-][rswx-]{9})|(\[generic\])|" - "(\[unknown\]))" - "((\s+\d+/\d+\s+)|(\s+))" - "(?P)(?P)" # just for the record - "(?P\d+)" - "\s+(\*{6}|\d+\.\d%)" - "\s(?P[JFMASOND][a-z]{2})\s+" # month - "(?P\d+)\s+" # day - "(?P\d{4}|(\d{2}:\d{2}))" # year/hour - "\s(?P.*)") - ARCHIVER = "lha" - CMDS = {"list": "lq", - "read": "pq", - "write": "aq", - "delete": "dq"} - DATETIME = "%(month)s %(day)s %(yh)s" + LINE_PAT = re.compile(b"^((?P[d-][rswx-]{9})|(\[generic\])|" + b"(\[unknown\]))" + b"((\s+\d+/\d+\s+)|(\s+))" + b"(?P)(?P)" # just for the record + b"(?P\d+)" + b"\s+(\*{6}|\d+\.\d%)" + b"\s(?P[JFMASOND][a-z]{2})\s+" # month + b"(?P\d+)\s+" # day + b"(?P\d{4}|(\d{2}:\d{2}))" # year/hour + b"\s(?P.*)") + ARCHIVER = b"lha" + CMDS = {"list": b"lq", + "read": b"pq", + "write": b"aq", + "delete": b"dq"} + DATETIME = b"%(month)s %(day)s %(yh)s" def _get_dir(self): """Prepare archive file listing""" @@ -54,37 +55,43 @@ class ULha(Archive): if not out: return - for line in out.split("\n"): + for line in out.split(b"\n"): # -lhd- can store empty directories - perms = "-rw-r--r--" - if line.endswith(os.path.sep): + perms = b"-rw-r--r--" + if line.endswith(bytes(os.path.sep, 'utf-8')): line = line[:-1] - perms = "drw-r--r--" + perms = b"drw-r--r--" match = self.LINE_PAT.match(line) if not match: continue - entry = match.groupdict() + match_entry = match.groupdict() + entry = {} + for key in match_entry: + entry[bytes(key, 'utf-8')] = match_entry[key] + del match_entry # UID and GID sometimes can have strange values depending on # the information that was written into archive. Most of the # times I was dealing with Amiga lha archives, so that i don't # really care about real user/group - entry['uid'] = self._uid - entry['gid'] = self._gid - entry['datetime'] = self.DATETIME % entry - if not entry['perms']: - entry['perms'] = perms + entry[b'uid'] = bytes(str(self._uid), 'utf-8') + entry[b'gid'] = bytes(str(self._gid), 'utf-8') + entry[b'datetime'] = self.DATETIME % entry - entry['display_name'] = self._map_name(entry['fpath']) + if not entry[b'perms']: + entry[b'perms'] = perms + + entry[b'display_name'] = self._map_name(entry[b'fpath']) contents.append(entry) + return contents def list(self): """Output contents of the archive to stdout""" for entry in self._contents: - sys.stdout.write(self.ITEM % entry) + sys.stdout.buffer.write(self.ITEM % entry) return 0 def rm(self, dst):