mirror of
https://github.com/novoid/appendfilename.git
synced 2026-02-16 04:44:16 +00:00
The pattern search about the smart-prepend option was rewritten to be easier to read and maintain. Signed-off-by: Norwid Behrnd <nbehrnd@yahoo.com>
487 lines
16 KiB
Org Mode
Executable file
487 lines
16 KiB
Org Mode
Executable file
# name: test_generator.org
|
||
# author: nbehrnd@yahoo.com
|
||
# date: 2022-01-05 (YYYY-MM-DD)
|
||
# edit: [2024-11-22 Fri]
|
||
# license: GPL3, 2022-2024
|
||
# Export the tangled files with C-c C-v t
|
||
|
||
#+PROPERTY: header-args :tangle yes
|
||
|
||
* Intent
|
||
|
||
The application =appendfilename= by Karl Voit /et al./ ([[https://github.com/novoid/appendfilename][source]])
|
||
allows the programmatic addition of user defined strings to one or
|
||
multiple already existing file names (e.g., add =travel= to file
|
||
=example.jpg= to yield =example_travel.jpg=). This file
|
||
=test_generator.org= provides the files for a programmatic test
|
||
of =appendfilename= by means of /code tangle/ from Emacs orgmode.
|
||
|
||
* Deployment
|
||
|
||
The tests presume a working installation of Python 3. After the
|
||
activation of a virtual environment, one suitable approach is to
|
||
resolve the dependencies by
|
||
|
||
#+begin_src shell :tangle no
|
||
python -m pip install -r requirements.txt
|
||
#+end_src
|
||
|
||
with data by PyPI. Subsequently, the tests can be launched by
|
||
|
||
#+begin_src shell :tangle no
|
||
python -m pytest
|
||
#+end_src
|
||
|
||
where each period (=.=) indicates a passing, and each =E= a failing
|
||
test. An optional flag
|
||
|
||
- =-v= allows a more verbose output to the CLI
|
||
- =-x= stops the sequence after the first failing test
|
||
- =-s= occasionally provides information e.g., about the tests' criteria
|
||
|
||
The tests are organized in sets =default=, =prepend=, and =smart_prepend=.
|
||
This allows to selectively run only checks which are about the
|
||
results by =appendfilename= in the /default/ mode, /prepend/ mode,
|
||
or /smart-prepend/ mode alone, e.g.
|
||
|
||
#+begin_src shell :tangle no
|
||
python -m pytest -m "prepend"
|
||
#+end_src
|
||
|
||
The tests were revised to work equally well in Linux Debian 13/trixie
|
||
(e.g., Python 3.12.6 and pytest 8.3.3) as well as Windows 10.
|
||
|
||
* Setup of Emacs
|
||
|
||
The edit of this =.org= file in Emacs and the subsequent export
|
||
(tangle) of the files are affected by Emacs' own parameters (e.g.,
|
||
the indentation in Python). It is recommended to access this file
|
||
with Emacs in a session started by =emacs -q test_generator.org &=
|
||
and to evaluate the following block by =C-c C-c=; this explicitly
|
||
adjusts a few basic settings, but does not permanently overwrite an
|
||
already existing personalized Emacs configuration.
|
||
|
||
Most of these instructions are elements of Hendrik Suenkler's
|
||
annotated Emacs configuration ([[https://www.suenkler.info/notes/emacs-config/][blog post]]) which are reused with his
|
||
permission.
|
||
|
||
#+begin_src emacs-lisp :tangle no
|
||
;; support these languages at all:
|
||
(org-babel-do-load-languages
|
||
'org-babel-load-languages
|
||
'((emacs-lisp . t)
|
||
(org . t)
|
||
(shell . t)
|
||
(python . t)))
|
||
|
||
;; enable syntax highlighting:
|
||
(setq org-src-fontify-natively t)
|
||
|
||
;; adjust indentations, set tabs as explicit 4 spaces:
|
||
(setq-default indent-tabs-mode nil)
|
||
(setq default-tab-width 4)
|
||
|
||
(setq custom-tab-width 4)
|
||
(setq-default python-indent-offset custom-tab-width)
|
||
|
||
(setq org-edit-src-content-indentation 0)
|
||
(setq org-src-tab-acts-natively t)
|
||
(setq org-src-preserve-indentation t)
|
||
|
||
;; some comfort functions Suenkler mentions:
|
||
(delete-selection-mode 1)
|
||
(defalias 'yes-or-no-p 'y-or-n-p)
|
||
|
||
(show-paren-mode 1)
|
||
(setq show-paren-style 'parenthesis)
|
||
|
||
(column-number-mode nil)
|
||
|
||
(setq org-src-fontify-natively t)
|
||
#+end_src
|
||
|
||
#+RESULTS:
|
||
: t
|
||
|
||
If the previous block was evaluated as .TRUE. (=t=), test script and
|
||
=pytest.ini= may be tangled right now by =C-c C-v t=. After closing
|
||
this =.org= file, deploy them as indicated.
|
||
|
||
* Building the tests
|
||
|
||
** Building file =pytest.ini=
|
||
|
||
File =pytest.ini= lists markers in file =test_appendfilename.py=
|
||
to optionally constrain =pytest= tests. Rather than to launch
|
||
a check on all tests defined, a call like e.g.,
|
||
|
||
#+begin_src bash :tangle no
|
||
python -m pytest -m "prepend"
|
||
#+end_src
|
||
|
||
only checks those of set =prepend=. At present, tests are
|
||
grouped as
|
||
|
||
#+begin_src shell :tangle pytest.ini
|
||
[pytest]
|
||
markers =
|
||
default: test appendfilename's default string insertion
|
||
prepend: test appendfilename's optional -p/--prepend flag
|
||
smart_prepend: test appendfilename's optional --smart-prepend flag
|
||
#+end_src
|
||
|
||
It is possible to run one, two, or all three groups in one run of
|
||
pytest, for instance
|
||
|
||
#+begin_src bash :tangle no
|
||
pytest-3 test_appendfilename.py -m "default and prepend" -v
|
||
#+end_src
|
||
|
||
** Building the test script
|
||
|
||
*** header section
|
||
|
||
#+BEGIN_SRC python :tangle test_appendfilename.py
|
||
#!/bin/usr/env python3
|
||
|
||
# name: test_appendfilename.py
|
||
# author: nbehrnd@yahoo.com
|
||
# license: GPL v3, 2022.
|
||
# date: 2022-01-05 (YYYY-MM-DD)
|
||
# edit: [2024-11-22 Fri]
|
||
#
|
||
"""Test pad for functions by appendfilename with pytest.
|
||
|
||
Initially written for Python 3.9.9 and pytest 6.2.4 and recently update
|
||
for Python 3.12.6/pytest 8.3.3, this script provides a programmatic check
|
||
of functions offered by appendfilename. Deposit this script in the root
|
||
of the folder fetched and unzipped from PyPi or GitHub. Create a virtual
|
||
environment for Python, e.g. by
|
||
|
||
```shell
|
||
python -m venv sup
|
||
```
|
||
|
||
In the activated virtual environment, resolve the dependencies - either by
|
||
`pip install pyreadline3 pytest`, or `pip install -r requirements.txt` -
|
||
and launch the tests by
|
||
|
||
```shell
|
||
python -m pytest
|
||
```
|
||
|
||
As a reminder, the following optional pytest flags may be useful to obtain
|
||
a report tailored to your needs:
|
||
|
||
- `-x` exit after the first failing test (reported by `E` instead of `.`)
|
||
- `-v` provide a more verbose output
|
||
- `-s` equally report the test criterion, e.g. the queried file name
|
||
|
||
Equally keep in mind you can constrain pytest tests. Labels assigned are
|
||
|
||
- default: test appendfilename's default string insertion
|
||
- prepend: test appendfilename's optional -p/--prepend flag
|
||
- smart_prepend: test appendfilename's optional --smart-prepend flag
|
||
"""
|
||
|
||
import re
|
||
import os
|
||
import shlex
|
||
import subprocess
|
||
|
||
from itertools import product
|
||
|
||
import pytest
|
||
|
||
PROGRAM = os.path.join("appendfilename", "__init__.py")
|
||
#+end_src
|
||
|
||
*** appendfilename, default position
|
||
|
||
Departing from e.g., file =2021-12-31_test.txt=, appendfile's addition of
|
||
=example= should yield =2021-12-31_test example.txt=.
|
||
|
||
#+begin_src python :tangle test_appendfilename.py
|
||
# The following section tests the applications default pattern where a
|
||
# string is added to the file name, just prior to the file's file
|
||
# extension. The permutations of the arguments define 120 tests.
|
||
|
||
arg1_values = [
|
||
"test.txt", "2021-12-31_test.txt", "2021-12-31T18.48.22_test.txt"
|
||
]
|
||
arg2_values = [
|
||
"-t book", "-t book_shelf", "--text book", "--text book_shelf"
|
||
]
|
||
arg3_values = [
|
||
"", # i.e. fall back to default single space
|
||
"--separator '!'",
|
||
"--separator '@'",
|
||
"--separator '#'",
|
||
"--separator '$'",
|
||
"--separator '%'",
|
||
"--separator '_'",
|
||
"--separator '+'",
|
||
"--separator '='",
|
||
"--separator '-'"
|
||
]
|
||
# Note: In Windows 10, the check with pytest and `*` as separator fails
|
||
# because it is not a permitted character in a file name there. See
|
||
# <https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file>
|
||
|
||
# create the permutations:
|
||
test_cases = list(product(arg1_values, arg2_values, arg3_values))
|
||
|
||
|
||
@pytest.mark.default
|
||
@pytest.mark.parametrize("arg1, arg2, arg3", test_cases)
|
||
def test_append(arg1, arg2, arg3):
|
||
"""Test default which appends a string just prior file extension
|
||
|
||
arg1 the test file to process, partly inspired by `date2name`
|
||
arg2 the text string to be added
|
||
arg3 the separator (at least in Windows 10, do not use `*`)"""
|
||
|
||
# create a test file:
|
||
with open(arg1, mode="w", encoding="utf-8") as newfile:
|
||
newfile.write("This is a place holder.\n")
|
||
|
||
# run the test to be tested:
|
||
full_command = ["python", PROGRAM, arg1
|
||
] + shlex.split(arg2) + shlex.split(arg3)
|
||
subprocess.run(full_command, text=True, check=True)
|
||
|
||
# construct the new file name to be tested:
|
||
if len(shlex.split(arg3)) == 0:
|
||
separator = " "
|
||
else:
|
||
separator = shlex.split(arg3)[1]
|
||
|
||
new_filename = "".join(
|
||
[arg1[:-4], separator, shlex.split(arg2)[1], ".txt"])
|
||
print(f"test criterion: {new_filename}") # for an optional `pytest -s`
|
||
|
||
# is the new file present?
|
||
assert os.path.isfile(new_filename)
|
||
|
||
# check if the OS can process the new file / space cleaning
|
||
os.remove(new_filename)
|
||
assert os.path.isfile(new_filename) is False
|
||
#+end_src
|
||
|
||
*** appendfilename, prepend position
|
||
|
||
Departing with file =test.txt=, appendfile's addition of =example=
|
||
should yield =example test.txt=. The 240 tests equally consider the
|
||
separator between the string added, and the original file name.
|
||
|
||
#+begin_src python :tangle test_appendfilename.py
|
||
|
||
# The following section is about tests to prepend a user defined string
|
||
# and an adjustable separator to the original file name of the submitted
|
||
# file. The permutation of the parameters defines 240 tests.
|
||
|
||
|
||
arg1_values = [
|
||
"test.txt", "2021-12-31_test.txt", "2021-12-31T18.48.22_test.txt"
|
||
]
|
||
arg2_values = [
|
||
"-t book", "-t book_shelf", "--text book", "--text book_shelf"
|
||
]
|
||
arg3_values = [
|
||
"", # i.e. fall back to default single space
|
||
"--separator '!'",
|
||
"--separator '@'",
|
||
"--separator '#'",
|
||
"--separator '$'",
|
||
"--separator '%'",
|
||
"--separator '_'",
|
||
"--separator '+'",
|
||
"--separator '='",
|
||
"--separator '-'"
|
||
]
|
||
# Note: The check with pytest and `*` as separator in Windows 10 fails.
|
||
|
||
arg4_values = [
|
||
"-p", "--prepend"
|
||
]
|
||
|
||
# create the permutations:
|
||
test_cases = list(product(arg1_values, arg2_values, arg3_values, arg4_values))
|
||
|
||
|
||
@pytest.mark.prepend
|
||
@pytest.mark.parametrize("arg1, arg2, arg3, arg4", test_cases)
|
||
def test_prepend(arg1, arg2, arg3, arg4):
|
||
"""test to prepend a string to the original file name
|
||
|
||
arg1 the test file to process, partly inspired by `date2name`
|
||
arg2 the text string to be added
|
||
arg3 the separator (at least in Windows 10, do not use `*`)
|
||
arg4 either short of long form to introduce the string as leading """
|
||
|
||
# create a test file:
|
||
with open(arg1, mode="w", encoding="utf-8") as newfile:
|
||
newfile.write("This is a place holder.\n")
|
||
|
||
# run the test to be tested:
|
||
full_command = [
|
||
"python", PROGRAM, arg1
|
||
] + shlex.split(arg2) + shlex.split(arg3) + shlex.split(arg4)
|
||
subprocess.run(full_command, text=True, check=True)
|
||
|
||
# construct the new file name to be tested:
|
||
if len(shlex.split(arg3)) == 0:
|
||
separator = " "
|
||
else:
|
||
separator = shlex.split(arg3)[1]
|
||
|
||
new_filename = "".join([shlex.split(arg2)[1], separator, arg1])
|
||
print(f"test criterion: {new_filename}") # visible by optional `pytest -s`
|
||
|
||
# is the new file present?
|
||
assert os.path.isfile(new_filename)
|
||
|
||
# check if the OS can process the new file / space cleaning
|
||
os.remove(new_filename)
|
||
assert os.path.isfile(new_filename) is False
|
||
#+end_src
|
||
|
||
*** appendfilename, smart prepend position
|
||
|
||
Here, the additional string follows the time stamp, and leads
|
||
the rest of the file's file name. Of five patterns provided by
|
||
=date2name=, only =--withtime= and the default YYYY-MM-DD are
|
||
checked. The other three (=--compact=, =--month=, and =--short=)
|
||
are muted for their pattern still different to the other two.
|
||
Equally see [[https://github.com/novoid/appendfilename/issues/15]]
|
||
and [[https://github.com/novoid/appendfilename/issues/16]].
|
||
|
||
The permutation of the parameter's active levels define 8 tests.
|
||
|
||
#+begin_src python :tangle test_appendfilename.py
|
||
|
||
# This section tests the insertion of a string into the file's file name
|
||
# just after the file's time or date stamp as provided `date2name`.
|
||
|
||
|
||
arg1_values = [
|
||
"2021-12-31T18.48.22_test.txt",
|
||
"2021-12-31_test.txt",
|
||
# "20211231_test.txt", # by now `20211231_test.txt` -> 20211231_test ping.txt
|
||
# "2021-12_test.txt", # by now `2021-12_test.txt` -> `2021-12_test ping.txt`
|
||
# "211231_test.txt" # by now `211231_test.txt` -> `211231_test ping.txt`
|
||
]
|
||
arg2_values = [
|
||
"-t book",
|
||
"-t book_shelf",
|
||
"--text book",
|
||
"--text book_shelf"
|
||
]
|
||
arg3_values = [
|
||
"", # i.e. fall back to default single space
|
||
# "--separator '!'",
|
||
# "--separator '@'",
|
||
# "--separator '#'",
|
||
# "--separator '$'",
|
||
# "--separator '%'",
|
||
# "--separator '_'",
|
||
# "--separator '+'",
|
||
# "--separator '='",
|
||
# "--separator '-'"
|
||
]
|
||
# Note: The check with pytest and `*` as separator in Windows 10 fails.
|
||
# Contrasting to Linux Debian 13, a `pytest` in Windows 10 revealed every
|
||
# of these special characters can not safely used as an additional separator.
|
||
|
||
# create the permutations:
|
||
test_cases = list(product(arg1_values, arg2_values, arg3_values))
|
||
|
||
|
||
@pytest.mark.smart_prepend
|
||
@pytest.mark.parametrize("arg1, arg2, arg3", test_cases)
|
||
def test_smart_prepend(arg1, arg2, arg3):
|
||
"""test the insertion of a new string just past the time stamp
|
||
|
||
arg1 the test file to process, partly inspired by `date2name`
|
||
arg2 the text string to be added
|
||
arg3 the separator (at least in Windows 10, do not use `*`
|
||
"""
|
||
timestamp = ""
|
||
# timestamp_separator = ""
|
||
old_filename_no_timestamp = ""
|
||
|
||
# create a test file:
|
||
with open(arg1, mode="w", encoding="utf-8") as newfile:
|
||
newfile.write("this is a placeholder\n")
|
||
|
||
# run `appendfilename` on this test file
|
||
run_appendfilename = " ".join(
|
||
["python", PROGRAM, arg1, arg2, arg3, " --smart-prepend"])
|
||
subprocess.run(run_appendfilename, shell=True, check=True)
|
||
|
||
# construct the new file name to be testedt:
|
||
old_filename = arg1
|
||
|
||
# account for the implicit separator, i.e. the single space:
|
||
if len(shlex.split(arg3)) == 0:
|
||
separator = " "
|
||
else:
|
||
separator = shlex.split(arg3)[1]
|
||
|
||
# Timestamps `date2name` provides can be either one of five formats
|
||
#
|
||
# YYYY-MM-DDTHH.MM.SS `--withtime`
|
||
# YYYY-MM-DD default
|
||
# YYYYMMDD `--compact`
|
||
# YYYY-MM `--month`
|
||
# YYMMDD `--short`
|
||
|
||
# Currently, one observes two patterns by `appendfilename`: one which
|
||
# substitutes the separator by `date2name`, the other which retains it.
|
||
# Note patterns `compact`, `month`, and `short`, currently append the
|
||
# additional string rather than smartly prepend after the date stamp --
|
||
# for now, these three are not tested. Equally see discussions 15 and 16,
|
||
# https://github.com/novoid/appendfilename/issues/15
|
||
# https://github.com/novoid/appendfilename/issues/16
|
||
|
||
patterns = [
|
||
r"^\d{4}-[012]\d-[0-3]\dT[012]\d\.[0-5]\d\.[0-5]\d",
|
||
r"^\d{4}-[012]\d-[0-3]\d",
|
||
r"^\d{4}[012]\d[0-3]\d",
|
||
r"^\d{4}-[012]\d",
|
||
r"^\d{2}[012]\d[0-3]\d"
|
||
]
|
||
|
||
for pattern in patterns:
|
||
match = re.search(pattern, old_filename)
|
||
if match:
|
||
timestamp = re.findall(pattern, old_filename)[0]
|
||
timestamp_separator = str(old_filename)[len(timestamp)]
|
||
old_filename_no_timestamp = old_filename[len(timestamp) + 1:]
|
||
|
||
print("\n\ntest of option smart-prepend:") # `pytest -s` diagnosis
|
||
print("old_filename:")
|
||
print(old_filename)
|
||
print("timestamp, timestamp_separator, old_filename_no_timestamp")
|
||
print(timestamp)
|
||
print(timestamp_separator)
|
||
print(old_filename_no_timestamp)
|
||
|
||
break
|
||
|
||
new_filename = "".join([
|
||
timestamp, # timestamp_separator,
|
||
separator, shlex.split(arg2)[1], separator,
|
||
old_filename_no_timestamp
|
||
])
|
||
|
||
# is the new file present?
|
||
print("new_filename") # optional check for `pytest -s`
|
||
print(new_filename)
|
||
assert os.path.isfile(new_filename)
|
||
|
||
# check if the IS can process the new file / space cleaning
|
||
os.remove(new_filename)
|
||
assert os.path.isfile(new_filename) is False
|
||
#+end_src
|
||
|