iolanta 2.1.1__tar.gz → 2.1.6__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 (145) hide show
  1. {iolanta-2.1.1 → iolanta-2.1.6}/PKG-INFO +7 -7
  2. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/main.py +83 -43
  3. iolanta-2.1.6/iolanta/declension/data/declension.yamlld +39 -0
  4. iolanta-2.1.6/iolanta/declension/facet.py +44 -0
  5. iolanta-2.1.6/iolanta/declension/sparql/declension.sparql +8 -0
  6. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/cli/record.py +2 -2
  7. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/facet.py +1 -22
  8. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/foaf_person_title/facet.py +2 -2
  9. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/generic/bool_literal.py +3 -3
  10. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/generic/date_literal.py +1 -1
  11. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/generic/default.py +2 -2
  12. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/html/code_literal.py +3 -3
  13. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/icon.py +1 -1
  14. iolanta-2.1.6/iolanta/facets/locator/sparql/get-query-to-facet.sparql +5 -0
  15. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/locator.py +5 -11
  16. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/qname.py +2 -2
  17. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/app.py +7 -3
  18. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/facet.py +1 -1
  19. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/page_switcher.py +13 -18
  20. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_class/facets.py +3 -3
  21. iolanta-2.1.6/iolanta/facets/textual_class/sparql/instances.sparql +8 -0
  22. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/facets.py +5 -5
  23. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/widgets.py +1 -1
  24. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_graph/facets.py +3 -3
  25. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_graph_triples.py +1 -1
  26. iolanta-2.1.6/iolanta/facets/textual_graphs/__init__.py +6 -0
  27. iolanta-2.1.6/iolanta/facets/textual_graphs/data/textual_graphs.yamlld +23 -0
  28. iolanta-2.1.6/iolanta/facets/textual_graphs/facets.py +138 -0
  29. iolanta-2.1.6/iolanta/facets/textual_graphs/sparql/graphs.sparql +5 -0
  30. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_link/facet.py +4 -4
  31. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/facet.py +1 -1
  32. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_no_facet_found.py +3 -1
  33. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_ontology/facets.py +3 -3
  34. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_provenance/facets.py +1 -1
  35. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/title/facets.py +1 -4
  36. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/wikibase_statement_title/facets.py +2 -2
  37. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/iolanta.py +2 -2
  38. iolanta-2.1.1/iolanta/mermaid/mermaid.yamlld → iolanta-2.1.6/iolanta/labeled_triple_set/data/labeled_triple_set.yamlld +5 -5
  39. iolanta-2.1.6/iolanta/labeled_triple_set/labeled_triple_set.py +137 -0
  40. iolanta-2.1.6/iolanta/labeled_triple_set/sparql/triples.sparql +5 -0
  41. iolanta-2.1.6/iolanta/mcp/cli.py +39 -0
  42. iolanta-2.1.6/iolanta/mcp/prompts/nanopublication_assertion_authoring_rules.md +63 -0
  43. iolanta-2.1.6/iolanta/mcp/prompts/rules.md +83 -0
  44. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/mermaid/facet.py +0 -3
  45. iolanta-2.1.6/iolanta/mermaid/mermaid.yamlld +25 -0
  46. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/mermaid/models.py +38 -9
  47. iolanta-2.1.6/iolanta/mermaid/sparql/ask-has-triples.sparql +3 -0
  48. iolanta-2.1.6/iolanta/resolvers/__init__.py +0 -0
  49. iolanta-2.1.6/iolanta/sparqlspace/__init__.py +0 -0
  50. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/sparqlspace/processor.py +6 -3
  51. iolanta-2.1.6/iolanta/widgets/__init__.py +0 -0
  52. {iolanta-2.1.1 → iolanta-2.1.6}/pyproject.toml +10 -4
  53. iolanta-2.1.1/iolanta/facets/textual_class/sparql/instances.sparql +0 -5
  54. {iolanta-2.1.1 → iolanta-2.1.6}/README.md +0 -0
  55. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/__init__.py +0 -0
  56. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/base_plugin.py +0 -0
  57. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/__init__.py +0 -0
  58. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/formatters/__init__.py +0 -0
  59. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/formatters/choose.py +0 -0
  60. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/formatters/csv.py +0 -0
  61. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/formatters/json.py +0 -0
  62. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/formatters/pretty.py +0 -0
  63. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/models.py +0 -0
  64. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/cli/pretty_print.py +0 -0
  65. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/context.py +0 -0
  66. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/conversions.py +0 -0
  67. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/data/context.yaml +0 -0
  68. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/data/graph-triples.yamlld +0 -0
  69. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/data/iolanta.yaml +0 -0
  70. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/data/textual-browser.yaml +0 -0
  71. {iolanta-2.1.1/iolanta/mermaid → iolanta-2.1.6/iolanta/declension}/__init__.py +0 -0
  72. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/ensure_is_context.py +0 -0
  73. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/entry_points.py +0 -0
  74. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/errors.py +0 -0
  75. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/__init__.py +0 -0
  76. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/cli/__init__.py +0 -0
  77. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/cli/base.py +0 -0
  78. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/cli/default.py +0 -0
  79. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/cli/sparql/link.sparql +0 -0
  80. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/cli/sparql/record.sparql +0 -0
  81. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/errors.py +0 -0
  82. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/foaf_person_title/__init__.py +0 -0
  83. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/foaf_person_title/sparql/names.sparql +0 -0
  84. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/generic/__init__.py +0 -0
  85. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/generic/sparql/default.sparql +0 -0
  86. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/html/__init__.py +0 -0
  87. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/html/base.py +0 -0
  88. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/html/default.py +0 -0
  89. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/page_title.py +0 -0
  90. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/__init__.py +0 -0
  91. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/history.py +0 -0
  92. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/home.py +0 -0
  93. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/location.py +0 -0
  94. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/models.py +0 -0
  95. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_browser/page.py +0 -0
  96. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_class/__init__.py +0 -0
  97. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/__init__.py +0 -0
  98. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/inverse-properties.sparql +0 -0
  99. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/label.sparql +0 -0
  100. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +0 -0
  101. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/properties.sparql +0 -0
  102. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/tcss/default.tcss +0 -0
  103. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/templates/default.md +0 -0
  104. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_default/triple_uri_ref.py +0 -0
  105. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_graph/__init__.py +0 -0
  106. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_graph/sparql/triples.sparql +0 -0
  107. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_link/__init__.py +0 -0
  108. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/__init__.py +0 -0
  109. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/models.py +0 -0
  110. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/nanopublication_widget.py +0 -0
  111. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/term_list_widget.py +0 -0
  112. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/term_widget.py +0 -0
  113. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_ontology/__init__.py +0 -0
  114. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_ontology/sparql/terms.sparql +0 -0
  115. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +0 -0
  116. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_property_pairs_table.py +0 -0
  117. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_provenance/__init__.py +0 -0
  118. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_provenance/sparql/graphs.sparql +0 -0
  119. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/textual_provenance/sparql/triples.sparql +0 -0
  120. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/title/__init__.py +0 -0
  121. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/title/sparql/title.sparql +0 -0
  122. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/wikibase_statement_title/__init__.py +0 -0
  123. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +0 -0
  124. {iolanta-2.1.1/iolanta/resolvers → iolanta-2.1.6/iolanta/labeled_triple_set}/__init__.py +0 -0
  125. {iolanta-2.1.1/iolanta/sparqlspace → iolanta-2.1.6/iolanta/mcp}/__init__.py +0 -0
  126. {iolanta-2.1.1/iolanta/widgets → iolanta-2.1.6/iolanta/mermaid}/__init__.py +0 -0
  127. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/mermaid/sparql/graph.sparql +0 -0
  128. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/mermaid/sparql/subgraphs.sparql +0 -0
  129. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/models.py +0 -0
  130. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/namespaces.py +0 -0
  131. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/node_to_qname.py +0 -0
  132. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/parse_quads.py +0 -0
  133. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/plugin.py +0 -0
  134. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/query_result.py +0 -0
  135. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/reformat_blank_nodes.py +0 -0
  136. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/resolvers/base.py +0 -0
  137. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/resolvers/dispatch.py +0 -0
  138. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/resolvers/pypi.py +0 -0
  139. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/resolvers/python_import.py +0 -0
  140. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/sparqlspace/cli.py +0 -0
  141. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/sparqlspace/inference/wikibase-claim.sparql +0 -0
  142. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/sparqlspace/inference/wikibase-statement-property.sparql +0 -0
  143. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/sparqlspace/sparqlspace.py +0 -0
  144. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/widgets/description.py +0 -0
  145. {iolanta-2.1.1 → iolanta-2.1.6}/iolanta/widgets/mixin.py +0 -0
