Added support for Python 3 for ulha

ulzx and uadf currently are broken. Fix for both of them is on the way.
This commit is contained in:
2019-06-27 21:35:35 +02:00
parent 0a972a5bce
commit 3d269303d9
2 changed files with 103 additions and 37 deletions

View File

@@ -2,9 +2,10 @@
extfslib is a library which contains Archive class to support writing extfs extfslib is a library which contains Archive class to support writing extfs
plugins for Midnight Commander. 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: Changelog:
2.0 Switch to python3
1.1 Added item pattern, and common git/uid attrs 1.1 Added item pattern, and common git/uid attrs
1.0 Initial release 1.0 Initial release
@@ -13,6 +14,7 @@ Date: 2013-05-12
Version: 1.1 Version: 1.1
Licence: BSD Licence: BSD
""" """
import argparse
import os import os
import sys import sys
import re import re
@@ -33,8 +35,8 @@ class Archive(object):
"read": "r", "read": "r",
"write": "w", "write": "w",
"delete": "d"} "delete": "d"}
ITEM = ("%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(datetime)s " ITEM = (b"%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(datetime)s "
"%(display_name)s\n") b"%(display_name)s\n")
def __init__(self, fname): def __init__(self, fname):
"""Prepare archive content for operations""" """Prepare archive content for operations"""
@@ -43,13 +45,14 @@ class Archive(object):
self._uid = os.getuid() self._uid = os.getuid()
self._gid = os.getgid() self._gid = os.getgid()
self._arch = fname self._arch = fname
self.name_map = {}
self._contents = self._get_dir() self._contents = self._get_dir()
def _map_name(self, name): def _map_name(self, name):
"""MC still have a bug in extfs subsystem, in case of filepaths with """MC still have a bug in extfs subsystem, in case of filepaths with
leading space. This is workaround to this bug, which replaces leading leading space. This is workaround to this bug, which replaces leading
space with tilda.""" space with tilda."""
if name.startswith(" "): if name.startswith(b" "):
new_name = "".join(["~", name[1:]]) new_name = "".join(["~", name[1:]])
return new_name return new_name
return name return name
@@ -58,8 +61,9 @@ class Archive(object):
"""Get real filepath of the file. See _map_name docstring for """Get real filepath of the file. See _map_name docstring for
details.""" details."""
for item in self._contents: for item in self._contents:
if item['display_name'] == name: if item[b'display_name'] == name.encode('utf-8',
return item['fpath'] 'surrogateescape'):
return item[b'fpath']
return None return None
def _get_dir(self): def _get_dir(self):
@@ -72,7 +76,7 @@ class Archive(object):
if not out: if not out:
return return
for line in out.split("\n"): for line in out.split(b"\n"):
match = self.LINE_PAT.match(line) match = self.LINE_PAT.match(line)
if not match: if not match:
continue continue
@@ -147,6 +151,61 @@ def usage():
{"prg": sys.argv[0]}) {"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): def parse_args(arch_class):
"""Retrive and parse arguments from commandline and apply them into passed """Retrive and parse arguments from commandline and apply them into passed
arch_class class object.""" arch_class class object."""

67
ulha
View File

@@ -1,12 +1,13 @@
#! /usr/bin/env python #!/usr/bin/env python3
""" """
Lha Virtual filesystem executive for Midnight Commander. 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 [1] http://lha.sourceforge.jp
Changelog: Changelog:
2.0 Switch to python3
1.2 Moved item pattern to extfslib module 1.2 Moved item pattern to extfslib module
1.1 Moved common code into extfslib library 1.1 Moved common code into extfslib library
1.0 Initial release 1.0 Initial release
@@ -29,22 +30,22 @@ from extfslib import Archive, parse_args
class ULha(Archive): class ULha(Archive):
"""Archive handle. Provides interface to MC's extfs subsystem""" """Archive handle. Provides interface to MC's extfs subsystem"""
LINE_PAT = re.compile("^((?P<perms>[d-][rswx-]{9})|(\[generic\])|" LINE_PAT = re.compile(b"^((?P<perms>[d-][rswx-]{9})|(\[generic\])|"
"(\[unknown\]))" b"(\[unknown\]))"
"((\s+\d+/\d+\s+)|(\s+))" b"((\s+\d+/\d+\s+)|(\s+))"
"(?P<uid>)(?P<gid>)" # just for the record b"(?P<uid>)(?P<gid>)" # just for the record
"(?P<size>\d+)" b"(?P<size>\d+)"
"\s+(\*{6}|\d+\.\d%)" b"\s+(\*{6}|\d+\.\d%)"
"\s(?P<month>[JFMASOND][a-z]{2})\s+" # month b"\s(?P<month>[JFMASOND][a-z]{2})\s+" # month
"(?P<day>\d+)\s+" # day b"(?P<day>\d+)\s+" # day
"(?P<yh>\d{4}|(\d{2}:\d{2}))" # year/hour b"(?P<yh>\d{4}|(\d{2}:\d{2}))" # year/hour
"\s(?P<fpath>.*)") b"\s(?P<fpath>.*)")
ARCHIVER = "lha" ARCHIVER = b"lha"
CMDS = {"list": "lq", CMDS = {"list": b"lq",
"read": "pq", "read": b"pq",
"write": "aq", "write": b"aq",
"delete": "dq"} "delete": b"dq"}
DATETIME = "%(month)s %(day)s %(yh)s" DATETIME = b"%(month)s %(day)s %(yh)s"
def _get_dir(self): def _get_dir(self):
"""Prepare archive file listing""" """Prepare archive file listing"""
@@ -54,37 +55,43 @@ class ULha(Archive):
if not out: if not out:
return return
for line in out.split("\n"): for line in out.split(b"\n"):
# -lhd- can store empty directories # -lhd- can store empty directories
perms = "-rw-r--r--" perms = b"-rw-r--r--"
if line.endswith(os.path.sep): if line.endswith(bytes(os.path.sep, 'utf-8')):
line = line[:-1] line = line[:-1]
perms = "drw-r--r--" perms = b"drw-r--r--"
match = self.LINE_PAT.match(line) match = self.LINE_PAT.match(line)
if not match: if not match:
continue 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 # UID and GID sometimes can have strange values depending on
# the information that was written into archive. Most of the # the information that was written into archive. Most of the
# times I was dealing with Amiga lha archives, so that i don't # times I was dealing with Amiga lha archives, so that i don't
# really care about real user/group # really care about real user/group
entry['uid'] = self._uid
entry['gid'] = self._gid
entry['datetime'] = self.DATETIME % entry
if not entry['perms']: entry[b'uid'] = bytes(str(self._uid), 'utf-8')
entry['perms'] = perms 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) contents.append(entry)
return contents return contents
def list(self): def list(self):
"""Output contents of the archive to stdout""" """Output contents of the archive to stdout"""
for entry in self._contents: for entry in self._contents:
sys.stdout.write(self.ITEM % entry) sys.stdout.buffer.write(self.ITEM % entry)
return 0 return 0
def rm(self, dst): def rm(self, dst):