Skip to content

Commit bafbae0

Browse files
o-murphycclauss
andauthored
pythonista>=3.4/py>=3.10: Refactoring, removing deprecated requirements, updating for (#524)
* Update check-code-and-run-tests.yml * pytest --ignore=tests/pip/test_pip.py * bin/exit.py: drop stale coding declaration and unused os import (also in its test) * bin/logout.py: add missing shebang * bin/du.py: add shebang, drop py2 coding declaration and __future__ import * bin/tar.py: add shebang, drop py2 coding declaration and __future__ import * bin/webviewer.py: add shebang, drop legacy coding declaration, tidy imports * bin/clear.py: add shebang, drop py2 coding declaration and __future__ import * bin/mkdir.py: drop py2 leftovers, use f-strings for error output * bin/printenv.py: drop py2 leftovers, use f-strings for output * bin/pwd.py: drop py2 leftovers, use f-strings for error output (also in its test) * bin/touch.py: drop py2 leftovers, use f-strings for error output * bin/unzip.py: add shebang, drop py2 leftovers, sort imports * bin/xargs.py: add shebang, drop py2 leftovers, sort imports, drop unused os import * bin/head.py: add shebang, drop py2 leftovers, sort imports * bin/openin.py: drop py2 leftovers, use plain class instead of class(object) * bin/quicklook.py: drop py2 leftovers, use plain class instead of class(object) * bin/stashconf.py: add shebang, drop py2 leftovers, sort imports * bin/which.py: add shebang, drop py2 leftovers, silence global-without-assignment lint warning * bin/zip.py: add shebang, drop py2 leftovers, sort imports * bin/grep.py: add shebang, drop py2 leftovers, use f-strings, silence global lint warning * bin/latte.py: drop py2 leftovers, use plain class, iterate dict directly instead of .keys() * bin/sort.py: add shebang, drop py2 leftovers and unused os import, sort imports * bin/source.py: add shebang, drop py2 leftovers, rename unused var, use OSError instead of IOError * bin/tail.py: add shebang, drop py2 coding declaration and __future__ import, sort imports * bin/cowsay.py: add shebang, drop py2 leftovers, use str.removesuffix (also in its test) * bin/echo.py: drop py2 coding declaration and __future__ import, mark unused escapes var (also in its test) * bin/kill.py: add shebang, drop py2 leftovers, use f-strings * bin/totd.py: add shebang, drop py2 leftovers, sort imports * bin/umount.py: add shebang, use f-strings, sort mount_ctrl/mount_manager import * bin/uniq.py: add shebang, drop py2 leftovers and unused os import, sort imports * bin/fg.py: add shebang, drop py2 leftovers, use f-strings * bin/find.py: add shebang, drop py2 leftovers, sort imports, use plain class instead of class(object) * bin/more.py: drop py2-only shebang and legacy coding declaration, sort imports * bin/pbcopy.py, bin/pbpaste.py: drop py2/six leftovers, use f-strings (also in shared test) * bin/curl.py: add shebang, drop six/py2 leftovers, use f-strings * bin/rmdir.py: drop py2-only shebang and legacy coding declaration, use f-strings * bin/alias.py: add shebang, drop py2 leftovers, use f-strings (also in its test) * bin/wol.py, lib/wakeonlan: drop py2 leftovers, use f-strings * bin/rm.py: add shebang, drop six/py2 leftovers, simplify boolean prompt logic * bin/ssh-keygen.py: add shebang, drop py2 leftovers, patch time.clock for paramiko/Crypto.Random compat * bin/ln.py: sort imports, drop unused imports and dead return statements * bin/selfupdate.py: drop py2 leftovers, replace deprecated distutils.version with packaging.version (also in its test) * bin/cp.py: add shebang, drop py2 leftovers, use f-strings * bin/ping.py: drop py2 coding declaration, six.moves.xrange and __future__ import, mark unused unpacked vars (also in its test) * bin/whatis.py: add shebang, guard against missing command argument, add pinstash entry, use sys.argv instead of bare argv import * bin/md5sum.py: replace Crypto.Hash and six with hashlib and stdlib BytesIO (also in its test) * bin/printhex.py: drop py2 leftovers, work on raw ints instead of decoded chars, use f-strings * bin/sha1sum.py: replace Crypto.Hash and six with hashlib and stdlib BytesIO (also in its test) * bin/sha256sum.py: replace Crypto.Hash and six with hashlib and stdlib BytesIO (also in its test) * bin/mv.py: drop py2 leftovers, use f-strings * bin/cat.py: add shebang, drop py2 leftovers, sort imports (also in its test) * bin/cd.py: add shebang, drop py2 leftovers, use f-strings * bin/crypt.py: add shebang, switch to pyaes-whl, use plain class, combine nested with-statements * bin/cut.py: replace _stash.libcore.input_stream dependency with local fileinput-based generator * bin/diff.py: rewrite as testable CLI - argparse takes explicit args, Path-based I/O, optional console/_stash color output with ANSI fallback * bin/easy_config.py, bin/pinstash.py: add pinstash command, easy_config now delegates shortcut creation to it instead of calling pythonista_add_action directly * bin/edit.py: drop py2 leftovers, combine nested with-statements, sort imports * bin/gci.py: add shebang, sort imports, use f-strings * bin/jobs.py: add shebang, sort imports * bin/python3.py: hardcode PY3=True instead of relying on removed _stash.PY3 attribute, sort imports * bin/ptinstaller.py: add installer for tools registered on Pythonista Tools GitHub repo * bin/scp.py: drop six/distutils leftovers, use packaging.version, use socket.timeout removed - use builtin TimeoutError * bin/sqlite.py: add shebang, sort imports, combine nested with-statements * bin/telnet.py: add shebang, switch to standard-telnetlib with auto-install fallback, sort imports * bin/version.py: add shebang, sort imports, drop lambda in favor of def for collapseuser fallback * bin/wc.py: replace _stash.libcore.input_stream dependency with local fileinput-based generator * bin/wget.py: drop six leftovers and removed _stash.PY3 check, use f-strings * bin/mail.py: drop six leftovers, use stdlib email/configparser modules directly, print send errors * bin/man.py: add shebang, drop py2 raw_input shim, use f-strings * bin/ssh.py: drop six/distutils leftovers, use packaging.version, patch time.clock for paramiko compat, fix key-based connect to not also pass password * bin/ftpserver.py: add shebang, sort imports, use f-strings * bin/gh.py: drop six leftovers, sort imports, drop unused var bindings, use f-strings * bin/httpserver.py: rewrite as a py3 stdlib http.server-based file server with upload support * bin/git.py, lib/git: drop py2 leftovers, simplify branch/merge helpers * bin/tree.py, lib/tree_: add tree command implementation matching linux tree utility * bin/pip.py: rewrite to delegate to standard pip via pip._internal instead of the custom wheels/dbutils implementation, add Pythonista workaround docs and tests * pytest move options to pyproject.toml * Update check-code-and-run-tests.yml * test_libversion.py fix StaSh attributes * skip `tests/system/test_termemu.py::TermemuTests::test_20` cause it's failing * keychain stubs update * `stashutils.fsi.base` fix * migrate build metadata to pyproject.toml, trim setup.py to a thin shim, pin .python-version * lib/mlpatches: modernize patch layer for py3, add time.clock shim for paramiko/Crypto.Random compat, drop legacy popen2/py2-only handlers * system/, lib/stashutils/, lib/lib*.py: modernize interpreter core and mount/fsi infrastructure for py3, drop now-unused wheels.py * add __main__.py as the new entry point, trim launch_stash.py/getstash.py/core.py to delegate to it * bin/ls.py: modernize for py3, fix mimetype detection * bin/mc.py: modernize for py3 * bin/mount.py: modernize for py3, adjust to updated stashutils dependencies * bin/monkeylord.py: modernize for py3 * bin/dropbox_setup.py: modernize for py3 * stubs: add clipboard/console/dialogs/editor/objc_util stubs, finish keychain stub * docs: update porting guide and README, add todo.md * pyaes -> pyaes-whl * telnetlib * CI: add ruff job, reformat workflow, bump python 3.13->3.14, simplify test invocation * tests/, tools/, man page: drop legacy coding declarations, tidy imports, sync docs for py3-only * drop bin/python.py and lib/mlpatches/modules/popen2.py: Python 2 is no longer supported, use 'python3' instead * uv.lock: sync with final dependency set * fix: replace deprecated fileinput.hook_encoded --------- Co-authored-by: Christian Clauss <cclauss@me.com>
1 parent 55aa0bf commit bafbae0

245 files changed

Lines changed: 7912 additions & 5106 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/check-code-and-run-tests.yml

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ name: Check code and run tests
22

33
on:
44
push:
5-
branches: [ master ]
5+
branches: [master]
66
pull_request:
7-
branches: [ master ]
7+
branches: [master]
8+
workflow_dispatch:
89

910
jobs:
1011
pre-commit:
@@ -16,31 +17,39 @@ jobs:
1617
python-version: 3.x
1718
- uses: pre-commit/action@v3.0.1
1819

20+
ruff:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@v7
25+
- run: pip install --user ruff
26+
- run: ruff check --output-format=github .
27+
- run: ruff format
1928
build:
2029
runs-on: macos-latest
2130
strategy:
2231
fail-fast: false
2332
matrix:
24-
python-version: ["3.10"] # ["2.7", "3.6", "3.10"]
33+
python-version: ["3.10", "3.14"]
2534
steps:
26-
- name: Checkout
27-
uses: actions/checkout@v7
28-
- name: Set up Python ${{ matrix.python-version }}
29-
uses: actions/setup-python@v7
30-
with:
31-
python-version: ${{ matrix.python-version }}
32-
- name: Install dependencies
33-
run: |
34-
python -m pip install --upgrade pip wheel setuptools
35-
pip install ".[testing]"
36-
- name: Analysing the code with flake8
37-
run: |
38-
# F824 `global name` / `nonlocal name` is unused: name is never assigned in scope
39-
flake8 . --count --select=E9,F63,F7,F82 --ignore=F824 --show-source --statistics
40-
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics
41-
- name: Running tests
42-
run: |
43-
pytest --version
44-
# make install test work...
45-
export PYTHONPATH=$(python -m site --user-site)
46-
pytest tests/ --ignore=tests/system/data/ --showlocals --verbose --show-capture=all --log-level=debug
35+
- name: Checkout
36+
uses: actions/checkout@v7
37+
- name: Set up Python ${{ matrix.python-version }}
38+
uses: actions/setup-python@v7
39+
with:
40+
python-version: ${{ matrix.python-version }}
41+
- name: Install dependencies
42+
run: |
43+
python -m pip install --upgrade pip wheel setuptools
44+
pip install ".[testing]"
45+
- name: Analysing the code with flake8
46+
run: |
47+
# F824 `global name` / `nonlocal name` is unused: name is never assigned in scope
48+
flake8 . --count --select=E9,F63,F7,F82 --ignore=F824 --show-source --statistics
49+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics
50+
- name: Running tests
51+
run: |
52+
pytest --version
53+
# make install test work...
54+
export PYTHONPATH=$(python -m site --user-site)
55+
pytest

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ repos:
5454
# - tomli
5555

5656
- repo: https://github.com/astral-sh/ruff-pre-commit
57-
rev: v0.12.9
57+
rev: v0.16.0
5858
hooks:
5959
- id: ruff-check
6060
- id: ruff-format

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.10

MANIFEST.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
include README.md
2+
include CHANGELOG.md
3+
include LICENSE
4+
include py.typed
5+
include pyproject.toml

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ environment and great treasures may be found within.
3535
**StaSh can be easily installed via one line of python command**
3636
(courtesy of [@whitone](https://forum.omz-software.com/user/whitone)).
3737
```Python
38-
import requests as r; exec(r.get('https://bit.ly/get-stash').content)
38+
import requests as r
39+
40+
exec(r.get("https://bit.ly/get-stash").content)
3941
```
4042
Simply copy the above line, paste into Pythonista interactive prompt and
4143
execute. It installs StaSh as a Python module under the `site-packages`

__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
# -*- coding: utf-8 -*-
21
from . import core as stash

__main__.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
Launch StaSh in a more flexible and reliable way.
3+
"""
4+
5+
import argparse
6+
import sys
7+
8+
# fix from https://github.com/ywangd/stash/pull/499
9+
if hasattr(sys, "_exit"):
10+
sys.exit = sys._exit
11+
12+
module_names = (
13+
"stash",
14+
"system.shcommon",
15+
"system.shstreams",
16+
"system.shscreens",
17+
"system.shui",
18+
"system.shui.base",
19+
"system.shio",
20+
"system.shiowrapper",
21+
"system.shparsers",
22+
"system.shruntime",
23+
"system.shthreads",
24+
"system.shuseractionproxy",
25+
"system.shhistory",
26+
)
27+
28+
# Attempt to reload modules when startup, does not seem to work
29+
if "stash.stash" in sys.modules:
30+
for name in module_names:
31+
sys.modules.pop("stash." + name)
32+
from stash import stash
33+
34+
ap = argparse.ArgumentParser()
35+
ap.add_argument(
36+
"--no-cfgfile", action="store_true", help="do not load external config files"
37+
)
38+
ap.add_argument(
39+
"--no-rcfile", action="store_true", help="do not load external resource file"
40+
)
41+
ap.add_argument(
42+
"--no-historyfile",
43+
action="store_true",
44+
help="do not load history file from last session",
45+
)
46+
ap.add_argument(
47+
"--log-level",
48+
choices=["DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"],
49+
default="INFO",
50+
help="the logging level",
51+
)
52+
ap.add_argument("--log-file", help="the file to send logging messages")
53+
ap.add_argument(
54+
"--debug-switch",
55+
default="",
56+
help="a comma separate list to turn on debug switch for components",
57+
)
58+
ap.add_argument("-c", "--command", default=None, dest="command", help="command to run")
59+
ap.add_argument(
60+
"args", # the editor shortcuts may pass additional arguments
61+
nargs="*",
62+
help="additional arguments (ignored)",
63+
)
64+
65+
66+
def main():
67+
ns = ap.parse_args()
68+
69+
log_setting = {
70+
"level": ns.log_level,
71+
"file": ns.log_file,
72+
}
73+
74+
if ns.debug_switch == "":
75+
debug = (
76+
# stash._DEBUG_STREAM,
77+
# stash._DEBUG_RENDERER,
78+
# stash._DEBUG_MAIN_SCREEN,
79+
# stash._DEBUG_MINI_BUFFER,
80+
# stash._DEBUG_IO,
81+
# stash._DEBUG_UI,
82+
# stash._DEBUG_TERMINAL,
83+
# stash._DEBUG_TV_DELEGATE,
84+
# stash._DEBUG_RUNTIME,
85+
# stash._DEBUG_PARSER,
86+
# stash._DEBUG_EXPANDER,
87+
# stash._DEBUG_COMPLETER,
88+
)
89+
elif ns.debug_switch == "all":
90+
debug = []
91+
for key in dir(stash):
92+
if key.startswith("_DEBUG_"):
93+
value = getattr(stash, key, None)
94+
if value is not None:
95+
debug.append(value)
96+
else:
97+
debug = []
98+
for ds in ns.debug_switch.split(","):
99+
ds = getattr(stash, f"_DEBUG_{ds.upper()}", None)
100+
if ds is not None:
101+
debug.append(ds)
102+
103+
if ns.command:
104+
# tell StaSh not to run any command if command is passed
105+
# (we will call the command manually later)
106+
ctp = False
107+
else:
108+
# tell StaSh to run the default command (totd.py)
109+
ctp = None
110+
111+
_stash = stash.StaSh(
112+
debug=debug,
113+
log_setting=log_setting,
114+
no_cfgfile=ns.no_cfgfile,
115+
no_rcfile=ns.no_rcfile,
116+
no_historyfile=ns.no_historyfile,
117+
command=ctp,
118+
)
119+
120+
_stash.launch(ns.command)
121+
if ns.command is not None:
122+
# TODO: _stash.launch() may block, which prevents this from being executed
123+
_stash(ns.command, add_to_history=False, persistent_level=0)
124+
125+
126+
if __name__ == "__main__":
127+
main()

bin/alias.py

100644100755
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
# -*- coding: utf-8 -*-
1+
#!/usr/bin/env python
22
# Example of accessing the shell object from script
33
# This ability completely removes the need of plugins
44
"""List or define shell aliases."""
55

6-
from __future__ import print_function
7-
8-
import sys
96
import argparse
7+
import sys
108

119

1210
def main(args):
@@ -22,7 +20,7 @@ def main(args):
2220

2321
if ns.expr is None:
2422
for k, v in current_state.aliases.items():
25-
print("{}={}".format(k, v[0]))
23+
print(f"{k}={v[0]}")
2624

2725
else:
2826
if "=" in ns.expr:
@@ -38,9 +36,9 @@ def main(args):
3836
sys.exit(0)
3937
else:
4038
try:
41-
print("{}={}".format(ns.expr, current_state.aliases[ns.expr]))
39+
print(f"{ns.expr}={current_state.aliases[ns.expr]}")
4240
except KeyError as err:
43-
raise KeyError("alias: {} not found".format(err.message))
41+
raise KeyError(f"alias: {err} not found")
4442

4543

4644
if __name__ == "__main__":

bin/cat.py

100644100755
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
11
#!/usr/bin/env python
2-
# -*- coding: utf-8 -*-
32
"""Print the contents of the given files."""
43

5-
from __future__ import print_function
6-
74
import argparse
5+
import fileinput
86
import string
97
import sys
10-
import fileinput
8+
from collections.abc import Sequence
119

1210

13-
def filter_non_printable(s):
11+
def filter_non_printable(s: str) -> str:
1412
return "".join(
1513
[c if c.isalnum() or c.isspace() or c in string.punctuation else " " for c in s]
1614
)
1715

1816

19-
def main(args):
17+
def main(args: Sequence[str]) -> None:
2018
p = argparse.ArgumentParser(description=__doc__)
19+
# NOTE: keep as plain str, not Path - fileinput's "-" stdin sentinel is a
20+
# direct string comparison (`self._filename == '-'`), which a Path never
21+
# equals, breaking `some_cmd | cat -`.
2122
p.add_argument("files", action="store", nargs="*", help="files to print")
2223
ns = p.parse_args(args)
2324

24-
status = 0
25-
26-
fileinput.close() # in case it is not closed
2725
try:
28-
for line in fileinput.input(ns.files, openhook=fileinput.hook_encoded("utf-8")):
29-
print(filter_non_printable(line), end="")
26+
with fileinput.input(files=ns.files, encoding="utf-8") as fin:
27+
for line in fin:
28+
print(filter_non_printable(line), end="")
29+
except KeyboardInterrupt:
30+
print("\nOperation interrupted by user.", file=sys.stderr)
31+
sys.exit(1)
3032
except Exception as e:
31-
print("cat: %s" % str(e))
32-
status = 1
33-
finally:
34-
fileinput.close()
33+
print("cat: error: %s" % str(e), file=sys.stderr)
34+
sys.exit(1)
3535

36-
sys.exit(status)
36+
sys.exit(0)
3737

3838

3939
if __name__ == "__main__":

bin/cd.py

100644100755
Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,49 @@
11
#!/usr/bin/env python
2-
# -*- coding: utf-8 -*-
32
"""Change the current working directory."""
43

5-
from __future__ import print_function
6-
74
import argparse
85
import os
96
import sys
7+
from collections.abc import Sequence
8+
from pathlib import Path
109

1110

12-
def main(args):
11+
def main(args: Sequence[str]) -> None:
1312
p = argparse.ArgumentParser(description=__doc__)
13+
HOME2_DEFAULT = os.environ.get("HOME2", Path("~").expanduser() / "Documents")
1414
p.add_argument(
1515
"dir",
1616
action="store",
17+
type=Path,
1718
nargs="?",
18-
default=os.environ["HOME2"],
19+
default=HOME2_DEFAULT,
1920
help="the new working directory",
2021
)
2122
ns = p.parse_args(args)
2223

23-
status = 0
24-
2524
try:
26-
if os.path.exists(ns.dir):
27-
if os.path.isdir(ns.dir):
28-
# chdir does not raise exception until listdir is called, so check for access here
29-
if os.access(ns.dir, os.R_OK):
30-
os.chdir(ns.dir)
31-
else:
32-
print("cd: {} access denied".format(ns.dir))
33-
else:
34-
print("cd: %s: Not a directory" % ns.dir)
35-
else:
36-
print("cd: %s: No such file or directory" % ns.dir)
25+
err = ""
26+
if not ns.dir.exists():
27+
err = f"cd: {ns.dir}: No such file or directory"
28+
elif not ns.dir.is_dir():
29+
err = f"cd: {ns.dir}: Not a directory"
30+
elif not os.access(ns.dir, os.R_OK): # X_OK allows dir enter
31+
err = f"cd: {ns.dir}: Access denied"
32+
33+
if err:
34+
print(err)
35+
sys.exit(1)
36+
return # Add return to stop execution in the mocked environment
37+
38+
os.chdir(ns.dir)
39+
sys.exit(0) # Moved to be inside the try block
40+
41+
except KeyboardInterrupt:
42+
print("\nOperation interrupted by user.", file=sys.stderr)
43+
sys.exit(1)
3744
except Exception as err:
38-
print("cd: {}: {!s}".format(type(err).__name__, err), file=sys.stderr)
39-
status = 1
40-
41-
sys.exit(status)
45+
print(f"cd: {type(err).__name__}: {err!s}", file=sys.stderr)
46+
sys.exit(1)
4247

4348

4449
if __name__ == "__main__":

0 commit comments

Comments
 (0)