@@ -1,16 +1,15 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: iolanta
3
- Version: 2.1.1
3
+ Version: 2.1.6
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
7
7
  Author-email: altaisoft@gmail.com
8
- Requires-Python: >=3.10,<4.0
8
+ Requires-Python: >=3.12,<4.0
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
11
  Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
14
13
  Provides-Extra: all
15
14
  Requires-Dist: boltons (>=24.0.0)
16
15
  Requires-Dist: classes (>=0.4.0)
@@ -18,10 +17,11 @@ Requires-Dist: deepmerge (>=0.1.1)
18
17
  Requires-Dist: diskcache (>=5.6.3)
19
18
  Requires-Dist: documented (>=0.1.1)
20
19
  Requires-Dist: dominate (>=2.6.0)
20
+ Requires-Dist: fastmcp (>=2.12.4,<3.0.0)
21
21
  Requires-Dist: funcy (>=2.0)
22
22
  Requires-Dist: loguru (>=0.7.3)
23
23
  Requires-Dist: more-itertools (>=9.0.0)
24
- Requires-Dist: nanopub (>=2.0.1)
24
+ Requires-Dist: nanopub (>=2.1.0,<3.0.0)
25
25
  Requires-Dist: owlrl (>=6.0.2)
26
26
  Requires-Dist: oxrdflib (>=0.4.0)
