iolanta 2.1.11__tar.gz → 2.1.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {iolanta-2.1.11 → iolanta-2.1.13}/PKG-INFO +2 -2
- iolanta-2.1.13/iolanta/cli/main.py +299 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/facet.py +15 -9
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/facet.py +133 -0
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/inference/blocks.sparql +13 -0
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/inference/has-task-default-type.sparql +16 -0
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/inference/task.sparql +26 -0
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/inference/unblocked.sparql +21 -0
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/mermaid_roadmap.yamlld +59 -0
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/sparql/edges.sparql +25 -0
- iolanta-2.1.13/iolanta/facets/mermaid_roadmap/sparql/nodes.sparql +17 -0
- iolanta-2.1.13/iolanta/facets/query/ask_result_csv.py +23 -0
- iolanta-2.1.13/iolanta/facets/query/ask_result_json.py +24 -0
- iolanta-2.1.13/iolanta/facets/query/ask_result_table.py +23 -0
- iolanta-2.1.13/iolanta/facets/query/construct_result_csv.py +34 -0
- iolanta-2.1.13/iolanta/facets/query/construct_result_json.py +32 -0
- iolanta-2.1.13/iolanta/facets/query/construct_result_table.py +55 -0
- iolanta-2.1.13/iolanta/facets/query/data/query_result.yamlld +102 -0
- iolanta-2.1.13/iolanta/facets/query/select_result_csv.py +36 -0
- iolanta-2.1.13/iolanta/facets/query/select_result_json.py +24 -0
- iolanta-2.1.13/iolanta/facets/query/select_result_table.py +48 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/iolanta.py +146 -55
- iolanta-2.1.13/iolanta/mcp/cli.py +37 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/mermaid/models.py +74 -40
- iolanta-2.1.13/iolanta/sparqlspace/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/sparqlspace/processor.py +232 -179
- iolanta-2.1.13/iolanta/widgets/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/pyproject.toml +12 -2
- iolanta-2.1.11/iolanta/cli/main.py +0 -179
- iolanta-2.1.11/iolanta/mcp/cli.py +0 -24
- {iolanta-2.1.11 → iolanta-2.1.13}/README.md +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/base_plugin.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/formatters/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/formatters/choose.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/formatters/csv.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/formatters/json.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/formatters/pretty.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/models.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/cli/pretty_print.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/context.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/conversions.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/data/context.yaml +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/data/graph-triples.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/data/iolanta.yaml +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/data/textual-browser.yaml +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/declension/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/declension/data/declension.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/declension/facet.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/declension/sparql/declension.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/ensure_is_context.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/entry_points.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/errors.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/cli/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/cli/base.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/cli/default.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/cli/record.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/cli/sparql/link.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/cli/sparql/record.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/errors.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/foaf_person_title/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/foaf_person_title/facet.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/foaf_person_title/sparql/names.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/generic/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/generic/bool_literal.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/generic/date_literal.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/generic/default.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/generic/sparql/default.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/html/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/html/base.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/html/code_literal.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/html/default.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/icon.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/locator/sparql/get-query-to-facet.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/locator.py +0 -0
- {iolanta-2.1.11/iolanta/labeled_triple_set → iolanta-2.1.13/iolanta/facets/mermaid_roadmap}/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/mkdocs_material_insiders_markdown/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/mkdocs_material_insiders_markdown/data/mkdocs_material_insiders_markdown.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/mkdocs_material_insiders_markdown/facet.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/mkdocs_material_insiders_markdown/templates/datatype.jinja2.md +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/page_title.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/qname.py +0 -0
- {iolanta-2.1.11/iolanta/mcp → iolanta-2.1.13/iolanta/facets/query}/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/app.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/facet.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/history.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/home.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/location.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/models.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/page.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_browser/page_switcher.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_class/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_class/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_class/sparql/instances.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_class/textual-class.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/sparql/inverse-properties.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/sparql/label.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/sparql/properties.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/tcss/default.tcss +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/templates/default.md +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/textual-inverse-properties.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/triple_uri_ref.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_default/widgets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graph/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graph/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graph/sparql/triples.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graph_triples.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graphs/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graphs/data/textual_graphs.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graphs/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_graphs/sparql/graphs.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_link/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_link/facet.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_nanopublication/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_nanopublication/facet.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_nanopublication/models.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_nanopublication/nanopublication_widget.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_nanopublication/term_list_widget.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_nanopublication/term_widget.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_no_facet_found.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_ontology/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_ontology/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_ontology/sparql/terms.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_property_pairs_table.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_provenance/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_provenance/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_provenance/sparql/graphs.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/textual_provenance/sparql/triples.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/title/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/title/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/title/sparql/title.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/title/title.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/wikibase_statement_title/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/wikibase_statement_title/facets.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +0 -0
- {iolanta-2.1.11/iolanta/mermaid → iolanta-2.1.13/iolanta/labeled_triple_set}/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/labeled_triple_set/data/labeled_triple_set.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/labeled_triple_set/labeled_triple_set.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/labeled_triple_set/sparql/triples.sparql +0 -0
- {iolanta-2.1.11/iolanta/resolvers → iolanta-2.1.13/iolanta/mcp}/__init__.py +0 -0
- {iolanta-2.1.11/iolanta/sparqlspace → iolanta-2.1.13/iolanta/mermaid}/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/mermaid/facet.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/mermaid/mermaid.yamlld +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/mermaid/sparql/ask-has-triples.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/mermaid/sparql/graph.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/mermaid/sparql/subgraphs.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/models.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/namespaces.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/node_to_qname.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/parse_quads.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/plugin.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/query_result.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/reformat_blank_nodes.py +0 -0
- {iolanta-2.1.11/iolanta/widgets → iolanta-2.1.13/iolanta/resolvers}/__init__.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/resolvers/base.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/resolvers/dispatch.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/resolvers/pypi.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/resolvers/python_import.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/sparqlspace/cli.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/sparqlspace/inference/wikidata-prop-label.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/sparqlspace/inference/wikidata-statement-label.sparql +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/sparqlspace/redirects.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/sparqlspace/sparqlspace.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/widgets/description.py +0 -0
- {iolanta-2.1.11 → iolanta-2.1.13}/iolanta/widgets/mixin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iolanta
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.13
|
|
4
4
|
Summary: Semantic Web browser
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Anatoly Scherbakov
|
|
@@ -34,7 +34,7 @@ Requires-Dist: rich (>=13.3.1)
|
|
|
34
34
|
Requires-Dist: textual (>=0.83.0)
|
|
35
35
|
Requires-Dist: typer (>=0.9.0)
|
|
36
36
|
Requires-Dist: watchfiles (>=1.0.4)
|
|
37
|
-
Requires-Dist: yaml-ld (>=1.1.
|
|
37
|
+
Requires-Dist: yaml-ld (>=1.1.16)
|
|
38
38
|
Requires-Dist: yarl (>=1.9.4)
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import locale
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import loguru
|
|
8
|
+
import platformdirs
|
|
9
|
+
from documented import DocumentedError
|
|
10
|
+
from rdflib import Graph, Literal, URIRef
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.markdown import Markdown
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from typer import Argument, Exit, Option, Typer
|
|
15
|
+
from yarl import URL
|
|
16
|
+
|
|
17
|
+
from iolanta.cli.models import LogLevel
|
|
18
|
+
from iolanta.facets.errors import FacetNotFound
|
|
19
|
+
from iolanta.iolanta import Iolanta
|
|
20
|
+
from iolanta.models import NotLiteralNode
|
|
21
|
+
from iolanta.namespaces import DATATYPES
|
|
22
|
+
from iolanta.query_result import (
|
|
23
|
+
QueryResult,
|
|
24
|
+
SPARQLParseException,
|
|
25
|
+
SelectResult,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
DEFAULT_LANGUAGE = locale.getlocale()[0].split("_")[0]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
app = Typer(no_args_is_help=True)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def string_to_node(name: str) -> NotLiteralNode:
|
|
38
|
+
"""
|
|
39
|
+
Parse a string into a node identifier.
|
|
40
|
+
|
|
41
|
+
String might be:
|
|
42
|
+
* a URL,
|
|
43
|
+
* or a local disk path.
|
|
44
|
+
"""
|
|
45
|
+
url = URL(name)
|
|
46
|
+
if url.scheme:
|
|
47
|
+
return URIRef(name)
|
|
48
|
+
|
|
49
|
+
path = Path(name).absolute()
|
|
50
|
+
return URIRef(f"file://{path}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def decode_datatype(datatype: str) -> URIRef:
|
|
54
|
+
if datatype.startswith("http"):
|
|
55
|
+
return URIRef(datatype)
|
|
56
|
+
|
|
57
|
+
return URIRef(f"https://iolanta.tech/datatypes/{datatype}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def setup_logging(log_level: LogLevel):
|
|
61
|
+
"""Configure and return logger."""
|
|
62
|
+
level = {
|
|
63
|
+
LogLevel.DEBUG: logging.DEBUG,
|
|
64
|
+
LogLevel.INFO: logging.INFO,
|
|
65
|
+
LogLevel.WARNING: logging.WARNING,
|
|
66
|
+
LogLevel.ERROR: logging.ERROR,
|
|
67
|
+
}[log_level]
|
|
68
|
+
|
|
69
|
+
log_file_path = (
|
|
70
|
+
platformdirs.user_log_path(
|
|
71
|
+
"iolanta",
|
|
72
|
+
ensure_exists=True,
|
|
73
|
+
)
|
|
74
|
+
/ "iolanta.log"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
level_name = {
|
|
78
|
+
logging.DEBUG: "DEBUG",
|
|
79
|
+
logging.INFO: "INFO",
|
|
80
|
+
logging.WARNING: "WARNING",
|
|
81
|
+
logging.ERROR: "ERROR",
|
|
82
|
+
}[level]
|
|
83
|
+
|
|
84
|
+
loguru.logger.remove()
|
|
85
|
+
loguru.logger.add(
|
|
86
|
+
log_file_path,
|
|
87
|
+
level=level_name,
|
|
88
|
+
format="{time} {level} {message}",
|
|
89
|
+
enqueue=True,
|
|
90
|
+
)
|
|
91
|
+
loguru.logger.add(
|
|
92
|
+
sys.stderr,
|
|
93
|
+
level=level_name,
|
|
94
|
+
format="{time} | {level:<8} | {name}:{function}:{line} - {message}",
|
|
95
|
+
)
|
|
96
|
+
loguru.logger.level(level_name)
|
|
97
|
+
|
|
98
|
+
return loguru.logger
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handle_error(
|
|
102
|
+
error: Exception,
|
|
103
|
+
log_level: LogLevel,
|
|
104
|
+
use_markdown: bool = True,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Handle an error by checking log level and printing appropriately.
|
|
108
|
+
|
|
109
|
+
If log level is DEBUG or INFO, re-raise the error.
|
|
110
|
+
Otherwise, print it and exit with code 1.
|
|
111
|
+
"""
|
|
112
|
+
level = {
|
|
113
|
+
LogLevel.DEBUG: logging.DEBUG,
|
|
114
|
+
LogLevel.INFO: logging.INFO,
|
|
115
|
+
LogLevel.WARNING: logging.WARNING,
|
|
116
|
+
LogLevel.ERROR: logging.ERROR,
|
|
117
|
+
}[log_level]
|
|
118
|
+
|
|
119
|
+
if level in {logging.DEBUG, logging.INFO}:
|
|
120
|
+
raise error
|
|
121
|
+
|
|
122
|
+
if use_markdown:
|
|
123
|
+
console.print(
|
|
124
|
+
Markdown(
|
|
125
|
+
str(error),
|
|
126
|
+
justify="left",
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
console.print(str(error))
|
|
131
|
+
|
|
132
|
+
raise Exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def create_query_node(query_result: QueryResult) -> Literal:
|
|
136
|
+
"""
|
|
137
|
+
Create a Literal node from a query result.
|
|
138
|
+
|
|
139
|
+
Converts QueryResult (SelectResult, Graph, or bool) into a Literal
|
|
140
|
+
with the appropriate datatype for facet rendering.
|
|
141
|
+
"""
|
|
142
|
+
match query_result:
|
|
143
|
+
case SelectResult():
|
|
144
|
+
return Literal(
|
|
145
|
+
query_result,
|
|
146
|
+
datatype=DATATYPES["sparql-select-result"],
|
|
147
|
+
)
|
|
148
|
+
case Graph():
|
|
149
|
+
return Literal(
|
|
150
|
+
query_result,
|
|
151
|
+
datatype=DATATYPES["sparql-construct-result"],
|
|
152
|
+
)
|
|
153
|
+
case bool():
|
|
154
|
+
return Literal(
|
|
155
|
+
query_result,
|
|
156
|
+
datatype=DATATYPES["sparql-ask-result"],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def render_and_return(
|
|
161
|
+
node: Literal | URIRef,
|
|
162
|
+
as_datatype: str,
|
|
163
|
+
language: str = DEFAULT_LANGUAGE,
|
|
164
|
+
log_level: LogLevel = LogLevel.ERROR,
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
Render a node.
|
|
168
|
+
|
|
169
|
+
The node must be either a URIRef (for URLs) or a Literal (for query results).
|
|
170
|
+
"""
|
|
171
|
+
logger = setup_logging(log_level)
|
|
172
|
+
|
|
173
|
+
# Determine Iolanta instance based on node type
|
|
174
|
+
if isinstance(node, Literal):
|
|
175
|
+
# Literal nodes (e.g., from query results) are used directly
|
|
176
|
+
# Use current directory as project_root for query results
|
|
177
|
+
iolanta: Iolanta = Iolanta(
|
|
178
|
+
language=Literal(language),
|
|
179
|
+
logger=logger,
|
|
180
|
+
project_root=Path.cwd(),
|
|
181
|
+
)
|
|
182
|
+
elif isinstance(node, URIRef):
|
|
183
|
+
# URIRef - determine project_root if it's a file:// URI
|
|
184
|
+
if str(node).startswith("file://"):
|
|
185
|
+
path = Path(str(node).replace("file://", ""))
|
|
186
|
+
iolanta: Iolanta = Iolanta(
|
|
187
|
+
language=Literal(language),
|
|
188
|
+
logger=logger,
|
|
189
|
+
project_root=path,
|
|
190
|
+
)
|
|
191
|
+
else:
|
|
192
|
+
iolanta: Iolanta = Iolanta(
|
|
193
|
+
language=Literal(language),
|
|
194
|
+
logger=logger,
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
# This should never happen due to type checking, but kept for safety
|
|
198
|
+
raise TypeError(f"Expected Literal or URIRef, got {type(node)}")
|
|
199
|
+
|
|
200
|
+
return iolanta.render(
|
|
201
|
+
node=node,
|
|
202
|
+
as_datatype=decode_datatype(as_datatype),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@app.command(name="render")
|
|
207
|
+
def render_command( # noqa: WPS231, WPS238, WPS210, C901
|
|
208
|
+
url: Annotated[str | None, Argument()] = None,
|
|
209
|
+
query: Annotated[
|
|
210
|
+
str | None,
|
|
211
|
+
Option(
|
|
212
|
+
"--query",
|
|
213
|
+
help="SPARQL query to execute.",
|
|
214
|
+
),
|
|
215
|
+
] = None,
|
|
216
|
+
as_datatype: Annotated[
|
|
217
|
+
str | None,
|
|
218
|
+
Option(
|
|
219
|
+
"--as",
|
|
220
|
+
),
|
|
221
|
+
] = None,
|
|
222
|
+
language: Annotated[
|
|
223
|
+
str,
|
|
224
|
+
Option(
|
|
225
|
+
help="Data language to prefer.",
|
|
226
|
+
),
|
|
227
|
+
] = DEFAULT_LANGUAGE,
|
|
228
|
+
log_level: LogLevel = LogLevel.ERROR,
|
|
229
|
+
):
|
|
230
|
+
"""Render a given URL."""
|
|
231
|
+
if query is not None:
|
|
232
|
+
# For queries, default to 'table' format
|
|
233
|
+
if as_datatype is None:
|
|
234
|
+
as_datatype = "table"
|
|
235
|
+
|
|
236
|
+
# Setup logging and create Iolanta instance (unlikely to raise)
|
|
237
|
+
logger = setup_logging(log_level)
|
|
238
|
+
iolanta: Iolanta = Iolanta(
|
|
239
|
+
language=Literal(language),
|
|
240
|
+
logger=logger,
|
|
241
|
+
project_root=Path.cwd(),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
renderable = render_and_return(
|
|
246
|
+
node=create_query_node(iolanta.query(query)),
|
|
247
|
+
as_datatype=as_datatype,
|
|
248
|
+
language=language,
|
|
249
|
+
log_level=log_level,
|
|
250
|
+
)
|
|
251
|
+
except (SPARQLParseException, DocumentedError, FacetNotFound) as error:
|
|
252
|
+
handle_error(error, log_level, use_markdown=True)
|
|
253
|
+
except Exception as error:
|
|
254
|
+
handle_error(error, log_level, use_markdown=False)
|
|
255
|
+
else:
|
|
256
|
+
# FIXME: An intermediary Literal can be used to dispatch rendering.
|
|
257
|
+
match renderable:
|
|
258
|
+
case Table() as table:
|
|
259
|
+
console.print(table)
|
|
260
|
+
|
|
261
|
+
case unknown:
|
|
262
|
+
console.print(unknown)
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
if url is None:
|
|
266
|
+
console.print("Error: Either URL or --query must be provided")
|
|
267
|
+
raise Exit(1)
|
|
268
|
+
|
|
269
|
+
# For URLs, default to interactive mode
|
|
270
|
+
if as_datatype is None:
|
|
271
|
+
as_datatype = "https://iolanta.tech/cli/interactive"
|
|
272
|
+
|
|
273
|
+
# Parse string URL to URIRef (URL() is permissive and won't raise)
|
|
274
|
+
node_url = URL(url)
|
|
275
|
+
if node_url.scheme and node_url.scheme != "file":
|
|
276
|
+
node = URIRef(url)
|
|
277
|
+
else:
|
|
278
|
+
path = Path(node_url.path).absolute()
|
|
279
|
+
node = URIRef(f"file://{path}")
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
renderable = render_and_return(
|
|
283
|
+
node=node,
|
|
284
|
+
as_datatype=as_datatype,
|
|
285
|
+
language=language,
|
|
286
|
+
log_level=log_level,
|
|
287
|
+
)
|
|
288
|
+
except DocumentedError as error:
|
|
289
|
+
handle_error(error, log_level, use_markdown=True)
|
|
290
|
+
except Exception as error:
|
|
291
|
+
handle_error(error, log_level, use_markdown=False)
|
|
292
|
+
else:
|
|
293
|
+
# FIXME: An intermediary Literal can be used to dispatch rendering.
|
|
294
|
+
match renderable:
|
|
295
|
+
case Table() as table:
|
|
296
|
+
console.print(table)
|
|
297
|
+
|
|
298
|
+
case unknown:
|
|
299
|
+
console.print(unknown)
|
|
@@ -2,32 +2,38 @@ import inspect
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from functools import cached_property
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any, Generic,
|
|
5
|
+
from typing import Any, Generic, Optional, TypeVar, Union
|
|
6
6
|
|
|
7
|
-
from rdflib.term import
|
|
7
|
+
from rdflib.term import Literal, Node
|
|
8
8
|
|
|
9
|
-
from iolanta.models import NotLiteralNode
|
|
9
|
+
from iolanta.models import NotLiteralNode
|
|
10
10
|
from iolanta.query_result import QueryResult, SPARQLQueryArgument
|
|
11
11
|
|
|
12
|
-
FacetOutput = TypeVar(
|
|
12
|
+
FacetOutput = TypeVar("FacetOutput")
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@dataclass
|
|
16
|
-
class Facet(Generic[FacetOutput]):
|
|
16
|
+
class Facet(Generic[FacetOutput]): # noqa: WPS214
|
|
17
17
|
"""Base facet class."""
|
|
18
18
|
|
|
19
19
|
this: Node
|
|
20
|
-
iolanta:
|
|
20
|
+
iolanta: "iolanta.Iolanta" = field(repr=False)
|
|
21
21
|
as_datatype: Optional[NotLiteralNode] = None
|
|
22
22
|
|
|
23
23
|
def __post_init__(self):
|
|
24
|
-
if
|
|
25
|
-
|
|
24
|
+
if not isinstance(self.this, Node):
|
|
25
|
+
facet_name = self.__class__.__name__
|
|
26
|
+
this_type = type(self.this).__name__
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Facet {facet_name} received a non-Node as this: {self.this} (type: {this_type})"
|
|
29
|
+
)
|
|
26
30
|
|
|
27
31
|
@property
|
|
28
32
|
def stored_queries_path(self) -> Path:
|
|
29
33
|
"""Construct directory for stored queries for this facet."""
|
|
30
|
-
return Path(inspect.getfile(self.__class__)).parent /
|
|
34
|
+
return Path(inspect.getfile(self.__class__)).parent / "sparql"
|
|
35
|
+
|
|
36
|
+
inference_path: Optional[Path] = None
|
|
31
37
|
|
|
32
38
|
def query(
|
|
33
39
|
self,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
from rdflib import BNode, Literal, Node, URIRef
|
|
5
|
+
|
|
6
|
+
from iolanta import Facet
|
|
7
|
+
from iolanta.mermaid.models import (
|
|
8
|
+
Diagram,
|
|
9
|
+
MermaidBlankNode,
|
|
10
|
+
MermaidEdge,
|
|
11
|
+
MermaidLiteral,
|
|
12
|
+
MermaidScalar,
|
|
13
|
+
MermaidSubgraph,
|
|
14
|
+
MermaidURINode,
|
|
15
|
+
)
|
|
16
|
+
from iolanta.namespaces import DATATYPES
|
|
17
|
+
from pydantic import AnyUrl
|
|
18
|
+
from rdflib import URIRef as RDFURIRef
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TaskNode(MermaidScalar):
|
|
22
|
+
"""A Mermaid node with attached CSS classes."""
|
|
23
|
+
|
|
24
|
+
node: MermaidURINode | MermaidBlankNode | MermaidLiteral
|
|
25
|
+
classes: list[str] = []
|
|
26
|
+
|
|
27
|
+
def __str__(self) -> str:
|
|
28
|
+
"""Render the wrapped node."""
|
|
29
|
+
return str(self.node)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def id(self) -> str:
|
|
33
|
+
"""Get the ID of the wrapped node."""
|
|
34
|
+
return self.node.id
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BlocksEdge(MermaidEdge):
|
|
38
|
+
"""
|
|
39
|
+
{self.source.id} --> {self.target.id}
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, source, target):
|
|
43
|
+
# Initialize with empty title and a dummy predicate
|
|
44
|
+
super().__init__(
|
|
45
|
+
source=source,
|
|
46
|
+
target=target,
|
|
47
|
+
predicate=RDFURIRef('https://iolanta.tech/roadmap/blocks'),
|
|
48
|
+
title='',
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
# Override to remove intermediate node - just direct arrow
|
|
53
|
+
return f'{self.source.id} --> {self.target.id}'
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Rebuild Pydantic models to resolve forward references
|
|
57
|
+
# Need to rebuild MermaidEdge first so MermaidSubgraph is available
|
|
58
|
+
MermaidEdge.model_rebuild()
|
|
59
|
+
BlocksEdge.model_rebuild()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MermaidRoadmap(Facet[str]):
|
|
63
|
+
"""Mermaid roadmap diagram."""
|
|
64
|
+
|
|
65
|
+
META = Path(__file__).parent / 'mermaid_roadmap.yamlld'
|
|
66
|
+
|
|
67
|
+
inference_path = Path(__file__).parent / 'inference'
|
|
68
|
+
|
|
69
|
+
def show(self) -> str:
|
|
70
|
+
"""Render mermaid roadmap diagram."""
|
|
71
|
+
children = list(self.construct_mermaid_for_graph(self.this))
|
|
72
|
+
|
|
73
|
+
# Extract class assignments from TaskNode instances
|
|
74
|
+
tail_parts = ['classDef unblocked fill:#0a5,stroke:#063,stroke-width:2px,color:#fff;']
|
|
75
|
+
for child in children:
|
|
76
|
+
if isinstance(child, TaskNode) and child.classes:
|
|
77
|
+
for class_name in child.classes:
|
|
78
|
+
tail_parts.append(f'class {child.id} {class_name}')
|
|
79
|
+
|
|
80
|
+
tail = '\n'.join(tail_parts)
|
|
81
|
+
|
|
82
|
+
return str(Diagram(
|
|
83
|
+
children=children,
|
|
84
|
+
tail=tail,
|
|
85
|
+
))
|
|
86
|
+
|
|
87
|
+
def as_mermaid(self, node: Node):
|
|
88
|
+
"""Convert RDF node to Mermaid node."""
|
|
89
|
+
match node:
|
|
90
|
+
case URIRef() as uri:
|
|
91
|
+
return MermaidURINode(
|
|
92
|
+
uri=uri,
|
|
93
|
+
url=AnyUrl(uri),
|
|
94
|
+
title=self.render(uri, as_datatype=DATATYPES.title),
|
|
95
|
+
)
|
|
96
|
+
case Literal() as literal:
|
|
97
|
+
return MermaidLiteral(literal=literal)
|
|
98
|
+
case BNode() as bnode:
|
|
99
|
+
return MermaidBlankNode(
|
|
100
|
+
node=bnode,
|
|
101
|
+
title=self.render(bnode, as_datatype=DATATYPES.title),
|
|
102
|
+
)
|
|
103
|
+
case unknown:
|
|
104
|
+
unknown_type = type(unknown)
|
|
105
|
+
raise ValueError(f'Unknown node type: {unknown} ({unknown_type})')
|
|
106
|
+
|
|
107
|
+
def construct_mermaid_for_graph(self, graph: URIRef) -> Iterable[MermaidScalar]:
|
|
108
|
+
"""Render graph as mermaid."""
|
|
109
|
+
# Get nodes
|
|
110
|
+
node_rows = self.stored_query('nodes.sparql')
|
|
111
|
+
node_rows_list = list(node_rows)
|
|
112
|
+
|
|
113
|
+
nodes = [
|
|
114
|
+
TaskNode(
|
|
115
|
+
node=self.as_mermaid(row['node']),
|
|
116
|
+
classes=['unblocked'] if row.get('is_unblocked', False) else [],
|
|
117
|
+
)
|
|
118
|
+
for row in node_rows_list
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
# Get edges for roadmap:blocks relationships
|
|
122
|
+
edge_rows = self.stored_query('edges.sparql')
|
|
123
|
+
edge_rows_list = list(edge_rows)
|
|
124
|
+
|
|
125
|
+
edges = [
|
|
126
|
+
BlocksEdge(
|
|
127
|
+
source=self.as_mermaid(row['source']),
|
|
128
|
+
target=self.as_mermaid(row['target']),
|
|
129
|
+
)
|
|
130
|
+
for row in edge_rows_list
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
return [*nodes, *edges]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
PREFIX roadmap: <https://iolanta.tech/roadmap/>
|
|
2
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
3
|
+
|
|
4
|
+
CONSTRUCT {
|
|
5
|
+
?item a roadmap:Task .
|
|
6
|
+
}
|
|
7
|
+
WHERE {
|
|
8
|
+
# Find items that are values of roadmap:has-task property
|
|
9
|
+
?container roadmap:has-task ?item .
|
|
10
|
+
|
|
11
|
+
# Only infer if the item isn't already typed as a roadmap type
|
|
12
|
+
FILTER NOT EXISTS {
|
|
13
|
+
?item rdf:type ?type .
|
|
14
|
+
FILTER(?type IN (roadmap:Task, roadmap:Event, roadmap:Bug))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# It is imperative that this query executes after `blocks.sparql` because it relies upon proper `roadmap:blocks` relations.
|
|
2
|
+
|
|
3
|
+
PREFIX roadmap: <https://iolanta.tech/roadmap/>
|
|
4
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
5
|
+
|
|
6
|
+
CONSTRUCT {
|
|
7
|
+
?other a roadmap:Task .
|
|
8
|
+
}
|
|
9
|
+
WHERE {
|
|
10
|
+
# Match either Task or Event
|
|
11
|
+
{
|
|
12
|
+
?task rdf:type roadmap:Task .
|
|
13
|
+
} UNION {
|
|
14
|
+
?task rdf:type roadmap:Event .
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Find nodes connected via roadmap:blocks chain (in either direction)
|
|
18
|
+
# ^roadmap:blocks is the inverse (equivalent to roadmap:is-blocked-by)
|
|
19
|
+
?task (roadmap:blocks | ^roadmap:blocks)+ ?other .
|
|
20
|
+
|
|
21
|
+
# Only infer if the node isn't already a Task
|
|
22
|
+
# (to avoid redundant triples, but allow inference even if node has other types)
|
|
23
|
+
FILTER NOT EXISTS {
|
|
24
|
+
?other rdf:type roadmap:Task .
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
PREFIX roadmap: <https://iolanta.tech/roadmap/>
|
|
2
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
3
|
+
|
|
4
|
+
CONSTRUCT {
|
|
5
|
+
?task a roadmap:Unblocked .
|
|
6
|
+
}
|
|
7
|
+
WHERE {
|
|
8
|
+
# Match Task, Event, or Bug instances
|
|
9
|
+
{
|
|
10
|
+
?task a roadmap:Task .
|
|
11
|
+
} UNION {
|
|
12
|
+
?task a roadmap:Event .
|
|
13
|
+
} UNION {
|
|
14
|
+
?task a roadmap:Bug .
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Only infer if there are no incoming blocks links
|
|
18
|
+
FILTER NOT EXISTS {
|
|
19
|
+
?other roadmap:blocks ?task .
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"@context":
|
|
2
|
+
"@import": https://json-ld.org/contexts/dollar-convenience.jsonld
|
|
3
|
+
roadmap: https://roadmap.iolanta.tech/
|
|
4
|
+
iolanta: https://iolanta.tech/
|
|
5
|
+
rdfs: http://www.w3.org/2000/01/rdf-schema#
|
|
6
|
+
prov: http://www.w3.org/ns/prov#
|
|
7
|
+
owl: http://www.w3.org/2002/07/owl#
|
|
8
|
+
|
|
9
|
+
$: rdfs:label
|
|
10
|
+
|
|
11
|
+
rdfs:subClassOf:
|
|
12
|
+
"@type": "@id"
|
|
13
|
+
|
|
14
|
+
rdfs:subPropertyOf:
|
|
15
|
+
"@type": "@id"
|
|
16
|
+
|
|
17
|
+
owl:inverseOf:
|
|
18
|
+
"@type": "@id"
|
|
19
|
+
|
|
20
|
+
→:
|
|
21
|
+
"@type": "@id"
|
|
22
|
+
"@id": iolanta:outputs
|
|
23
|
+
|
|
24
|
+
↦:
|
|
25
|
+
"@id": iolanta:matches
|
|
26
|
+
"@type": iolanta:SPARQLText
|
|
27
|
+
|
|
28
|
+
$id: pkg:pypi/iolanta#mermaid-roadmap
|
|
29
|
+
$: Mermaid Roadmap
|
|
30
|
+
|
|
31
|
+
→:
|
|
32
|
+
$id: https://iolanta.tech/roadmap/datatypes/mermaid
|
|
33
|
+
$: Mermaid
|
|
34
|
+
$type: iolanta:OutputDatatype
|
|
35
|
+
rdfs:subClassOf: https://iolanta.tech/datatypes/mermaid
|
|
36
|
+
|
|
37
|
+
↦:
|
|
38
|
+
- ASK WHERE { GRAPH $this { ?s ?p ?o } }
|
|
39
|
+
|
|
40
|
+
$included:
|
|
41
|
+
- $id: roadmap:Task
|
|
42
|
+
rdfs:subClassOf: prov:Activity
|
|
43
|
+
$: Task
|
|
44
|
+
|
|
45
|
+
- $id: roadmap:Bug
|
|
46
|
+
rdfs:subClassOf: roadmap:Task
|
|
47
|
+
$: Bug
|
|
48
|
+
|
|
49
|
+
- $id: roadmap:Event
|
|
50
|
+
rdfs:subClassOf: prov:Activity
|
|
51
|
+
$: Event
|
|
52
|
+
|
|
53
|
+
- $id: roadmap:is-blocked-by
|
|
54
|
+
rdfs:subPropertyOf: prov:wasInformedBy
|
|
55
|
+
$: Is blocked by
|
|
56
|
+
|
|
57
|
+
- $id: roadmap:blocks
|
|
58
|
+
owl:inverseOf: roadmap:is-blocked-by
|
|
59
|
+
$: Blocks
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
PREFIX roadmap: <https://iolanta.tech/roadmap/>
|
|
2
|
+
|
|
3
|
+
SELECT DISTINCT ?source ?target WHERE {
|
|
4
|
+
# Find roadmap:blocks relationships across all graphs (including inferred)
|
|
5
|
+
?source roadmap:blocks ?target .
|
|
6
|
+
|
|
7
|
+
# Only include edges where both source and target are Task/Event/Bug nodes
|
|
8
|
+
# (they will be filtered by the nodes query, but this ensures we only show
|
|
9
|
+
# relevant edges)
|
|
10
|
+
{
|
|
11
|
+
?source a roadmap:Task .
|
|
12
|
+
} UNION {
|
|
13
|
+
?source a roadmap:Event .
|
|
14
|
+
} UNION {
|
|
15
|
+
?source a roadmap:Bug .
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
?target a roadmap:Task .
|
|
20
|
+
} UNION {
|
|
21
|
+
?target a roadmap:Event .
|
|
22
|
+
} UNION {
|
|
23
|
+
?target a roadmap:Bug .
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
PREFIX roadmap: <https://iolanta.tech/roadmap/>
|
|
2
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
3
|
+
|
|
4
|
+
SELECT DISTINCT ?node ?is_unblocked WHERE {
|
|
5
|
+
# Search across all graphs (including inference graphs)
|
|
6
|
+
# This finds both original types and inferred types
|
|
7
|
+
{
|
|
8
|
+
?node rdf:type roadmap:Task .
|
|
9
|
+
} UNION {
|
|
10
|
+
?node rdf:type roadmap:Event .
|
|
11
|
+
} UNION {
|
|
12
|
+
?node rdf:type roadmap:Bug .
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Check if node is unblocked (has roadmap:Unblocked type)
|
|
16
|
+
BIND(EXISTS { ?node rdf:type roadmap:Unblocked } AS ?is_unblocked)
|
|
17
|
+
}
|