-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimpleskeleton.py
executable file
·153 lines (110 loc) · 3.78 KB
/
simpleskeleton.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env python
"""
simpleskeleton.py by Preston Hunt <[email protected]>
https://github.com/presto8/pythonskeletonsimple
A simple single-file Python boilerplate for writing simple command-line shell
scripts.
"""
import argparse
import fcntl
import os
import shelve
import subprocess
import sys
from typing import NamedTuple
def parse_args():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
subparsers = parser.add_subparsers(dest='command')
commands = []
def add_command(name, *args, **kwargs):
commands.append(name)
return subparsers.add_parser(name, *args, **kwargs)
subcmd = add_command('hello', help='say hello')
subcmd.add_argument('--name', type=str)
add_command('bye', help='say bye')
# uncomment next line to disable partial commands, e.g., "h" or "he" will match "hello"
resolve_partial_command(commands)
parser.add_argument('paths', nargs='*', help='paths to process')
parser.add_argument('--verbose', default=False, action='store_true', help='show more detailed messages')
parser.add_argument('--database', default="data.db", help='location of persistent data storage')
return parser.parse_args()
def main():
mutex()
print("parsed ARGS:", ARGS)
try:
cmd_func = globals()["cmd_" + ARGS.command]
except KeyError:
raise Fail("could not find handler for:", ARGS.command)
cmd_func()
if ARGS.verbose:
print("verbose mode enabled, will display abspath")
with shelve.open(ARGS.database, writeback=True) as db:
if not 'paths' in db:
db['paths'] = []
print("previous paths:", db['paths'])
for path in ARGS.paths:
db['paths'].append(path)
print(worker(path))
def cmd_hello():
name = ARGS.name or "stranger"
print(f"hello, {name}!")
def resolve_partial_command(commands: list[str]):
try:
command = sys.argv[1]
except IndexError:
sys.argv.append("--help")
return
if command in commands:
return command
possibles = []
for maybe_cmd in commands:
if maybe_cmd.startswith(command):
possibles.append(maybe_cmd)
if len(possibles) != 1:
return
sys.argv[1] = possibles[0]
def mutex():
this_script = os.path.realpath(__file__)
lockfd = os.open(this_script, os.O_RDONLY)
try:
fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
raise Fail(f"{this_script} is already running")
def scantree(path, follow_symlinks=False, recursive=True):
passthru = [follow_symlinks, recursive]
for entry in os.scandir(path):
if entry.is_dir(follow_symlinks=follow_symlinks) and recursive:
yield from scantree(entry.path, *passthru)
else:
yield entry
class ParsedPath(NamedTuple):
ok: bool
input_path: str
basename: str
abspath: str
def parse_path(path) -> ParsedPath:
result = dict(ok=False, input_path=path)
result['basename'] = os.path.basename(path)
result['abspath'] = os.path.abspath(path)
result['ok'] = True
return ParsedPath(**result)
def worker(path) -> str:
ppath = parse_path(path)
if ARGS.verbose:
print(ppath)
return ppath.abspath if ARGS.verbose else ppath.basename
def run(*args):
return subprocess.run(args, capture_output=True, text=True)
class Fail(Exception):
pass
if __name__ == '__main__':
try:
# Command-line arguments are considered as immutable constants of the
# universe, and thus are globally available in this script.
ARGS = parse_args()
main()
except Fail as f:
print(*f.args, file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("Ctrl+C")