27
27
  Requires-Dist: packageurl-python (>=0.17.5)
@@ -33,7 +33,7 @@ Requires-Dist: rich (>=13.3.1)
33
33
  Requires-Dist: textual (>=0.83.0)
34
34
  Requires-Dist: typer (>=0.9.0)
35
35
  Requires-Dist: watchfiles (>=1.0.4)
36
- Requires-Dist: yaml-ld (>=1.1.10)
36
+ Requires-Dist: yaml-ld (>=1.1.14,<2.0.0)
37
37
  Requires-Dist: yarl (>=1.9.4)
38
38
  Description-Content-Type: text/markdown
39
39
 
@@ -1,5 +1,6 @@
1
1
  import locale
2
2
  import logging
3
+ import sys
3
4
  from pathlib import Path
4
5
  from typing import Annotated
5
6
 
@@ -9,7 +10,8 @@ from documented import DocumentedError
9
10
  from rdflib import Literal, URIRef
10
11
  from rich.console import Console
11
12
  from rich.markdown import Markdown
12
- from typer import Argument, Exit, Option, Typer
13
+ from rich.table import Table
14
+ from typer import Argument, Exit, Option, Typer, Context
13
15
  from yarl import URL
14
16
 
15
17
  from iolanta.cli.models import LogLevel
@@ -22,23 +24,7 @@ DEFAULT_LANGUAGE = locale.getlocale()[0].split('_')[0]
22
24
  console = Console()
23
25
 
24
26
 
25
- def construct_app() -> Typer:
26
- """
27
- Construct Typer app.
28
-
29
- FIXME: Remove this function, just create the app on module level.
30
- """
31
- iolanta = Iolanta()
32
-
33
- return Typer(
34
- no_args_is_help=True,
35
- context_settings={
36
- 'obj': iolanta,
37
- },
38
- )
39
-
40
-
41
- app = construct_app()
27
+ app = Typer(no_args_is_help=True)
42
28
 
43
29
 
44
30
  def string_to_node(name: str) -> NotLiteralNode:
@@ -57,19 +43,17 @@ def string_to_node(name: str) -> NotLiteralNode:
57
43
  return URIRef(f'file://{path}')
58
44
 
59
45
 
60
- @app.command(name='browse')
61
- def render_command( # noqa: WPS231, WPS238, WPS210, C901
62
- url: Annotated[str, Argument()],
63
- as_datatype: Annotated[
64
- str, Option(
65
- '--as',
66
- ),
67
- ] = 'https://iolanta.tech/cli/interactive',
68
- language: Annotated[
69
- str, Option(
70
- help='Data language to prefer.',
71
- ),
72
- ] = DEFAULT_LANGUAGE,
46
+ def decode_datatype(datatype: str) -> URIRef:
47
+ if datatype.startswith('http'):
48
+ return URIRef(datatype)
49
+
50
+ return URIRef(f'https://iolanta.tech/datatypes/{datatype}')
51
+
52
+
53
+ def render_and_return(
54
+ url: str,
55
+ as_datatype: str,
56
+ language: str = DEFAULT_LANGUAGE,
73
57
  log_level: LogLevel = LogLevel.ERROR,
74
58
  ):
75
59
  """Render a given URL."""
@@ -85,14 +69,32 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
85
69
  ensure_exists=True,
86
70
  ) / 'iolanta.log'
