diff --git a/AUTHORS b/AUTHORS index 7d5c12fce..06d3fd51b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,4 +22,6 @@ Philip Partsch David Le Nir -Baran Barış Yıldızlı \ No newline at end of file +Baran Barış Yıldızlı + +Roberto Rötting diff --git a/docs/changelog.rst b/docs/changelog.rst index 2af0d8b8d..bebdad3dd 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,8 @@ License ----- :Released: under development +* Improvement: Add permanent link layout function. + (`#390 `_) * Bugfix: :ref:`needextract` not correctly rendering nested :ref:`need`s. (`#329 `_) diff --git a/docs/conf.py b/docs/conf.py index 14aa7ace9..3da483464 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -298,7 +298,17 @@ "meta": ['**status**: <>', '**author**: <>'], "side": ['<>'], }, - } + }, + "permalink_example": { + "grid": "simple", + "layout": { + "head": [ + '<>: **<>** <> <> <> ' + ], + "meta": ["<>", "<>"], + }, + }, } needs_service_all_data = True @@ -336,6 +346,9 @@ }, } +# build needs.json to make permalinks work +needs_build_json = True + # Get and maybe set Github credentials for services. # This is needed as the rate limit for not authenticated users is too low for the amount of requests we # need to perform for this documentation diff --git a/docs/configuration.rst b/docs/configuration.rst index 0429ff459..483462c2b 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1498,6 +1498,45 @@ Default: False The created ``needs.json`` file gets stored in the ``outdir`` of the current builder. So if ``html`` is used as builder, the final location is e.g. ``_build/html/needs.json``. +.. _needs_permalink_file: + +needs_permalink_file +~~~~~~~~~~~~~~~~~~~~ + +The option specifies the name of the permalink html file, +which will be copied to the html build directory during build. + +The permalink web site will load a ``needs.json`` file as specified +by :ref:`needs_permalink_data` and re-direct the web browser to the html document +of the need, which is specified by appending the need ID as a query +parameter, e.g., ``http://localhost:8000/permalink.html?id=REQ_4711``. + +Example: + +.. code-block:: python + + needs_permalink_file = "my_permalink.html" + +Results in a file ``my_permalink.html`` in the +html build directory. +If this directory is served on ``localhost:8000``, then the file will be +available at ``http://localhost:8000/my_permalink.html``. + +Default value: ``permalink.html`` + +.. _needs_permalink_data: + +needs_permalink_data +~~~~~~~~~~~~~~~~~~~~ + +This options sets the location of a ``needs.json`` file. +This file is used to create permanent links for needs as described +in :ref:`needs_permalink_file`. + +The path can be a relative path (relative to the permalink html file), +an absolute path (on the web server) or an URL. + +Default value: ``needs.json`` Removed options --------------- diff --git a/docs/examples/index.rst b/docs/examples/index.rst index 6e5a2d221..4e2a0a82a 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -44,6 +44,14 @@ Different need layouts and styles This example uses the value from **author** to reference an image. See :ref:`layouts_styles` for the complete explanation. +.. req:: A requirement with a permalink + :id: EX_REQ_5 + :tags: example + :status: open + :layout: permalink_example + + This is like a normal requirement looks like but additionally a permalink icon is shown next to the ID. + Used rst-code for all three examples: .. code-block:: rst @@ -84,6 +92,15 @@ Used rst-code for all three examples: This example uses the value from **author** to reference an image. See :ref:`layouts_styles` for the complete explanation. + .. req:: A requirement with a permalink + :id: EX_REQ_5 + :tags: example + :status: open + :layout: permalink_example + + This is like a normal requirement looks like but additionally + a permalink icon is shown next to the ID. + Referencing and filtering needs ------------------------------- .. req:: My first requirement diff --git a/docs/layout_styles.rst b/docs/layout_styles.rst index 81f817031..665c1a7f0 100644 --- a/docs/layout_styles.rst +++ b/docs/layout_styles.rst @@ -362,6 +362,7 @@ Available layout functions are: * :func:`meta_id ` * :func:`image ` * :func:`link ` +* :func:`permalink ` * :func:`collapse_button ` .. autofunction:: sphinxcontrib.needs.layout.LayoutHandler.meta(name, prefix=None, show_empty=False) @@ -378,6 +379,8 @@ Available layout functions are: .. autofunction:: sphinxcontrib.needs.layout.LayoutHandler.link(url, text=None, image_url=None, image_height=None, image_width=None) +.. autofunction:: sphinxcontrib.needs.layout.LayoutHandler.permalink(image_url=None, image_height=None, image_width=None, text=None) + .. autofunction:: sphinxcontrib.needs.layout.LayoutHandler.collapse_button(target='meta', collapsed='Show', visible='Close', initial=False) .. _styles: diff --git a/sphinxcontrib/needs/environment.py b/sphinxcontrib/needs/environment.py index 0f3e3daff..cb2eb444b 100644 --- a/sphinxcontrib/needs/environment.py +++ b/sphinxcontrib/needs/environment.py @@ -2,6 +2,7 @@ from typing import Iterable import sphinx +from jinja2 import Environment, PackageLoader, select_autoescape from pkg_resources import parse_version from sphinx.application import Sphinx from sphinx.util.console import brown @@ -194,3 +195,26 @@ def install_lib_static_files(app: Sphinx, env): lib_path = Path("sphinx-needs") / "libs" / "html" for f in ["datatables.min.js", "datatables_loader.js", "datatables.min.css", "sphinx_needs_collapse.js"]: safe_add_file(lib_path / f, app) + + +def install_permalink_file(app: Sphinx, env): + """ + Creates permalink.html in build dir + :param app: + :param env: + :return: + """ + # Do not copy static_files for our "needs" builder + if app.builder.name == "needs": + return + + # load jinja template + jinja_env = Environment(loader=PackageLoader("sphinxcontrib.needs"), autoescape=select_autoescape()) + template = jinja_env.get_template("permalink.html") + + # save file to build dir + out_file = Path(app.builder.outdir) / Path(env.config.needs_permalink_file).name + with open(out_file, "w", encoding="utf-8") as f: + f.write( + template.render(permalink_file=env.config.needs_permalink_file, needs_file=env.config.needs_permalink_data) + ) diff --git a/sphinxcontrib/needs/layout.py b/sphinxcontrib/needs/layout.py index caf4b6852..4248a2cc3 100644 --- a/sphinxcontrib/needs/layout.py +++ b/sphinxcontrib/needs/layout.py @@ -254,6 +254,7 @@ def __init__(self, app, need, layout, node, style=None, fromdocname=None): "image": self.image, "link": self.link, "collapse_button": self.collapse_button, + "permalink": self.permalink, } # Prepare string_links dict, so that regex and templates get not recompiled too often. @@ -767,7 +768,12 @@ def image(self, url, height=None, width=None, align=None, no_link=False, prefix= url = file_path - image_node = nodes.image(url, classes=["needs_image"], **options) + if no_link: + classes = ["needs_image", "no-scaled-link"] + else: + classes = ["needs_image"] + + image_node = nodes.image(url, classes=classes, **options) image_node["candidates"] = {"*": url} # image_node['candidates'] = '*' image_node["uri"] = url @@ -777,18 +783,6 @@ def image(self, url, height=None, width=None, align=None, no_link=False, prefix= # Otherwise the images gets not copied to the later build-output location self.app.env.images.add_file(self.need["docname"], url) - # Okay, this is really ugly. - # Sphinx does automatically wrap all images into a reference node, which links to the image file. - # See Bug: https://github.com/sphinx-doc/sphinx/issues/7032 - # This behavior can only be avoided by not using width/height attributes or by adding our - # own reference node. - # We do last one here and set class to "no_link", which is later used by some javascript to avoid - # being clickable, so that the page does not "jump" - if no_link: - ref_node = nodes.reference("test", "", refuri="#", classes=["no_link"]) - ref_node.append(image_node) - return ref_node - data_container.append(image_node) return data_container @@ -829,7 +823,7 @@ def link(self, url, text=None, image_url=None, image_height=None, image_width=No link_node = nodes.reference(text, text, refuri=url) if image_url: - image_node = self.image(image_url, image_height, image_width) + image_node = self.image(image_url, image_height, image_width, no_link=True) link_node.append(image_node) data_container.append(link_node) @@ -887,6 +881,47 @@ def collapse_button(self, target="meta", collapsed="Show", visible="Close", init return coll_container + def permalink(self, image_url=None, image_height=None, image_width=None, text=None, prefix=""): + """ + Shows a permanent link to the need. + Link can be a text, an image or both + + :param image_url: image for an image link + :param image_height: None + :param image_width: None + :param text: text for a text link + :param prefix: Additional string infront of the string + :return: + + Examples:: + + <> + <> + <> + """ + + if image_url is None and text is None: + image_url = "icon:share-2" + image_width = "17px" + + config = self.app.config + permalink = config.needs_permalink_file + id = self.need["id"] + docname = self.need["docname"] + permalink_url = "" + for _ in range(0, len(docname.split("/")) - 1): + permalink_url += "../" + permalink_url += permalink + "?id=" + id + + return self.link( + url=permalink_url, + text=text, + image_url=image_url, + image_width=image_width, + image_height=image_height, + prefix=prefix, + ) + def _grid_simple(self, colwidths, side_left, side_right, footer): """ Creates most "simple" grid layouts. diff --git a/sphinxcontrib/needs/needs.py b/sphinxcontrib/needs/needs.py index 2ba1dee2c..06fe841b0 100644 --- a/sphinxcontrib/needs/needs.py +++ b/sphinxcontrib/needs/needs.py @@ -82,6 +82,7 @@ ) from sphinxcontrib.needs.environment import ( install_lib_static_files, + install_permalink_file, install_styles_static_files, ) from sphinxcontrib.needs.external_needs import load_external_needs @@ -226,6 +227,12 @@ def setup(app): app.add_config_value("needs_build_json", False, "html", types=[bool]) + # Permalink related config values. + # path to permalink.html; absolute path from web-root + app.add_config_value("needs_permalink_file", "permalink.html", "html") + # path to needs.json relative to permalink.html + app.add_config_value("needs_permalink_data", "needs.json", "html") + # Define nodes app.add_node(Need, html=(html_visit, html_depart), latex=(latex_visit, latex_depart)) app.add_node( @@ -320,6 +327,7 @@ def setup(app): app.connect("build-finished", process_warnings) app.connect("build-finished", build_needs_json) app.connect("env-updated", install_lib_static_files) + app.connect("env-updated", install_permalink_file) # Called during consistency check, which if after everything got read in. # app.connect('env-check-consistency', process_warnings) diff --git a/sphinxcontrib/needs/templates/permalink.html b/sphinxcontrib/needs/templates/permalink.html new file mode 100644 index 000000000..f072fae8b --- /dev/null +++ b/sphinxcontrib/needs/templates/permalink.html @@ -0,0 +1,68 @@ + + + + + + + + sphinx-needs permalink + + + + + + + + +