iolanta 1.2.7__tar.gz → 1.2.8__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-1.2.7 → iolanta-1.2.8}/PKG-INFO +2 -1
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/main.py +10 -6
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/processor.py +10 -8
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/page_title.py +1 -2
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/app.py +25 -1
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/location.py +3 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/page_switcher.py +153 -30
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/facets.py +2 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/widgets.py +3 -5
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/facets.py +4 -3
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/iolanta.py +25 -14
- {iolanta-1.2.7 → iolanta-1.2.8}/pyproject.toml +2 -1
- iolanta-1.2.7/iolanta/data/foaf-meta.yaml +0 -113
- iolanta-1.2.7/iolanta/data/ontologies-meta.yaml +0 -57
- {iolanta-1.2.7 → iolanta-1.2.8}/README.md +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/base_plugin.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/choose.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/csv.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/json.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/pretty.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/models.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/pretty_print.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/context.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/conversions.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/inference/wikibase-claim.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/inference/wikibase-statement-property.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/cli.yaml +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/context.yaml +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/html.yaml +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/iolanta.yaml +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/textual-browser.yaml +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/ensure_is_context.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/entry_points.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/errors.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/base.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/default.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/record.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/sparql/link.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/sparql/record.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/errors.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/facet.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/foaf_person_title/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/foaf_person_title/facet.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/foaf_person_title/sparql/names.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/bool_literal.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/date_literal.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/default.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/sparql/default.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/base.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/code_literal.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/default.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/icon.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/locator.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/qname.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/facet.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/history.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/home.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/models.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/page.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_class/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_class/facets.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_class/sparql/instances.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/inverse-properties.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/label.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/properties.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/tcss/default.tcss +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/templates/default.md +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/triple_uri_ref.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph/facets.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph/sparql/triples.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph_triples.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_link/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_link/facet.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/facet.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/models.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/nanopublication_widget.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/term_list_widget.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/term_widget.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/facets.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/sparql/terms.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/sparql/graphs.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/sparql/triples.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/title/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/title/facets.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/title/sparql/title.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/wikibase_statement_title/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/wikibase_statement_title/facets.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/base.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/data_type_choice.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/dict_loader.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/errors.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/http.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/local_directory.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/local_file.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/scheme_choice.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/models.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/namespaces.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/node_to_qname.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parse_quads.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/base.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/dict_parser.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/errors.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/json.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/markdown.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/yaml.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/plugin.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/reformat_blank_nodes.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/resolvers/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/resolvers/base.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/resolvers/python_import.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/shortcuts.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/stack.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/widgets/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/widgets/mixin.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/ldflex/__init__.py +0 -0
- {iolanta-1.2.7 → iolanta-1.2.8}/ldflex/ldflex.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iolanta
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.8
|
|
4
4
|
Summary: Semantic Web browser
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Anatoly Scherbakov
|
|
@@ -32,6 +32,7 @@ Requires-Dist: requests (>=2.25.1)
|
|
|
32
32
|
Requires-Dist: rich (>=13.3.1)
|
|
33
33
|
Requires-Dist: textual (>=0.83.0)
|
|
34
34
|
Requires-Dist: typer (>=0.9.0)
|
|
35
|
+
Requires-Dist: watchfiles (>=1.0.4,<2.0.0)
|
|
35
36
|
Requires-Dist: yaml-ld (>=1.1.3)
|
|
36
37
|
Requires-Dist: yarl (>=1.9.4)
|
|
37
38
|
Description-Content-Type: text/markdown
|
|
@@ -93,18 +93,22 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
|
|
|
93
93
|
enqueue=True,
|
|
94
94
|
)
|
|
95
95
|
|
|
96
|
-
iolanta: Iolanta = Iolanta(
|
|
97
|
-
language=Literal(language),
|
|
98
|
-
logger=logger,
|
|
99
|
-
)
|
|
100
|
-
|
|
101
96
|
node_url = URL(url)
|
|
102
97
|
if node_url.scheme:
|
|
103
98
|
node = URIRef(url)
|
|
99
|
+
|
|
100
|
+
iolanta: Iolanta = Iolanta(
|
|
101
|
+
language=Literal(language),
|
|
102
|
+
logger=logger,
|
|
103
|
+
)
|
|
104
104
|
else:
|
|
105
105
|
path = Path(url).absolute()
|
|
106
106
|
node = URIRef(f'file://{path}')
|
|
107
|
-
iolanta
|
|
107
|
+
iolanta: Iolanta = Iolanta(
|
|
108
|
+
language=Literal(language),
|
|
109
|
+
logger=logger,
|
|
110
|
+
project_root=path,
|
|
111
|
+
)
|
|
108
112
|
|
|
109
113
|
try:
|
|
110
114
|
renderable, stack = iolanta.render(
|
|
@@ -131,6 +131,10 @@ def _extract_from_mapping( # noqa: WPS213
|
|
|
131
131
|
yield from extract_mentioned_urls(algebra['p2'])
|
|
132
132
|
yield from extract_mentioned_urls(algebra['expr'])
|
|
133
133
|
|
|
134
|
+
case 'Join':
|
|
135
|
+
yield from extract_mentioned_urls(algebra['p1'])
|
|
136
|
+
yield from extract_mentioned_urls(algebra['p2'])
|
|
137
|
+
|
|
134
138
|
case 'ConditionalOrExpression' | 'ConditionalAndExpression':
|
|
135
139
|
yield from extract_mentioned_urls(algebra['expr'])
|
|
136
140
|
yield from extract_mentioned_urls(algebra['other'])
|
|
@@ -149,10 +153,8 @@ def _extract_from_mapping( # noqa: WPS213
|
|
|
149
153
|
case unknown_name:
|
|
150
154
|
formatted_keys = ', '.join(algebra.keys())
|
|
151
155
|
loguru.logger.error(
|
|
152
|
-
'Unknown SPARQL expression
|
|
153
|
-
unknown_name,
|
|
154
|
-
formatted_keys,
|
|
155
|
-
algebra,
|
|
156
|
+
'Unknown SPARQL expression '
|
|
157
|
+
f'{unknown_name}({formatted_keys}): {algebra}',
|
|
156
158
|
)
|
|
157
159
|
return
|
|
158
160
|
|
|
@@ -422,7 +424,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
|
|
|
422
424
|
and isinstance(self.load(maybe_iri), Loaded)
|
|
423
425
|
):
|
|
424
426
|
is_anything_loaded = True # noqa: WPS220
|
|
425
|
-
self.logger.
|
|
427
|
+
self.logger.info( # noqa: WPS220
|
|
426
428
|
'Newly loaded: {uri}',
|
|
427
429
|
uri=maybe_iri,
|
|
428
430
|
)
|
|
@@ -464,7 +466,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
|
|
|
464
466
|
if existing_triple is not None:
|
|
465
467
|
return Skipped()
|
|
466
468
|
else:
|
|
467
|
-
self.logger.
|
|
469
|
+
self.logger.info(f'Existing triples not found for {source_uri}')
|
|
468
470
|
|
|
469
471
|
# FIXME This is definitely inefficient. However, python-yaml-ld caches
|
|
470
472
|
# the document, so the performance overhead is not super high.
|
|
@@ -565,10 +567,10 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
|
|
|
565
567
|
)
|
|
566
568
|
return Loaded()
|
|
567
569
|
except ParserNotFound as parser_not_found:
|
|
568
|
-
self.logger.info('
|
|
570
|
+
self.logger.info(f'{source} | {parser_not_found}')
|
|
569
571
|
return Loaded()
|
|
570
572
|
except YAMLLDError as yaml_ld_error:
|
|
571
|
-
self.logger.error('
|
|
573
|
+
self.logger.error(f'{source} | {yaml_ld_error}')
|
|
572
574
|
return Loaded()
|
|
573
575
|
|
|
574
576
|
try:
|
|
@@ -22,8 +22,7 @@ class PageTitle(IolantaWidgetMixin, Static):
|
|
|
22
22
|
"""Initialize."""
|
|
23
23
|
self.iri = iri
|
|
24
24
|
self.extra = extra
|
|
25
|
-
|
|
26
|
-
super().__init__(qname)
|
|
25
|
+
super().__init__(iri)
|
|
27
26
|
|
|
28
27
|
def construct_title(self):
|
|
29
28
|
"""Render the title via Iolanta in a thread."""
|
|
@@ -4,6 +4,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
4
4
|
from rdflib.term import Node
|
|
5
5
|
from rich.console import RenderableType
|
|
6
6
|
from textual.app import App, ComposeResult
|
|
7
|
+
from textual.css.query import NoMatches
|
|
7
8
|
from textual.widgets import Footer, Header
|
|
8
9
|
|
|
9
10
|
from iolanta.facets.textual_browser.page_switcher import (
|
|
@@ -13,6 +14,8 @@ from iolanta.facets.textual_browser.page_switcher import (
|
|
|
13
14
|
)
|
|
14
15
|
from iolanta.iolanta import Iolanta
|
|
15
16
|
|
|
17
|
+
POPUP_TIMEOUT = 30 # seconds
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
class DevConsoleHandler(logging.Handler):
|
|
18
21
|
"""Pipe log output → dev console."""
|
|
@@ -28,6 +31,17 @@ class DevConsoleHandler(logging.Handler):
|
|
|
28
31
|
self.console.write(message)
|
|
29
32
|
|
|
30
33
|
|
|
34
|
+
def _log_message_to_dev_console(app: App):
|
|
35
|
+
"""Log a message to the dev console."""
|
|
36
|
+
def log_message_to_dev_console(message: str): # noqa: WPS430
|
|
37
|
+
try:
|
|
38
|
+
app.query_one(DevConsole).write(message)
|
|
39
|
+
except NoMatches:
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
return log_message_to_dev_console
|
|
43
|
+
|
|
44
|
+
|
|
31
45
|
class IolantaBrowser(App): # noqa: WPS214, WPS230
|
|
32
46
|
"""Browse Linked Data."""
|
|
33
47
|
|
|
@@ -69,11 +83,21 @@ class IolantaBrowser(App): # noqa: WPS214, WPS230
|
|
|
69
83
|
|
|
70
84
|
# Log to the dev console.
|
|
71
85
|
self.iolanta.logger.add(
|
|
72
|
-
|
|
86
|
+
_log_message_to_dev_console(self),
|
|
73
87
|
level='INFO',
|
|
74
88
|
format='{time} {level} {message}',
|
|
75
89
|
)
|
|
76
90
|
|
|
91
|
+
self.iolanta.logger.add(
|
|
92
|
+
lambda msg: self.notify(
|
|
93
|
+
msg,
|
|
94
|
+
severity='warning',
|
|
95
|
+
timeout=POPUP_TIMEOUT,
|
|
96
|
+
),
|
|
97
|
+
level='WARNING',
|
|
98
|
+
format='{message}',
|
|
99
|
+
)
|
|
100
|
+
|
|
77
101
|
def action_toggle_dark(self) -> None:
|
|
78
102
|
"""Toggle dark mode."""
|
|
79
103
|
self.dark = not self.dark
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import functools
|
|
2
|
+
import threading
|
|
2
3
|
import uuid
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
3
6
|
|
|
7
|
+
import watchfiles
|
|
4
8
|
from rdflib import BNode, URIRef
|
|
5
9
|
from textual.widgets import ContentSwitcher, RichLog
|
|
6
10
|
from textual.worker import Worker, WorkerState
|
|
@@ -18,6 +22,21 @@ from iolanta.namespaces import DATATYPES
|
|
|
18
22
|
from iolanta.widgets.mixin import IolantaWidgetMixin
|
|
19
23
|
|
|
20
24
|
|
|
25
|
+
@dataclass
|
|
26
|
+
class RenderResult:
|
|
27
|
+
"""
|
|
28
|
+
We asked a thread to render something for us.
|
|
29
|
+
|
|
30
|
+
This is what did we get back.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
iri: NotLiteralNode
|
|
34
|
+
renderable: Any
|
|
35
|
+
flip_options: list[FlipOption]
|
|
36
|
+
facet_iri: URIRef
|
|
37
|
+
is_reload: bool
|
|
38
|
+
|
|
39
|
+
|
|
21
40
|
class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
|
|
22
41
|
"""
|
|
23
42
|
Container for open pages.
|
|
@@ -28,12 +47,15 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
|
|
|
28
47
|
BINDINGS = [ # noqa: WPS115
|
|
29
48
|
('alt+left', 'back', 'Back'),
|
|
30
49
|
('alt+right', 'forward', 'Fwd'),
|
|
50
|
+
('f5', 'reload', '🔄 Reload'),
|
|
51
|
+
('escape', 'abort', '🛑 Abort'),
|
|
31
52
|
('f12', 'console', 'Console'),
|
|
32
53
|
]
|
|
33
54
|
|
|
34
55
|
def __init__(self):
|
|
35
56
|
"""Set Home as first tab."""
|
|
36
57
|
super().__init__(id='page_switcher', initial='home')
|
|
58
|
+
self.stop_file_watcher_event = threading.Event()
|
|
37
59
|
|
|
38
60
|
def action_console(self):
|
|
39
61
|
"""Open dev console."""
|
|
@@ -53,22 +75,39 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
|
|
|
53
75
|
def on_mount(self):
|
|
54
76
|
"""Navigate to the initial page."""
|
|
55
77
|
self.action_goto(self.app.iri)
|
|
78
|
+
if self.iolanta.project_root:
|
|
79
|
+
self.run_worker(
|
|
80
|
+
self._watch_files,
|
|
81
|
+
thread=True,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def on_unmount(self) -> None:
|
|
85
|
+
"""Stop watching files."""
|
|
86
|
+
self.stop_file_watcher_event.set()
|
|
87
|
+
|
|
88
|
+
def _watch_files(self):
|
|
89
|
+
for _ in watchfiles.watch( # noqa: WPS352
|
|
90
|
+
self.iolanta.project_root,
|
|
91
|
+
stop_event=self.stop_file_watcher_event,
|
|
92
|
+
):
|
|
93
|
+
self.app.call_from_thread(self.action_reload)
|
|
56
94
|
|
|
57
95
|
def render_iri( # noqa: WPS210
|
|
58
|
-
self,
|
|
59
|
-
|
|
96
|
+
self,
|
|
97
|
+
destination: NotLiteralNode,
|
|
98
|
+
facet_iri: URIRef | None,
|
|
99
|
+
is_reload: bool,
|
|
100
|
+
) -> RenderResult:
|
|
60
101
|
"""Render an IRI in a thread."""
|
|
61
102
|
self.iri = destination
|
|
62
103
|
iolanta: Iolanta = self.iolanta
|
|
63
104
|
|
|
64
105
|
as_datatype = URIRef('https://iolanta.tech/cli/textual')
|
|
65
|
-
choices =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
).choices,
|
|
71
|
-
)
|
|
106
|
+
choices = FacetFinder(
|
|
107
|
+
iolanta=self.iolanta,
|
|
108
|
+
node=destination,
|
|
109
|
+
as_datatype=as_datatype,
|
|
110
|
+
).choices()
|
|
72
111
|
|
|
73
112
|
if not choices:
|
|
74
113
|
raise FacetNotFound(
|
|
@@ -106,11 +145,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
|
|
|
106
145
|
)
|
|
107
146
|
|
|
108
147
|
try:
|
|
109
|
-
|
|
110
|
-
destination,
|
|
111
|
-
self.app.call_from_thread(facet.show),
|
|
112
|
-
flip_options,
|
|
113
|
-
)
|
|
148
|
+
renderable = facet.show()
|
|
114
149
|
|
|
115
150
|
except Exception as err:
|
|
116
151
|
raise FacetError(
|
|
@@ -119,6 +154,14 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
|
|
|
119
154
|
error=err,
|
|
120
155
|
) from err
|
|
121
156
|
|
|
157
|
+
return RenderResult(
|
|
158
|
+
iri=destination,
|
|
159
|
+
renderable=renderable,
|
|
160
|
+
flip_options=flip_options,
|
|
161
|
+
facet_iri=facet_iri,
|
|
162
|
+
is_reload=is_reload,
|
|
163
|
+
)
|
|
164
|
+
|
|
122
165
|
def on_worker_state_changed( # noqa: WPS210
|
|
123
166
|
self,
|
|
124
167
|
event: Worker.StateChanged,
|
|
@@ -126,24 +169,98 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
|
|
|
126
169
|
"""Render a page as soon as it is ready."""
|
|
127
170
|
match event.state:
|
|
128
171
|
case WorkerState.SUCCESS:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
172
|
+
render_result: RenderResult = event.worker.result
|
|
173
|
+
|
|
174
|
+
if render_result.is_reload:
|
|
175
|
+
# We are reloading the current page.
|
|
176
|
+
current_page = self.query_one(f'#{self.current}', Page)
|
|
177
|
+
current_page.remove_children()
|
|
178
|
+
current_page.mount(render_result.renderable)
|
|
179
|
+
|
|
180
|
+
# FIXME: This does not actually change the flip options,
|
|
181
|
+
# but maybe that's okay
|
|
182
|
+
current_page.flip_options = render_result.flip_options
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
# We are navigating to a new page.
|
|
186
|
+
page_uid = uuid.uuid4().hex
|
|
187
|
+
page_id = f'page_{page_uid}'
|
|
188
|
+
page = Page(
|
|
189
|
+
render_result.renderable,
|
|
190
|
+
iri=render_result.iri,
|
|
191
|
+
page_id=page_id,
|
|
192
|
+
flip_options=render_result.flip_options,
|
|
193
|
+
)
|
|
194
|
+
self.mount(page)
|
|
195
|
+
self.current = page_id
|
|
196
|
+
page.focus()
|
|
197
|
+
self.history.goto(
|
|
198
|
+
Location(
|
|
199
|
+
page_id,
|
|
200
|
+
url=render_result.iri,
|
|
201
|
+
facet_iri=render_result.facet_iri,
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
self.app.sub_title = render_result.iri
|
|
143
205
|
|
|
144
206
|
case WorkerState.ERROR:
|
|
145
207
|
raise ValueError(event)
|
|
146
208
|
|
|
209
|
+
@property
|
|
210
|
+
def is_loading(self) -> bool:
|
|
211
|
+
"""Determine if the app is presently loading something."""
|
|
212
|
+
for worker in self.workers:
|
|
213
|
+
if worker.name == 'render_iri':
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
def action_reload(self):
|
|
219
|
+
"""Reset Iolanta graph and re-render current view."""
|
|
220
|
+
self.iolanta.reset()
|
|
221
|
+
|
|
222
|
+
self.run_worker(
|
|
223
|
+
functools.partial(
|
|
224
|
+
self.render_iri,
|
|
225
|
+
destination=self.history.current.url,
|
|
226
|
+
facet_iri=self.history.current.facet_iri,
|
|
227
|
+
is_reload=True,
|
|
228
|
+
),
|
|
229
|
+
thread=True,
|
|
230
|
+
exclusive=True,
|
|
231
|
+
name='render_iri',
|
|
232
|
+
)
|
|
233
|
+
self.refresh_bindings()
|
|
234
|
+
|
|
235
|
+
def action_abort(self):
|
|
236
|
+
"""Abort loading."""
|
|
237
|
+
self.notify(
|
|
238
|
+
'Aborted.',
|
|
239
|
+
severity='warning',
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
for worker in self.workers:
|
|
243
|
+
if worker.name == 'render_iri':
|
|
244
|
+
worker.cancel()
|
|
245
|
+
break
|
|
246
|
+
|
|
247
|
+
self.refresh_bindings()
|
|
248
|
+
|
|
249
|
+
def check_action(
|
|
250
|
+
self,
|
|
251
|
+
action: str,
|
|
252
|
+
parameters: tuple[object, ...], # noqa: WPS110
|
|
253
|
+
) -> bool | None:
|
|
254
|
+
"""Check if action is available."""
|
|
255
|
+
is_loading = self.is_loading
|
|
256
|
+
match action:
|
|
257
|
+
case 'reload':
|
|
258
|
+
return not is_loading
|
|
259
|
+
case 'abort':
|
|
260
|
+
return is_loading
|
|
261
|
+
|
|
262
|
+
return True
|
|
263
|
+
|
|
147
264
|
def action_goto(
|
|
148
265
|
self,
|
|
149
266
|
destination: str,
|
|
@@ -158,19 +275,25 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
|
|
|
158
275
|
self.run_worker(
|
|
159
276
|
functools.partial(
|
|
160
277
|
self.render_iri,
|
|
161
|
-
iri,
|
|
162
|
-
facet_iri and URIRef(facet_iri),
|
|
278
|
+
destination=iri,
|
|
279
|
+
facet_iri=facet_iri and URIRef(facet_iri),
|
|
280
|
+
is_reload=False,
|
|
163
281
|
),
|
|
164
282
|
thread=True,
|
|
283
|
+
exclusive=True,
|
|
284
|
+
name='render_iri',
|
|
165
285
|
)
|
|
286
|
+
self.refresh_bindings()
|
|
166
287
|
|
|
167
288
|
def action_back(self):
|
|
168
289
|
"""Go backward."""
|
|
169
290
|
self.current = self.history.back().page_id
|
|
291
|
+
self.focus()
|
|
170
292
|
|
|
171
293
|
def action_forward(self):
|
|
172
294
|
"""Go forward."""
|
|
173
295
|
self.current = self.history.forward().page_id
|
|
296
|
+
self.focus()
|
|
174
297
|
|
|
175
298
|
|
|
176
299
|
class ConsoleSwitcher(ContentSwitcher):
|
|
@@ -58,6 +58,7 @@ class TextualDefaultFacet(Facet[Widget]): # noqa: WPS214
|
|
|
58
58
|
for property_iri, property_values in self.grouped_properties.items():
|
|
59
59
|
property_name = PropertyName(
|
|
60
60
|
iri=property_iri,
|
|
61
|
+
qname=self.iolanta.node_as_qname(property_iri),
|
|
61
62
|
)
|
|
62
63
|
|
|
63
64
|
property_values = [
|
|
@@ -69,6 +70,7 @@ class TextualDefaultFacet(Facet[Widget]): # noqa: WPS214
|
|
|
69
70
|
property_value=property_value,
|
|
70
71
|
subject=self.iri,
|
|
71
72
|
property_iri=property_iri,
|
|
73
|
+
property_qname=self.iolanta.node_as_qname(property_iri),
|
|
72
74
|
)
|
|
73
75
|
for property_value in property_values
|
|
74
76
|
]
|
|
@@ -48,11 +48,11 @@ class PropertyName(Widget, can_focus=True, inherit_bindings=False):
|
|
|
48
48
|
def __init__(
|
|
49
49
|
self,
|
|
50
50
|
iri: NotLiteralNode,
|
|
51
|
+
qname: str,
|
|
51
52
|
):
|
|
52
53
|
"""Set the IRI."""
|
|
53
54
|
self.iri = iri
|
|
54
55
|
super().__init__()
|
|
55
|
-
qname = self.app.iolanta.node_as_qname(iri)
|
|
56
56
|
self.renderable = Text( # noqa: WPS601
|
|
57
57
|
f'⏳ {qname}',
|
|
58
58
|
style='#696969',
|
|
@@ -130,17 +130,15 @@ class PropertyValue(Widget, can_focus=True, inherit_bindings=False):
|
|
|
130
130
|
property_value: Node,
|
|
131
131
|
subject: NotLiteralNode,
|
|
132
132
|
property_iri: NotLiteralNode,
|
|
133
|
+
property_qname: str,
|
|
133
134
|
):
|
|
134
135
|
"""Initialize parameters for rendering, navigation, & provenance."""
|
|
135
136
|
self.property_value = property_value
|
|
136
137
|
self.subject = subject
|
|
137
138
|
self.property_iri = property_iri
|
|
138
139
|
super().__init__()
|
|
139
|
-
qname = self.app.iolanta.node_as_qname( # noqa: WPS601
|
|
140
|
-
property_value,
|
|
141
|
-
)
|
|
142
140
|
self.renderable = Text( # noqa: WPS601
|
|
143
|
-
f'⏳ {
|
|
141
|
+
f'⏳ {property_qname}',
|
|
144
142
|
style='#696969',
|
|
145
143
|
)
|
|
146
144
|
|
|
@@ -80,18 +80,19 @@ class ProvenanceView(Vertical):
|
|
|
80
80
|
"""Build page structure."""
|
|
81
81
|
yield Title('Provenan©e for a triple')
|
|
82
82
|
|
|
83
|
+
# TODO: Calculate QNames somehow.
|
|
83
84
|
yield PropertyRow(
|
|
84
|
-
PropertyName(RDF.subject),
|
|
85
|
+
PropertyName(RDF.subject, qname=str(RDF.subject)),
|
|
85
86
|
RDFTermView(self.triple.subject),
|
|
86
87
|
)
|
|
87
88
|
|
|
88
89
|
yield PropertyRow(
|
|
89
|
-
PropertyName(RDF.predicate),
|
|
90
|
+
PropertyName(RDF.predicate, qname=str(RDF.predicate)),
|
|
90
91
|
RDFTermView(self.triple.predicate),
|
|
91
92
|
)
|
|
92
93
|
|
|
93
94
|
yield PropertyRow(
|
|
94
|
-
PropertyName(RDF.object),
|
|
95
|
+
PropertyName(RDF.object, qname=str(RDF.object)),
|
|
95
96
|
RDFTermView(self.triple.object),
|
|
96
97
|
)
|
|
97
98
|
|
|
@@ -2,6 +2,7 @@ import functools
|
|
|
2
2
|
from dataclasses import dataclass, field, replace
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import ( # noqa: WPS235
|
|
5
|
+
Annotated,
|
|
5
6
|
Any,
|
|
6
7
|
Dict,
|
|
7
8
|
Iterable,
|
|
@@ -30,7 +31,6 @@ from iolanta.errors import UnresolvedIRI
|
|
|
30
31
|
from iolanta.facets.errors import FacetError
|
|
31
32
|
from iolanta.facets.facet import Facet
|
|
32
33
|
from iolanta.facets.locator import FacetFinder
|
|
33
|
-
from iolanta.loaders import Loader
|
|
34
34
|
from iolanta.loaders.base import SourceType
|
|
35
35
|
from iolanta.loaders.local_directory import merge_contexts
|
|
36
36
|
from iolanta.models import (
|
|
@@ -45,7 +45,6 @@ from iolanta.parse_quads import parse_quads
|
|
|
45
45
|
from iolanta.parsers.yaml import YAML
|
|
46
46
|
from iolanta.plugin import Plugin
|
|
47
47
|
from iolanta.resolvers.python_import import PythonImportResolver
|
|
48
|
-
from iolanta.shortcuts import construct_root_loader
|
|
49
48
|
from iolanta.stack import Stack
|
|
50
49
|
from ldflex import LDFlex
|
|
51
50
|
|
|
@@ -72,19 +71,24 @@ class LoggerProtocol(Protocol):
|
|
|
72
71
|
"""Log a WARNING message."""
|
|
73
72
|
|
|
74
73
|
|
|
74
|
+
def _create_default_graph():
|
|
75
|
+
return ConjunctiveGraph(identifier=namespaces.LOCAL.term('_inference'))
|
|
76
|
+
|
|
77
|
+
|
|
75
78
|
@dataclass
|
|
76
79
|
class Iolanta: # noqa: WPS214
|
|
77
80
|
"""Iolanta is a Semantic web browser."""
|
|
78
81
|
|
|
79
82
|
language: Literal = Literal('en')
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
project_root: Annotated[
|
|
85
|
+
Path | None,
|
|
86
|
+
(
|
|
87
|
+
'File or directory the contents of which '
|
|
88
|
+
'Iolanta will automatically load into the graph.'
|
|
86
89
|
),
|
|
87
|
-
|
|
90
|
+
] = None
|
|
91
|
+
graph: ConjunctiveGraph = field(default_factory=_create_default_graph)
|
|
88
92
|
force_plugins: List[Type[Plugin]] = field(default_factory=list)
|
|
89
93
|
|
|
90
94
|
facet_resolver: Mapping[URIRef, Type[Facet]] = field(
|
|
@@ -121,7 +125,7 @@ class Iolanta: # noqa: WPS214
|
|
|
121
125
|
for plugin_class in self.plugin_classes
|
|
122
126
|
]
|
|
123
127
|
|
|
124
|
-
@
|
|
128
|
+
@property
|
|
125
129
|
def ldflex(self) -> LDFlex:
|
|
126
130
|
"""
|
|
127
131
|
Create ldflex instance.
|
|
@@ -147,7 +151,12 @@ class Iolanta: # noqa: WPS214
|
|
|
147
151
|
)
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
def
|
|
154
|
+
def reset(self):
|
|
155
|
+
"""Reset Iolanta graph."""
|
|
156
|
+
self.graph = _create_default_graph() # noqa: WPS601
|
|
157
|
+
self.__post_init__()
|
|
158
|
+
|
|
159
|
+
def add( # noqa: C901, WPS231, WPS210, WPS213
|
|
151
160
|
self,
|
|
152
161
|
source: Path,
|
|
153
162
|
context: Optional[LDContext] = None,
|
|
@@ -167,20 +176,20 @@ class Iolanta: # noqa: WPS214
|
|
|
167
176
|
try: # noqa: WPS225
|
|
168
177
|
ld_rdf = yaml_ld.to_rdf(source_file)
|
|
169
178
|
except ConnectionError as name_resolution_error:
|
|
170
|
-
self.logger.
|
|
179
|
+
self.logger.warning(
|
|
171
180
|
'%s | name resolution error: %s',
|
|
172
181
|
source_file,
|
|
173
182
|
str(name_resolution_error),
|
|
174
183
|
)
|
|
175
184
|
continue
|
|
176
185
|
except ParserNotFound as parser_not_found:
|
|
177
|
-
self.logger.
|
|
186
|
+
self.logger.error(f'{source} | {parser_not_found}')
|
|
178
187
|
continue
|
|
179
188
|
except YAMLLDError as yaml_ld_error:
|
|
180
|
-
self.logger.
|
|
189
|
+
self.logger.error(f'{source} | {yaml_ld_error}')
|
|
181
190
|
continue
|
|
182
191
|
except ValueError as value_error:
|
|
183
|
-
self.logger.
|
|
192
|
+
self.logger.error(f'{source} | {value_error}')
|
|
184
193
|
continue
|
|
185
194
|
|
|
186
195
|
self.logger.info(f'{source_file} is loaded.')
|
|
@@ -304,6 +313,8 @@ class Iolanta: # noqa: WPS214
|
|
|
304
313
|
"""
|
|
305
314
|
self.bind_namespaces()
|
|
306
315
|
self.add_files_from_plugins()
|
|
316
|
+
if self.project_root:
|
|
317
|
+
self.add(self.project_root)
|
|
307
318
|
|
|
308
319
|
def render(
|
|
309
320
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "iolanta"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.8"
|
|
4
4
|
description = "Semantic Web browser"
|
|
5
5
|
authors = ["Anatoly Scherbakov <altaisoft@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -35,6 +35,7 @@ oxrdflib = ">=0.4.0"
|
|
|
35
35
|
loguru = ">=0.7.3"
|
|
36
36
|
nanopub = ">=2.0.1"
|
|
37
37
|
diskcache = ">=5.6.3"
|
|
38
|
+
watchfiles = "^1.0.4"
|
|
38
39
|
|
|
39
40
|
[tool.poetry.scripts]
|
|
40
41
|
iolanta = "iolanta.cli:app"
|