87
71
 
88
- logger = loguru.logger
89
- logger.add(
72
+ # Get the level name first
73
+ level_name = {
74
+ logging.DEBUG: 'DEBUG',
75
+ logging.INFO: 'INFO',
76
+ logging.WARNING: 'WARNING',
77
+ logging.ERROR: 'ERROR',
78
+ }[level]
79
+
80
+ # Configure global loguru logger BEFORE creating Iolanta instance
81
+ loguru.logger.remove()
82
+ loguru.logger.add(
90
83
  log_file_path,
91
- level=level,
84
+ level=level_name,
92
85
  format='{time} {level} {message}',
93
86
  enqueue=True,
94
87
  )
95
-
88
+ loguru.logger.add(
89
+ sys.stderr,
90
+ level=level_name,
91
+ format='{time} | {level:<8} | {name}:{function}:{line} - {message}',
92
+ )
93
+ loguru.logger.level(level_name)
94
+
95
+ # Use the global logger
96
+ logger = loguru.logger
97
+
96
98
  node_url = URL(url)
97
99
  if node_url.scheme and node_url.scheme != 'file':
98
100
  node = URIRef(url)
@@ -101,6 +103,7 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
101
103
  language=Literal(language),
102
104
  logger=logger,
103
105
  )
106
+
104
107
  else:
105
108
  path = Path(node_url.path).absolute()
106
109
  node = URIRef(f'file://{path}')
@@ -110,14 +113,39 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
110
113
  project_root=path,
111
114
  )
112
115
 
113
- try:
114
- renderable = iolanta.render(
115
- node=URIRef(node),
116
- as_datatype=URIRef(as_datatype),
117
- )
116
+ return iolanta.render(
117
+ node=URIRef(node),
118
+ as_datatype=decode_datatype(as_datatype),
119
+ )
120
+
118
121
 
122
+ @app.command(name='render')
123
+ def render_command( # noqa: WPS231, WPS238, WPS210, C901
124
+ url: Annotated[str, Argument()],
125
+ as_datatype: Annotated[
126
+ str, Option(
127
+ '--as',
128
+ ),
129
+ ] = 'https://iolanta.tech/cli/interactive',
130
+ language: Annotated[
131
+ str, Option(
132
+ help='Data language to prefer.',
133
+ ),
134
+ ] = DEFAULT_LANGUAGE,
135
+ log_level: LogLevel = LogLevel.ERROR,
136
+ ):
137
+ """Render a given URL."""
138
+ try:
139
+ renderable = render_and_return(url, as_datatype, language, log_level)
119
140
  except DocumentedError as documented_error:
120
- if iolanta.logger.level in {logging.DEBUG, logging.INFO}:
141
+ level = {
142
+ LogLevel.DEBUG: logging.DEBUG,
143
+ LogLevel.INFO: logging.INFO,
144
+ LogLevel.WARNING: logging.WARNING,
145
+ LogLevel.ERROR: logging.ERROR,
146
+ }[log_level]
147
+
148
+ if level in {logging.DEBUG, logging.INFO}:
121
149
  raise
122
150
 
