""" novelWriter – Tokenizer Class Tester ==================================== This file is a part of novelWriter Copyright 2018–2024, Veronica Berglyd Olsen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ from __future__ import annotations import json import pytest from PyQt5.QtGui import QFont from novelwriter import CONFIG from novelwriter.constants import nwHeadFmt, nwStyles from novelwriter.core.project import NWProject from novelwriter.formats.tokenizer import ( BlockFmt, BlockTyp, HeadingFormatter, TextFmt, Tokenizer, stripEscape ) from novelwriter.formats.tomarkdown import ToMarkdown from novelwriter.formats.toraw import ToRaw from tests.tools import C, buildTestProject, readFile class BareTokenizer(Tokenizer): def doConvert(self): super().doConvert() # type: ignore (deliberate check) def saveDocument(self, path) -> None: super().saveDocument(path) # type: ignore (deliberate check) @pytest.mark.core def testFmtToken_Abstracts(mockGUI, tstPaths): """Test all the abstract methods of the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) with pytest.raises(NotImplementedError): tokens.doConvert() with pytest.raises(NotImplementedError): tokens.saveDocument(tstPaths) @pytest.mark.core def testFmtToken_Setters(mockGUI): """Test all the setters for the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) # Verify defaults assert tokens._fmtPart == nwHeadFmt.TITLE assert tokens._fmtChapter == nwHeadFmt.TITLE assert tokens._fmtUnNum == nwHeadFmt.TITLE assert tokens._fmtScene == nwHeadFmt.TITLE assert tokens._fmtHScene == nwHeadFmt.TITLE assert tokens._fmtSection == nwHeadFmt.TITLE assert tokens._textFont == QFont("Serif", 11) assert tokens._lineHeight == 1.15 assert tokens._blockIndent == 4.0 assert tokens._doJustify is False assert tokens._marginTitle == nwStyles.T_MARGIN["H0"] assert tokens._marginHead1 == nwStyles.T_MARGIN["H1"] assert tokens._marginHead2 == nwStyles.T_MARGIN["H2"] assert tokens._marginHead3 == nwStyles.T_MARGIN["H3"] assert tokens._marginHead4 == nwStyles.T_MARGIN["H4"] assert tokens._marginSep == nwStyles.T_MARGIN["SP"] assert tokens._marginText == nwStyles.T_MARGIN["TT"] assert tokens._marginMeta == nwStyles.T_MARGIN["MT"] assert tokens._hidePart is False assert tokens._hideChapter is False assert tokens._hideUnNum is False assert tokens._hideScene is False assert tokens._hideHScene is False assert tokens._hideSection is False assert tokens._linkHeadings is False assert tokens._doBodyText is True assert tokens._doSynopsis is False assert tokens._doComments is False assert tokens._doKeywords is False # Set new values tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}", True) tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}", True) tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}", True) tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True) tokens.setHardSceneFormat(f"H: {nwHeadFmt.TITLE}", True) tokens.setSectionFormat(f"X: {nwHeadFmt.TITLE}", True) tokens.setFont(QFont("Monospace", 10)) tokens.setLineHeight(2.0) tokens.setBlockIndent(6.0) tokens.setJustify(True) tokens.setTitleMargins(2.0, 2.0) tokens.setHead1Margins(2.0, 2.0) tokens.setHead2Margins(2.0, 2.0) tokens.setHead3Margins(2.0, 2.0) tokens.setHead4Margins(2.0, 2.0) tokens.setTextMargins(2.0, 2.0) tokens.setMetaMargins(2.0, 2.0) tokens.setSeparatorMargins(2.0, 2.0) tokens.setLinkHeadings(True) tokens.setBodyText(False) tokens.setSynopsis(True) tokens.setComments(True) tokens.setKeywords(True) # Check new values assert tokens._fmtPart == f"T: {nwHeadFmt.TITLE}" assert tokens._fmtChapter == f"C: {nwHeadFmt.TITLE}" assert tokens._fmtUnNum == f"U: {nwHeadFmt.TITLE}" assert tokens._fmtScene == f"S: {nwHeadFmt.TITLE}" assert tokens._fmtHScene == f"H: {nwHeadFmt.TITLE}" assert tokens._fmtSection == f"X: {nwHeadFmt.TITLE}" assert tokens._textFont == QFont("Monospace", 10) assert tokens._lineHeight == 2.0 assert tokens._blockIndent == 6.0 assert tokens._doJustify is True assert tokens._marginTitle == (2.0, 2.0) assert tokens._marginHead1 == (2.0, 2.0) assert tokens._marginHead2 == (2.0, 2.0) assert tokens._marginHead3 == (2.0, 2.0) assert tokens._marginHead4 == (2.0, 2.0) assert tokens._marginText == (2.0, 2.0) assert tokens._marginMeta == (2.0, 2.0) assert tokens._marginSep == (2.0, 2.0) assert tokens._hidePart is True assert tokens._hideChapter is True assert tokens._hideUnNum is True assert tokens._hideScene is True assert tokens._hideHScene is True assert tokens._hideSection is True assert tokens._linkHeadings is True assert tokens._doBodyText is False assert tokens._doSynopsis is True assert tokens._doComments is True assert tokens._doKeywords is True # Properties assert tokens.result == "" assert tokens.allMarkdown == [] assert tokens.textStats == {} assert tokens.errData == [] # Check Limits tokens.setLineHeight(0.0) assert tokens._lineHeight == 0.5 tokens.setLineHeight(10.0) assert tokens._lineHeight == 5.0 tokens.setBlockIndent(-6.0) assert tokens._blockIndent == 0.0 tokens.setBlockIndent(60.0) assert tokens._blockIndent == 10.0 @pytest.mark.core def testFmtToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath): """Test handling files and text in the Tokenizer class.""" project = NWProject() mockRnd.reset() buildTestProject(project, fncPath) project.data.setLanguage("en") project._loadProjectLocalisation() tokens = ToRaw(project) # Set some content to work with docText = ( "### Scene Six\n\n" "This is text with _italic text_, some **bold text**, some ~~deleted text~~, " "and some **_mixed text_** and **some _nested_ text**.\n\n" "#### Replace\n\n" "Also, replace and .\n\n" ) docTextR = docText.replace("", "this").replace("", "that") nDoc = project.storage.getDocument(C.hSceneDoc) assert nDoc.writeDocument(docText) project.data.setAutoReplace({"A": "this", "B": "that"}) assert project.saveProject() # Root Heading assert len(tokens._tokens) == 0 tokens.addRootHeading("stuff") tokens.addRootHeading(C.hSceneDoc) assert len(tokens._tokens) == 0 # First Page tokens.addRootHeading(C.hPlotRoot) assert tokens.allMarkdown[-1] == "#! Notes: Plot\n\n" assert tokens._tokens[-1] == ( BlockTyp.TITLE, 1, "Notes: Plot", [], BlockFmt.CENTRE ) # Not First Page tokens.addRootHeading(C.hPlotRoot) assert tokens.allMarkdown[-1] == "#! Notes: Plot\n\n" assert tokens._tokens[-1] == ( BlockTyp.TITLE, 1, "Notes: Plot", [], BlockFmt.CENTRE | BlockFmt.PBB ) # Set Text tokens.setText("stuff") assert tokens._text == "" tokens.setText(C.hSceneDoc) assert tokens._text == docText tokens.setText(C.hSceneDoc, docText) assert tokens._text == docText assert tokens._isNovel is True # Pre Processing tokens.doPreProcessing() assert tokens._text == docTextR # Save File savePath = fncPath / "dump.nwd" tokens.saveRawDocument(savePath, asJson=False) assert readFile(savePath) == ( "#! Notes: Plot\n\n" "#! Notes: Plot\n\n" ) tokens.saveRawDocument(savePath, asJson=True) assert json.loads(readFile(savePath))["text"] == { "nwd": [ ["#! Notes: Plot"], ["#! Notes: Plot"] ] } @pytest.mark.core def testFmtToken_StripEscape(): """Test the stripEscape helper function.""" text1 = "This is text with escapes: \\** \\~~ \\__" text2 = "This is text with escapes: ** ~~ __" assert stripEscape(text1) == "This is text with escapes: ** ~~ __" assert stripEscape(text2) == "This is text with escapes: ** ~~ __" @pytest.mark.core def testFmtToken_HeaderFormat(mockGUI): """Test the tokenization of header formats in the Tokenizer class.""" project = NWProject() tokens = ToRaw(project) # Title # ===== # Story File tokens._isNovel = True tokens._isFirst = True tokens._text = "#! Novel Title\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TITLE, 1, "Novel Title", [], BlockFmt.CENTRE), ] assert tokens.allMarkdown[-1] == "#! Novel Title\n\n" # Note File tokens._isNovel = False tokens._isFirst = True tokens._text = "#! Note Title\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TITLE, 1, "Note Title", [], BlockFmt.CENTRE), ] assert tokens.allMarkdown[-1] == "#! Note Title\n\n" # Header 1 # ======== # Story File tokens._isNovel = True tokens._isFirst = True tokens._text = "# Novel Title\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Novel Title", [], BlockFmt.CENTRE), ] assert tokens.allMarkdown[-1] == "# Novel Title\n\n" # Note File tokens._isNovel = False tokens._isFirst = True tokens._text = "# Note Title\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Note Title", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "# Note Title\n\n" # Header 2 # ======== # Story File tokens._isNovel = True tokens._text = "## Chapter One\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "Chapter One", [], BlockFmt.PBB), ] assert tokens.allMarkdown[-1] == "## Chapter One\n\n" # Note File tokens._isNovel = False tokens._text = "## Heading 2\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "Heading 2", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "## Heading 2\n\n" # Header 3 # ======== # Story File tokens._isNovel = True tokens._text = "### Scene One\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD3, 1, "Scene One", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "### Scene One\n\n" # Note File tokens._isNovel = False tokens._text = "### Heading 3\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD3, 1, "Heading 3", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "### Heading 3\n\n" # Header 4 # ======== # Story File tokens._isNovel = True tokens._text = "#### A Section\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD4, 1, "A Section", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "#### A Section\n\n" # Note File tokens._isNovel = False tokens._text = "#### Heading 4\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD4, 1, "Heading 4", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "#### Heading 4\n\n" # Title # ===== # Story File tokens._isNovel = True tokens._isFirst = False tokens._text = "#! Title\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TITLE, 1, "Title", [], BlockFmt.PBB | BlockFmt.CENTRE), ] assert tokens.allMarkdown[-1] == "#! Title\n\n" # Note File tokens._isNovel = False tokens._isFirst = False tokens._text = "#! Title\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TITLE, 1, "Title", [], BlockFmt.PBB | BlockFmt.CENTRE), ] assert tokens.allMarkdown[-1] == "#! Title\n\n" # Unnumbered # ========== # Story File tokens._isNovel = True tokens._text = "##! Prologue\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "Prologue", [], BlockFmt.PBB), ] assert tokens.allMarkdown[-1] == "##! Prologue\n\n" # Note File tokens._isNovel = False tokens._text = "##! Prologue\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "Prologue", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "##! Prologue\n\n" @pytest.mark.core def testFmtToken_HeaderStyle(mockGUI): """Test the styling of headers in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) def processStyle(text: str, first: bool) -> BlockFmt: tokens._text = text tokens._isFirst = first tokens.tokenizeText() return tokens._tokens[0][4] # No Styles # ========= tokens.setPartitionStyle(False, False) tokens.setChapterStyle(False, False) tokens.setSceneStyle(False, False) assert tokens._partStyle == BlockFmt.NONE assert tokens._chapterStyle == BlockFmt.NONE assert tokens._sceneStyle == BlockFmt.NONE # Novel Docs tokens._isNovel = True # First Document is False assert processStyle("# Title\n", False) == BlockFmt.NONE assert processStyle("## Chapter\n", False) == BlockFmt.NONE assert processStyle("### Scene\n", False) == BlockFmt.NONE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.NONE # First Document is True assert processStyle("# Title\n", True) == BlockFmt.NONE assert processStyle("## Chapter\n", True) == BlockFmt.NONE assert processStyle("### Scene\n", True) == BlockFmt.NONE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.NONE # Note Docs tokens._isNovel = False # First Document is False assert processStyle("# Title\n", False) == BlockFmt.NONE assert processStyle("## Chapter\n", False) == BlockFmt.NONE assert processStyle("### Scene\n", False) == BlockFmt.NONE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.NONE # First Document is True assert processStyle("# Title\n", True) == BlockFmt.NONE assert processStyle("## Chapter\n", True) == BlockFmt.NONE assert processStyle("### Scene\n", True) == BlockFmt.NONE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.NONE # Center Headers # ============== tokens.setPartitionStyle(True, False) tokens.setChapterStyle(True, False) tokens.setSceneStyle(True, False) assert tokens._partStyle == BlockFmt.CENTRE assert tokens._chapterStyle == BlockFmt.CENTRE assert tokens._sceneStyle == BlockFmt.CENTRE # Novel Docs tokens._isNovel = True # First Document is False assert processStyle("# Title\n", False) == BlockFmt.CENTRE assert processStyle("## Chapter\n", False) == BlockFmt.CENTRE assert processStyle("### Scene\n", False) == BlockFmt.CENTRE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.CENTRE # First Document is True assert processStyle("# Title\n", True) == BlockFmt.CENTRE assert processStyle("## Chapter\n", True) == BlockFmt.CENTRE assert processStyle("### Scene\n", True) == BlockFmt.CENTRE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.CENTRE # Note Docs tokens._isNovel = False # First Document is False assert processStyle("# Title\n", False) == BlockFmt.NONE assert processStyle("## Chapter\n", False) == BlockFmt.NONE assert processStyle("### Scene\n", False) == BlockFmt.NONE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.NONE # First Document is True assert processStyle("# Title\n", True) == BlockFmt.NONE assert processStyle("## Chapter\n", True) == BlockFmt.NONE assert processStyle("### Scene\n", True) == BlockFmt.NONE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.NONE # Page Break Headers # ================== tokens.setPartitionStyle(False, True) tokens.setChapterStyle(False, True) tokens.setSceneStyle(False, True) assert tokens._partStyle == BlockFmt.PBB assert tokens._chapterStyle == BlockFmt.PBB assert tokens._sceneStyle == BlockFmt.PBB # Novel Docs tokens._isNovel = True # First Document is False assert processStyle("# Title\n", False) == BlockFmt.PBB assert processStyle("## Chapter\n", False) == BlockFmt.PBB assert processStyle("### Scene\n", False) == BlockFmt.PBB assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.PBB # First Document is True assert processStyle("# Title\n", True) == BlockFmt.NONE assert processStyle("## Chapter\n", True) == BlockFmt.NONE assert processStyle("### Scene\n", True) == BlockFmt.NONE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.NONE # Note Docs tokens._isNovel = False # First Document is False assert processStyle("# Title\n", False) == BlockFmt.NONE assert processStyle("## Chapter\n", False) == BlockFmt.NONE assert processStyle("### Scene\n", False) == BlockFmt.NONE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.NONE # First Document is True assert processStyle("# Title\n", True) == BlockFmt.NONE assert processStyle("## Chapter\n", True) == BlockFmt.NONE assert processStyle("### Scene\n", True) == BlockFmt.NONE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.NONE # Page Break and Centre Headers # ============================= tokens.setPartitionStyle(True, True) tokens.setChapterStyle(True, True) tokens.setSceneStyle(True, True) assert tokens._partStyle == BlockFmt.CENTRE | BlockFmt.PBB assert tokens._chapterStyle == BlockFmt.CENTRE | BlockFmt.PBB assert tokens._sceneStyle == BlockFmt.CENTRE | BlockFmt.PBB # Novel Docs tokens._isNovel = True # First Document is False assert processStyle("# Title\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("## Chapter\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("### Scene\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.CENTRE | BlockFmt.PBB # First Document is True assert processStyle("# Title\n", True) == BlockFmt.CENTRE assert processStyle("## Chapter\n", True) == BlockFmt.CENTRE assert processStyle("### Scene\n", True) == BlockFmt.CENTRE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.CENTRE # Note Docs tokens._isNovel = False # First Document is False assert processStyle("# Title\n", False) == BlockFmt.NONE assert processStyle("## Chapter\n", False) == BlockFmt.NONE assert processStyle("### Scene\n", False) == BlockFmt.NONE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.NONE # First Document is True assert processStyle("# Title\n", True) == BlockFmt.NONE assert processStyle("## Chapter\n", True) == BlockFmt.NONE assert processStyle("### Scene\n", True) == BlockFmt.NONE assert processStyle("#### Section\n", True) == BlockFmt.NONE assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE assert processStyle("##! Prologue\n", True) == BlockFmt.NONE # Check Separation # ================ tokens._isNovel = True # Title Styles tokens.setPartitionStyle(True, True) tokens.setChapterStyle(False, False) tokens.setSceneStyle(False, False) assert tokens._partStyle == BlockFmt.CENTRE | BlockFmt.PBB assert tokens._chapterStyle == BlockFmt.NONE assert tokens._sceneStyle == BlockFmt.NONE assert processStyle("# Title\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("## Chapter\n", False) == BlockFmt.NONE assert processStyle("### Scene\n", False) == BlockFmt.NONE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.NONE # Chapter Styles tokens.setPartitionStyle(False, False) tokens.setChapterStyle(True, True) tokens.setSceneStyle(False, False) assert tokens._partStyle == BlockFmt.NONE assert tokens._chapterStyle == BlockFmt.CENTRE | BlockFmt.PBB assert tokens._sceneStyle == BlockFmt.NONE assert processStyle("# Title\n", False) == BlockFmt.NONE assert processStyle("## Chapter\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("### Scene\n", False) == BlockFmt.NONE assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.CENTRE | BlockFmt.PBB # Scene Styles tokens.setPartitionStyle(False, False) tokens.setChapterStyle(False, False) tokens.setSceneStyle(True, True) assert tokens._partStyle == BlockFmt.NONE assert tokens._chapterStyle == BlockFmt.NONE assert tokens._sceneStyle == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("# Title\n", False) == BlockFmt.NONE assert processStyle("## Chapter\n", False) == BlockFmt.NONE assert processStyle("### Scene\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("#### Section\n", False) == BlockFmt.NONE assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB assert processStyle("##! Prologue\n", False) == BlockFmt.NONE @pytest.mark.core def testFmtToken_MetaFormat(mockGUI): """Test the tokenization of meta formats in the Tokenizer class.""" project = NWProject() tokens = ToRaw(project) # Comment tokens._text = "% A comment\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.COMMENT, 0, "A comment", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "\n" tokens.setComments(True) tokens.tokenizeText() assert tokens.allMarkdown[-1] == "% A comment\n\n" # Ignore Text tokens._text = "%~ Some text\n" tokens.tokenizeText() assert tokens._tokens == [] assert tokens.allMarkdown[-1] == "\n" # Synopsis tokens._text = "%synopsis: The synopsis\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.SYNOPSIS, 0, "The synopsis", [], BlockFmt.NONE), ] tokens._text = "% synopsis: The synopsis\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.SYNOPSIS, 0, "The synopsis", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "\n" tokens.setSynopsis(True) tokens.tokenizeText() assert tokens.allMarkdown[-1] == "% synopsis: The synopsis\n\n" # Short tokens.setSynopsis(False) tokens._text = "% short: A short description\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.SHORT, 0, "A short description", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "\n" tokens.setSynopsis(True) tokens.tokenizeText() assert tokens.allMarkdown[-1] == "% short: A short description\n\n" # Keyword tokens._text = "@char: Bod\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.KEYWORD, 0, "char: Bod", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "\n" tokens.setKeywords(True) tokens.tokenizeText() assert tokens.allMarkdown[-1] == "@char: Bod\n\n" tokens._text = "@pov: Bod\n@plot: Main\n@location: Europe\n" tokens.tokenizeText() styTop = BlockFmt.NONE | BlockFmt.Z_BTMMRG styMid = BlockFmt.NONE | BlockFmt.Z_BTMMRG | BlockFmt.Z_TOPMRG styBtm = BlockFmt.NONE | BlockFmt.Z_TOPMRG assert tokens._tokens == [ (BlockTyp.KEYWORD, 0, "pov: Bod", [], styTop), (BlockTyp.KEYWORD, 0, "plot: Main", [], styMid), (BlockTyp.KEYWORD, 0, "location: Europe", [], styBtm), ] assert tokens.allMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n" # Ignored keywords tokens._text = "@pov: Bod\n@plot: Main\n@location: Europe\n" tokens.setIgnoredKeywords("@plot, @location") tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.KEYWORD, 0, "pov: Bod", [], BlockFmt.NONE), ] @pytest.mark.core def testFmtToken_MarginFormat(mockGUI): """Test the tokenization of margin formats in the Tokenizer class.""" project = NWProject() tokens = ToRaw(project) # Alignment and Indentation dblIndent = BlockFmt.IND_L | BlockFmt.IND_R rIndAlign = BlockFmt.RIGHT | BlockFmt.IND_R tokens._text = ( "Some regular text\n\n" "Some left-aligned text <<\n\n" ">> Some right-aligned text\n\n" ">> Some centered text <<\n\n" "> Left-indented block\n\n" "Right-indented block <\n\n" "> Double-indented block <\n\n" ">> Right-indent, right-aligned <\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TEXT, 0, "Some regular text", [], BlockFmt.NONE), (BlockTyp.TEXT, 0, "Some left-aligned text", [], BlockFmt.LEFT), (BlockTyp.TEXT, 0, "Some right-aligned text", [], BlockFmt.RIGHT), (BlockTyp.TEXT, 0, "Some centered text", [], BlockFmt.CENTRE), (BlockTyp.TEXT, 0, "Left-indented block", [], BlockFmt.IND_L), (BlockTyp.TEXT, 0, "Right-indented block", [], BlockFmt.IND_R), (BlockTyp.TEXT, 0, "Double-indented block", [], dblIndent), (BlockTyp.TEXT, 0, "Right-indent, right-aligned", [], rIndAlign), ] assert tokens.allMarkdown[-1] == ( "Some regular text\n\n" "Some left-aligned text\n\n" "Some right-aligned text\n\n" "Some centered text\n\n" "Left-indented block\n\n" "Right-indented block\n\n" "Double-indented block\n\n" "Right-indent, right-aligned\n\n\n" ) @pytest.mark.core def testFmtToken_ExtractFormats(mockGUI): """Test the extraction of formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) # Markdown # ======== # Plain bold text, fmt = tokens._extractFormats("Text with **bold** in it.") assert text == "Text with bold in it." assert fmt == [(10, TextFmt.B_B, ""), (14, TextFmt.B_E, "")] # Plain italics text, fmt = tokens._extractFormats("Text with _italics_ in it.") assert text == "Text with italics in it." assert fmt == [(10, TextFmt.I_B, ""), (17, TextFmt.I_E, "")] # Plain strikethrough text, fmt = tokens._extractFormats("Text with ~~strikethrough~~ in it.") assert text == "Text with strikethrough in it." assert fmt == [(10, TextFmt.D_B, ""), (23, TextFmt.D_E, "")] # Nested bold/italics text, fmt = tokens._extractFormats("Text with **bold and _italics_** in it.") assert text == "Text with bold and italics in it." assert fmt == [ (10, TextFmt.B_B, ""), (19, TextFmt.I_B, ""), (26, TextFmt.I_E, ""), (26, TextFmt.B_E, ""), ] # Bold with overlapping italics # Here, bold is ignored because it is not on word boundary text, fmt = tokens._extractFormats("Text with **bold and overlapping _italics**_ in it.") assert text == "Text with **bold and overlapping italics** in it." assert fmt == [(33, TextFmt.I_B, ""), (42, TextFmt.I_E, "")] # Shortcodes # ========== # Plain bold text, fmt = tokens._extractFormats("Text with [b]bold[/b] in it.") assert text == "Text with bold in it." assert fmt == [(10, TextFmt.B_B, ""), (14, TextFmt.B_E, "")] # Plain italics text, fmt = tokens._extractFormats("Text with [i]italics[/i] in it.") assert text == "Text with italics in it." assert fmt == [(10, TextFmt.I_B, ""), (17, TextFmt.I_E, "")] # Plain strikethrough text, fmt = tokens._extractFormats("Text with [s]strikethrough[/s] in it.") assert text == "Text with strikethrough in it." assert fmt == [(10, TextFmt.D_B, ""), (23, TextFmt.D_E, "")] # Plain underline text, fmt = tokens._extractFormats("Text with [u]underline[/u] in it.") assert text == "Text with underline in it." assert fmt == [(10, TextFmt.U_B, ""), (19, TextFmt.U_E, "")] # Plain mark text, fmt = tokens._extractFormats("Text with [m]highlight[/m] in it.") assert text == "Text with highlight in it." assert fmt == [(10, TextFmt.M_B, ""), (19, TextFmt.M_E, "")] # Plain superscript text, fmt = tokens._extractFormats("Text with super[sup]script[/sup] in it.") assert text == "Text with superscript in it." assert fmt == [(15, TextFmt.SUP_B, ""), (21, TextFmt.SUP_E, "")] # Plain subscript text, fmt = tokens._extractFormats("Text with sub[sub]script[/sub] in it.") assert text == "Text with subscript in it." assert fmt == [(13, TextFmt.SUB_B, ""), (19, TextFmt.SUB_E, "")] # Nested bold/italics text, fmt = tokens._extractFormats("Text with [b]bold and [i]italics[/i][/b] in it.") assert text == "Text with bold and italics in it." assert fmt == [ (10, TextFmt.B_B, ""), (19, TextFmt.I_B, ""), (26, TextFmt.I_E, ""), (26, TextFmt.B_E, ""), ] # Bold with overlapping italics # With shortcodes, this works text, fmt = tokens._extractFormats( "Text with [b]bold and overlapping [i]italics[/b][/i] in it." ) assert text == "Text with bold and overlapping italics in it." assert fmt == [ (10, TextFmt.B_B, ""), (31, TextFmt.I_B, ""), (38, TextFmt.B_E, ""), (38, TextFmt.I_E, ""), ] # So does this text, fmt = tokens._extractFormats( "Text with [b]bold and overlapping [i]italics[/b] in[/i] it." ) assert text == "Text with bold and overlapping italics in it." assert fmt == [ (10, TextFmt.B_B, ""), (31, TextFmt.I_B, ""), (38, TextFmt.B_E, ""), (41, TextFmt.I_E, ""), ] @pytest.mark.core def testFmtToken_Paragraphs(mockGUI): """Test the splitting of paragraphs.""" project = NWProject() tokens = BareTokenizer(project) # Collapse empty lines tokens._text = "First paragraph\n\n\nSecond paragraph\n\n\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TEXT, 0, "First paragraph", [], BlockFmt.NONE), (BlockTyp.TEXT, 0, "Second paragraph", [], BlockFmt.NONE), ] # Combine multi-line paragraphs, keep breaks tokens._text = "This is text\nspanning multiple\nlines" tokens.setKeepLineBreaks(True) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TEXT, 0, "This is text\nspanning multiple\nlines", [], BlockFmt.NONE), ] # Combine multi-line paragraphs, remove breaks tokens._text = "This is text\nspanning multiple\nlines" tokens.setKeepLineBreaks(False) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TEXT, 0, "This is text spanning multiple lines", [], BlockFmt.NONE), ] # Combine multi-line paragraphs, remove breaks, with formatting tokens._text = "This **is text**\nspanning _multiple_\nlines" tokens.setKeepLineBreaks(False) tokens.tokenizeText() assert tokens._tokens == [ ( BlockTyp.TEXT, 0, "This is text spanning multiple lines", [ (5, TextFmt.B_B, ""), (12, TextFmt.B_E, ""), (22, TextFmt.I_B, ""), (30, TextFmt.I_E, ""), ], BlockFmt.NONE ), ] # Make sure titles break a paragraph tokens._text = "# Title\nText _on_\ntwo lines.\n## Chapter\nMore **text**\n_here_.\n\n\n" tokens.setKeepLineBreaks(False) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title", [], BlockFmt.NONE), ( BlockTyp.TEXT, 1, "Text on two lines.", [ (5, TextFmt.I_B, ""), (7, TextFmt.I_E, ""), ], BlockFmt.NONE ), (BlockTyp.HEAD2, 2, "Chapter", [], BlockFmt.NONE), ( BlockTyp.TEXT, 2, "More text here.", [ (5, TextFmt.B_B, ""), (9, TextFmt.B_E, ""), (10, TextFmt.I_B, ""), (14, TextFmt.I_E, ""), ], BlockFmt.NONE ), ] @pytest.mark.core def testFmtToken_TextFormat(mockGUI): """Test the tokenization of text formats in the Tokenizer class.""" project = NWProject() tokens = ToRaw(project) # Text tokens._text = "Some plain text\non two lines\n\n\n" tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TEXT, 0, "Some plain text\non two lines", [], BlockFmt.NONE), ] assert tokens.allMarkdown[-1] == "Some plain text\non two lines\n\n\n\n" tokens.setBodyText(False) tokens.tokenizeText() assert tokens._tokens == [] assert tokens.allMarkdown[-1] == "\n\n\n" tokens.setBodyText(True) # Text Emphasis tokens._text = "Some **bolded text** on this lines\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Some bolded text on this lines", [ (5, TextFmt.B_B, ""), (16, TextFmt.B_E, ""), ], BlockFmt.NONE )] assert tokens.allMarkdown[-1] == "Some **bolded text** on this lines\n\n" tokens._text = "Some _italic text_ on this lines\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Some italic text on this lines", [ (5, TextFmt.I_B, ""), (16, TextFmt.I_E, ""), ], BlockFmt.NONE )] assert tokens.allMarkdown[-1] == "Some _italic text_ on this lines\n\n" tokens._text = "Some **_bold italic text_** on this lines\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Some bold italic text on this lines", [ (5, TextFmt.B_B, ""), (5, TextFmt.I_B, ""), (21, TextFmt.I_E, ""), (21, TextFmt.B_E, ""), ], BlockFmt.NONE )] assert tokens.allMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n" tokens._text = "Some ~~strikethrough text~~ on this lines\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Some strikethrough text on this lines", [ (5, TextFmt.D_B, ""), (23, TextFmt.D_E, ""), ], BlockFmt.NONE )] assert tokens.allMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n" tokens._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Some nested bold and italic and strikethrough text here", [ (5, TextFmt.B_B, ""), (21, TextFmt.I_B, ""), (27, TextFmt.I_E, ""), (32, TextFmt.D_B, ""), (45, TextFmt.D_E, ""), (50, TextFmt.B_E, ""), ], BlockFmt.NONE )] assert tokens.allMarkdown[-1] == ( "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n" ) @pytest.mark.core def testFmtToken_Dialogue(mockGUI): """Test the tokenization of dialogue in the Tokenizer class.""" CONFIG.fmtDQuoteOpen = "\u201c" CONFIG.fmtDQuoteClose = "\u201d" CONFIG.fmtSQuoteOpen = "\u2018" CONFIG.fmtSQuoteClose = "\u2019" CONFIG.dialogStyle = 3 CONFIG.altDialogOpen = "::" CONFIG.altDialogClose = "::" CONFIG.dialogLine = "\u2013" CONFIG.narratorBreak = "\u2013" project = NWProject() tokens = BareTokenizer(project) tokens.setDialogueHighlight(True) tokens._isNovel = True # Single quotes tokens._text = "Text with \u2018dialogue one,\u2019 and \u2018dialogue two.\u2019\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Text with \u2018dialogue one,\u2019 and \u2018dialogue two.\u2019", [ (10, TextFmt.DL_B, ""), (25, TextFmt.DL_E, ""), (30, TextFmt.DL_B, ""), (45, TextFmt.DL_E, ""), ], BlockFmt.NONE )] # Double quotes tokens._text = "Text with \u201cdialogue one,\u201d and \u201cdialogue two.\u201d\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Text with \u201cdialogue one,\u201d and \u201cdialogue two.\u201d", [ (10, TextFmt.DL_B, ""), (25, TextFmt.DL_E, ""), (30, TextFmt.DL_B, ""), (45, TextFmt.DL_E, ""), ], BlockFmt.NONE )] # Alt quotes tokens._text = "Text with ::dialogue one,:: and ::dialogue two.::\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "Text with ::dialogue one,:: and ::dialogue two.::", [ (10, TextFmt.ADL_B, ""), (27, TextFmt.ADL_E, ""), (32, TextFmt.ADL_B, ""), (49, TextFmt.ADL_E, ""), ], BlockFmt.NONE )] # Dialogue line with narrator break tokens._text = "\u2013 Dialogue with a narrator break, \u2013he said,\u2013 see?\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "\u2013 Dialogue with a narrator break, \u2013he said,\u2013 see?", [ (0, TextFmt.DL_B, ""), (34, TextFmt.DL_E, ""), (44, TextFmt.DL_B, ""), (49, TextFmt.DL_E, ""), ], BlockFmt.NONE )] # Special Cases # ============= # Dialogue + formatting on same index (Issue #2012) tokens._text = "[i]\u201cDialogue text.\u201d[/i]\n" tokens.tokenizeText() assert tokens._tokens == [( BlockTyp.TEXT, 0, "\u201cDialogue text.\u201d", [ (0, TextFmt.I_B, ""), (0, TextFmt.DL_B, ""), (16, TextFmt.I_E, ""), (16, TextFmt.DL_E, ""), ], BlockFmt.NONE )] @pytest.mark.core def testFmtToken_SpecialFormat(mockGUI): """Test the tokenization of special formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) tokens._isNovel = True # New Page # ======== correctResp = [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.CENTRE), (BlockTyp.HEAD1, 2, "Title Two", [], BlockFmt.CENTRE | BlockFmt.PBB), ] # Command wo/Space tokens._isFirst = True tokens._text = ( "# Title One\n\n" "[newpage]\n\n" "# Title Two\n\n" ) tokens.tokenizeText() assert tokens._tokens == correctResp # Command w/Space tokens._isFirst = True tokens._text = ( "# Title One\n\n" "[new page]\n\n" "# Title Two\n\n" ) tokens.tokenizeText() assert tokens._tokens == correctResp # Trailing Spaces tokens._isFirst = True tokens._text = ( "# Title One\n\n" "[new page] \t\n\n" "# Title Two\n\n" ) tokens.tokenizeText() assert tokens._tokens == correctResp # Single Empty Paragraph # ====================== tokens._text = ( "# Title One\n\n" "[vspace] \n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] # Multiple Empty Paragraphs # ========================= # One Skip tokens._text = ( "# Title One\n\n" "[vspace:1] \n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] # Three Skips tokens._text = ( "# Title One\n\n" "[vspace:3] \n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] # Malformed Command, Case 1 tokens._text = ( "# Title One\n\n" "[vspace:3xa] \n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] # Malformed Command, Case 2 tokens._text = ( "# Title One\n\n" "[vspace:3.5]\n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] # Malformed Command, Case 3 tokens._text = ( "# Title One\n\n" "[vspace:-1]\n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] # Empty Paragraph and Page Break # ============================== # Single Skip tokens._text = ( "# Title One\n\n" "[new page]\n\n" "[vspace]\n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.SKIP, 1, "", [], BlockFmt.PBB), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] # Multiple Skip tokens._text = ( "# Title One\n\n" "[new page]\n\n" "[vspace:3]\n\n" "Some text to go here ...\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.PBB | BlockFmt.CENTRE), (BlockTyp.SKIP, 1, "", [], BlockFmt.PBB), (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), (BlockTyp.TEXT, 1, "Some text to go here ...", [], BlockFmt.NONE), ] @pytest.mark.core def testFmtToken_TextIndent(mockGUI): """Test the handling of text indent in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) # No First Indent tokens.setFirstLineIndent(True, 1.0, False) assert tokens._noIndent is False assert tokens._firstIndent is True assert tokens._firstWidth == 1.0 assert tokens._indentFirst is False # Page One # Two paragraphs in the same scene tokens._text = ( "# Title One\n\n" "### Scene One\n\n" "First paragraph.\n\n" "Second paragraph.\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.NONE), (BlockTyp.HEAD3, 2, "Scene One", [], BlockFmt.NONE), (BlockTyp.TEXT, 2, "First paragraph.", [], BlockFmt.NONE), (BlockTyp.TEXT, 2, "Second paragraph.", [], BlockFmt.IND_T), ] assert tokens._noIndent is False # Page Two # New scene with only a synopsis tokens._text = ( "### Scene Two\n\n" "%Synopsis: Stuff happens.\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD3, 1, "Scene Two", [], BlockFmt.NONE), (BlockTyp.SYNOPSIS, 1, "Stuff happens.", [], BlockFmt.NONE), ] assert tokens._noIndent is True # Page Three # Two paragraphs for the scene on the previous page tokens._text = ( "First paragraph.\n\n" "Second paragraph.\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.TEXT, 0, "First paragraph.", [], BlockFmt.NONE), (BlockTyp.TEXT, 0, "Second paragraph.", [], BlockFmt.IND_T), ] assert tokens._noIndent is False # First Indent tokens.setFirstLineIndent(True, 1.0, True) assert tokens._noIndent is False assert tokens._firstIndent is True assert tokens._firstWidth == 1.0 assert tokens._indentFirst is True # Page Four # Two paragraphs in the same scene tokens._text = ( "# Title One\n\n" "### Scene One\n\n" "First paragraph.\n\n" "Second paragraph.\n\n" ) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "Title One", [], BlockFmt.NONE), (BlockTyp.HEAD3, 2, "Scene One", [], BlockFmt.NONE), (BlockTyp.TEXT, 2, "First paragraph.", [], BlockFmt.IND_T), (BlockTyp.TEXT, 2, "Second paragraph.", [], BlockFmt.IND_T), ] assert tokens._noIndent is False @pytest.mark.core def testFmtToken_ProcessHeaders(mockGUI): """Test the header and page parser of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") project._loadProjectLocalisation() tokens = BareTokenizer(project) tokens._isNovel = True # Titles # ====== # H1: Title, First Page assert tokens._isFirst is True tokens._text = "# Part One\n" tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "T: Part One", [], BlockFmt.CENTRE), ] # H1: Title, Not First Page assert tokens._isFirst is False tokens._text = "# Part One\n" tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD1, 1, "T: Part One", [], BlockFmt.PBB | BlockFmt.CENTRE), ] # Chapters # ======== # H2: Chapter tokens._text = "## Chapter One\n" tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}") tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "C: Chapter One", [], BlockFmt.PBB), ] # H2: Unnumbered Chapter tokens._text = "##! Prologue\n" tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}") tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "U: Prologue", [], BlockFmt.PBB), ] # H2: Chapter Word Number tokens._text = "## Chapter\n" tokens.setChapterFormat(f"Chapter {nwHeadFmt.CH_WORD}") tokens._hFormatter._chCount = 0 tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "Chapter One", [], BlockFmt.PBB), ] # H2: Chapter Roman Number Upper Case tokens._text = "## Chapter\n" tokens.setChapterFormat(f"Chapter {nwHeadFmt.CH_ROMU}") tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "Chapter II", [], BlockFmt.PBB), ] # H2: Chapter Roman Number Lower Case tokens._text = "## Chapter\n" tokens.setChapterFormat(f"Chapter {nwHeadFmt.CH_ROML}") tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD2, 1, "Chapter iii", [], BlockFmt.PBB), ] # Scenes # ====== # H3: Scene w/Title tokens._text = "### Scene One\n" tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", False) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD3, 1, "S: Scene One", [], BlockFmt.NONE), ] # H3: Scene Hidden wo/Format tokens._text = "### Scene One\n" tokens.setSceneFormat("", True) tokens.tokenizeText() assert tokens._tokens == [] # H3: Scene wo/Format, first tokens._text = "### Scene One\n" tokens.setSceneFormat("", False) tokens._noSep = True tokens.tokenizeText() assert tokens._tokens == [] # H3: Scene wo/Format, not first tokens._text = "### Scene One\n" tokens.setSceneFormat("", False) tokens._noSep = False tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), ] # H3: Scene Separator, first tokens._text = "### Scene One\n" tokens.setSceneFormat("* * *", False) tokens._noSep = True tokens.tokenizeText() assert tokens._tokens == [] # H3: Scene Separator, not first tokens._text = "### Scene One\n" tokens.setSceneFormat("* * *", False) tokens._noSep = False tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.SEP, 1, "* * *", [], BlockFmt.CENTRE), ] # H3: Scene w/Absolute Number tokens._text = "### A Scene\n" tokens.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}", False) tokens._hFormatter._scAbsCount = 0 tokens._hFormatter._scChCount = 0 tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD3, 1, "Scene 1", [], BlockFmt.NONE), ] # H3: Scene w/Chapter Number tokens._text = "### A Scene\n" tokens.setSceneFormat(f"Scene {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM}", False) tokens._hFormatter._scAbsCount = 0 tokens._hFormatter._scChCount = 1 tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD3, 1, "Scene 3.2", [], BlockFmt.NONE), ] # Sections # ======== # H4: Section Hidden wo/Format tokens._text = "#### A Section\n" tokens.setSectionFormat("", True) tokens.tokenizeText() assert tokens._tokens == [] # H4: Section Visible wo/Format tokens._text = "#### A Section\n" tokens.setSectionFormat("", False) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.SKIP, 1, "", [], BlockFmt.NONE), ] # H4: Section w/Format tokens._text = "#### A Section\n" tokens.setSectionFormat(f"X: {nwHeadFmt.TITLE}", False) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.HEAD4, 1, "X: A Section", [], BlockFmt.NONE), ] # H4: Section Separator tokens._text = "#### A Section\n" tokens.setSectionFormat("* * *", False) tokens.tokenizeText() assert tokens._tokens == [ (BlockTyp.SEP, 1, "* * *", [], BlockFmt.CENTRE), ] # Check the first scene detector, plain text tokens._noSep = True tokens._text = "Some text ...\n" tokens.tokenizeText() assert tokens._noSep is True # Check the first scene detector, text plus scene tokens._noSep = True tokens._text = "Some text ...\n\n### Scene\n\nText" tokens.tokenizeText() assert tokens._noSep is False @pytest.mark.core def testFmtToken_BuildOutline(mockGUI, ipsumText): """Test stats counter of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") project._loadProjectLocalisation() tokens = BareTokenizer(project) # Novel tokens._isNovel = True tokens._handle = "0000000000000" tokens._text = ( "#! My Novel\n\n" "# Part One\n\n" "## Chapter One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "Text\n\n" "## Chapter Two\n\n" "### Scene Three\n\n" "Text\n\n" "###! Scene Four\n\n" "Text\n\n" "#### Section\n\n" "Text\n\n" ) tokens.tokenizeText() tokens.buildOutline() # Note tokens._isNovel = False tokens._handle = "0000000000001" tokens._text = ( "#! My Notes\n\n" "# Header 1\n\n" "Text\n\n" "## Header 2\n\n" "Text\n\n" "### Header 3\n\n" "Text\n\n" "#### Header 4\n\n" "Text\n\n" ) tokens.tokenizeText() tokens.buildOutline() # Check Outline assert tokens.textOutline == { "0000000000000:T0001": "TT|My Novel", "0000000000000:T0002": "PT|Part One", "0000000000000:T0003": "CH|Chapter One", "0000000000000:T0004": "SC|Scene One", "0000000000000:T0005": "SC|Scene Two", "0000000000000:T0006": "CH|Chapter Two", "0000000000000:T0007": "SC|Scene Three", "0000000000000:T0008": "SC|Scene Four", "0000000000001:T0001": "TT|My Notes", "0000000000001:T0002": "H1|Header 1", "0000000000001:T0003": "H2|Header 2", "0000000000001:T0004": "H3|Header 3", } @pytest.mark.core def testFmtToken_CountStats(mockGUI, ipsumText): """Test stats counter of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") project._loadProjectLocalisation() tokens = BareTokenizer(project) tokens._isNovel = True # Short Text # ========== # Header wo/Format tokens._text = "## A Chapter Title\n\n" tokens._counts = {} tokens.tokenizeText() tokens.countStats() assert tokens._tokens[0][2] == "A Chapter Title" assert tokens.textStats == { "titleCount": 1, "paragraphCount": 0, "allWords": 3, "textWords": 0, "titleWords": 3, "allChars": 15, "textChars": 0, "titleChars": 15, "allWordChars": 13, "textWordChars": 0, "titleWordChars": 13 } # Header w/Format tokens._text = "## A Chapter Title\n\n" tokens._counts = {} tokens.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}") tokens._hFormatter.resetAll() tokens.tokenizeText() tokens.countStats() assert tokens._tokens[0][2] == "C 1: A Chapter Title" assert tokens.textStats == { "titleCount": 1, "paragraphCount": 0, "allWords": 5, "textWords": 0, "titleWords": 5, "allChars": 20, "textChars": 0, "titleChars": 20, "allWordChars": 16, "textWordChars": 0, "titleWordChars": 16 } # Two Paragraphs # First break should be counted, the double breaks not. tokens._text = "Some text\non two lines.\n\nWith a second paragraph.\n\n" tokens._counts = {} tokens.tokenizeText() tokens.countStats() assert tokens.textStats == { "titleCount": 0, "paragraphCount": 2, "allWords": 9, "textWords": 9, "titleWords": 0, "allChars": 47, "textChars": 47, "titleChars": 0, "allWordChars": 40, "textWordChars": 40, "titleWordChars": 0 } # Two Scenes w/Separator tokens._text = "## Chapter\n\n### Scene\n\nText\n\n### Scene\n\nText" tokens._counts = {} tokens.setChapterFormat(nwHeadFmt.TITLE) tokens.setSceneFormat("* * *", False) tokens.tokenizeText() tokens.countStats() assert [t[2] for t in tokens._tokens] == ["Chapter", "Text", "* * *", "Text"] assert tokens.textStats == { "titleCount": 1, "paragraphCount": 2, "allWords": 6, "textWords": 2, "titleWords": 1, "allChars": 20, "textChars": 8, "titleChars": 7, "allWordChars": 18, "textWordChars": 8, "titleWordChars": 7 } # Scene w/Synopsis # Synopsis does not count as a paragraph, and counts as "Synopsis: Stuff" tokens._text = "## Chapter\n\n### Scene\n\n%Synopsis: Stuff\n\nText" tokens._counts = {} tokens.setChapterFormat(nwHeadFmt.TITLE) tokens.setSceneFormat("* * *", False) tokens.setSynopsis(True) tokens.tokenizeText() tokens.countStats() assert [t[2] for t in tokens._tokens] == ["Chapter", "Stuff", "Text"] assert tokens.textStats == { "titleCount": 1, "paragraphCount": 1, "allWords": 4, "textWords": 1, "titleWords": 1, "allChars": 26, "textChars": 4, "titleChars": 7, "allWordChars": 25, "textWordChars": 4, "titleWordChars": 7 } # Scene w/Short # Short does not count as a paragraph, and counts as "Short Description: Stuff" tokens._text = "## Chapter\n\n### Scene\n\n%Short: Stuff\n\nText" tokens._counts = {} tokens.setChapterFormat(nwHeadFmt.TITLE) tokens.setSceneFormat("* * *", False) tokens.setSynopsis(True) tokens.tokenizeText() tokens.countStats() assert [t[2] for t in tokens._tokens] == ["Chapter", "Stuff", "Text"] assert tokens.textStats == { "titleCount": 1, "paragraphCount": 1, "allWords": 5, "textWords": 1, "titleWords": 1, "allChars": 35, "textChars": 4, "titleChars": 7, "allWordChars": 33, "textWordChars": 4, "titleWordChars": 7 } # Scene w/Comment # Comment does not count as a paragraph, and counts as "Comment: Stuff" tokens._text = "## Chapter\n\n### Scene\n\n% Stuff\n\nText" tokens._counts = {} tokens.setChapterFormat(nwHeadFmt.TITLE) tokens.setSceneFormat("* * *", False) tokens.setComments(True) tokens.tokenizeText() tokens.countStats() assert [t[2] for t in tokens._tokens] == ["Chapter", "Stuff", "Text"] assert tokens.textStats == { "titleCount": 1, "paragraphCount": 1, "allWords": 4, "textWords": 1, "titleWords": 1, "allChars": 25, "textChars": 4, "titleChars": 7, "allWordChars": 24, "textWordChars": 4, "titleWordChars": 7 } # Scene w/Keyword # Keyword does not count as a paragraph, and counts as "Point of View: Jane" tokens._text = "## Chapter\n\n### Scene\n\n@pov: Jane\n\nText" tokens._counts = {} tokens.setChapterFormat(nwHeadFmt.TITLE) tokens.setSceneFormat("* * *", False) tokens.setKeywords(True) tokens.tokenizeText() tokens.countStats() assert [t[2] for t in tokens._tokens] == ["Chapter", "pov: Jane", "Text"] assert tokens.textStats == { "titleCount": 1, "paragraphCount": 1, "allWords": 6, "textWords": 1, "titleWords": 1, "allChars": 30, "textChars": 4, "titleChars": 7, "allWordChars": 27, "textWordChars": 4, "titleWordChars": 7 } # Long Text # ========= tokens._text = ( "# Act One\n\n" "## Chapter\n\n" "### Scene\n\n" "@pov: Jane\n" "@char: Jane, John\n\n" "%Synopsis: A scene\n\n" f"{ipsumText[0]}.\n\n" "### Scene\n\n" "@pov: Jane\n" "@char: Jane, John\n\n" "%Synopsis: A scene\n\n" f"{ipsumText[1]}.\n\n" "## Chapter\n\n" "### Scene\n\n" "@pov: Jane\n" "@char: Jane, John\n\n" "%Synopsis: A scene\n\n" f"{ipsumText[2]}.\n\n" "### Scene\n\n" "@pov: Jane\n" "@char: Jane, John\n\n" "%Synopsis: A scene\n\n" f"{ipsumText[3]}.\n\n" "## Chapter\n\n" "### Scene\n\n" "@pov: Jane\n" "@char: Jane, John\n\n" "%Synopsis: A scene\n\n" f"{ipsumText[4]}.\n\n" ) tokens._counts = {} tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") tokens.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}") tokens.setSceneFormat("* * *", False) tokens.setSynopsis(True) tokens.setComments(True) tokens.setKeywords(True) tokens.tokenizeText() tokens.countStats() assert tokens.textStats == { "titleCount": 4, "paragraphCount": 5, "allWords": 596, "textWords": 528, "titleWords": 12, "allChars": 3859, "textChars": 3513, "titleChars": 46, "allWordChars": 3289, "textWordChars": 2990, "titleWordChars": 38 } @pytest.mark.core def testFmtToken_SceneSeparators(mockGUI): """Test the section and scene separators of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") project._loadProjectLocalisation() md = ToMarkdown(project, False) md._isNovel = True # Separator Handling, Titles # ========================== md._text = ( "# Title One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "Text\n\n" "# Title Two\n\n" "### Scene Three\n\n" "Text\n\n" "###! Scene Four\n\n" "Text\n\n" ) md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") md.setChapterFormat(f"C: {nwHeadFmt.TITLE}") md.setSectionFormat("", True) # Static Separator md.setSceneFormat("~", False) md.setHardSceneFormat("* * *", False) md.tokenizeText() md.doConvert() assert md.result == ( "# T: Title One\n\n" "Text\n\n" "~\n\n" "Text\n\n" "# T: Title Two\n\n" "Text\n\n" "* * *\n\n" "Text\n\n" ) # Scene Title Formatted md.setSceneFormat(f"S: {nwHeadFmt.TITLE}", False) md.setHardSceneFormat(f"H: {nwHeadFmt.TITLE}", False) md.tokenizeText() md.doConvert() assert md.result == ( "# T: Title One\n\n" "### S: Scene One\n\n" "Text\n\n" "### S: Scene Two\n\n" "Text\n\n" "# T: Title Two\n\n" "### S: Scene Three\n\n" "Text\n\n" "### H: Scene Four\n\n" "Text\n\n" ) # Separator Handling, Chapters # ============================ md._text = ( "# Title One\n\n" "## Chapter One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "Text\n\n" "## Chapter Two\n\n" "### Scene Three\n\n" "Text\n\n" "###! Scene Four\n\n" "Text\n\n" ) md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") md.setChapterFormat(f"C: {nwHeadFmt.TITLE}") md.setSectionFormat("", True) # Static Separator md.setSceneFormat("* * *", False) md.setHardSceneFormat("* * *", False) md.tokenizeText() md.doConvert() assert md.result == ( "# T: Title One\n\n" "## C: Chapter One\n\n" "Text\n\n" "* * *\n\n" "Text\n\n" "## C: Chapter Two\n\n" "Text\n\n" "* * *\n\n" "Text\n\n" ) # Scene Title Formatted md.setSceneFormat(f"S: {nwHeadFmt.TITLE}", False) md.setHardSceneFormat(f"H: {nwHeadFmt.TITLE}", False) md.tokenizeText() md.doConvert() assert md.result == ( "# T: Title One\n\n" "## C: Chapter One\n\n" "### S: Scene One\n\n" "Text\n\n" "### S: Scene Two\n\n" "Text\n\n" "## C: Chapter Two\n\n" "### S: Scene Three\n\n" "Text\n\n" "### H: Scene Four\n\n" "Text\n\n" ) # Separators with Scenes Only # =========================== # Requires a fresh builder class md = ToMarkdown(project, True) md._isNovel = True md._text = ( "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "Text\n\n" "### Scene Three\n\n" "Text\n\n" "###! Scene Four\n\n" "Text\n\n" ) md.setSceneFormat("", False) md.setHardSceneFormat("* * *", False) md.tokenizeText() md.doConvert() assert md.result == ( "Text\n\n" "\u205f\n\n" "Text\n\n" "\u205f\n\n" "Text\n\n" "* * *\n\n" "Text\n\n" ) @pytest.mark.core def testFmtToken_HeaderVisibility(mockGUI): """Test the heading visibility settings of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") project._loadProjectLocalisation() md = ToMarkdown(project, False) md._text = ( "#! Novel\n\n" "# Title One\n\n" "##! Prologue\n\n" "Text\n\n" "## Chapter One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "#### Section Two\n\n" "Text\n\n" "## Chapter Two\n\n" "### Scene Three\n\n" "Text\n\n" "###! Scene Four\n\n" "Text\n\n" ) # Novel Files # =========== md._isNovel = True # Show All md.setPartitionFormat(nwHeadFmt.TITLE, False) md.setChapterFormat(nwHeadFmt.TITLE, False) md.setUnNumberedFormat(nwHeadFmt.TITLE, False) md.setSceneFormat(nwHeadFmt.TITLE, False) md.setHardSceneFormat(nwHeadFmt.TITLE, False) md.setSectionFormat(nwHeadFmt.TITLE, False) md.tokenizeText() md.doConvert() assert md.result == ( "# Novel\n\n" "# Title One\n\n" "## Prologue\n\n" "Text\n\n" "## Chapter One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "#### Section Two\n\n" "Text\n\n" "## Chapter Two\n\n" "### Scene Three\n\n" "Text\n\n" "### Scene Four\n\n" "Text\n\n" ) # Hide All md.setPartitionFormat(nwHeadFmt.TITLE, True) md.setChapterFormat(nwHeadFmt.TITLE, True) md.setUnNumberedFormat(nwHeadFmt.TITLE, True) md.setSceneFormat(nwHeadFmt.TITLE, True) md.setHardSceneFormat(nwHeadFmt.TITLE, True) md.setSectionFormat(nwHeadFmt.TITLE, True) md.tokenizeText() md.doConvert() assert md.result == ( "# Novel\n\n" "Text\n\n" "Text\n\n" "Text\n\n" "Text\n\n" "Text\n\n" ) # Note Files # ========== md._isNovel = False # Hide All md.setPartitionFormat(nwHeadFmt.TITLE, True) md.setChapterFormat(nwHeadFmt.TITLE, True) md.setUnNumberedFormat(nwHeadFmt.TITLE, True) md.setSceneFormat(nwHeadFmt.TITLE, True) md.setHardSceneFormat(nwHeadFmt.TITLE, True) md.setSectionFormat(nwHeadFmt.TITLE, True) md.tokenizeText() md.doConvert() assert md.result == ( "# Novel\n\n" "# Title One\n\n" "## Prologue\n\n" "Text\n\n" "## Chapter One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "#### Section Two\n\n" "Text\n\n" "## Chapter Two\n\n" "### Scene Three\n\n" "Text\n\n" "### Scene Four\n\n" "Text\n\n" ) @pytest.mark.core def testFmtToken_CounterHandling(mockGUI): """Test the heading counter of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") project._loadProjectLocalisation() md = ToMarkdown(project, False) md._isNovel = True # Counter Handling, Novel Titles # ============================== # This also checks that only numbered chapters bump the counter md._text = ( "#! Novel One\n\n" "##! Prologue\n\n" "Text\n\n" "## Chapter One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "Text\n\n" "## Chapter Two\n\n" "### Scene Three\n\n" "Text\n\n" "###! Scene Four\n\n" "Text\n\n" "#! Novel Two\n\n" "##! Prologue\n\n" "Text\n\n" "## Chapter One\n\n" "### Scene One\n\n" "Text\n\n" "### Scene Two\n\n" "Text\n\n" "## Chapter Two\n\n" "### Scene Three\n\n" "Text\n\n" "###! Scene Four\n\n" "Text\n\n" ) md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") md.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}") md.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}") md.setSceneFormat( f"S {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM} ({nwHeadFmt.SC_ABS}): {nwHeadFmt.TITLE}", False ) md.setHardSceneFormat( f"H {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM} ({nwHeadFmt.SC_ABS}): {nwHeadFmt.TITLE}", False ) md.setSectionFormat("", True) # Two Novel Format md.tokenizeText() md.doConvert() assert md.result == ( "# Novel One\n\n" "## U: Prologue\n\n" "Text\n\n" "## C 1: Chapter One\n\n" "### S 1.1 (1): Scene One\n\n" "Text\n\n" "### S 1.2 (2): Scene Two\n\n" "Text\n\n" "## C 2: Chapter Two\n\n" "### S 2.1 (3): Scene Three\n\n" "Text\n\n" "### H 2.2 (4): Scene Four\n\n" "Text\n\n" "# Novel Two\n\n" "## U: Prologue\n\n" "Text\n\n" "## C 1: Chapter One\n\n" "### S 1.1 (1): Scene One\n\n" "Text\n\n" "### S 1.2 (2): Scene Two\n\n" "Text\n\n" "## C 2: Chapter Two\n\n" "### S 2.1 (3): Scene Three\n\n" "Text\n\n" "### H 2.2 (4): Scene Four\n\n" "Text\n\n" ) @pytest.mark.core def testFmtToken_HeadingFormatter(fncPath, mockGUI, mockRnd): """Check the HeadingFormatter class.""" project = NWProject() project.setProjectLang("en_GB") mockRnd.reset() buildTestProject(project, fncPath) nHandle = project.newFile("Hello", C.hNovelRoot) cHandle = project.newFile("Jane", C.hCharRoot) dHandle = project.newFile("John", C.hCharRoot) assert isinstance(nHandle, str) assert isinstance(cHandle, str) assert isinstance(dHandle, str) assert project.index.scanText(cHandle, ( "# Jane Smith\n" "@tag: Jane | Jane Smith\n" )) assert project.index.scanText(dHandle, ( "# John Smith\n" "@tag: John | John Smith\n" )) assert project.index.scanText(nHandle, ( "## Hi Bob\n" "@pov: Jane\n" "@focus: John\n" "@char: Jane, John\n\n" "This is a story about Jane Smith.\n\n" "Well, not really.\n" )) formatter = HeadingFormatter(project) formatter.setHandle(nHandle) # Counters # ======== cFormat = ( f"Chapter {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM} - " f"Scene {nwHeadFmt.SC_ABS} - {nwHeadFmt.TITLE}" ) # Initial assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 0.0 - Scene 0 - Hi Bob" # First Chapter and Scene formatter.incChapter() formatter.incScene() assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.1 - Scene 1 - Hi Bob" # Next Scenes formatter.incScene() assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.2 - Scene 2 - Hi Bob" formatter.incScene() assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.3 - Scene 3 - Hi Bob" formatter.incScene() assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.4 - Scene 4 - Hi Bob" # Next Chapter and Scene formatter.incChapter() formatter.resetScene() formatter.incScene() assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 2.1 - Scene 5 - Hi Bob" # New Main Title formatter.resetAll() formatter.incChapter() formatter.incScene() assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.1 - Scene 1 - Hi Bob" # Special Formats # =============== formatter._chCount = 2 formatter._scChCount = 3 formatter._scAbsCount = 8 # Chapter Number Word cFormat = f"Chapter {nwHeadFmt.CH_WORD}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.TITLE}" assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter Two, Scene 3 - Hi Bob" # Chapter Upper Case Roman cFormat = f"Chapter {nwHeadFmt.CH_ROMU}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.TITLE}" assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter II, Scene 3 - Hi Bob" # Chapter Lower Case Roman cFormat = f"Chapter {nwHeadFmt.CH_ROML}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.TITLE}" assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter ii, Scene 3 - Hi Bob" # Chapter w/PoV Character cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_POV}" assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 2, Scene 3 - Jane Smith" # Chapter w/Focus Character cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_FOCUS}" assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 2, Scene 3 - John Smith" # Chapter w/Fallback PoV cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_POV}" assert formatter.apply(cFormat, "Hi Bob", 0) == "Chapter 2, Scene 3 - Point of View" # Chapter w/Fallback Focus cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_FOCUS}" assert formatter.apply(cFormat, "Hi Bob", 0) == "Chapter 2, Scene 3 - Focus"