From 725fbe7067f1df40b90d592dbb95764069f6cbfd Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Wed, 19 Aug 2026 09:09:24 -0600 Subject: [PATCH] Let an author carry a note which is not an affiliation AASTeX reserves `\altaffiliation` for what needs saying about an author besides where they work, and the v7 guide gives "Hubble Fellow" and "author is deceased" as its examples. There was no way to write either one, so `Author` gains an `altaffiliation` field. The footnote symbol attaches to whatever text precedes it, so the command has to follow `\author` immediately, which is why it is emitted before the email rather than beside the affiliations. A deceased coauthor is added to the document behind `test_generate_archive_compiles`, so the markup is exercised through a real LaTeX run rather than only compared as a string. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BYjDL98znSud1yFh9chnkP --- aastex/_aastex.py | 24 +++++++++++++++++++++++- aastex/_tests/test_aastex.py | 26 +++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/aastex/_aastex.py b/aastex/_aastex.py index 9dd9add..7eb7379 100644 --- a/aastex/_aastex.py +++ b/aastex/_aastex.py @@ -80,6 +80,16 @@ class Author(pylatex.base_classes.LatexObject): organization where the work was done is usually given first. """ + altaffiliation: None | str = None + """ + A note about the author which is not an organization, rendered as a + footnote on their name. + + This is where AASTeX puts anything which needs saying about an author + besides where they work, such as ``"Hubble Fellow"``, or ``"Deceased"`` + for an author who died before the article was published. + """ + email: None | str = None """ The email address of the author. @@ -113,6 +123,18 @@ def dumps(self) -> str: options=NoEscape(self.orcid) if self.orcid is not None else None, ).dumps() + # The footnote symbol attaches to whatever precedes it, so this has to + # follow `\author` immediately. + altaffiliation = "" + if self.altaffiliation is not None: + altaffiliation = ( + pylatex.Command( + command="altaffiliation", + arguments=self.altaffiliation, + ).dumps() + + "\n" + ) + # AASTeX v7 requires an `\email` command for every author, # so emit an empty one if no email address was given. email = pylatex.Command( @@ -123,7 +145,7 @@ def dumps(self) -> str: affiliation = "\n".join(a.dumps() for a in self.affiliations) - result += f"{author}\n{email}\n{affiliation}" + result += f"{author}\n{altaffiliation}{email}\n{affiliation}" return result diff --git a/aastex/_tests/test_aastex.py b/aastex/_tests/test_aastex.py index f1ba785..038658e 100644 --- a/aastex/_tests/test_aastex.py +++ b/aastex/_tests/test_aastex.py @@ -65,6 +65,11 @@ def test_dumps(self, a: aastex.Affiliation): aastex.Affiliation("Another Fancy University"), ], ), + aastex.Author( + name="John Roe", + affiliation=aastex.Affiliation("Fancy University"), + altaffiliation="Deceased", + ), ], ) class TestAuthor: @@ -88,6 +93,20 @@ def test_affiliations(self, a: aastex.Author): for affiliation in result: assert affiliation.name in dumps + def test_altaffiliation(self, a: aastex.Author): + result = a.altaffiliation + dumps = a.dumps() + if result is not None: + assert isinstance(result, str) + + # the footnote symbol attaches to the preceding text, so this has + # to follow `\author` and nothing else + author = dumps.index(r"\author") + assert dumps.index(r"\altaffiliation") > author + assert dumps.index(result, author) < dumps.index(r"\email") + else: + assert r"\altaffiliation" not in dumps + def test_orcid(self, a: aastex.Author): result = a.orcid if result is not None: @@ -675,7 +694,12 @@ def _submittable_document() -> aastex.Document: aastex.Author( name="Jane Doe", affiliation=aastex.Affiliation("Fancy University"), - ) + ), + aastex.Author( + name="John Roe", + affiliation=aastex.Affiliation("Fancy University"), + altaffiliation="Deceased", + ), ] section = aastex.Section("A section") section.append("Some text.")