123
151
  console.print(
@@ -129,11 +157,23 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
129
157
  raise Exit(1)
130
158
 
131
159
  except Exception as err:
160
+ level = {
161
+ LogLevel.DEBUG: logging.DEBUG,
162
+ LogLevel.INFO: logging.INFO,
163
+ LogLevel.WARNING: logging.WARNING,
164
+ LogLevel.ERROR: logging.ERROR,
165
+ }[log_level]
166
+
132
167
  if level in {logging.DEBUG, logging.INFO}:
133
168
  raise
134
169
 
135
170
  console.print(str(err))
136
171
  raise Exit(1)
137
-
138
172
  else:
139
- print(renderable)
173
+ # FIXME: An intermediary Literal can be used to dispatch rendering.
174
+ match renderable:
175
+ case Table() as table:
176
+ console.print(table)
177
+
178
+ case unknown:
179
+ print(unknown)
@@ -0,0 +1,39 @@
1
+ "@context":
2
+ "@import": https://json-ld.org/contexts/dollar-convenience.jsonld
3
+ vann: https://purl.org/vocab/vann/
4
+ foaf: https://xmlns.com/foaf/0.1/
5
+ owl: https://www.w3.org/2002/07/owl#
6
+ iolanta: https://iolanta.tech/
7
+ rdfs: "https://www.w3.org/2000/01/rdf-schema#"
8
+ rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
9
+ dcterms: https://purl.org/dc/terms/
10
+ dcam: https://purl.org/dc/dcam/
11
+
12
+ iolanta:outputs:
13
+ "@type": "@id"
14
+
15
+ iolanta:when-no-facet-found:
16
+ "@type": "@id"
17
+
18
+ $: rdfs:label
19
+ →:
20
+ "@type": "@id"
21
+ "@id": iolanta:outputs
22
+
23
+ ⊆:
24
+ "@type": "@id"
25
+ "@id": rdfs:subClassOf
26
+
27
+ ⪯:
28
+ "@type": "@id"
29
+ "@id": iolanta:is-preferred-over
30
+
31
+ ↦: iolanta:matches
32
+ iolanta:hasDefaultFacet:
33
+ "@type": "@id"
34
+
35
+ $id: https://w3id.org/np/RASSJ01s2tPPJfFVx__m9gHtY4ct1YSd1ntL3R1OaQfLI/EasternArmenianPersonalPronoun
36
+ iolanta:facet:
37
+ $id: pkg:pypi/iolanta#rich-declension-table
38
+ $: Declension Table
39
+ →: https://iolanta.tech/datatypes/rich
@@ -0,0 +1,44 @@
1
+ from pathlib import Path
2
+
3
+ import funcy
4
+ from docutils.nodes import row
5
+ from rdflib import Literal, Namespace
6
+ from rich.table import Table
7
+
8
+ from iolanta import Facet
9
+ from iolanta.namespaces import DATATYPES, LOCAL
10
+
11
+ LEXINFO = Namespace('https://www.lexinfo.net/ontology/2.0/lexinfo#')
12
+
13
+
14
+ class RichDeclensionTable(Facet[Table]):
15
+ """Declension forms of something."""
16
+
17
+ META = Path(__file__).parent / 'data' / 'declension.yamlld'
18
+
19
+ def show(self) -> Table:
20
+ """Render declension forms of something."""
21
+ rows = self.stored_query('declension.sparql', this=self.this)
22
+ form_by_number_and_person = [
23
+ ((row['number'], row['person']), self.render(row['declension'], as_datatype=DATATYPES.title))
24
+ for row in rows
25
+ ]
26
+
27
+ declensions = funcy.group_values(form_by_number_and_person)
28
+
29
+ table = Table('', 'Singular', 'Plural', title='Declension')
30
+
31
+ table_rows = [
32
+ [
33
+ ', '.join(declensions[number, person])
34
+ for number in [LEXINFO.singular, LEXINFO.plural]
35
+ ]
36
+ for person in [LEXINFO.firstPerson, LEXINFO.secondPerson, LEXINFO.thirdPerson]
37
+ ]
38
+
39
+ row_titles = ['1st', '2nd', '3rd']
40
+
41
+ for title, table_row in zip(row_titles, table_rows):
42
+ table.add_row(title, *table_row)
43
+
44
+ return table
@@ -0,0 +1,8 @@
1
+ PREFIX lexinfo: <https://www.lexinfo.net/ontology/2.0/lexinfo#>
2
+
3
+ SELECT * WHERE {
4
+ ?declension
5
+ rdf:type $this ;
6
+ lexinfo:person ?person ;
7
+ lexinfo:number ?number .
8
+ }
@@ -17,9 +17,9 @@ class Record(RichFacet):
17
17
 
18
18
  def show(self) -> Renderable:
19
19
  return "RECORD"
20
- rows = self.stored_query('record.sparql', node=self.iri)
20
+ rows = self.stored_query('record.sparql', node=self.this)
21
21
 
22
- caption = self.render(self.iri, as_datatype=IOLANTA['cli/title'])
22
+ caption = self.render(self.this, as_datatype=IOLANTA['cli/title'])
23
23
 
24
24
  table = Table(
25
25
  show_header=False,
@@ -16,28 +16,15 @@ FacetOutput = TypeVar('FacetOutput')
16
16
  class Facet(Generic[FacetOutput]):
17
17
  """Base facet class."""
18
18
 
19
- iri: NotLiteralNode
19
+ this: Node
20
20
  iolanta: 'iolanta.Iolanta' = field(repr=False)
21
21
  as_datatype: Optional[NotLiteralNode] = None
22
22
 
23
- @property
24
- def this(self) -> NotLiteralNode:
25
- """This node."""
26
- return self.iri
27
-
28
23
  @property
29
24
  def stored_queries_path(self) -> Path:
30
25
  """Construct directory for stored queries for this facet."""
31
26
  return Path(inspect.getfile(self.__class__)).parent / 'sparql'
32
27
 
33
- @cached_property
34
- def uriref(self) -> NotLiteralNode:
35
- """Format as URIRef."""
36
- if isinstance(self.iri, BNode):
37
- return self.iri
38
-
39
- return URIRef(self.iri)
40
-
41
28
  def query(
42
29
  self,
43
30
  query_text: str,
@@ -60,14 +47,6 @@ class Facet(Generic[FacetOutput]):
60
47
  as_datatype=as_datatype,
61
48
  )
62
49
 
63
- def render_all(
64
- self,
65
- node: Node,
66
- as_datatype: NotLiteralNode,
67
- ) -> Iterable[Any]:
68
- """Render all we can."""
69
- return self.iolanta.render_all(node=node, as_datatype=as_datatype)
70
-
71
50
  def stored_query(self, file_name: str, **kwargs: SPARQLQueryArgument):
72
51
  """Execute a stored SPARQL query."""
73
52
  query_text = (self.stored_queries_path / file_name).read_text()
@@ -9,12 +9,12 @@ class FOAFPersonTitle(Facet[str]):
9
9
 
10
10
  def show(self) -> str:
11
11
  """Render full name of a person."""
12
- row = funcy.first(self.stored_query('names.sparql', person=self.iri))
12
+ row = funcy.first(self.stored_query('names.sparql', person=self.this))
13
13
 
14
14
  if row is None:
15
15
  # Render default title.
16
16
  return self.render(
17
- self.iri,
17
+ self.this,
18
18
  as_datatype=DATATYPES['fallback-title'],
19
19
  )
20
20
 
@@ -9,9 +9,9 @@ class BoolLiteral(Facet):
9
9
 
10
10
  def show(self):
11
11
  """Render as icon."""
12
- if not isinstance(self.iri, Literal):
12
+ if not isinstance(self.this, Literal):
13
13
  raise NotALiteral(
14
- node=self.iri,
14
+ node=self.this,
15
15
  )
16
16
 
17
- return '✔️' if self.iri.value else '❌'
17
+ return '✔️' if self.this.value else '❌'
@@ -11,7 +11,7 @@ class DateLiteral(Facet):
11
11
 
12
12
  def show(self):
13
13
  """Render date or datetime as a date."""
14
- literal = cast(Literal, self.iri)
14
+ literal = cast(Literal, self.this)
15
15
 
16
16
  date_value = literal.value
17
17
 
@@ -27,7 +27,7 @@ class DefaultMixin(Facet[FacetOutput]):
27
27
  def description(self) -> Description:
28
28
  return Description(
29
29
  **funcy.first(
30
- self.stored_query('default.sparql', iri=self.iri),
30
+ self.stored_query('default.sparql', iri=self.this),
31
31
  ),
32
32
  )
33
33
 
@@ -59,7 +59,7 @@ class DefaultMixin(Facet[FacetOutput]):
59
59
  return label
60
60
 
61
61
  def render_fallback(self) -> str:
62
- string_iri = str(self.iri)
62
+ string_iri = str(self.this)
63
63
 
64
64
  if string_iri.startswith('local:'):
65
65
  string_iri = string_iri.removeprefix(
@@ -9,9 +9,9 @@ class CodeLiteral(Facet):
9
9
 
10
10
  def show(self):
11
11
  """Render as icon."""
12
- if not isinstance(self.iri, Literal):
12
+ if not isinstance(self.this, Literal):
13
13
  raise NotALiteral(
14
- node=self.iri,
14
+ node=self.this,
15
15
  )
16
16
 
17
- return f'<code>{self.iri.value}</code>'
17
+ return f'<code>{self.this.value}</code>'
@@ -15,7 +15,7 @@ class IconFacet(Facet[str]):
15
15
  $iri iolanta:icon ?icon .
16
16
  }
17
17
  """,
18
- iri=self.iri,
18
+ iri=self.this,
19
19
  ),
20
20
  )
21
21
 
@@ -0,0 +1,5 @@
1
+ SELECT ?facet ?match WHERE {
2
+ ?facet
3
+ iolanta:matches ?match ;
4
+ iolanta:outputs $as_datatype .
5
+ }
@@ -1,6 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from functools import cached_property
3
3
  from graphlib import TopologicalSorter
4
+ from pathlib import Path
4
5
  from typing import Iterable, List, TypedDict
5
6
 
6
7
  import funcy
@@ -18,13 +19,9 @@ class FoundRow(TypedDict):
18
19
  output_datatype: NotLiteralNode
19
20
 
20
21
 
21
- GET_QUERY_TO_FACET = """
22
- SELECT ?facet ?match WHERE {
23
- ?facet
24
- iolanta:matches ?match ;
25
- iolanta:outputs $as_datatype .
26
- }
27
- """
22
+ GET_QUERY_TO_FACET = (
23
+ Path(__file__).parent / 'locator' / 'sparql' / 'get-query-to-facet.sparql'
24
+ ).read_text()
28
25
 
29
26
 
30
27
  def reorder_rows_by_facet_preferences( # noqa: WPS214, WPS210
@@ -114,9 +111,6 @@ class FacetFinder: # noqa: WPS214
114
111
 
115
112
  def by_sparql(self) -> Iterable[FoundRow]:
116
113
  """Determine facet by SHACL shape of the data."""
117
- if not isinstance(self.node, URIRef):
118
- return
119
-
120
114
  rows = self.iolanta.query(
121
115
  GET_QUERY_TO_FACET,
122
116
  as_datatype=self.as_datatype,
@@ -138,7 +132,7 @@ class FacetFinder: # noqa: WPS214
138
132
 
139
133
  TODO fix this to allow arbitrary prefixes.
140
134
  """
141
- scheme = URL(self.node).scheme
135
+ scheme = URL(str(self.node)).scheme
142
136
  if scheme != 'urn':
143
137
  return []
144
138
 
@@ -9,11 +9,11 @@ class QNameFacet(Facet[str]):
9
9
  def show(self) -> str:
10
10
  """Return a qname."""
11
11
  qname: ComputedQName | NotLiteralNode = node_to_qname(
12
- self.iri,
12
+ self.this,
13
13
  self.iolanta.graph,
14
14
  )
15
15
 
16
16
  if isinstance(qname, ComputedQName):
17
17
  return f'{qname.namespace_name}:{qname.term}'
18
18
 
19
- return str(self.iri)
19
+ return str(self.this)
@@ -45,10 +45,10 @@ def _log_message_to_dev_console(app: App):
45
45
  class IolantaBrowser(App): # noqa: WPS214, WPS230
46
46
  """Browse Linked Data."""
47
47
 
48
- def __init__(self, iolanta: Iolanta, iri: Node):
48
+ def __init__(self, iolanta: Iolanta, this: Node):
49
49
  """Set up parameters for the browser."""
50
50
  self.iolanta = iolanta
51
- self.iri = iri
51
+ self.this = this
52
52
  self.renderers = ThreadPoolExecutor()
53
53
  super().__init__()
54
54
 
@@ -79,7 +79,11 @@ class IolantaBrowser(App): # noqa: WPS214, WPS230
79
79
  )
80
80
 
81
81
  # Disable stderr logging, to not break the TUI.
82
- self.iolanta.logger.remove(0)
82
+ # Remove only the stderr handler, keep file handler
83
+ for handler_id in list(self.iolanta.logger._core.handlers.keys()):
84
+ handler = self.iolanta.logger._core.handlers[handler_id]
85
+ if hasattr(handler, 'sink') and str(handler.sink) == '<stderr>':
86
+ self.iolanta.logger.remove(handler_id)
83
87
 
84
88
  # Log to the dev console.
85
89
  self.iolanta.logger.add(
@@ -20,7 +20,7 @@ class TextualBrowserFacet(Facet[ReturnType | None]):
20
20
 
21
21
  app = IolantaBrowser(
22
22
  iolanta=self.iolanta,
23
- iri=self.iri,
23
+ this=self.this,
24
24
  )
25
25
  try:
26
26
  app.run()
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  from typing import Any
6
6
 
7
7
  import watchfiles
8
- from rdflib import BNode, URIRef
8
+ from rdflib import BNode, Node, URIRef
9
9
  from textual.widgets import ContentSwitcher, RichLog
10
10
  from textual.worker import Worker, WorkerState
11
11
 
@@ -74,7 +74,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
74
74
 
75
75
  def on_mount(self):
76
76
  """Navigate to the initial page."""
77
- self.action_goto(self.app.iri)
77
+ self.action_goto(self.app.this)
78
78
  if self.iolanta.project_root:
79
79
  self.run_worker(
80
80
  self._watch_files,
@@ -94,24 +94,24 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
94
94
 
95
95
  def render_iri( # noqa: WPS210
96
96
  self,
97
- destination: NotLiteralNode,
97
+ this: NotLiteralNode,
98
98
  facet_iri: URIRef | None,
99
99
  is_reload: bool,
100
100
  ) -> RenderResult:
101
101
  """Render an IRI in a thread."""
102
- self.iri = destination
102
+ self.this = this
103
103
  iolanta: Iolanta = self.iolanta
104
104
 
105
105
  as_datatype = URIRef('https://iolanta.tech/cli/textual')
106
106
  choices = FacetFinder(
107
107
  iolanta=self.iolanta,
108
- node=destination,
108
+ node=this,
109
109
  as_datatype=as_datatype,
110
110
  ).choices()
111
111
 
112
112
  if not choices:
113
113
  raise FacetNotFound(
114
- node=self.iri,
114
+ node=self.this,
115
115
  as_datatype=as_datatype,
116
116
  node_types=[],
117
117
  )
@@ -139,7 +139,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
139
139
  facet_class = iolanta.facet_resolver.resolve(facet_iri)
140
140
 
141
141
  facet = facet_class(
142
- iri=self.iri,
142
+ this=self.this,
143
143
  iolanta=iolanta,
144
144
  as_datatype=URIRef('https://iolanta.tech/cli/textual'),
145
145
  )
@@ -149,13 +149,13 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
149
149
 
150
150
  except Exception as err:
151
151
  raise FacetError(
152
- node=self.iri,
152
+ node=self.this,
153
153
  facet_iri=facet_iri,
154
154
  error=err,
155
155
  ) from err
156
156
 
157
157
  return RenderResult(
158
- iri=destination,
158
+ iri=this,
159
159
  renderable=renderable,
160
160
  flip_options=flip_options,
161
161
  facet_iri=facet_iri,
@@ -222,7 +222,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
222
222
  self.run_worker(
223
223
  functools.partial(
224
224
  self.render_iri,
225
- destination=self.history.current.url,
225
+ this=self.history.current.url,
226
226
  facet_iri=self.history.current.facet_iri,
227
227
  is_reload=True,
228
228
  ),
@@ -267,19 +267,14 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
267
267
 
268
268
  def action_goto(
269
269
  self,
270
- destination: str,
270
+ this: Node,
271
271
  facet_iri: str | None = None,
272
272
  ):
273
273
  """Go to an IRI."""
274
- if destination.startswith('_:'):
275
- iri = BNode(destination)
276
- else:
277
- iri = URIRef(destination)
278
-
279
274
  self.run_worker(
280
275
  functools.partial(
281
276
  self.render_iri,
282
- destination=iri,
277
+ this=this,
283
278
  facet_iri=facet_iri and URIRef(facet_iri),
284
279
  is_reload=False,
285
280
  ),
@@ -328,7 +323,7 @@ class DevConsole(RichLog):
328
323
 
329
324
  def __init__(self):
330
325
  """Set default props for console."""
331
- super().__init__(highlight=True, markup=True, id='console')
326
+ super().__init__(highlight=False, markup=False, id='console')
332
327
 
333
328
  def action_close(self):
334
329
  """Close the dev console."""