diff --git a/tests/data/storyformat.st.txt b/tests/data/storyformat.st.txt new file mode 100644 index 0000000..5483e37 --- /dev/null +++ b/tests/data/storyformat.st.txt @@ -0,0 +1,43 @@ +movie: Good Format (2000) +========================= + +:: Title +Good Format + +:: Date +2000-01-01 + +:: Description +A story with a recognized story format. + +:: Story Format +film + + +movie: Bad Format (2001) +======================== + +:: Title +Bad Format + +:: Date +2001-01-01 + +:: Description +A story with an unrecognized story format. + +:: Story Format +graphic novel + + +movie: No Format (2002) +======================= + +:: Title +No Format + +:: Date +2002-01-01 + +:: Description +A story that omits the story format field entirely. diff --git a/tests/test_totolo.py b/tests/test_totolo.py index 34b4414..ddc0b0f 100644 --- a/tests/test_totolo.py +++ b/tests/test_totolo.py @@ -406,6 +406,16 @@ def test_multiple_entries(self): warnings = list(ontology._impl.validate_entries()) assert any("Multiple TOStory with name" in x for x in warnings) + def test_storyformat_warning(self): + ontology = totolo.files("tests/data/storyformat.st.txt") + warnings = list(ontology._impl.validate_storyformat()) + assert any("Bad Format" in x and "graphic novel" in x for x in warnings) + assert not any("Good Format" in x for x in warnings) + assert not any("No Format" in x for x in warnings) + # surfaces through the public validate() entry point as well + assert any( + "Unrecognized 'story format'" in x for x in ontology.validate() + ) def test_component_warning(self): ontology = totolo.files("tests/data/dangling-component.st.txt") warnings = list(ontology._impl.validate_components()) diff --git a/totolo/__init__.py b/totolo/__init__.py index 8205d33..13dd175 100644 --- a/totolo/__init__.py +++ b/totolo/__init__.py @@ -5,7 +5,7 @@ remote = TORemote() -__version__ = "2.1.4" +__version__ = "2.2.0" __ALL__ = [ empty, files, diff --git a/totolo/impl/to_base.py b/totolo/impl/to_base.py index 28c3417..b0cb53f 100644 --- a/totolo/impl/to_base.py +++ b/totolo/impl/to_base.py @@ -10,6 +10,18 @@ from .to_containers import TODict +#: Permitted values for the optional "Story Format" field on a story. This is a +#: high-level, objective classification of the medium (not genre). The field may +#: be omitted entirely; if present it must be one of these values. +STORY_FORMATS = frozenset({ + "film", + "tv", + "stage", + "prose", + "game", +}) + + class TOBase(TOObject): story = a(TODict) theme = a(TODict) @@ -159,6 +171,7 @@ def organize_collections(self): def validate(self): yield from self._impl.validate_entries() yield from self._impl.validate_storythemes() + yield from self._impl.validate_storyformat() yield from self._impl.validate_components() yield from self._impl.validate_cycles() @@ -300,6 +313,14 @@ def validate_storythemes(self): yield (f"{story.name}: Undefined '{weight} theme' with " f"name '{kwfield.keyword}'") + def validate_storyformat(self): + """Detect stories whose 'Story Format' is set to an unrecognized value.""" + for story in self.o.stories(): + value = str(story.get("Story Format")).strip() + if value and value not in STORY_FORMATS: + allowed = ", ".join(sorted(STORY_FORMATS)) + yield (f"{story.name}: Unrecognized 'story format' " + f"'{value}' (expected one of: {allowed})") def validate_components(self): """Detect component stories of collections that reference undefined stories.""" for story in self.o.stories(): diff --git a/totolo/story.py b/totolo/story.py index 1e1d608..3006cbc 100644 --- a/totolo/story.py +++ b/totolo/story.py @@ -21,6 +21,7 @@ class TOStory(TOEntry): Collections = sa("list") Component_Stories = sa("list") Related_Stories = sa("list") + Story_Format = sa("text") Choice_Themes = sa("kwlist") Major_Themes = sa("kwlist") Minor_Themes = sa("kwlist")