DocName: The Canonical creators__year__title Document Name#
- class my.files.DocName.DocName(*, creators: str, year: str, title: str)#
One document’s canonical
creators__year__titlename.The model is frozen and validates its own grammar, so a
DocNamethat exists is a name that can be written to disk. Build one withmint()from bibliographic metadata, or withparse()from a name that already exists.Examples
Mint a name from what a document’s front matter yielded:
>>> from my.files import DocName >>> str(DocName.mint(creators=['Hannah Arendt'], year=1958, ... title='The Human Condition')) 'arendt-h__1958__the-human-condition'
Fall through to the identifier when the creators could not be read:
>>> str(DocName.mint(title='A Survey of Agent Memory', year=2025, ... uids={'arxiv': '2501.13956'})) 'uid-arxiv__2025__a-survey-of-agent-memory'
Parse a name back into its slots:
>>> DocName.parse('aristotle__-0350__categories').year '-0350'
Building a name#
- classmethod DocName.mint(title: str = '', creators: Iterable[str] = (), year: int | str | None = None, uids: dict[str, str] | None = None, site: str = '', et_al: bool = False) Self | None#
Build the best canonical name the given metadata can support.
Walks the evidence ladder, best first: named creators, then the work’s own identifier, then whoever published it. Each rung yields a name a reader can find again; only a work with no title and no identifier at all is unnameable.
- Parameters:
title – The work’s title, as written.
creators – Creator names, as written, most significant first.
year – Publication year,
'undated', orNonewhen untriaged.uids – Identifiers by scheme, e.g.
{'arxiv': '2501.13956'}.site – Publisher, website, or bare domain, for an author-less work.
et_al – Force the
et-alsub-unit even whencreatorswas complete.
- Returns:
The minted name, or
Nonewhen the metadata names nothing.
- classmethod DocName.parse(text: str) Self | None#
Read an existing document name, tolerating a path and a file extension.
- Parameters:
text – A name, filename, or directory name.
- Returns:
The parsed name, or
Nonewhentextdoes not obey the grammar.
- static DocName.is_valid(text: str) bool#
Whether
textobeys the typed-slot grammar and creator delineation.- Parameters:
text – A candidate name.
- Returns:
Whether the shelf would accept it.
Examples
The
et-alsub-unit must be delineated, not hyphenated on:>>> from my.files import DocName >>> DocName.is_valid('cuneo-n_et-al__2005__the-normative-web') True >>> DocName.is_valid('cuneo-n-et-al__2005__the-normative-web') False
Using a name#
- DocName.filename(suffix: str = '') str#
This name as a filename.
- Parameters:
suffix – A file extension, with or without its leading dot.
- Returns:
The name, with
suffixappended when one was given.
- DocName.disambiguated(taken: Container[str], suffix: str = '') Self#
A variant of this name that
takendoes not already hold.Two different works can mint one name – a second edition, a reprint, two papers a year apart with one title. The shelf must never silently absorb one into the other, so the copy takes an ordinal sub-unit on its title slot:
syntactic-structuresbecomessyntactic-structures_2.- Parameters:
taken – The names already shelved. Membership is tested against
filename(), so pass the same shape you store.suffix – The file extension
takenis keyed by, if any.
- Returns:
This name when it is free, else the first free ordinal variant.
Slots#
- my.files.DocName.creators_slot(names: Iterable[str], et_al: bool = False, limit: int = 2) str#
The creator slot for
names: up tolimitpeople,_-joined, then_et-al.- Parameters:
names – Creator names, as written, most significant first.
et_al – Force the
et-alsub-unit even whennameswas already complete.limit – How many named creators the slot holds before the rest collapse.
- Returns:
The creator slot, or
''when no name survives cleaning.
Examples
Two creators join with
_; a third collapses intoet-al:>>> from my.files import DocName >>> DocName.creators_slot(['Tamar Gendler', 'John Hawthorne']) 'gendler-t_hawthorne-j' >>> DocName.creators_slot(['Noam Chomsky', 'Robert Pollin', 'C. J. Polychroniou']) 'chomsky-n_pollin-r_et-al'
- my.files.DocName.person_slot(name: str) str#
One creator as
surname-initials, or a bare surname when no given name is known.- Parameters:
name – One person’s name, as written.
- Returns:
The sub-unit for this person, or
''when no surname survives cleaning.
Examples
Name a person in either written order:
>>> from my.files import DocName >>> DocName.person_slot('Hannah Arendt') 'arendt-h' >>> DocName.person_slot('Austin, J. L.') 'austin-jl' >>> DocName.person_slot('Aristotle') 'aristotle'
- my.files.DocName.split_person(name: str) tuple[str, str]#
Split one written name into
(surname, given).Reads both
Firstname LastnameandLastname, Firstnameorders.A particle’s own case decides whether it belongs to the surname, which is the bibliographic rule and the one the shelf follows: a lowercase particle is detachable and drops out of the sort form (
Simone de Beauvoirfiles underbeauvoir-s), while a capitalized one is part of the surname proper (Helen De Cruzstaysde-cruz-h).- Parameters:
name – One person’s name, as written.
- Returns:
The surname and given-name halves, either of which may be
''. A detached lowercase particle appears in neither – it is not part of the sort form.
- my.files.DocName.year_slot(value: int | str | None) str#
The year slot for
value: signed and zero-padded to four digits.- Parameters:
value – A year as an integer or string, the literal
'undated', orNone.- Returns:
A signed four-digit year,
'undated', or'0000'when nothing was readable.
Examples
Pad, sign, and pass through the two non-numeric answers:
>>> from my.files import DocName >>> DocName.year_slot(1958), DocName.year_slot(-350), DocName.year_slot(950) ('1958', '-0350', '0950') >>> DocName.year_slot(None), DocName.year_slot('undated') ('0000', 'undated')
- my.files.DocName.title_slot(title: str, max_length: int = 48) str#
The title slot for
title, cut at its first subtitle mark then cleaned.- Parameters:
title – The work’s title, as written.
max_length – Length cap, in characters.
- Returns:
The cleaned title slot, or
''when nothing survives.
Examples
A subtitle is dropped at the mark that introduces it:
>>> from my.files import DocName >>> DocName.title_slot('Steps to an Ecology of Mind: Collected Essays') 'steps-to-an-ecology-of-mind'
- my.files.DocName.uid_slot(scheme: str, value: str) str#
The creator-slot stand-in for a work identified by
scheme, e.g.uid-arxiv.- Parameters:
scheme – The identifier’s namespace (
arxiv,doi,isbn, …).value – The identifier itself. Only used to prove one exists.
- Returns:
The
uid-<scheme>sub-unit, or''when neither half survives cleaning.
- my.files.DocName.uid_handle(value: str) str#
One identifier as a slot sub-unit, keeping its internal structure as
-.- Parameters:
value – The identifier, as written.
- Returns:
The cleaned handle, or
''when nothing survives.
Examples
An arXiv id and a DOI keep their shape:
>>> from my.files import DocName >>> DocName.uid_handle('2501.13956') '2501-13956' >>> DocName.uid_handle('10.1145/3597503') '10-1145-3597503'
- my.files.DocName.site_slot(site: str) str#
The creator-slot stand-in for an author-less work, named by who published it.
- Parameters:
site – A publisher, website, or bare domain.
- Returns:
The
site-<host>sub-unit, or''when nothing survives cleaning.
Examples
A domain keeps its labels; a publisher name is cleaned like any other text:
>>> from my.files import DocName >>> DocName.site_slot('https://example.org/news/x') 'site-example-org' >>> DocName.site_slot('Cambridge University Press') 'site-cambridge-university-press'
Cleaning#
- my.files.DocName.clean_section(text: str, max_length: int = 48) str#
Clean one slot’s text and cut it back to the last whole word that fits.
Never drops an internal article or preposition – those are title identity. Only a stopword left dangling by the cut is removed, because it names nothing on its own.
Punctuation that separated two phrases becomes the
_sub-unit delimiter, so a slot keeps the shape its source had. Cutting a title at its subtitle mark is a different act, and belongs totitle_slot().- Parameters:
text – Raw slot text.
max_length – Length cap, in characters.
- Returns:
The cleaned, truncated slot, or
''when nothing survives.
Examples
Clean and cap a title, cutting back to a whole word:
>>> from my.files import DocName >>> DocName.clean_section('The Structure of Scientific Revolutions') 'the-structure-of-scientific-revolutions' >>> DocName.clean_section('Design for a Brain the Origin of Adaptive', 30) 'design-for-a-brain-the-origin'
Punctuation between phrases delimits sub-units:
>>> DocName.clean_section('Design for a Brain: The Origin of Adaptive', 30) 'design-for-a-brain_the-origin'
- my.files.DocName.uid_clean(text: str) str#
Strip identifier detritus (dots, quotes, commas) and surrounding quote marks.
- my.files.DocName.without_leading_article(title: str) str#
Remove one identity-neutral leading article, preserving every internal word.
Examples
Only the leading article goes; internal ones are the title’s identity:
>>> from my.files import DocName >>> DocName.without_leading_article('the-human-condition') 'human-condition' >>> DocName.without_leading_article('being-and-time') 'being-and-time'