Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8b1d41398 | ||
|
|
2dc08013b7 |
@@ -6,6 +6,8 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import termios
|
||||||
|
import tty
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -24,6 +26,9 @@ TELEGRAM_PATTERN = re.compile(
|
|||||||
|
|
||||||
NORMALIZED = re.compile(r'^\d{4}-\d{2}-\d{2} .+')
|
NORMALIZED = re.compile(r'^\d{4}-\d{2}-\d{2} .+')
|
||||||
|
|
||||||
|
# Windows hidden-file attribute, mirrored from the stat module.
|
||||||
|
FILE_ATTRIBUTE_HIDDEN = 0x2
|
||||||
|
|
||||||
STATUS_RENAME = 'rename'
|
STATUS_RENAME = 'rename'
|
||||||
STATUS_ALREADY = 'skip (already normalized)'
|
STATUS_ALREADY = 'skip (already normalized)'
|
||||||
STATUS_NO_MATCH = 'skip (no rule matched)'
|
STATUS_NO_MATCH = 'skip (no rule matched)'
|
||||||
@@ -146,7 +151,19 @@ def get_date_for_file(filepath, parse_mode):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def collect_files(paths):
|
def is_hidden(filepath):
|
||||||
|
"""Check whether a file is hidden (dotfile, or hidden attribute on Windows)."""
|
||||||
|
if filepath.name.startswith('.'):
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
attrs = filepath.stat().st_file_attributes
|
||||||
|
except (AttributeError, OSError):
|
||||||
|
return False
|
||||||
|
return bool(attrs & FILE_ATTRIBUTE_HIDDEN)
|
||||||
|
|
||||||
|
|
||||||
|
def collect_files(paths, include_hidden=False):
|
||||||
"""Resolve the list of files to process."""
|
"""Resolve the list of files to process."""
|
||||||
if not paths:
|
if not paths:
|
||||||
paths = ['.']
|
paths = ['.']
|
||||||
@@ -155,8 +172,12 @@ def collect_files(paths):
|
|||||||
for path in paths:
|
for path in paths:
|
||||||
candidate = Path(path)
|
candidate = Path(path)
|
||||||
if candidate.is_dir():
|
if candidate.is_dir():
|
||||||
files.extend(sorted(file for file in candidate.iterdir() if file.is_file()))
|
files.extend(sorted(
|
||||||
|
file for file in candidate.iterdir()
|
||||||
|
if file.is_file() and (include_hidden or not is_hidden(file))
|
||||||
|
))
|
||||||
elif candidate.is_file():
|
elif candidate.is_file():
|
||||||
|
# Explicitly named files are always processed.
|
||||||
files.append(candidate)
|
files.append(candidate)
|
||||||
return files
|
return files
|
||||||
|
|
||||||
@@ -273,13 +294,23 @@ def apply_renames(plan):
|
|||||||
|
|
||||||
|
|
||||||
def confirm(prompt='Apply renames? [y/n] '):
|
def confirm(prompt='Apply renames? [y/n] '):
|
||||||
"""Ask the user for confirmation."""
|
"""Ask the user for confirmation, reacting to a single keypress."""
|
||||||
while True:
|
print(prompt, end='', flush=True)
|
||||||
answer = input(prompt).strip().lower()
|
if not sys.stdin.isatty():
|
||||||
if answer == 'y':
|
return sys.stdin.read(1).strip().lower() == 'y'
|
||||||
return True
|
|
||||||
if answer == 'n':
|
fd = sys.stdin.fileno()
|
||||||
return False
|
old_settings = termios.tcgetattr(fd)
|
||||||
|
try:
|
||||||
|
# cbreak (not raw) keeps Ctrl-C as SIGINT and newlines well behaved.
|
||||||
|
tty.setcbreak(fd)
|
||||||
|
while (key := sys.stdin.read(1).lower()) not in ('y', 'n', ''):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
|
|
||||||
|
print(key)
|
||||||
|
return key == 'y'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -306,6 +337,12 @@ def main():
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Force numbering even for already-normalized files (used with -n).',
|
help='Force numbering even for already-normalized files (used with -n).',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-a',
|
||||||
|
'--all',
|
||||||
|
action='store_true',
|
||||||
|
help='Include hidden files when scanning directories.',
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'files',
|
'files',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
@@ -314,7 +351,7 @@ def main():
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
files = collect_files(args.files)
|
files = collect_files(args.files, args.all)
|
||||||
if not files:
|
if not files:
|
||||||
print('No files found.')
|
print('No files found.')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -337,4 +374,8 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\nAborted.')
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user