Initial working commit with some tests
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
843b2d833e
commit
2b7acbc3b1
16
.drone.star
16
.drone.star
@ -99,16 +99,16 @@ def notify_step():
|
||||
|
||||
# Builds a notify pipeline that will notify when a dependency fails
|
||||
def notify(depends_on=None):
|
||||
if depends_on is None:
|
||||
if not depends_on:
|
||||
depends_on = ["tests"]
|
||||
|
||||
return list(dict(
|
||||
kind="pipeline",
|
||||
name="notify",
|
||||
depends_on=depends_on,
|
||||
trigger=dict(status=["failure"]),
|
||||
steps=[notify_step()]
|
||||
))
|
||||
return [{
|
||||
"kind": "pipeline",
|
||||
"name": "notify",
|
||||
"depends_on": depends_on,
|
||||
"trigger": {"status": ["failure"]},
|
||||
"steps": [notify_step()]
|
||||
}]
|
||||
|
||||
|
||||
# Push package to pypi
|
||||
|
18
Makefile
18
Makefile
@ -1,5 +1,5 @@
|
||||
OPEN_CMD := $(shell type xdg-open &> /dev/null && echo 'xdg-open' || echo 'open')
|
||||
NAME := paddy
|
||||
NAME := padio
|
||||
ENV := env
|
||||
|
||||
.PHONY: default
|
||||
@ -98,19 +98,3 @@ htmlcov/index.html: .coverage
|
||||
.PHONY: open-coverage
|
||||
open-coverage: htmlcov/index.html
|
||||
$(OPEN_CMD) htmlcov/index.html
|
||||
|
||||
# Cleans out docs
|
||||
.PHONY: docs-clean
|
||||
docs-clean:
|
||||
rm -fr docs/build/* docs/source/code/*
|
||||
|
||||
# Builds docs
|
||||
docs/build/html/index.html:
|
||||
$(ENV)/bin/tox -e docs
|
||||
|
||||
# Shorthand for building docs
|
||||
.PHONY: docs
|
||||
docs: docs/build/html/index.html
|
||||
|
||||
.PHONY: clean-all
|
||||
clean-all: clean dist-clean docs-clean
|
||||
|
46
README.md
46
README.md
@ -1,7 +1,49 @@
|
||||
# paddy
|
||||
# padio
|
||||
|
||||
Zero pad numeric filenames
|
||||
|
||||
Turn a bunch of files like this:
|
||||
|
||||
file1.txt
|
||||
file10.txt
|
||||
file5.txt
|
||||
|
||||
and want them to be sorted like this:
|
||||
|
||||
file01.txt
|
||||
file05.txt
|
||||
file10.txt
|
||||
|
||||
you can run:
|
||||
|
||||
padio *.txt
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
pip install padio
|
||||
|
||||
## Usage
|
||||
|
||||
usage: padio [-h] [-l LENGTH] [-f] [-v] [-d] [-i REGEX] [--ignore-files IGNOREFILE [IGNOREFILE ...]] file [file ...]
|
||||
|
||||
Pads numbers in file names so they consistently align and sort
|
||||
|
||||
positional arguments:
|
||||
file Files to be renamed
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-l LENGTH, --length LENGTH
|
||||
Length of numbers after padding (default: auto)
|
||||
-f, --force Force rename, even if file at destination exists
|
||||
-v, --verbose Print all actions
|
||||
-d, --dry-run Print actions only without modifying any file. Implies --verbose
|
||||
-i REGEX, --ignore REGEX
|
||||
Regular expression used to ignore files matching the name
|
||||
--ignore-files IGNOREFILE [IGNOREFILE ...]
|
||||
Files to ignore for renaming. Must add -- before positional arguments
|
||||
|
||||
## Original repo
|
||||
|
||||
Originally hosted at https://git.iamthefij.com/iamthefij/paddy.git
|
||||
Originally hosted at https://git.iamthefij.com/iamthefij/padio.git
|
||||
|
@ -1,52 +0,0 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'paddy'
|
||||
copyright = '2021, iamthefij'
|
||||
author = 'iamthefij'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
@ -1,20 +0,0 @@
|
||||
.. paddy documentation master file, created by
|
||||
sphinx-quickstart on Fri Feb 5 15:06:05 2021.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to paddy's documentation!
|
||||
========================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
155
padio.py
Executable file
155
padio.py
Executable file
@ -0,0 +1,155 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
num_matcher = re.compile(r"([0-9]+)")
|
||||
|
||||
|
||||
def extract_numbers(s: str) -> str:
|
||||
number_match = num_matcher.search(s)
|
||||
if number_match:
|
||||
return number_match.group(1)
|
||||
return ""
|
||||
|
||||
|
||||
def calc_pad_length(files: List[Path]) -> int:
|
||||
max_len = 0
|
||||
for f in files:
|
||||
numbers = extract_numbers(f.name)
|
||||
max_len = max(len(numbers), max_len)
|
||||
return max_len
|
||||
|
||||
|
||||
def parse_args(sys_args: Optional[List[str]] = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Pads numbers in file names so they consistently align and sort"
|
||||
)
|
||||
parser.add_argument("files", nargs="+", metavar="file", help="Files to be renamed")
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--length",
|
||||
type=int,
|
||||
help="Length of numbers after padding (default: auto)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Force rename, even if file at destination exists",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="store_true", help="Print all actions"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Print actions only without modifying any file. Implies --verbose",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--ignore",
|
||||
metavar="REGEX",
|
||||
help="Regular expression used to ignore files matching the name",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore-files",
|
||||
nargs="+",
|
||||
metavar="IGNOREFILE",
|
||||
help="Files to ignore for renaming. Must add -- before positional arguments",
|
||||
)
|
||||
args = parser.parse_args(sys_args)
|
||||
|
||||
# Dry run implies verbose
|
||||
if args.dry_run:
|
||||
args.verbose = True
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def get_files(
|
||||
filenames: List[str],
|
||||
ignore_filenames: List[str],
|
||||
ignore_pattern: str,
|
||||
) -> List[Path]:
|
||||
# Compile ignore pattern, if provided
|
||||
ignore_matcher: Optional[re.Pattern] = None
|
||||
if ignore_pattern:
|
||||
ignore_matcher = re.compile(ignore_pattern)
|
||||
|
||||
p = Path(".")
|
||||
files: List[Path] = []
|
||||
for f in filenames:
|
||||
if ignore_filenames and f in ignore_filenames:
|
||||
continue
|
||||
if ignore_matcher and ignore_matcher.match(f):
|
||||
continue
|
||||
files.append(p / f)
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def pad_files(
|
||||
files: List[Path],
|
||||
pad_len: int,
|
||||
verbose=False,
|
||||
) -> Generator[Tuple[Path, Path], None, None]:
|
||||
for f in files:
|
||||
numbers = extract_numbers(f.name)
|
||||
if len(numbers) == pad_len:
|
||||
if verbose:
|
||||
print(f"{f.name} is already padded.")
|
||||
continue
|
||||
|
||||
# Pad number and get destination path
|
||||
new_numbers = numbers.zfill(pad_len)
|
||||
new_name = num_matcher.sub(new_numbers, f.name, count=1)
|
||||
new_file = f.parent / new_name
|
||||
|
||||
if f == new_file:
|
||||
if verbose:
|
||||
print(f"{f.name} already matches destination.")
|
||||
continue
|
||||
|
||||
yield f, new_file
|
||||
|
||||
|
||||
def main(sys_args: Optional[List[str]] = None) -> int:
|
||||
args = parse_args(sys_args)
|
||||
|
||||
# Build list of files to act on
|
||||
files = get_files(args.files, args.ignore_files, args.ignore)
|
||||
|
||||
pad_len = args.length
|
||||
if pad_len is None:
|
||||
pad_len = calc_pad_length(files)
|
||||
|
||||
if args.verbose:
|
||||
print(f"Padding to {pad_len}")
|
||||
|
||||
status = 0
|
||||
for f, new_file in pad_files(files, pad_len, args.verbose):
|
||||
# Possibly rename unless exists or forced
|
||||
if not new_file.exists() or args.force:
|
||||
if args.verbose:
|
||||
print(f"Rename {f.name} to {new_file.name}")
|
||||
if not args.dry_run:
|
||||
f.rename(new_file)
|
||||
else:
|
||||
print(
|
||||
f"Could not rename {f.name} to {new_file.name}. Destination file exists."
|
||||
)
|
||||
status = 1
|
||||
|
||||
return status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
3
requirements-dev.txt
Normal file
3
requirements-dev.txt
Normal file
@ -0,0 +1,3 @@
|
||||
-e .
|
||||
pytest
|
||||
coverage
|
8
setup.py
8
setup.py
@ -11,13 +11,13 @@ with open(path.join(here, "README.md"), encoding="utf-8") as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="paddy",
|
||||
name="padio",
|
||||
version="0.0.0",
|
||||
description="Zero pad numeric filenames",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://git.iamthefij.com/iamthefij/paddy.git",
|
||||
download_url=("https://git.iamthefij.com/iamthefij/paddy.git/archive/master.tar.gz"),
|
||||
url="https://git.iamthefij.com/iamthefij/padio.git",
|
||||
download_url=("https://git.iamthefij.com/iamthefij/padio.git/archive/master.tar.gz"),
|
||||
author="iamthefij",
|
||||
author_email="",
|
||||
classifiers=[
|
||||
@ -39,7 +39,7 @@ setup(
|
||||
install_requires=[],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"paddy=paddy.main:main",
|
||||
"padio=padio:main",
|
||||
],
|
||||
},
|
||||
)
|
||||
|
90
tests/test_paddy.py
Normal file
90
tests/test_paddy.py
Normal file
@ -0,0 +1,90 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import padio
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
(
|
||||
("file123", "123"),
|
||||
("123file", "123"),
|
||||
("123file456", "123"),
|
||||
("file", ""),
|
||||
),
|
||||
)
|
||||
def test_extract_numbers(value, expected):
|
||||
assert padio.extract_numbers(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_paths,expected",
|
||||
(
|
||||
([Path("/path/to/file.a123.txt"), Path("/path/to/file.b123.txt")], 3),
|
||||
([Path("/path/to/file.a023.txt"), Path("/path/to/file.b023.txt")], 3),
|
||||
([Path("/path/to/file.a123.txt"), Path("/path/to/file.b12.txt")], 3),
|
||||
),
|
||||
)
|
||||
def test_calc_pad_length(file_paths, expected):
|
||||
assert padio.calc_pad_length(file_paths) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"files,ignore_files,ignore,expected",
|
||||
(
|
||||
(
|
||||
[
|
||||
"file.a123.txt",
|
||||
"file.b123.txt",
|
||||
],
|
||||
[],
|
||||
"",
|
||||
[
|
||||
Path("./file.a123.txt"),
|
||||
Path("./file.b123.txt"),
|
||||
],
|
||||
),
|
||||
(
|
||||
[
|
||||
"file.a123.txt",
|
||||
"file.b123.txt",
|
||||
],
|
||||
[
|
||||
"file.a123.txt",
|
||||
],
|
||||
"",
|
||||
[
|
||||
Path("./file.b123.txt"),
|
||||
],
|
||||
),
|
||||
(
|
||||
[
|
||||
"file.a123.txt",
|
||||
"file.b123.txt",
|
||||
"ignore.txt"
|
||||
],
|
||||
[
|
||||
"file.a123.txt",
|
||||
],
|
||||
"ignore.*",
|
||||
[
|
||||
Path("./file.b123.txt"),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_get_files(files, ignore_files, ignore, expected):
|
||||
assert padio.get_files(files, ignore_files, ignore) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"files,pad_len,expected",
|
||||
(
|
||||
([Path("./file.a12")], 1, []),
|
||||
([Path("./file.a12")], 2, []),
|
||||
([Path("./file.a12")], 3, [Path("./file.a012")]),
|
||||
),
|
||||
)
|
||||
def test_pad_files(files, pad_len, expected):
|
||||
assert list(padio.pad_files(files, pad_len)) == list(zip(files, expected))
|
16
tox.ini
16
tox.ini
@ -1,13 +1,13 @@
|
||||
[tox]
|
||||
envlist = py3,pypy3
|
||||
envlist = py3,py36,py37,py38,pypy3
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
-rrequirements-dev.txt
|
||||
commands =
|
||||
coverage erase
|
||||
coverage run --source=paddy/ -m pytest --capture=no -vv {posargs:tests}
|
||||
coverage report -m --fail-under 70
|
||||
coverage run --source=padio -m pytest --capture=no -vv {posargs:tests}
|
||||
coverage report -m --fail-under 50
|
||||
pre-commit run --all-files
|
||||
|
||||
[testenv:pre-commit]
|
||||
@ -15,13 +15,3 @@ deps =
|
||||
pre-commit
|
||||
commands =
|
||||
pre-commit {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
deps =
|
||||
{[base]deps}
|
||||
sphinx
|
||||
sphinx_rtd_theme
|
||||
changedir = docs
|
||||
commands =
|
||||
sphinx-apidoc -f -e -o source/code ../paddy
|
||||
sphinx-build -b html -d build/doctrees source/ build/html
|
||||
|
Loading…
Reference in New Issue
Block a user