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.
Files changed (135) hide show
  1. {iolanta-1.2.7 → iolanta-1.2.8}/PKG-INFO +2 -1
  2. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/main.py +10 -6
  3. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/processor.py +10 -8
  4. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/page_title.py +1 -2
  5. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/app.py +25 -1
  6. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/location.py +3 -0
  7. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/page_switcher.py +153 -30
  8. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/facets.py +2 -0
  9. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/widgets.py +3 -5
  10. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/facets.py +4 -3
  11. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/iolanta.py +25 -14
  12. {iolanta-1.2.7 → iolanta-1.2.8}/pyproject.toml +2 -1
  13. iolanta-1.2.7/iolanta/data/foaf-meta.yaml +0 -113
  14. iolanta-1.2.7/iolanta/data/ontologies-meta.yaml +0 -57
  15. {iolanta-1.2.7 → iolanta-1.2.8}/README.md +0 -0
  16. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/__init__.py +0 -0
  17. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/base_plugin.py +0 -0
  18. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/__init__.py +0 -0
  19. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/__init__.py +0 -0
  20. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/choose.py +0 -0
  21. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/csv.py +0 -0
  22. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/json.py +0 -0
  23. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/formatters/pretty.py +0 -0
  24. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/models.py +0 -0
  25. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cli/pretty_print.py +0 -0
  26. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/context.py +0 -0
  27. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/conversions.py +0 -0
  28. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/__init__.py +0 -0
  29. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/inference/wikibase-claim.sparql +0 -0
  30. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/cyberspace/inference/wikibase-statement-property.sparql +0 -0
  31. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/cli.yaml +0 -0
  32. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/context.yaml +0 -0
  33. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/html.yaml +0 -0
  34. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/iolanta.yaml +0 -0
  35. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/data/textual-browser.yaml +0 -0
  36. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/ensure_is_context.py +0 -0
  37. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/entry_points.py +0 -0
  38. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/errors.py +0 -0
  39. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/__init__.py +0 -0
  40. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/__init__.py +0 -0
  41. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/base.py +0 -0
  42. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/default.py +0 -0
  43. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/record.py +0 -0
  44. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/sparql/link.sparql +0 -0
  45. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/cli/sparql/record.sparql +0 -0
  46. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/errors.py +0 -0
  47. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/facet.py +0 -0
  48. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/foaf_person_title/__init__.py +0 -0
  49. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/foaf_person_title/facet.py +0 -0
  50. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/foaf_person_title/sparql/names.sparql +0 -0
  51. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/__init__.py +0 -0
  52. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/bool_literal.py +0 -0
  53. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/date_literal.py +0 -0
  54. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/default.py +0 -0
  55. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/generic/sparql/default.sparql +0 -0
  56. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/__init__.py +0 -0
  57. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/base.py +0 -0
  58. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/code_literal.py +0 -0
  59. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/html/default.py +0 -0
  60. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/icon.py +0 -0
  61. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/locator.py +0 -0
  62. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/qname.py +0 -0
  63. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/__init__.py +0 -0
  64. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/facet.py +0 -0
  65. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/history.py +0 -0
  66. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/home.py +0 -0
  67. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/models.py +0 -0
  68. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_browser/page.py +0 -0
  69. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_class/__init__.py +0 -0
  70. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_class/facets.py +0 -0
  71. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_class/sparql/instances.sparql +0 -0
  72. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/__init__.py +0 -0
  73. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/inverse-properties.sparql +0 -0
  74. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/label.sparql +0 -0
  75. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +0 -0
  76. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/sparql/properties.sparql +0 -0
  77. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/tcss/default.tcss +0 -0
  78. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/templates/default.md +0 -0
  79. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_default/triple_uri_ref.py +0 -0
  80. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph/__init__.py +0 -0
  81. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph/facets.py +0 -0
  82. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph/sparql/triples.sparql +0 -0
  83. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_graph_triples.py +0 -0
  84. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_link/__init__.py +0 -0
  85. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_link/facet.py +0 -0
  86. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/__init__.py +0 -0
  87. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/facet.py +0 -0
  88. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/models.py +0 -0
  89. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/nanopublication_widget.py +0 -0
  90. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/term_list_widget.py +0 -0
  91. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_nanopublication/term_widget.py +0 -0
  92. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/__init__.py +0 -0
  93. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/facets.py +0 -0
  94. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/sparql/terms.sparql +0 -0
  95. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +0 -0
  96. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/__init__.py +0 -0
  97. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/sparql/graphs.sparql +0 -0
  98. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/textual_provenance/sparql/triples.sparql +0 -0
  99. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/title/__init__.py +0 -0
  100. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/title/facets.py +0 -0
  101. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/title/sparql/title.sparql +0 -0
  102. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/wikibase_statement_title/__init__.py +0 -0
  103. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/wikibase_statement_title/facets.py +0 -0
  104. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +0 -0
  105. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/__init__.py +0 -0
  106. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/base.py +0 -0
  107. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/data_type_choice.py +0 -0
  108. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/dict_loader.py +0 -0
  109. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/errors.py +0 -0
  110. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/http.py +0 -0
  111. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/local_directory.py +0 -0
  112. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/local_file.py +0 -0
  113. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/loaders/scheme_choice.py +0 -0
  114. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/models.py +0 -0
  115. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/namespaces.py +0 -0
  116. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/node_to_qname.py +0 -0
  117. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parse_quads.py +0 -0
  118. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/__init__.py +0 -0
  119. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/base.py +0 -0
  120. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/dict_parser.py +0 -0
  121. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/errors.py +0 -0
  122. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/json.py +0 -0
  123. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/markdown.py +0 -0
  124. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/parsers/yaml.py +0 -0
  125. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/plugin.py +0 -0
  126. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/reformat_blank_nodes.py +0 -0
  127. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/resolvers/__init__.py +0 -0
  128. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/resolvers/base.py +0 -0
  129. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/resolvers/python_import.py +0 -0
  130. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/shortcuts.py +0 -0
  131. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/stack.py +0 -0
  132. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/widgets/__init__.py +0 -0
  133. {iolanta-1.2.7 → iolanta-1.2.8}/iolanta/widgets/mixin.py +0 -0
  134. {iolanta-1.2.7 → iolanta-1.2.8}/ldflex/__init__.py +0 -0
  135. {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.7
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.add(path)
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 %s(%s): %s',
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.warning( # noqa: WPS220
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.warning(f'Existing triples not found for {source_uri}')
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('%s | %s', source, str(parser_not_found))
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('%s | %s', source, str(yaml_ld_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
- qname = self.iolanta.node_as_qname(iri)
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
- lambda msg: self.query_one(DevConsole).write(msg),
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,5 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
 
3
+ from rdflib import URIRef
4
+
3
5
 
4
6
  @dataclass
5
7
  class Location:
@@ -7,3 +9,4 @@ class Location:
7
9
 
8
10
  page_id: str
9
11
  url: str
12
+ facet_iri: URIRef | None = None
@@ -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, destination: NotLiteralNode, facet_iri: URIRef | None,
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 = self.app.call_from_thread(
66
- FacetFinder(
67
- iolanta=self.iolanta,
68
- node=destination,
69
- as_datatype=as_datatype,
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
- return (
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
- iri, renderable, flip_options = event.worker.result
130
- page_uid = uuid.uuid4().hex
131
- page_id = f'page_{page_uid}'
132
- page = Page(
133
- renderable,
134
- iri=iri,
135
- page_id=page_id,
136
- flip_options=flip_options,
137
- )
138
- self.mount(page)
139
- self.current = page_id
140
- page.focus()
141
- self.history.goto(Location(page_id, iri))
142
- self.app.sub_title = iri
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'⏳ {qname}',
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
- retrieval_directory: Optional[Path] = None
82
- graph: ConjunctiveGraph = field(
83
- default_factory=functools.partial(
84
- ConjunctiveGraph,
85
- identifier=namespaces.LOCAL.term('_inference'),
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
- @functools.cached_property
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 add( # noqa: C901, WPS231, WPS210
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.info(
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.info(f'{source} | {parser_not_found}')
186
+ self.logger.error(f'{source} | {parser_not_found}')
178
187
  continue
179
188
  except YAMLLDError as yaml_ld_error:
180
- self.logger.info(f'{source} | {yaml_ld_error}')
189
+ self.logger.error(f'{source} | {yaml_ld_error}')
181
190
  continue
182
191
  except ValueError as value_error:
183
- self.logger.info(f'{source} | {value_error}')
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.7"
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"