diff --git a/Makefile b/Makefile
index 5ba02f1..7c22cdf 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-all: setup format mypy test pylint flake8
+all: setup format mypy cov pylint flake8
 
 .PHONY: setup
 	pipenv run which coverage || pipenv install --dev
diff --git a/publ/entry.py b/publ/entry.py
index 900f452..cd54ae5 100644
--- a/publ/entry.py
+++ b/publ/entry.py
@@ -12,12 +12,13 @@ import uuid
 
 import arrow
 import flask
+import slugify
 from pony import orm
 from werkzeug.utils import cached_property
 
 from . import (caching, cards, config, html_entry, links, markdown, model,
                path_alias, queries, tokens, user, utils)
-from .utils import CallableProxy, CallableValue, TrueCallableProxy, make_slug
+from .utils import CallableProxy, CallableValue, TrueCallableProxy
 
 LOGGER = logging.getLogger(__name__)
 
@@ -47,7 +48,7 @@ class Entry(caching.Memoizable):
 
         self._fingerprint = model.FileFingerprint.get(file_path=record.file_path)
         LOGGER.debug('loaded entry %d, fingerprint=%s', record.id,
-            self._fingerprint.fingerprint if self._fingerprint else None)
+                     self._fingerprint.fingerprint if self._fingerprint else None)
 
         # maps (section,footnotes_enabled) -> toc/footnote counter
         self._counters: typing.Dict[typing.Tuple[str, bool], markdown.ItemCounter] = {}
@@ -729,7 +730,7 @@ def scan_file(fullpath: str, relpath: typing.Optional[str], assign_id: bool) ->
         'category': entry.get('Category', utils.get_category(relpath)),
         'status': model.PublishStatus[entry.get('Status', 'SCHEDULED').upper()].value,
         'entry_type': entry.get('Entry-Type', ''),
-        'slug_text': make_slug(entry.get('Slug-Text', title)),
+        'slug_text': slugify.slugify(entry.get('Slug-Text', title)),
         'redirect_url': entry.get('Redirect-To', ''),
         'title': title,
         'sort_title': entry.get('Sort-Title', title),
diff --git a/publ/image/local.py b/publ/image/local.py
index 6d2ff74..0ec1f6c 100644
--- a/publ/image/local.py
+++ b/publ/image/local.py
@@ -10,6 +10,7 @@ import typing
 
 import flask
 import PIL.Image
+import slugify
 from atomicwrites import atomic_write
 from werkzeug.utils import cached_property
 
@@ -95,7 +96,7 @@ class LocalImage(Image):
         """ implements get_rendition and returns tuple of out_rel_path,size,pending """
         basename, ext = os.path.splitext(
             os.path.basename(self._record.file_path))
-        basename = utils.make_slug(basename)
+        basename = slugify.slugify(basename)
 
         if kwargs.get('format'):
             ext = '.' + kwargs['format']
diff --git a/publ/utils.py b/publ/utils.py
index 16d46de..2b2c802 100644
--- a/publ/utils.py
+++ b/publ/utils.py
@@ -12,15 +12,12 @@ import urllib.parse
 
 import arrow
 import flask
-import slugify
 import werkzeug.routing
 
 from . import config
 
 LOGGER = logging.getLogger(__name__)
 
