diff --git a/CHANGELOG.md b/CHANGELOG.md index cb14503..4abe27a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## Unreleased + +### Added + +- Options for customizing proof directive title format. [#163](https://github.com/executablebooks/sphinx-proof/pull/163) +- Possibility for shared numbering of groups of directives, fixing [\#64](https://github.com/executablebooks/sphinx-proof/issues/64). [\#https://github.com/executablebooks/sphinx-proof/pull/161](https://github.com/executablebooks/sphinx-proof/pull/161) + +### Fixed + +- Inconsistencies and missing colors within CSS styles. [b337c21](https://github.com/executablebooks/sphinx-proof/commit/b337c21675464224a4418beb9e70d3b6dc8e4127) +- Missing documentation for notation directive. [6f33744](https://github.com/executablebooks/sphinx-proof/commit/6f33744544b7596a30beba5b8807491d642b77a7) and [b15c7c0](https://github.com/executablebooks/sphinx-proof/commit/b15c7c09adefe85e0fdb8dea6dc460bd0f51fb1f) +- Nesting of unnumbered directives, fixing [\#165](https://github.com/executablebooks/sphinx-proof/issues/165). + ## v0.3.0 (2025-10-20) ### NEW ✨ diff --git a/README.md b/README.md index 2696c10..e62f5e0 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,18 @@ This package contains a [Sphinx](http://www.sphinx-doc.org/) extension for producing proof, theorem, axiom, lemma, definition, criterion, remark, conjecture, -corollary, algorithm, example, property, observation, proposition and assumption directives. +corollary, algorithm, example, property, observation, proposition, assumption and notation directives. ## Features - **15 directive types** for mathematical proofs and theorems - **Automatic numbering** of directives + - optional shared numbering of groups of directives - **Cross-referencing** support via `prf:ref` role - **33 languages supported** - Complete translations for all directive types in English plus 32 additional languages (Arabic, Bengali, Bulgarian, Chinese, Czech, Danish, Dutch, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Malay, Norwegian, Persian, Polish, Portuguese, Romanian, Russian, Spanish, Swedish, Thai, Turkish, Ukrainian, Urdu, Vietnamese) -- **Customizable styling** with multiple theme options - +- **Customizable styling** with: + - two theme options + - directive title format customization ## Get started diff --git a/docs/source/conf.py b/docs/source/conf.py index a0347f4..1d89a4f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -56,3 +56,5 @@ # MyST Parser Configuration myst_enable_extensions = ["dollarmath", "amsmath"] + +prf_realtyp_to_countertyp = {} diff --git a/docs/source/index.md b/docs/source/index.md index c0924b8..0e24000 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -22,7 +22,7 @@ for producing [proof](syntax:proof), [theorem](syntax:theorem), [axiom](syntax:a [definition](syntax:definition), [criterion](syntax:criterion), [remark](syntax:remark), [conjecture](syntax:conjecture),[corollary](syntax:corollary), [algorithm](syntax:algorithm), [example](syntax:example), [property](syntax:property), [observation](syntax:observation), -[proposition](syntax:proposition) and [assumption](syntax:assumption) directives. +[proposition](syntax:proposition), [assumption](syntax:assumption) and [notation](syntax:notation) directives. **Features**: diff --git a/docs/source/options.md b/docs/source/options.md index 6433d49..d2c6942 100644 --- a/docs/source/options.md +++ b/docs/source/options.md @@ -1,5 +1,7 @@ # Options +## Minimal color scheme + This package has the option to choose a more **minimal** color scheme. The aim is to create admonitions that are clearly different to the core text with @@ -15,7 +17,7 @@ compared to the current default To enable the `minimal` color scheme you can use the following. -## Jupyter Book Project +### Jupyter Book Project Add `proof_minimal_theme = True` to your `_config.yml` @@ -25,6 +27,112 @@ sphinx: proof_minimal_theme: true ``` -## Sphinx Project +### Sphinx Project Add `proof_minimal_theme = True` to your `conf.py` + +## Shared numbering + +By default, each type of (prf-)directive has their own numbering and counter. This can be changed by setting the option `prf_realtyp_to_countertyp` to a dictionary associating to a directive which the counter of which directive it should use. + +### Sphinx Project + +In `conf.py`, e.g. to have a shared counter for all directives: + +``` +prf_realtyp_to_countertyp = { + "axiom": "theorem", + "theorem": "theorem", + "lemma": "theorem", + "algorithm": "theorem", + "definition": "theorem", + "remark": "theorem", + "conjecture": "theorem", + "corollary": "theorem", + "criterion": "theorem", + "example": "theorem", + "property": "theorem", + "observation": "theorem", + "proposition": "theorem", + "assumption": "theorem", + "notation": "theorem", +} +``` + +In the following case, the directives `lemma`, `conjecture`, `corollary` and `proposition` will share the counter with `theorem`, while `axiom` and `assumption` will share the counter with `definition`. All other directives would use their original counter. + + +``` +prf_realtyp_to_countertyp = { + "lemma": "theorem", + "conjecture": "theorem", + "corollary": "theorem", + "proposition": "theorem", + "axiom": "definition", + "assumption": "definition", +} +``` + +````{warning} +The association of a counter to a directive is not transitive: Let us consider the following configuration: + +``` +prf_realtyp_to_countertyp = { + "lemma": "theorem", + "conjecture": "lemma", +} +``` + +The `lemma` and `theorem` directives share a counter, however the `conjecture` directive has a separate counter (the `lemma` counter which is **not** used by `lemma` directives). +```` + +## Title format + +By default, the directive titles are formatted as `Name x.y.z (Title)`, where `Name` is the name of the directive (e.g., Proof, Theorem, Definition), `x.y.z` is the numbering of the directive, and `Title` is the optional title provided by the user. + +If no title is provided, only `Name x.y.z` is displayed. + +The font weight of the entire title (`Name x.y.z (Title)` or `Name x.y.z`) is set to `--pst-admonition-font-weight-heading` by default, which commonly results in a semi-bold appearance. + +In the reminder we call the part `Name x.y.z` the "number" and the part `(Title)` the "title". + +You can customize the title format using the `proof_title_format` option: + +- This option allows you to define how the title should be displayed by using `%t` as a placeholder for the user-provided title. +- The default format is ` (%t)`. +- A value of an empty string will result in no title being displayed. +- A `markdown` string can be used to format the title. + - For example, ` *%t*` will emphasize the title and contain no brackets. + +Note that the initial part of the title (i.e., `Name x.y.z`) is not customizable and will always be displayed. + +The font weight of the title can be adjusted using the `proof_title_weight` option: + +- Any valid CSS font-weight value can be used, such as `normal`, `bold`, `bolder`, `lighter`, or numeric values like `400`, `700`, etc. +- Default value is `var(--pst-admonition-font-weight-heading)`. + +The font weight of the number can be adjusted using the `proof_number_weight` option: +- Any valid CSS font-weight value can be used, such as `normal`, `bold`, `bolder`, `lighter`, or numeric values like `400`, `700`, etc. +- Default value is `var(--pst-admonition-font-weight-heading)`. + +### Jupyter Book Project + +Add `proof_title_format`, `proof_number_weight` and/or `proof_title_weight` to your `_config.yml` + +```yaml +sphinx: + config: + proof_title_format: " *%t*" + proof_number_weight: "bold" + proof_title_weight: "normal" +``` + +### Sphinx Project + +Add `proof_title_format`, `proof_number_weight` and/or `proof_title_weight` to your `conf.py` + +```python +proof_title_format = " *%t*" +proof_number_weight = "bold" +proof_title_weight = "normal" +``` diff --git a/docs/source/syntax.md b/docs/source/syntax.md index a73c848..cbf414c 100644 --- a/docs/source/syntax.md +++ b/docs/source/syntax.md @@ -749,6 +749,43 @@ This is a dummy assumption directive. You can refer to an assumption using the `{prf:ref}` role like: ```{prf:ref}`my-assumption` ```, which will replace the reference with the assumption number like so: {prf:ref}`my-assumption`. When an explicit text is provided, this caption will serve as the title of the reference. +(syntax:notation)= +### Notations + +A notation directive can be included using the `prf:notation` pattern. The directive is enumerated by default and can take in an optional title argument. The following options are also supported: + +* `label` : text + + A unique identifier for your notation that you can use to reference it with `{prf:ref}`. Cannot contain spaces or special characters. +* `class` : text + + Value of the notation’s class attribute which can be used to add custom CSS or JavaScript. +* `nonumber` : flag (empty) + + Turns off notation auto numbering. + +**Example** + +```{prf:notation} +:label: my-notation + +This is a dummy notation directive. +``` + +**MyST Syntax** + +``````md +```{prf:notation} +:label: my-notation + +This is a dummy notation directive. +``` +`````` + +#### Referencing Notations + +You can refer to a notation using the `{prf:ref}` role like: ```{prf:ref}`my-notation` ```, which will replace the reference with the notation number like so: {prf:ref}`my-notation`. When an explicit text is provided, this caption will serve as the title of the reference. + ## How to Hide Content Directive content can be hidden using the `dropdown` class which is available through [sphinx-togglebutton](https://sphinx-togglebutton.readthedocs.io/en/latest/). If your project utilizes the [MyST-NB](https://myst-nb.readthedocs.io/en/latest/) extension, there is no need to activate `sphinx-togglebutton` since it is already bundled with `MyST-NB`. diff --git a/sphinx_proof/__init__.py b/sphinx_proof/__init__.py index 61046f3..5f037c0 100644 --- a/sphinx_proof/__init__.py +++ b/sphinx_proof/__init__.py @@ -76,13 +76,39 @@ def copy_asset_files(app: Sphinx, exc: Union[bool, Exception]): if exc is None: for path in asset_files: copy_asset(path, str(Path(app.outdir).joinpath("_static").absolute())) + # if needed, load css to memory, + # adjust font-weight according to user's setting in config + # and write to output static file + if app.config.proof_number_weight or app.config.proof_title_weight: + # only if at least one of the two options is set + path = str(Path(app.outdir).joinpath("_static", "proof.css").absolute()) + with open(path, "r", encoding="utf-8") as f: + css_content = f.read() + if app.config.proof_number_weight: + css_content = css_content.replace( + "div.proof > p.admonition-title > span.caption-number {\n font-weight: var(--pst-admonition-font-weight-heading);\n}", # noqa: E501 + f"div.proof > p.admonition-title > span.caption-number {{\n font-weight: {app.config.proof_number_weight};\n}}", # noqa: E501 + ) + if app.config.proof_title_weight: + css_content = css_content.replace( + "div.proof > p.admonition-title {\n font-weight: var(--pst-admonition-font-weight-heading);\n}", # noqa: E501 + f"div.proof > p.admonition-title {{\n font-weight: {app.config.proof_title_weight};\n}}", # noqa: E501 + ) + out_path = Path(app.outdir).joinpath("_static", os.path.basename(path)) + with open(out_path, "w", encoding="utf-8") as f: + f.write(css_content) def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value("proof_minimal_theme", False, "html") + app.add_config_value("prf_realtyp_to_countertyp", {}, "html") + app.add_config_value("proof_title_format", " (%t)", "html") + app.add_config_value("proof_number_weight", "", "html") + app.add_config_value("proof_title_weight", "", "html") app.add_css_file("proof.css") + app.connect("config-inited", check_config_values) app.connect("build-finished", copy_asset_files) app.connect("config-inited", init_numfig) app.connect("env-purge-doc", purge_proofs) @@ -118,3 +144,64 @@ def setup(app: Sphinx) -> Dict[str, Any]: "parallel_read_safe": True, "parallel_write_safe": True, } + + +def check_config_values(app: Sphinx, config: Config) -> None: + """Check configuration values.""" + # Check if proof_minimal_theme is boolean + if not isinstance(config.proof_minimal_theme, bool): + logger.warning( + "'proof_minimal_theme' config value must be a boolean. " + "Using default value False." + ) + config.proof_minimal_theme = False + + # Check of prf_realtyp_to_countertyp is a dictionary + if not isinstance(config.prf_realtyp_to_countertyp, dict): + logger.warning( + "'prf_realtyp_to_countertyp' config value must be a dictionary. " + "Using default empty dictionary." + ) + config.prf_realtyp_to_countertyp = {} + # Check if each key and each value in prf_realtyp_to_countertyp + # is a valid proof type + for key, value in config.prf_realtyp_to_countertyp.items(): + if key not in PROOF_TYPES: + logger.warning( + f"Key '{key}' in 'prf_realtyp_to_countertyp' is not " + "a valid proof type. " + "It will be removed." + ) + del config.prf_realtyp_to_countertyp[key] + elif value not in PROOF_TYPES: + logger.warning( + f"Value '{value}' in 'prf_realtyp_to_countertyp' is not " + "a valid proof type. It will be removed." + ) + del config.prf_realtyp_to_countertyp[key] + # Check if proof_title_format is a string + if not isinstance(config.proof_title_format, str): + logger.warning( + "'proof_title_format' config value must be a string." + "Using default value ' (%t)'." + ) + config.proof_title_format = " (%t)" + elif "%t" not in config.proof_title_format: + logger.warning( + "'proof_title_format' config value must contain the " + "substring '%t' to print a title." + ) + # Check if proof_number_weight is a string + if not isinstance(config.proof_number_weight, str): + logger.warning( + "'proof_number_weight' config value must be a string. " + "Using default value ''." + ) + config.proof_number_weight = "" + # Check if proof_title_weight is a string + if not isinstance(config.proof_title_weight, str): + logger.warning( + "'proof_title_weight' config value must be a string. " + "Using default value ''." + ) + config.proof_title_weight = "" diff --git a/sphinx_proof/_static/minimal/proof.css b/sphinx_proof/_static/minimal/proof.css index e4aed47..2f2054c 100644 --- a/sphinx_proof/_static/minimal/proof.css +++ b/sphinx_proof/_static/minimal/proof.css @@ -12,9 +12,10 @@ --corollary-border-color: #c500c1; --example-border-color: #f9377b; --property-border-color: #fdf914; - --obseration-border-color: #7707ff; + --observation-border-color: #7707ff; --proposition-border-color: #4f7aa8; --assumption-border-color: #07fffb; + --notation-border-color: rgb(0, 255, 200); /* --note-title-color: rgba(68,138,255,.1); --note-border-color: #007bff; @@ -38,6 +39,15 @@ div.proof > .admonition-title::before { content: none; } +/* Set font weights */ +div.proof > p.admonition-title { + font-weight: var(--pst-admonition-font-weight-heading) !important; +} + +div.proof > p.admonition-title > span.caption-number { + font-weight: var(--pst-admonition-font-weight-heading) !important; +} + /********************************************* * Proof * *********************************************/ @@ -209,6 +219,7 @@ div.proposition { div.proposition > .admonition-title { background-color: transparent; } + /********************************************* * Assumption * *********************************************/ @@ -220,3 +231,15 @@ div.assumption { div.assumption > .admonition-title { background-color: transparent; } + +/********************************************* +* Notation * +*********************************************/ +div.notation { + border-color: var(--notation-border-color); + background-color: none; +} + +div.notation > .admonition-title { + background-color: transparent; +} diff --git a/sphinx_proof/_static/proof.css b/sphinx_proof/_static/proof.css index f975944..7462ec8 100644 --- a/sphinx_proof/_static/proof.css +++ b/sphinx_proof/_static/proof.css @@ -2,16 +2,46 @@ * Variables * *********************************************/ :root { - --note-title-color: rgba(68,138,255,.1); - --note-border-color: #007bff; + /* --note-title-color: rgba(68,138,255,.1); + --note-border-color: rgb(0, 123, 255); --warning-title-color: rgba(220,53,69,.1); - --warning-border-color: #dc3545; + --warning-border-color: rgb(220, 56, 72); --hint-title-color: rgba(255,193,7,.2); - --hint-border-color: #ffc107; + --hint-border-color: rgb(255, 193, 7); --caution-title-color: rgba(253,126,20,.1); - --caution-border-color: #fd7e14; + --caution-border-color: #fd7e14; */ --grey-title-color: rgba(204,204,204,.2); --grey-border-color: #ccc; + + --theorem-border-color: rgba(68,138,255); + --axiom-border-color: rgb(255, 193, 7); + --criterion-border-color: rgb(253, 126, 20); + --lemma-border-color: rgb(54, 207, 3); + --definition-border-color: rgb(0, 123, 255); + --remark-border-color: rgb(121, 255, 111); + --conjecture-border-color: rgb(119, 95, 21); + --corollary-border-color: rgb(197, 0, 193); + --example-border-color: rgb(249, 55, 123); + --property-border-color: rgb(253, 249, 20); + --observation-border-color: rgb(119, 7, 255); + --proposition-border-color: rgb(79, 122, 168); + --assumption-border-color: rgb(7, 255, 251); + --notation-border-color: rgb(0, 255, 200); + + --theorem-title-color: rgba(68,138,255,.1); + --axiom-title-color: rgb(255, 193, 7,.1); + --criterion-title-color: rgb(253, 126, 20,.1); + --lemma-title-color: rgb(54, 207, 3,.1); + --definition-title-color: rgb(0, 123, 255,.1); + --remark-title-color: rgb(121, 255, 111,.1); + --conjecture-title-color: rgb(119, 95, 21,.1); + --corollary-title-color: rgb(197, 0, 193,.1); + --example-title-color: rgb(249, 55, 123,.1); + --property-title-color: rgb(253, 249, 20,.1); + --observation-title-color: rgb(119, 7, 255,.1); + --proposition-title-color: rgb(79, 122, 168,.1); + --assumption-title-color: rgb(7, 255, 251,.1); + --notation-title-color: rgb(0, 255, 200,.1); } /********************************************* @@ -23,6 +53,15 @@ div.proof p.admonition-title::before { content: none; } +/* Set font weights */ +div.proof > p.admonition-title { + font-weight: var(--pst-admonition-font-weight-heading); +} + +div.proof > p.admonition-title > span.caption-number { + font-weight: var(--pst-admonition-font-weight-heading); +} + /********************************************* * Proof * *********************************************/ @@ -36,96 +75,96 @@ div#proof{ * Theorem * *********************************************/ div.theorem { - border-color: var(--note-border-color); - background-color: var(--note-title-color); + border-color: var(--theorem-border-color); + background-color: var(--theorem-title-color); } div.theorem p.admonition-title { - background-color: var(--note-title-color); + background-color: var(--theorem-title-color); } /********************************************* * Axiom * *********************************************/ div.axiom { - border-color: var(--hint-border-color); - background-color: var(--hint-title-color); + border-color: var(--axiom-border-color); + background-color: var(--axiom-title-color); } div.axiom p.admonition-title { - background-color: var(--hint-title-color); + background-color: var(--axiom-title-color); } /********************************************* * Criterion * *********************************************/ div.criterion { - border-color: var(--caution-border-color); - background-color: var(--caution-title-color); + border-color: var(--criterion-border-color); + background-color: var(--criterion-title-color); } div.criterion p.admonition-title { - background-color: var(--caution-title-color); + background-color: var(--criterion-title-color); } /********************************************* * Lemma * *********************************************/ div.lemma { - border-color: var(--hint-border-color); - background-color: var(--hint-title-color); + border-color: var(--lemma-border-color); + background-color: var(--lemma-title-color); } div.lemma p.admonition-title { - background-color: var(--hint-title-color); + background-color: var(--lemma-title-color); } /********************************************* * Definition * *********************************************/ div.definition { - border-color: var(--note-border-color); - background-color: var(--note-title-color); + border-color: var(--definition-border-color); + background-color: var(--definition-title-color); } div.definition p.admonition-title { - background-color: var(--note-title-color); + background-color: var(--definition-title-color); } /********************************************* * Remark * *********************************************/ div.remark { - border-color: var(--warning-border-color); - background-color: var(--warning-title-color); + border-color: var(--remark-border-color); + background-color: var(--remark-title-color); } div.remark p.admonition-title { - background-color: var(--warning-title-color); + background-color: var(--remark-title-color); } /********************************************* * Conjecture * *********************************************/ div.conjecture { - border-color: var(--hint-border-color); - background-color: var(--hint-title-color); + border-color: var(--conjecture-border-color); + background-color: var(--conjecture-title-color); } div.conjecture p.admonition-title { - background-color: var(--hint-title-color); + background-color: var(--conjecture-title-color); } /********************************************* * Corollary * *********************************************/ div.corollary { - border-color: var(--caution-border-color); - background-color: var(--caution-title-color); + border-color: var(--corollary-border-color); + background-color: var(--corollary-title-color); } div.corollary p.admonition-title { - background-color: var(--caution-title-color); + background-color: var(--corollary-title-color); } /********************************************* @@ -151,57 +190,70 @@ div.algorithm div.section { * Example * *********************************************/ div.example { - border-color: var(--hint-border-color); - background-color: none; + border-color: var(--example-border-color); + background-color: var(--example-title-color); } div.example p.admonition-title { - background-color: transparent; + background-color: var(--example-title-color); } /********************************************* * Property * *********************************************/ div.property { - border-color: var(--caution-border-color); - background-color: var(--caution-title-color); + border-color: var(--property-border-color); + background-color: var(--property-title-color); } div.property p.admonition-title { - background-color: var(--caution-title-color); + background-color: var(--property-title-color); } /********************************************* * Observation * *********************************************/ div.observation { - border-color: var(--hint-border-color); - background-color: var(--hint-title-color); + border-color: var(--observation-border-color); + background-color: var(--observation-title-color); } div.observation p.admonition-title { - background-color: var(--hint-title-color); + background-color: var(--observation-title-color); } /********************************************* * Proposition * *********************************************/ div.proposition { - border-color: var(--note-border-color); - background-color: var(--note-title-color); + border-color: var(--proposition-border-color); + background-color: var(--proposition-title-color); } div.proposition p.admonition-title { - background-color: var(--note-title-color); + background-color: var(--proposition-title-color); } + /********************************************* * Assumption * *********************************************/ div.assumption { - border-color: var(--hint-border-color); - background-color: var(--hint-title-color); + border-color: var(--assumption-border-color); + background-color: var(--assumption-title-color); } div.assumption p.admonition-title { - background-color: var(--hint-title-color); + background-color: var(--assumption-title-color); +} + +/********************************************* +* Notation * +*********************************************/ +div.notation { + border-color: var(--notation-border-color); + background-color: var(--notation-title-color); +} + +div.notation p.admonition-title { + background-color: var(--notation-title-color); } diff --git a/sphinx_proof/directive.py b/sphinx_proof/directive.py index 9d4595b..f6dd012 100644 --- a/sphinx_proof/directive.py +++ b/sphinx_proof/directive.py @@ -20,6 +20,25 @@ logger = logging.getLogger(__name__) +DEFAULT_REALTYP_TO_COUNTERTYP = { + "axiom": "axiom", + "theorem": "theorem", + "lemma": "lemma", + "algorithm": "algorithm", + "definition": "definition", + "remark": "remark", + "conjecture": "conjecture", + "corollary": "corollary", + "criterion": "criterion", + "example": "example", + "property": "property", + "observation": "observation", + "proposition": "proposition", + "assumption": "assumption", + "notation": "notation", +} + + class ElementDirective(SphinxDirective): """A custom Sphinx Directive""" @@ -36,13 +55,16 @@ class ElementDirective(SphinxDirective): def run(self) -> List[Node]: env = self.env - typ = self.name.split(":")[1] + realtyp = self.name.split(":")[1] + countertyp = env.config.prf_realtyp_to_countertyp.get( + realtyp, DEFAULT_REALTYP_TO_COUNTERTYP[realtyp] + ) serial_no = env.new_serialno() if not hasattr(env, "proof_list"): env.proof_list = {} # If class in options add to class array - classes, class_name = ["proof", typ], self.options.get("class", []) + classes, class_name = ["proof", realtyp], self.options.get("class", []) if class_name: classes.extend(class_name) @@ -53,34 +75,41 @@ def run(self) -> List[Node]: node_id = f"{label}" else: self.options["noindex"] = True - label = f"{typ}-{serial_no}" - node_id = f"{typ}-{serial_no}" + label = f"{realtyp}-{serial_no}" + node_id = f"{realtyp}-{serial_no}" ids = [node_id] # Duplicate label warning if not label == "" and label in env.proof_list.keys(): path = env.doc2path(env.docname)[:-3] other_path = env.doc2path(env.proof_list[label]["docname"]) - msg = f"duplicate {typ} label '{label}', other instance in {other_path}" + msg = f"duplicate {realtyp} label '{label}', other instance in {other_path}" logger.warning(msg, location=path, color="red") title_text = "" if self.arguments != []: - title_text += f" ({self.arguments[0]})" + title_format = self.config.proof_title_format + title_text += title_format.replace("%t", self.arguments[0]) + # title_text += f" ({self.arguments[0]})" textnodes, messages = self.state.inline_text(title_text, self.lineno) - section = nodes.section(classes=[f"{typ}-content"], ids=["proof-content"]) + section = nodes.section(classes=[f"{realtyp}-content"], ids=["proof-content"]) self.state.nested_parse(self.content, self.content_offset, section) if "nonumber" in self.options: node = unenumerable_node() else: - node_type = NODE_TYPES[typ] + node_type = NODE_TYPES[countertyp] node = node_type() node.document = self.state.document - node += nodes.title(title_text, "", *textnodes) + node_title = nodes.title(title_text, "", *textnodes) + if "nonumber" in self.options: + # add ids to the title node for nonumber + # nodes for later searching + node_title["ids"].extend(ids) + node += node_title node += section # Set node attributes @@ -88,17 +117,18 @@ def run(self) -> List[Node]: node["classes"].extend(classes) node["title"] = title_text node["label"] = label - node["type"] = typ + node["countertype"] = countertyp + node["realtype"] = realtyp env.proof_list[label] = { "docname": env.docname, - "type": typ, + "countertype": countertyp, + "realtype": realtyp, "ids": ids, "label": label, "prio": 0, "nonumber": True if "nonumber" in self.options else False, } - return [node] @@ -115,16 +145,16 @@ class ProofDirective(SphinxDirective): } def run(self) -> List[Node]: - typ = self.name.split(":")[1] + realtyp = self.name.split(":")[1] # If class in options add to class array - classes, class_name = ["proof", typ], self.options.get("class", []) + classes, class_name = ["proof", realtyp], self.options.get("class", []) if class_name: classes.extend(class_name) - section = nodes.admonition(classes=classes, ids=[typ]) + section = nodes.admonition(classes=classes, ids=[realtyp]) - self.content[0] = "{}. ".format(typ.title()) + self.content[0] + self.content[0] = "{}. ".format(realtyp.title()) + self.content[0] self.state.nested_parse(self.content, 0, section) node = proof_node() diff --git a/sphinx_proof/domain.py b/sphinx_proof/domain.py index ba8b0ea..7558a0f 100644 --- a/sphinx_proof/domain.py +++ b/sphinx_proof/domain.py @@ -45,7 +45,7 @@ def generate(self, docnames=None) -> Tuple[Dict[str, Any], bool]: return content, True proofs = self.domain.env.proof_list - # {'theorem-0': {'docname': 'start/overview', 'type': 'theorem', 'ids': ['theorem-0'], 'label': 'theorem-0', 'prio': 0, 'nonumber': False}} # noqa: E501 + # {'theorem-0': {'docname': 'start/overview', 'realtype': 'theorem', 'countertype': 'theorem', 'ids': ['theorem-0'], 'label': 'theorem-0', 'prio': 0, 'nonumber': False}} # noqa: E501 # name, subtype, docname, typ, anchor, extra, qualifier, description for anchor, values in proofs.items(): @@ -57,7 +57,7 @@ def generate(self, docnames=None) -> Tuple[Dict[str, Any], bool]: anchor, values["docname"], "", - values["type"], + values["realtype"], ) ) @@ -157,11 +157,12 @@ def resolve_xref( if target in contnode[0]: number = "" if not env.proof_list[target]["nonumber"]: - typ = env.proof_list[target]["type"] + countertyp = env.proof_list[target]["countertype"] number = ".".join( - map(str, env.toc_fignumbers[todocname][typ][target]) + map(str, env.toc_fignumbers[todocname][countertyp][target]) ) - title = nodes.Text(f"{translate(match['type'].title())} {number}") + type_title = translate(match["realtype"].title()) + title = nodes.Text(f"{type_title} {number}") # builder, fromdocname, todocname, targetid, child, title=None return make_refnode(builder, fromdocname, todocname, target, title) else: diff --git a/sphinx_proof/nodes.py b/sphinx_proof/nodes.py index 96bbff7..d77b257 100644 --- a/sphinx_proof/nodes.py +++ b/sphinx_proof/nodes.py @@ -13,6 +13,11 @@ from sphinx.writers.latex import LaTeXTranslator from sphinx.locale import get_translation + +from sphinx.util import logging + +logger = logging.getLogger(__name__) + MESSAGE_CATALOG_NAME = "proof" _ = get_translation(MESSAGE_CATALOG_NAME) @@ -31,17 +36,18 @@ def visit_enumerable_node(self, node: Node) -> None: def depart_enumerable_node(self, node: Node) -> None: - typ = node.attributes.get("type", "") + countertyp = node.attributes.get("countertype", "") + realtyp = node.attributes.get("realtype", "") if isinstance(self, LaTeXTranslator): - number = get_node_number(self, node, typ) + number = get_node_number(self, node, countertyp) idx = list_rindex(self.body, latex_admonition_start) + 2 - self.body.insert(idx, f"{typ.title()} {number}") + self.body.insert(idx, f"{realtyp.title()} {number}") self.body.append(latex_admonition_end) else: # Find index in list of 'Proof #' - number = get_node_number(self, node, typ) - idx = self.body.index(f"{typ} {number} ") - self.body[idx] = f"{_(typ.title())} {number} " + number = get_node_number(self, node, countertyp) + idx = self.body.index(f"{countertyp} {number} ") + self.body[idx] = f"{_(realtyp.title())} {number} " self.body.append("") @@ -55,18 +61,17 @@ def visit_unenumerable_node(self, node: Node) -> None: def depart_unenumerable_node(self, node: Node) -> None: - typ = node.attributes.get("type", "") - title = node.attributes.get("title", "") + realtyp = node.attributes.get("realtype", "") + id = node.attributes.get("ids", [""])[0] if isinstance(self, LaTeXTranslator): idx = list_rindex(self.body, latex_admonition_start) + 2 - self.body.insert(idx, f"{typ.title()}") + self.body.insert(idx, f"{realtyp.title()}") self.body.append(latex_admonition_end) else: - if title == "": - idx = list_rindex(self.body, '
') + 1 - else: - idx = list_rindex(self.body, title) - element = f"{_(typ.title())} " + # use the id to find the correct title location + search_str = f'
' + idx = list_rindex(self.body, search_str) + 1 + element = f'{_(realtyp.title())} ' self.body.insert(idx, element) self.body.append("") @@ -79,10 +84,10 @@ def depart_proof_node(self, node: Node) -> None: pass -def get_node_number(self, node: Node, typ) -> str: +def get_node_number(self, node: Node, countertyp) -> str: """Get the number for the directive node for HTML.""" ids = node.attributes.get("ids", [])[0] - key = typ + key = countertyp if isinstance(self, LaTeXTranslator): docname = find_parent(self.builder.env, node, "section") fignumbers = self.builder.env.toc_fignumbers.get( @@ -91,7 +96,7 @@ def get_node_number(self, node: Node, typ) -> str: else: fignumbers = self.builder.fignumbers if self.builder.name == "singlehtml": - key = "%s/%s" % (self.docnames[-1], typ) + key = "%s/%s" % (self.docnames[-1], countertyp) number = fignumbers.get(key, {}).get(ids, ()) return ".".join(map(str, number)) @@ -116,11 +121,15 @@ def find_parent(env, node, parent_tag): return None -def list_rindex(li, x) -> int: - """Getting the last occurence of an item in a list.""" +def list_rindex(li, x, skip=0) -> int: + """Getting the last occurrence of an item in a list.""" + """Skipping the first skip occurrences from the end.""" for i in reversed(range(len(li))): if li[i] == x: - return i + if skip == 0: + return i + else: + skip -= 1 raise ValueError("{} is not in list".format(x)) diff --git a/tests/conftest.py b/tests/conftest.py index 9efb7f5..c751e14 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,17 +1,17 @@ import shutil import pytest -from sphinx.testing.path import path +from pathlib import Path pytest_plugins = "sphinx.testing.fixtures" @pytest.fixture def rootdir(tmpdir): - src = path(__file__).parent.abspath() / "books" + src = Path(__file__).parent / "books" dst = tmpdir.join("books") shutil.copytree(src, dst) - books = path(dst) + books = Path(dst) yield books shutil.rmtree(dst) diff --git a/tests/test_html/algorithm/_algo_nonumber.html b/tests/test_html/algorithm/_algo_nonumber.html index d65590b..7fa6d0f 100644 --- a/tests/test_html/algorithm/_algo_nonumber.html +++ b/tests/test_html/algorithm/_algo_nonumber.html @@ -1,5 +1,5 @@
Algorithm (Test algorithm directive)
+Algorithm (Test algorithm directive)
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.