Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ NEW: Add NeedsLookUpTableBuilder #961

Open
wants to merge 53 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
bdc4d77
add builder needs look up for permalink
nhatnamnguyengtvthcm Jul 18, 2023
37deab7
keep variable for need_file
nhatnamnguyengtvthcm Jul 18, 2023
4095a0d
keep variable for need_file
nhatnamnguyengtvthcm Jul 18, 2023
b144bc3
change some env variable
nhatnamnguyengtvthcm Jul 20, 2023
0af5175
add config mode permalink
nhatnamnguyengtvthcm Jul 21, 2023
c9a10fd
add config mode permalink
nhatnamnguyengtvthcm Jul 21, 2023
db592c0
add config mode permalink
nhatnamnguyengtvthcm Jul 21, 2023
99dc2dd
add config mode permalink
nhatnamnguyengtvthcm Jul 21, 2023
9452708
fix ci test
nhatnamnguyengtvthcm Aug 10, 2023
9d6badf
update confiration for needs_lut
nhatnamnguyengtvthcm Aug 11, 2023
adfc885
update confiration for needs_lut
nhatnamnguyengtvthcm Aug 11, 2023
02e65be
add test-case and fix some setting for need per id builder
nhatnamnguyengtvthcm Aug 15, 2023
5199edc
update version
nhatnamnguyengtvthcm Aug 21, 2023
d4e4f04
Removed esbonio for IDE support
haiyangToAI Feb 17, 2023
390dd36
enable docker arm builds
cpolzer May 31, 2023
7177c03
🔧 Add .nox to gitignore
chrisjsewell Aug 2, 2023
61dcbef
Update pyproject.toml
chrisjsewell Aug 3, 2023
98030a8
Update pyproject.toml
chrisjsewell Aug 3, 2023
25d42f0
Update requirements.txt
chrisjsewell Aug 3, 2023
994b6b1
Update api.rst
chrisjsewell Aug 3, 2023
4f5d185
Added config allow unsafe filter for filter_func (#949)
haiyangToAI Aug 3, 2023
78aedd7
🔧 Update pre-commit hooks (#956)
chrisjsewell Aug 4, 2023
e86bb39
Bump actions/checkout from 3.3.0 to 3.5.3
dependabot[bot] Jun 12, 2023
3a0f270
needs: Remove some of the extra IDs in the output
tim-nordell-nimbelink Nov 21, 2022
c6f0c2d
Added config option needs_report_dead_links (#937)
haiyangToAI Aug 4, 2023
4babb2d
Raises version to 1.3.0
danwos Aug 16, 2023
4cec93b
👌 Performance: Memoize Inline Parser
chrisjsewell Aug 11, 2023
09b14ca
fix linting
chrisjsewell Aug 16, 2023
0230f3d
Add to changelog
chrisjsewell Aug 16, 2023
271f6a6
Easier Sphinx-Needs docs builds
danwos Aug 18, 2023
036c723
Docker fix
danwos Aug 18, 2023
d3e0f90
Removing docker context: ci
danwos Aug 18, 2023
e144909
♻️ Change `NeedsBuilder` format to `needs`
chrisjsewell Aug 21, 2023
d229036
Update changelog.rst
chrisjsewell Aug 21, 2023
4ead31a
add builder needs look up for permalink
nhatnamnguyengtvthcm Jul 18, 2023
c6723ea
fix ci test
nhatnamnguyengtvthcm Aug 10, 2023
d0e7849
update confiration for needs_lut
nhatnamnguyengtvthcm Aug 11, 2023
2faeec3
add test-case and fix some setting for need per id builder
nhatnamnguyengtvthcm Aug 15, 2023
3d32679
fix merge version 1.3.0
nhatnamnguyengtvthcm Aug 22, 2023
de90ff2
fix merge version 1.3.0
nhatnamnguyengtvthcm Aug 22, 2023
569d5a6
fix merge version 1.3.0
nhatnamnguyengtvthcm Aug 22, 2023
830c12a
update format builder
nhatnamnguyengtvthcm Aug 24, 2023
a93f75f
merge needsidbuilder
nhatnamnguyengtvthcm Sep 11, 2023
ff74834
turn off needs_lut_build_json
nhatnamnguyengtvthcm Sep 11, 2023
b69da59
change test case name
nhatnamnguyengtvthcm Sep 11, 2023
27bd083
Merge remote-tracking branch 'origin/master' into Namnn/needs-lookup
nhatnamnguyengtvthcm Sep 14, 2023
c77162c
refix needs look up builder, test_case
nhatnamnguyengtvthcm Sep 18, 2023
5fdcd3c
👌 Optimize needextend filter_needs usage (#1030)
danwos Sep 20, 2023
fd1f612
change logic write_lut_json and test case
nhatnamnguyengtvthcm Sep 25, 2023
6649155
git commit -m"fix conflict changelog.rst"
nhatnamnguyengtvthcm Sep 25, 2023
c3bb82c
Merge branch 'master' into Namnn/needs-lookup
nhatnamnguyengtvthcm Sep 25, 2023
0a20d81
fix-confict change log for needs_lut_builder
nhatnamnguyengtvthcm Oct 9, 2023
4472aed
refactor filter_needs in need_lut_builder
nhatnamnguyengtvthcm Oct 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion docs/builders.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,55 @@ Usage

.. code-block:: bash

sphinx-build -b needs_id source_dir build_dir
sphinx-build -b needs_id source_dir build_dir


.. _needs_lut_builder:

needs_lut
---------
.. versionadded:: 1.4.0

The **needs_lut** (Needs Lookup Table) builder exports all found needs to a single json file, which only include list of key ``id`` and value of ``docname`` or ``external_url``.

The build creates a file called **needs_lut.json** inside the given build-folder.
Usage
+++++

.. code-block:: bash

sphinx-build -b needs_lut source_dir build_dir

Format
++++++

.. code-block:: python

{
"extend_test_001": "directives/needextend",
"extend_test_002": "directives/needextend",

"req_arch_001": "directives/needarch",
"req_arch_004": "directives/needarch",

"spec_arch_001": "directives/needarch",
"test_arch_001": "directives/needarch",
"COMP_T_001": "directives/needarch",
"COMP_T_002": "directives/needarch",

"EX_REQ_1": "examples/index",
"EX_REQ_2": "examples/index",

"R_F4722": "examples/index",
"OWN_ID_123": "examples/index",
"IMPL_01": "examples/index",
"T_C3893": "examples/index",
"R_2A9D0": "filter",
"R_22EB2": "filter",
"S_D70B0": "filter",
"S_01A67": "filter",
"T_5CCAA": "filter",
"R_17EB4": "filter",
"req_flow_001": "directives/needflow",
"spec_flow_001": "directives/needflow",
}
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Released: under development
1.4.0
-----
Released: under development
* Improvement: Added Builder :ref:`needs_lut_builder` and config option :ref:`needs_lut_build_json` in `conf.py`.

* Improvement: Added Builder :ref:`needs_id_builder` added and config option :ref:`needs_build_json_per_id` in `conf.py`.

* Improvement: Reduce document build time, by memoizing the inline parse in ``build_need`` (`#968 <https://github.com/useblocks/sphinx-needs/pull/968>`_)
Expand Down
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,9 @@ def custom_defined_func():
# build needs_json for every needs-id to make detail panel
needs_build_json_per_id = False

# build needs_lut.json to make permalinks work
needs_lut_build_json = False

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

Expand Down
25 changes: 24 additions & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1865,7 +1865,7 @@ 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
The permalink web site will load a ``needs.json`` or ``needs_lut.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``.
Expand Down Expand Up @@ -1897,6 +1897,7 @@ an absolute path (on the web server) or an URL.

Default value: ``needs.json``

You can choose needs_lut_build_json ``needs_lut.json`` after setting :ref:`needs_lut_build_json`

.. _needs_constraints:

Expand Down Expand Up @@ -2333,3 +2334,25 @@ If true, need options like status, tags or links are collapsed and shown only af
Default value: True

Can be overwritten for each single need by setting :ref:`need_collapse`.

.. __needs_lut_build_json:

needs_lut_build_json
~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 1.4.0

Builds a ``needs_lut.json`` file during other builds (like ``html``), which is different from ``needs.json``, only include list of key ``id`` and value of ``docname`` or ``external_url``.
This is helpful for loading data fastly when you need improve performance.
Default: False

Example:

.. code-block:: python

needs_lut_build_json = False

.. hint::

The created ``needs_lut.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_lut.json``.
74 changes: 74 additions & 0 deletions sphinx_needs/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,77 @@ def build_needs_id_json(app: Sphinx, _exception: Exception) -> None:
needs_id_builder.set_environment(env)

needs_id_builder.finish()


class NeedsLookUpTableBuilder(Builder):
danwos marked this conversation as resolved.
Show resolved Hide resolved
"""
JSON builder for needs, which creates a simple JSON file including only all keys is need' id and the value is doc name or external_url
"""

name = "needs_lut"
format = "needs"
file_suffix = ".txt"
links_suffix = None

def write_doc(self, docname: str, doctree: nodes.document) -> None:
pass

def finish(self) -> None:
env = self.env
data = SphinxNeedsData(env)
needs = data.get_or_create_needs().values() # We need a list of needs for later filter checks
needs_dict = {}
needs_config = NeedsSphinxConfig(env.config)
filter_string = needs_config.builder_filter
from sphinx_needs.filter_common import filter_needs

filtered_needs = filter_needs(self.app, needs, filter_string)
for need in filtered_needs:
if need["is_external"]:
needs_dict[need["id"]] = need["external_url"]
else:
needs_dict[need["id"]] = need["docname"]

version = getattr(env.config, "version", "unset")
needs_list = NeedsList(env.config, self.outdir, self.srcdir)
needs_list.add_lut_need(version, needs_dict)
try:
needs_list.write_json("needs_lut.json")
except Exception as e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add this part (dict creation + json.dump) also to NeedsList as write_json_lut?
It's easier to maintain if all storage functions are defined in one place.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should stay with add_need (instead of add_lut_need) and transform the needed data in a special writer function write_json_lut.

log.error(f"Error during writing json file: {e}")
else:
log.info("Needs lookup table json successfully created")

def get_outdated_docs(self) -> Iterable[str]:
return []

def prepare_writing(self, _docnames: Set[str]) -> None:
pass

def write_doc_serialized(self, _docname: str, _doctree: nodes.document) -> None:
pass

def cleanup(self) -> None:
pass

def get_target_uri(self, _docname: str, _typ: Optional[str] = None) -> str:
return ""


def build_needs_look_up_json(app: Sphinx, _exception: Exception) -> None:
env = app.env

if not NeedsSphinxConfig(env.config).lut_build_json:
return

# Do not create an additional look up table json, if builder is already in use.
if isinstance(app.builder, NeedsLookUpTableBuilder):
return

try:
needs_lut_builder = NeedsLookUpTableBuilder(app, env)
except TypeError:
needs_lut_builder = NeedsLookUpTableBuilder(app)
needs_lut_builder.set_environment(env)

needs_lut_builder.finish()
1 change: 1 addition & 0 deletions sphinx_needs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ def __setattr__(self, name: str, value: Any) -> None:
# add config for needs_id_builder
build_json_per_id: bool = field(default=False, metadata={"rebuild": "html", "types": (bool,)})
build_json_per_id_path: str = field(default="needs_id", metadata={"rebuild": "html", "types": (str,)})
lut_build_json: bool = field(default=False, metadata={"rebuild": "html", "types": (bool,)})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lut_build_json: bool = field(default=False, metadata={"rebuild": "html", "types": (bool,)})
build_json_lut: bool = field(default=False, metadata={"rebuild": "html", "types": (bool,)})

This would be consistent with build_json and build_json_per_id


@classmethod
def add_config_values(cls, app: Sphinx) -> None:
Expand Down
5 changes: 4 additions & 1 deletion sphinx_needs/needs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
from sphinx_needs.builder import (
NeedsBuilder,
NeedsIdBuilder,
NeedsLookUpTableBuilder,
NeedumlsBuilder,
build_needs_id_json,
build_needs_json,
build_needs_look_up_json,
build_needumls_pumls,
)
from sphinx_needs.config import NEEDS_CONFIG, NeedsSphinxConfig
Expand Down Expand Up @@ -142,7 +144,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(NeedsBuilder)
app.add_builder(NeedumlsBuilder)
app.add_builder(NeedsIdBuilder)

app.add_builder(NeedsLookUpTableBuilder)
NeedsSphinxConfig.add_config_values(app)

# Define nodes
Expand Down Expand Up @@ -245,6 +247,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.connect("build-finished", build_needs_json)
app.connect("build-finished", build_needs_id_json)
app.connect("build-finished", build_needumls_pumls)
app.connect("build-finished", build_needs_look_up_json)
app.connect("build-finished", debug.process_timing)

# Be sure Sphinx-Needs config gets erased before any events or external API calls get executed.
Expand Down
5 changes: 4 additions & 1 deletion sphinx_needs/needsfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
import sys
from datetime import datetime
from typing import Any, List
from typing import Any, Dict, List

from jsonschema import Draft7Validator
from sphinx.config import Config
Expand Down Expand Up @@ -131,6 +131,9 @@ def load_json(self, file: str) -> None:

self.log.debug(f"needs.json file loaded: {file}")

def add_lut_need(self, version: str, needs_lut: Dict[str, Any]) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea was a little different.
Instead of adding a custom data structure and use the same writer-function, we could keep the data structure with add_need() and just do the output specific formating in a new writer function like write_lut_json.
In this case all the code is the same, till it comes to write it to the harddisk.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we keep formatting JSON and only map need["docname"] = need["external_url"] if need["is_external"] = True, It is easy to reuse flow generate need_lut.json like need.json. Otherwise, I will continue change formatting in write_lut_json like {[id] : [docname] or [external_url]}
@r-o-b-e-r-t-o @danwos

Copy link
Contributor

@r-o-b-e-r-t-o r-o-b-e-r-t-o Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nhatnamnguyengtvthcm Not sure if I got your point. But I think we must have a mapping for internal and external needs because otherwise you would not be redirected correctly by permalink.html in case an external need is referenced.

self.needs_list = needs_lut


class Errors:
def __init__(self, schema_errors: List[Any]):
Expand Down
62 changes: 43 additions & 19 deletions sphinx_needs/templates/permalink.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,55 @@
function main() {
loadJSON('{{ needs_file }}', function (response) {
const needs = JSON.parse(response);
const current_version = needs['current_version'];
const versions = needs['versions'];
const version = versions[current_version];
const needs_obj = version['needs'];
if(needs.hasOwnProperty("current_version")) {
const needs = JSON.parse(response);
const current_version = needs['current_version'];
const versions = needs['versions'];
const version = versions[current_version];
const needs_obj = version['needs'];

const id = getParameterByName('id');
var pathname = new URL(window.location.href).pathname;
pathname = pathname.substring(0, pathname.lastIndexOf('{{ permalink_file }}'));
const id = getParameterByName('id');
var pathname = new URL(window.location.href).pathname;
pathname = pathname.substring(0, pathname.lastIndexOf('{{ permalink_file }}'));

const keys = Object.keys(needs_obj);
const keys = Object.keys(needs_obj);

var docname = 'index';
var docname = 'index';

keys.forEach((key, index) => {
if (key === id) {
const need = needs_obj[key];
docname = need['docname'];
return;
}
});
keys.forEach((key, index) => {
if (key === id) {
const need = needs_obj[key];
docname = need['docname'];
return;
}
});

window.location.replace(pathname + docname + '.html#' + id);
}
else {
const needs = JSON.parse(response);
const id = getParameterByName('id');
var pathname = new URL(window.location.href).pathname;
pathname = pathname.substring(0, pathname.lastIndexOf('permalink.html'));
const keys = Object.keys(needs);
var docname = 'index';
keys.forEach((key, index) => {
if (key === id) {
docname = needs[key];
return;
}
});

window.location.replace(pathname + docname + '.html#' + id);
if (docname.includes("#" + id)) {
window.location.replace(pathname + docname);
} else {
window.location.replace(pathname + docname + '.html#' + id);
}
}
});
}


}
function getParameterByName(name, url = window.location.href) {
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
Expand All @@ -65,4 +89,4 @@
<body>
</body>

</html>
</html>
19 changes: 19 additions & 0 deletions tests/test_needs_look_up.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import json
from pathlib import Path

import pytest


@pytest.mark.parametrize(
"test_app", [{"buildername": "needs_lut", "srcdir": "doc_test/doc_needs_builder"}], indirect=True
)
def test_doc_needs_lut_builder(test_app):
app = test_app
app.build()

needs_json = Path(app.outdir, "needs_lut.json")
with open(needs_json) as needs_file:
needs_file_content = needs_file.read()

needs_list = json.loads(needs_file_content)
assert needs_list["TC_NEG_001"]