-LOG_TRIVIAL = logging.DEBUG - 1
-
 T = typing.TypeVar('T')  # pylint:disable=invalid-name
 ArgDict = typing.Dict[str, typing.Any]
 ListLike = typing.Union[typing.List[T],
@@ -42,10 +39,8 @@ class CallableProxy:
         self._func: typing.Callable[..., T] = func
 
     @functools.lru_cache()
-    def _cached_default(self, *cache_params):
+    def _cached_default(self, *_):
         """ caching wrapper to memoize call against everything that might affect it """
-        LOGGER.log(LOG_TRIVIAL, '%s %s _cached_default %s', self.__class__.__name__, self._func,
-                   cache_params)
         return self._func()
 
     def _default(self):
@@ -54,36 +49,38 @@ class CallableProxy:
 
     def __call__(self, *args, **kwargs) -> T:
         # use the new kwargs to override the defaults
-        LOGGER.log(LOG_TRIVIAL, '%s %s __call__ %s %s', self.__class__.__name__, self._func,
-                   args, kwargs)
         return self._func(*args, **kwargs)
 
     def __getattr__(self, name):
-        LOGGER.log(LOG_TRIVIAL, '%s %s __getattr__ %s', self.__class__.__name__, self._func,
-                   name)
         return getattr(self._default(), name)
 
     def __bool__(self) -> bool:
-        LOGGER.log(LOG_TRIVIAL, '%s %s __bool__', self.__class__.__name__, self._func)
         return bool(self._default())
 
     def __len__(self) -> int:
-        LOGGER.log(LOG_TRIVIAL, '%s %s __len__', self.__class__.__name__, self._func)
         return len(self._default())
 
     def __str__(self) -> str:
-        LOGGER.log(LOG_TRIVIAL, '%s %s __str__', self.__class__.__name__, self._func)
         return str(self._default())
 
     def __iter__(self):
-        LOGGER.log(LOG_TRIVIAL, '%s %s __iter__', self.__class__.__name__, self._func)
         return self._default().__iter__()
 
     def __getitem__(self, key):
-        LOGGER.log(LOG_TRIVIAL, '%s %s __getitem__ %s', self.__class__.__name__, self._func,
-                   key)
         return self._default().__getitem__(key)
 
+    def __hash__(self):
+        return hash((CallableProxy, self._func))
+
+    def __eq__(self, other):
+        return self._default() == other
+
+    def __contains__(self, item):
+        return item in self._default()
+
+    def __add__(self, other):
+        return self._default() + other
+
 
 class TrueCallableProxy(CallableProxy):
     """ A version of CallableProxy that is always truthy """
@@ -128,7 +125,7 @@ def parse_date(datestr: str) -> typing.Tuple[arrow.Arrow, str, str]:
     if not match:
         return (arrow.get(datestr,
                           tzinfo=config.timezone).replace(tzinfo=config.timezone),
-                'day', 'YYYY-MM-DD')
+                'day', DAY_FORMAT)
 
     year, month, day, week = match.group(1, 3, 5, 6)
     start = arrow.Arrow(year=int(year), month=int(
@@ -136,14 +133,14 @@ def parse_date(datestr: str) -> typing.Tuple[arrow.Arrow, str, str]:
 
     if week:
         return start.span('week')[0], 'week', WEEK_FORMAT
+
     if day:
         return start, 'day', DAY_FORMAT
+
     if month:
         return start, 'month', MONTH_FORMAT
-    if year:
-        return start, 'year', YEAR_FORMAT
 
-    raise ValueError("Could not parse date: {}".format(datestr))
+    return start, 'year', YEAR_FORMAT
 
 
 def find_file(path: str, search_path: typing.Union[str, ListLike[str]]) -> typing.Optional[str]:
@@ -163,11 +160,6 @@ def find_file(path: str, search_path: typing.Union[str, ListLike[str]]) -> typin
     return None
 
 
-def make_slug(title: str) -> str:
-    """ convert a title into a URL-friendly slug """
-    return slugify.slugify(title)
-
-
 def static_url(path: str, absolute: bool = False) -> str:
     """ Shorthand for returning a URL for the requested static file.
 
@@ -239,7 +231,7 @@ def file_fingerprint(fullpath: str) -> str:
 
 
 def remap_args(input_args: typing.Dict[str, typing.Any],
-               remap: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
+               remap: typing.Dict[str, str]) -> typing.Dict[str, typing.Any]:
     """ Generate a new argument list by remapping keys. The 'remap'
     dict maps from destination key -> priority list of source keys
     """
@@ -336,7 +328,7 @@ def prefix_normalize(kwargs: ArgDict) -> ArgDict:
 
 def is_list(item: typing.Any) -> bool:
     """ Return if this is a list-type thing """
-    return isinstance(item, (list, tuple, set, TagSet))
+    return getattr(item, '__iter__', None) and not isinstance(item, str)
 
 
 def as_list(item: typing.Any) -> ListLike:
@@ -429,11 +421,14 @@ def stash(key: str) -> typing.Callable:
     return decorator
 
 
-def parse_tuple_string(argument, type_func=int) -> typing.Tuple:
+def parse_tuple_string(argument: typing.Union[str, typing.Tuple, typing.List],
+                       type_func=int) -> typing.Optional[typing.Tuple]:
     """ Return a tuple from parsing 'a,b,c,d' -> (a,b,c,d) """
+    if argument is None:
+        return None
     if isinstance(argument, str):
         return tuple(type_func(p.strip()) for p in argument.split(','))
-    return argument
+    return tuple(argument)
 
 
 class TagSet(typing.Set[str]):

Publ could really use some actual automated tests
Unit tests where possible, integration tests in general.

Integration tests could be in the form of a test runner which is able to run through all of the test pages to capture their output and compare it with a reference output (with some added stuff for scenario testing).
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 0a79255..25678a3 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,10 +1,113 @@
 """ tests of publ.utils class """
+# pylint:disable=missing-function-docstring
 
-from publ.utils import TagSet
+import flask
+import pytest
+
+from publ import utils
+
+
+def test_callable_proxy():
+    """ Tests of the CallableProxy class """
+
+    def wrapped_func(retval="default"):
+        return retval
+
+    proxy = utils.CallableProxy(wrapped_func)
+
+    assert proxy() == "default"
+    assert proxy(1) == 1
+    assert proxy(True) is True
+    assert proxy(False) is False
+
+    app = flask.Flask(__name__)
+    with app.test_request_context("/"):
+        assert proxy.upper() == "DEFAULT"
+        assert bool(proxy)
+        assert len(proxy) == 7
+        assert str(proxy) == "default"
+        assert list(proxy) == ['d', 'e', 'f', 'a', 'u', 'l', 't']
+        assert proxy[3] == 'a'
+
+        assert proxy == "default"
+        assert 't' in proxy
+        assert proxy + "foo" == "defaultfoo"
+
+
+def test_callable_proxy_caching():
+    """ Test the caching-ness of CallableProxy's default """
+
+    app = flask.Flask(__name__)
+    app.secret_key = 'bogus'
+
+    stash = {'call_count': 0}
+
+    def wrapped_func():
+        stash['call_count'] += 1
+        return flask.request.path
+
+    proxy = utils.CallableProxy(wrapped_func)
+
+    with app.test_request_context("/foo/bar"):
+        # within same path the call should only happen once
+        assert str(proxy) == "/foo/bar"
+        assert stash['call_count'] == 1
+        assert str(proxy) == "/foo/bar"
+        assert stash['call_count'] == 1
+
+    with app.test_request_context("/foo/bar"):
+        # cached value should remain cached
+        assert str(proxy) == "/foo/bar"
+        assert stash['call_count'] == 1
+
+    with app.test_request_context("/foo/baz"):
+        # different path should call it again
+        assert str(proxy) == "/foo/baz"
+        assert stash['call_count'] == 2
+
+    with app.test_request_context("/foo/baz"):
+        # different user should call it again
+        flask.session['me'] = 'test:cachebuster'
+        assert str(proxy) == "/foo/baz"
+        assert stash['call_count'] == 3
+
+
+def test_truecallable_proxy():
+    """ Tests of TrueCallableProxy """
+
+    stash = {'call_count': 0}
+
+    def wrapped_func():
+        stash['call_count'] += 1
+        return False
+
+    proxy = utils.TrueCallableProxy(wrapped_func)
+
+    app = flask.Flask(__name__)
+    with app.test_request_context('/foo'):
+        assert stash['call_count'] == 0
+        assert proxy
+        assert stash['call_count'] == 0
+
+
+def test_callablevalue():
+    """ Tests of CallableValue """
+    proxy = utils.CallableValue("flonk")
+    falsy = utils.CallableValue(False)
+
+    app = flask.Flask(__name__)
+    with app.test_request_context("/bar"):
+        assert proxy == "flonk"
+        assert len(proxy) == 5
+        assert proxy + "foo" == "flonkfoo"
+        assert proxy
+        assert not falsy
 
 
 def test_tagset_membership():
     """ Membership tests for TagSet """
+    from publ.utils import TagSet
+
     items = ('a', 'S', 'd', 'F')
     others = ('Q', 'w', 'E', 'r')
     tags = TagSet(items)
@@ -38,6 +141,8 @@ def test_tagset_membership():
 
 def test_tagset_operators():
     """ Test operators  on TagSet """
+    from publ.utils import TagSet
+
     assert TagSet(('a', 's', 'D', 'F')) == TagSet(('A', 'S', 'd', 'f'))
     assert TagSet(('a', 's', 'D', 'F')) == {'A', 'S', 'd', 'f'}
     assert TagSet(('a', 's', 'd', 'f')) != TagSet(('a', 's', 'd'))
@@ -65,3 +170,147 @@ def test_tagset_operators():
     assert not TagSet(('a', 's', 'd')) < TagSet(('a', 's', 'd'))
     assert not TagSet(('a', 's', 'd')) < TagSet(('q', 'w', 'e'))
     assert not TagSet(('a', 's', 'd')) <= TagSet(('q', 'w', 'e'))
+
+
+def test_parse_tuple_string():
+    """ test of parse_tuple_string() """
+    assert utils.parse_tuple_string((1, 2, 3)) == (1, 2, 3)
+    assert utils.parse_tuple_string([1, 2, 3]) == (1, 2, 3)
+    assert utils.parse_tuple_string("1,2,3") == (1, 2, 3)
+    assert utils.parse_tuple_string("a,,", bool) == (True, False, False)
+    with pytest.raises(Exception):
+        utils.parse_tuple_string("a,b,c", float)
+    assert utils.parse_tuple_string(None) is None
+
+
+def test_make_tag():
+    """ test of utils.make_tag """
+
+    assert utils.make_tag('a', {'href': 'foo'}, False) == '<a href="foo">'
+    assert utils.make_tag('a',
+                          [('href', 'foo'), ('href', 'bar')],
+                          True) == '<a href="foo" href="bar" />'
+    with pytest.raises(Exception):
+        utils.make_tag('a', (('href', 'foo')))
+
+    app = flask.Flask(__name__)
+    with app.test_request_context():
+        proxy = utils.CallableValue("<hello>")
+        assert utils.make_tag('a', {'href': proxy}) == '<a href="&lt;hello&gt;">'
+
+    escaped = flask.Markup("&amp;")
+    assert utils.make_tag('a', {'href': escaped}) == '<a href="&amp;">'
+
+
+def test_listlike():
+    """ test functions involving the ListLike inference class """
+    assert utils.is_list((1, 2, 3))
+    assert utils.is_list([])
+    assert utils.is_list([1, 2, 3])
+    assert utils.is_list({1, 2, 3})
+    assert utils.is_list(frozenset((1, 2, 3)))
+    assert utils.is_list(utils.TagSet(('1', '2', 'A', 'a')))
+    assert not utils.is_list("")
+    assert not utils.is_list("foo")
+    assert not utils.is_list(123)
+    assert not utils.is_list(True)
+    assert not utils.is_list(None)
+
+    assert utils.as_list([1, 2, 3]) == [1, 2, 3]
+    assert utils.as_list((1, 2, 3)) == (1, 2, 3)
+    assert utils.as_list("foo") == ("foo",)
+    assert utils.as_list(True) == (True,)
+    assert utils.as_list(None) == ()
+
+
+def test_parse_date():
+    """ tests for the date parser """
+    import arrow
+    from publ import config
+
+    def make_date(year=None, month=None, day=None):
+        return arrow.Arrow(year=year or 1,
+                           month=month or 1,
+                           day=day or 1,
+                           tzinfo=config.timezone)
+
+    with pytest.raises(ValueError):
+        utils.parse_date("not a date")
+    with pytest.raises(ValueError):
+        utils.parse_date("12345678")
+    with pytest.raises(ValueError):
+        utils.parse_date("")
+
+    assert utils.parse_date('1978-06-14') == (make_date(1978, 6, 14), 'day', utils.DAY_FORMAT)
+    assert utils.parse_date('19780614') == (make_date(1978, 6, 14), 'day', utils.DAY_FORMAT)
+
+    assert utils.parse_date('1983-07') == (make_date(1983, 7), 'month', utils.MONTH_FORMAT)
+    assert utils.parse_date('198307') == (make_date(1983, 7), 'month', utils.MONTH_FORMAT)
+
+    assert utils.parse_date("1979") == (make_date(1979), 'year', utils.YEAR_FORMAT)
+
+    assert utils.parse_date('19810505_w') == (make_date(1981, 5, 4), 'week', utils.WEEK_FORMAT)
+
+
+def test_find_file():
+    """ tests for the file finder """
+    assert utils.find_file("anything", []) is None
+
+    assert utils.find_file("anything", ["tests/templates"]) is None
+    assert utils.find_file("auth", ["tests/templates"]) is None
+
+    assert utils.find_file("index.html",
+                           ("tests/templates")
+                           ) == "tests/templates/index.html"
+    assert utils.find_file("index.html",
+                           ("tests/templates", "tests/templates/auth")
+                           ) == "tests/templates/index.html"
+    assert utils.find_file("index.html",
+                           ("tests/templates/auth", "tests/templates")
+                           ) == "tests/templates/auth/index.html"
+
+
+def test_static_url():
+    """ tests for the static URL builder """
+    app = flask.Flask("tests", static_folder="asdf")
+    with app.test_request_context("https://foo.bar/poiupoiupoiu"):
+        assert utils.static_url("thing", absolute=False) == "/asdf/thing"
+        assert utils.static_url("thong", absolute=True) == "https://foo.bar/asdf/thong"
+
+
+def test_remap_link_target():
+    """ test the target remapper """
+    app = flask.Flask("tests", static_folder="feedme")
+    with app.test_request_context("https://feed.me/blah/merry"):
+        assert utils.remap_link_target("@fred") == "/feedme/fred"
+        assert utils.remap_link_target("@fred", absolute=True) == "https://feed.me/feedme/fred"
+        assert utils.remap_link_target("daphne") == "daphne"
+        assert utils.remap_link_target("daphne", absolute=True) == "https://feed.me/blah/daphne"
+
+
+def test_get_category():
+    import os.path
+    assert utils.get_category(os.path.join("foo", "bar", "entry.html")) == "foo/bar"
+
+
+def test_remap_args():
+    """ test the argument parser remapper """
+    args = {'foo': 'bar', 'quuz': 'quux'}
+    assert utils.remap_args(args, {}) == args
+    assert utils.remap_args(args, {"foo": "poiu"}) == args
+    assert utils.remap_args(args, {"foo": "quuz"})['foo'] == 'quux'
+
+
+def test_prefix_normalize():
+    """ test the prefix remapping thing """
+    args = {'here': '1',
+            'prefix_here':
+            '2', 'prefix_there': '3',
+            'anywhere': '4',
+            'prefix': 'prefix_'}
+    assert utils.prefix_normalize(args) == {
+        'here': '2',
+        'there': '3',
+        'anywhere': '4',
+        'prefix': 'prefix_'
+    }

pip install -e .[dev]
parse_log_pytest
Pipfile
pytest
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
arrow==1.2.3
astroid==2.15.8
atomicwrites==1.4.1
authl==0.6.0
autopep8==2.0.4
awesome-slugify==1.6.5
beautifulsoup4==4.13.3
bleach==6.0.0
blurhash==1.1.4
cachelib==0.9.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
click==8.1.8
coverage==7.2.7
cryptography==44.0.2
decorator==5.1.1
docutils==0.20.1
exceptiongroup==1.2.2
expiringdict==1.2.2
flake8==2.5.5
Flask==2.2.5
Flask-Caching==2.1.0
html5lib==1.1
idna==3.10
importlib-metadata==6.7.0
importlib-resources==5.12.0
iniconfig==2.0.0
isort==5.11.5
itsdangerous==2.1.2
jaraco.classes==3.2.3
jeepney==0.9.0
Jinja2==3.1.6
keyring==24.1.1
lazy-object-proxy==1.9.0
markdown-it-py==2.2.0
MarkupSafe==2.1.5
Mastodon.py==1.8.1
mccabe==0.4.0
mdurl==0.1.2
mf2py==1.1.3
misaka==2.1.1
more-itertools==9.1.0
mypy==1.4.1
mypy-extensions==1.0.0
oauthlib==3.2.2
packaging==24.0
pep8==1.7.1
Pillow==9.5.0
pipfile==0.0.2
pkginfo==1.10.0
pluggy==1.2.0
pony==0.7.19
-e git+https://github.com/PlaidWeb/Publ.git@e362925deaa43b514b2f6208961a604741394a36#egg=Publ
pycodestyle==2.10.0
pycparser==2.21
pyflakes==1.0.0
Pygments==2.17.2
pylint==2.3.0
pytest==7.4.4
python-dateutil==2.9.0.post0
python-magic==0.4.27
readme-renderer==37.3
regex==2024.4.16
requests==2.31.0
requests-oauthlib==1.3.1
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==13.8.1
SecretStorage==3.3.3
six==1.17.0
soupsieve==2.4.1
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
tomli==2.0.1
twine==4.0.2
typed-ast==1.5.5
typing_extensions==4.7.1
Unidecode==0.4.21
urllib3==2.0.7
validate-email==1.3
watchdog==3.0.0
webencodings==0.5.1
Werkzeug==2.2.3
wrapt==1.16.0
zipp==3.15.0

name: Publ
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - pipfile=0.0.2=py_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - arrow==1.2.3
      - astroid==2.15.8
      - atomicwrites==1.4.1
      - authl==0.6.0
      - autopep8==2.0.4
      - awesome-slugify==1.6.5
      - beautifulsoup4==4.13.3
      - bleach==6.0.0
      - blurhash==1.1.4
      - cachelib==0.9.0
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.2.7
      - cryptography==44.0.2
      - decorator==5.1.1
      - docutils==0.20.1
      - exceptiongroup==1.2.2
      - expiringdict==1.2.2
      - flake8==2.5.5
      - flask==2.2.5
      - flask-caching==2.1.0
      - html5lib==1.1
      - idna==3.10
      - importlib-metadata==6.7.0
      - importlib-resources==5.12.0
      - iniconfig==2.0.0
      - isort==5.11.5
      - itsdangerous==2.1.2
      - jaraco-classes==3.2.3
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==24.1.1
      - lazy-object-proxy==1.9.0
      - markdown-it-py==2.2.0
      - markupsafe==2.1.5
      - mastodon-py==1.8.1
      - mccabe==0.4.0
      - mdurl==0.1.2
      - mf2py==1.1.3
      - misaka==2.1.1
      - more-itertools==9.1.0
      - mypy==1.4.1
      - mypy-extensions==1.0.0
      - oauthlib==3.2.2
      - packaging==24.0
      - pep8==1.7.1
      - pillow==9.5.0
      - pkginfo==1.10.0
      - pluggy==1.2.0
      - pony==0.7.19
      - pycodestyle==2.10.0
      - pycparser==2.21
      - pyflakes==1.0.0
      - pygments==2.17.2
      - pylint==2.3.0
      - pytest==7.4.4
      - python-dateutil==2.9.0.post0
      - python-magic==0.4.27
      - readme-renderer==37.3
      - regex==2024.4.16
      - requests==2.31.0
      - requests-oauthlib==1.3.1
      - requests-toolbelt==1.0.0
      - rfc3986==2.0.0
      - rich==13.8.1
      - secretstorage==3.3.3
      - six==1.17.0
      - soupsieve==2.4.1
      - tomli==2.0.1
      - twine==4.0.2
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
      - unidecode==0.04.21
      - urllib3==2.0.7
      - validate-email==1.3
      - watchdog==3.0.0
      - webencodings==0.5.1
      - werkzeug==2.2.3
      - wrapt==1.16.0
      - zipp==3.15.0
prefix: /opt/conda/envs/Publ

tests/test_utils.py::test_callable_proxy tests/test_utils.py::test_callablevalue tests/test_utils.py::test_parse_tuple_string tests/test_utils.py::test_listlike
tests/test_utils.py::test_callable_proxy_caching tests/test_utils.py::test_truecallable_proxy tests/test_utils.py::test_tagset_membership tests/test_utils.py::test_tagset_operators tests/test_utils.py::test_make_tag tests/test_utils.py::test_parse_date tests/test_utils.py::test_find_file tests/test_utils.py::test_static_url tests/test_utils.py::test_remap_link_target tests/test_utils.py::test_get_category tests/test_utils.py::test_remap_args tests/test_utils.py::test_prefix_normalize
publ/image/local.py Makefile publ/utils.py publ/entry.py
publ/image/local.py Makefile publ/utils.py publ/entry.py