kibi-core 0.5.3 → 0.6.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-core",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
@@ -9,10 +9,7 @@
9
9
  "bun": ">=1.0"
10
10
  },
11
11
  "main": "./src/kb.pl",
12
- "files": [
13
- "src/**/*.pl",
14
- "schema/**/*.pl"
15
- ],
12
+ "files": ["src/**/*.pl", "schema/**/*.pl"],
16
13
  "license": "AGPL-3.0-or-later",
17
14
  "author": "Piotr Franczyk",
18
15
  "repository": {
@@ -47,6 +47,15 @@ entity_property(fact, closed_world, boolean).
47
47
  entity_property(fact, valid_from, datetime).
48
48
  entity_property(fact, valid_to, datetime).
49
49
  entity_property(fact, canonical_key, string).
50
+ entity_property(fact, predicate_name, string).
51
+ entity_property(fact, predicate_namespace, string).
52
+ entity_property(fact, predicate_arity, integer).
53
+ entity_property(fact, argument_names, list).
54
+ entity_property(fact, argument_types, list).
55
+ entity_property(fact, argument_descriptions, list).
56
+ entity_property(fact, aliases, list).
57
+ entity_property(fact, examples, list).
58
+ entity_property(fact, predicate_args, list).
50
59
 
51
60
  % Typed test verification fields - only valid for test entities
52
61
  entity_property(test, verification_scope, atom).
@@ -18,6 +18,7 @@ relationship_type(relates_to).
18
18
  relationship_type(supersedes).
19
19
  relationship_type(constrains).
20
20
  relationship_type(requires_property).
21
+ relationship_type(requires_predicate).
21
22
 
22
23
  % valid_relationship(RelType, FromType, ToType).
23
24
  valid_relationship(depends_on, req, req).
@@ -38,6 +39,7 @@ valid_relationship(publishes, symbol, event).
38
39
  valid_relationship(consumes, symbol, event).
39
40
  valid_relationship(constrains, req, fact).
40
41
  valid_relationship(requires_property, req, fact).
42
+ valid_relationship(requires_predicate, req, fact).
41
43
 
42
44
  %% supersedes(+NewAdrId, +OldAdrId)
43
45
  %% NewAdrId is the decision that replaces OldAdrId.
@@ -88,6 +88,15 @@ is_fact_only_field(closed_world).
88
88
  is_fact_only_field(valid_from).
89
89
  is_fact_only_field(valid_to).
90
90
  is_fact_only_field(canonical_key).
91
+ is_fact_only_field(predicate_name).
92
+ is_fact_only_field(predicate_namespace).
93
+ is_fact_only_field(predicate_arity).
94
+ is_fact_only_field(argument_names).
95
+ is_fact_only_field(argument_types).
96
+ is_fact_only_field(argument_descriptions).
97
+ is_fact_only_field(aliases).
98
+ is_fact_only_field(examples).
99
+ is_fact_only_field(predicate_args).
91
100
 
92
101
  % is_test_only_field(+Key) - true if Key is a test-specific field
93
102
  is_test_only_field(verification_scope).
@@ -97,7 +106,7 @@ is_test_only_field(verification_perspective).
97
106
  validate_fact_shape(subject, Props) :-
98
107
  memberchk(subject_key=_Val, Props),
99
108
  valid_optional_fact_enums(Props),
100
- valid_polarity_in_props(Props).
109
+ valid_strict_polarity_in_props(Props).
101
110
  validate_fact_shape(property_value, Props) :-
102
111
  memberchk(subject_key=_Subject, Props),
103
112
  memberchk(property_key=_Property, Props),
@@ -108,7 +117,7 @@ validate_fact_shape(property_value, Props) :-
108
117
  exactly_one_value_field(Props),
109
118
  value_type_matches_field(VT, Props),
110
119
  valid_optional_fact_enums(Props),
111
- valid_polarity_in_props(Props).
120
+ valid_strict_polarity_in_props(Props).
112
121
  validate_fact_shape(observation, Props) :-
113
122
  valid_optional_fact_enums(Props),
114
123
  valid_polarity_in_props(Props).
@@ -116,9 +125,26 @@ validate_fact_shape(meta, Props) :-
116
125
  % Meta facts are allowed but don't require full strict property tuple yet
117
126
  valid_optional_fact_enums(Props),
118
127
  valid_polarity_in_props(Props).
128
+ validate_fact_shape(predicate_schema, Props) :-
129
+ memberchk(predicate_name=_Name, Props),
130
+ memberchk(predicate_arity=Arity, Props),
131
+ Arity >= 1,
132
+ memberchk(argument_names=ArgumentNames, Props),
133
+ memberchk(argument_types=ArgumentTypes, Props),
134
+ same_length(ArgumentNames, ArgumentTypes),
135
+ length(ArgumentNames, Arity),
136
+ valid_optional_fact_enums(Props),
137
+ valid_polarity_in_props(Props).
138
+ validate_fact_shape(predicate, Props) :-
139
+ memberchk(predicate_name=_Name, Props),
140
+ memberchk(predicate_args=Args, Props),
141
+ Args \= [],
142
+ memberchk(canonical_key=_CanonicalKey, Props),
143
+ valid_optional_fact_enums(Props),
144
+ valid_predicate_polarity_in_props(Props).
119
145
  validate_fact_shape(Kind, _Props) :-
120
146
  % Unknown fact_kind values fail validation
121
- \+ memberchk(Kind, [subject, property_value, observation, meta]),
147
+ \+ memberchk(Kind, [subject, property_value, observation, meta, predicate_schema, predicate]),
122
148
  fail.
123
149
 
124
150
  % valid_operator(+Op)
@@ -140,6 +166,12 @@ valid_value_type(bool).
140
166
  valid_polarity_in_props(Props) :-
141
167
  ( memberchk(polarity=P, Props) -> valid_polarity(P) ; true ).
142
168
 
169
+ valid_strict_polarity_in_props(Props) :-
170
+ ( memberchk(polarity=P, Props) -> valid_strict_polarity(P) ; true ).
171
+
172
+ valid_predicate_polarity_in_props(Props) :-
173
+ ( memberchk(polarity=P, Props) -> valid_predicate_polarity(P) ; true ).
174
+
143
175
  % valid_optional_fact_enums(+Props)
144
176
  % Validates enum-typed fact fields whenever they are present
145
177
  valid_optional_fact_enums(Props) :-
@@ -155,6 +187,14 @@ valid_optional_test_enums(Props) :-
155
187
  % valid_polarity(+P)
156
188
  valid_polarity(require).
157
189
  valid_polarity(forbid).
190
+ valid_polarity(assert).
191
+ valid_polarity(deny).
192
+
193
+ valid_strict_polarity(require).
194
+ valid_strict_polarity(forbid).
195
+
196
+ valid_predicate_polarity(assert).
197
+ valid_predicate_polarity(deny).
158
198
 
159
199
  % valid_verification_scope(+Scope)
160
200
  valid_verification_scope(unit).
package/src/checks.pl CHANGED
@@ -33,7 +33,7 @@ required_fields([id, title, status, created_at, updated_at, source]).
33
33
  % Relationship types to check for dangling references
34
34
  all_relationship_types([
35
35
  depends_on, verified_by, validates, specified_by,
36
- constrains, requires_property, supersedes, relates_to
36
+ constrains, requires_property, requires_predicate, supersedes, relates_to
37
37
  ]).
38
38
 
39
39
  %% check_all(-ViolationsDict)
@@ -387,9 +387,25 @@ strict_fact_shape_violation(violation(
387
387
  -> fail % Observation facts have no required fields beyond fact_kind
388
388
  ; Kind = meta
389
389
  -> fail % Meta facts have no required fields beyond fact_kind
390
+ ; Kind = predicate_schema
391
+ -> findall(Msg, predicate_schema_shape_error(Props, Msg), Errors),
392
+ ( Errors = []
393
+ -> fail
394
+ ; Errors = [First|_],
395
+ Description = First,
396
+ Suggestion = "Ensure predicate_schema facts have predicate_name, predicate_arity, argument_names, and argument_types with matching arity"
397
+ )
398
+ ; Kind = predicate
399
+ -> findall(Msg, predicate_shape_error(Props, Msg), Errors),
400
+ ( Errors = []
401
+ -> fail
402
+ ; Errors = [First|_],
403
+ Description = First,
404
+ Suggestion = "Ensure predicate facts have predicate_name, non-empty predicate_args, canonical_key, and assert/deny polarity when present"
405
+ )
390
406
  ; % Unknown fact_kind - report as malformed
391
407
  format(string(Description), "Unknown fact_kind: ~w", [Kind]),
392
- Suggestion = "Use one of: subject, property_value, observation, meta"
408
+ Suggestion = "Use one of: subject, property_value, observation, meta, predicate_schema, predicate"
393
409
  ),
394
410
 
395
411
  ( memberchk(source=Source0, Props)
@@ -435,6 +451,68 @@ value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
435
451
  value_type_matches_field(number, Props) :- memberchk(value_number=_, Props), !.
436
452
  value_type_matches_field(bool, Props) :- memberchk(value_bool=_, Props), !.
437
453
 
454
+ predicate_schema_shape_error(Props, "Predicate schema fact missing required field: predicate_name") :-
455
+ \+ memberchk(predicate_name=_, Props).
456
+ predicate_schema_shape_error(Props, "Predicate schema fact missing required field: predicate_arity") :-
457
+ memberchk(predicate_name=_, Props),
458
+ \+ memberchk(predicate_arity=_, Props).
459
+ predicate_schema_shape_error(Props, "Predicate schema fact missing required field: argument_names") :-
460
+ memberchk(predicate_arity=_, Props),
461
+ \+ memberchk(argument_names=_, Props).
462
+ predicate_schema_shape_error(Props, "Predicate schema fact missing required field: argument_types") :-
463
+ memberchk(argument_names=_, Props),
464
+ \+ memberchk(argument_types=_, Props).
465
+ predicate_schema_shape_error(Props, "Predicate schema fact argument_names length must match predicate_arity") :-
466
+ memberchk(predicate_arity=RawArity, Props),
467
+ checks_normalize_integer(RawArity, Arity),
468
+ memberchk(argument_names=RawNames, Props),
469
+ checks_normalize_atom_list(RawNames, Names),
470
+ length(Names, Count),
471
+ Count =\= Arity.
472
+ predicate_schema_shape_error(Props, "Predicate schema fact argument_types length must match predicate_arity") :-
473
+ memberchk(predicate_arity=RawArity, Props),
474
+ checks_normalize_integer(RawArity, Arity),
475
+ memberchk(argument_types=RawTypes, Props),
476
+ checks_normalize_atom_list(RawTypes, Types),
477
+ length(Types, Count),
478
+ Count =\= Arity.
479
+
480
+ predicate_shape_error(Props, "Predicate fact missing required field: predicate_name") :-
481
+ \+ memberchk(predicate_name=_, Props).
482
+ predicate_shape_error(Props, "Predicate fact missing required field: predicate_args") :-
483
+ memberchk(predicate_name=_, Props),
484
+ \+ memberchk(predicate_args=_, Props).
485
+ predicate_shape_error(Props, "Predicate fact predicate_args must be non-empty") :-
486
+ memberchk(predicate_args=RawArgs, Props),
487
+ checks_normalize_atom_list(RawArgs, Args),
488
+ Args = [].
489
+ predicate_shape_error(Props, "Predicate fact missing required field: canonical_key") :-
490
+ memberchk(predicate_args=_, Props),
491
+ \+ memberchk(canonical_key=_, Props).
492
+ predicate_shape_error(Props, "Predicate fact polarity must be assert or deny") :-
493
+ memberchk(polarity=RawPolarity, Props),
494
+ normalize_term_atom(RawPolarity, Polarity),
495
+ \+ memberchk(Polarity, [assert, deny]).
496
+
497
+ checks_normalize_integer(Raw, Integer) :-
498
+ ( Raw = ^^(Value, _Type)
499
+ -> checks_normalize_integer(Value, Integer)
500
+ ; integer(Raw)
501
+ -> Integer = Raw
502
+ ; atom(Raw)
503
+ -> atom_number(Raw, Integer)
504
+ ; string(Raw)
505
+ -> number_string(Integer, Raw)
506
+ ).
507
+
508
+ checks_normalize_atom_list(Raw, Atoms) :-
509
+ ( Raw = ^^(Value, _Type)
510
+ -> checks_normalize_atom_list(Value, Atoms)
511
+ ; is_list(Raw)
512
+ -> maplist(normalize_term_atom, Raw, Atoms)
513
+ ; Atoms = []
514
+ ).
515
+
438
516
  %% check_domain_contradictions(-Violations)
439
517
  % Finds all pairs of requirements with contradicting required properties.
440
518
  check_domain_contradictions(Violations) :-
@@ -0,0 +1,65 @@
1
+ % Module: derived_chr
2
+ % Internal CHR pilot for bounded derived validation facts.
3
+
4
+ :- module(derived_chr, [
5
+ derive_chr_facts/0,
6
+ clear_chr_facts/0,
7
+ derived_coverage_gap/2,
8
+ derived_symbol_gap/2
9
+ ]).
10
+
11
+ :- use_module(library(chr)).
12
+ :- use_module('kb.pl').
13
+
14
+ :- chr_constraint seen_must_req/1, seen_scenario/1, seen_test/1,
15
+ seen_production_symbol/1, seen_symbol_req_coverage/1.
16
+
17
+ :- dynamic derived_coverage_gap/2.
18
+ :- dynamic derived_symbol_gap/2.
19
+ :- dynamic snapshot_must_req/1.
20
+ :- dynamic snapshot_scenario/1.
21
+ :- dynamic snapshot_test/1.
22
+ :- dynamic snapshot_production_symbol/1.
23
+ :- dynamic snapshot_symbol_req_coverage/1.
24
+
25
+ seen_must_req(Req) <=> assertz(snapshot_must_req(Req)).
26
+ seen_scenario(Req) <=> assertz(snapshot_scenario(Req)).
27
+ seen_test(Req) <=> assertz(snapshot_test(Req)).
28
+ seen_production_symbol(Symbol) <=> assertz(snapshot_production_symbol(Symbol)).
29
+ seen_symbol_req_coverage(Symbol) <=> assertz(snapshot_symbol_req_coverage(Symbol)).
30
+
31
+ clear_chr_facts :-
32
+ retractall(derived_coverage_gap(_, _)),
33
+ retractall(derived_symbol_gap(_, _)),
34
+ retractall(snapshot_must_req(_)),
35
+ retractall(snapshot_scenario(_)),
36
+ retractall(snapshot_test(_)),
37
+ retractall(snapshot_production_symbol(_)),
38
+ retractall(snapshot_symbol_req_coverage(_)).
39
+
40
+ derive_chr_facts :-
41
+ clear_chr_facts,
42
+ forall(kb:must_requirement(Req), seen_must_req(Req)),
43
+ forall(kb:has_scenario(Req), seen_scenario(Req)),
44
+ forall(kb:has_test(Req), seen_test(Req)),
45
+ forall(kb:production_symbol(Symbol), seen_production_symbol(Symbol)),
46
+ forall(kb:production_symbol_covered_for_requirement(Symbol, _), seen_symbol_req_coverage(Symbol)),
47
+ derive_coverage_gaps,
48
+ derive_symbol_gaps.
49
+
50
+ derive_coverage_gaps :-
51
+ forall(
52
+ ( snapshot_must_req(Req),
53
+ \+ snapshot_scenario(Req),
54
+ \+ snapshot_test(Req)
55
+ ),
56
+ assertz(derived_coverage_gap(Req, missing_scenario_and_test))
57
+ ).
58
+
59
+ derive_symbol_gaps :-
60
+ forall(
61
+ ( snapshot_production_symbol(Symbol),
62
+ \+ snapshot_symbol_req_coverage(Symbol)
63
+ ),
64
+ assertz(derived_symbol_gap(Symbol, no_qualifying_production_coverage))
65
+ ).
package/src/discovery.pl CHANGED
@@ -8,6 +8,7 @@
8
8
  ]).
9
9
 
10
10
  :- use_module(library(http/json)).
11
+ :- use_module(library(aggregate)).
11
12
  :- use_module('kb.pl').
12
13
  :- use_module('status.pl', [status_meta_dict/1]).
13
14
  :- use_module('../schema/relationships.pl', [relationship_type/1]).
@@ -90,10 +91,9 @@ relationship_counts(Id, CountsDict) :-
90
91
  dict_pairs(CountsDict, relationship_counts, Pairs).
91
92
 
92
93
  relationship_count(Id, Relationship, Count) :-
93
- findall(1,
94
+ aggregate_all(count,
94
95
  (kb_relationship(Relationship, Id, _); kb_relationship(Relationship, _, Id)),
95
- Matches),
96
- length(Matches, Count).
96
+ Count).
97
97
 
98
98
  coverage_rows(req, Tags, IncludePassing, IncludeTransitive, Rows, Summary) :-
99
99
  !,
package/src/kb.pl CHANGED
@@ -39,6 +39,8 @@
39
39
  adr_chain/2,
40
40
  deprecated_no_successor/1,
41
41
  symbol_no_req_coverage/2,
42
+ predicate_schema/6,
43
+ predicate_fact/5,
42
44
  contradicting_reqs/3,
43
45
  check_req_contradiction/1,
44
46
  normalize_term_atom/2,
@@ -962,6 +964,57 @@ fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope,
962
964
  ( memberchk(scope=ScopeRaw, Props) -> normalize_term_atom(ScopeRaw, Scope) ; Scope = '' ),
963
965
  ( memberchk(polarity=PolarityRaw, Props) -> normalize_term_atom(PolarityRaw, Polarity) ; Polarity = require ).
964
966
 
967
+ %% predicate_schema(+FactId, -Namespace, -Name, -Arity, -ArgumentNames, -ArgumentTypes)
968
+ % Read one project-local ontology predicate schema fact.
969
+ predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes) :-
970
+ kb_entity(FactId, fact, Props),
971
+ memberchk(fact_kind=KindRaw, Props),
972
+ normalize_term_atom(KindRaw, predicate_schema),
973
+ memberchk(predicate_name=NameRaw, Props),
974
+ normalize_term_atom(NameRaw, Name),
975
+ ( memberchk(predicate_namespace=NamespaceRaw, Props) -> normalize_term_atom(NamespaceRaw, Namespace) ; Namespace = default ),
976
+ memberchk(predicate_arity=ArityRaw, Props),
977
+ normalize_term_integer(ArityRaw, Arity),
978
+ memberchk(argument_names=ArgumentNamesRaw, Props),
979
+ normalize_term_atom_list(ArgumentNamesRaw, ArgumentNames),
980
+ memberchk(argument_types=ArgumentTypesRaw, Props),
981
+ normalize_term_atom_list(ArgumentTypesRaw, ArgumentTypes).
982
+
983
+ %% predicate_fact(+FactId, -Namespace, -Name, -Args, -Polarity)
984
+ % Read one ground ontology predicate fact.
985
+ predicate_fact(FactId, Namespace, Name, Args, Polarity) :-
986
+ kb_entity(FactId, fact, Props),
987
+ memberchk(fact_kind=KindRaw, Props),
988
+ normalize_term_atom(KindRaw, predicate),
989
+ memberchk(predicate_name=NameRaw, Props),
990
+ normalize_term_atom(NameRaw, Name),
991
+ ( memberchk(predicate_namespace=NamespaceRaw, Props) -> normalize_term_atom(NamespaceRaw, Namespace) ; Namespace = default ),
992
+ memberchk(predicate_args=ArgsRaw, Props),
993
+ normalize_term_atom_list(ArgsRaw, Args),
994
+ ( memberchk(polarity=PolarityRaw, Props) -> normalize_term_atom(PolarityRaw, Polarity) ; Polarity = assert ).
995
+
996
+ normalize_term_atom_list(List, Atoms) :-
997
+ is_list(List),
998
+ !,
999
+ maplist(normalize_term_atom, List, Atoms).
1000
+ normalize_term_atom_list(Raw, Atoms) :-
1001
+ unwrap_rdf_value(Raw, Value),
1002
+ is_list(Value),
1003
+ !,
1004
+ maplist(normalize_term_atom, Value, Atoms).
1005
+
1006
+ normalize_term_integer(Raw, Integer) :-
1007
+ unwrap_rdf_value(Raw, Value),
1008
+ ( integer(Value)
1009
+ -> Integer = Value
1010
+ ; atom(Value)
1011
+ -> atom_number(Value, Integer)
1012
+ ; string(Value)
1013
+ -> number_string(Integer, Value)
1014
+ ; Value = ^^(Nested, _Type)
1015
+ -> normalize_term_integer(Nested, Integer)
1016
+ ).
1017
+
965
1018
  %% fact_valid_interval(+FactId, -ValidFrom, -ValidTo)
966
1019
  % Extract optional validity bounds for property_value facts.
967
1020
  fact_valid_interval(FactId, ValidFrom, ValidTo) :-
@@ -0,0 +1,150 @@
1
+ % Module: kibi_sparql_client
2
+ % Explicit remote SPARQL client wrapper.
3
+
4
+ :- module(kibi_sparql_client, [
5
+ remote_sparql_select_json/4
6
+ ]).
7
+
8
+ :- use_module(library(error)).
9
+ :- use_module(library(http/json)).
10
+ :- use_module(library(semweb/sparql_client)).
11
+
12
+ remote_sparql_select_json(Endpoint, Query, Options, JsonString) :-
13
+ validate_non_empty_atom(endpoint, Endpoint),
14
+ validate_non_empty_atom(query, Query),
15
+ validate_remote_endpoint(Endpoint),
16
+ validate_select_query(Query),
17
+ validate_sparql_options(Options, SparqlOptions),
18
+ sparql_query(Query, Rows, [endpoint(Endpoint), variable_names(Variables)|SparqlOptions]),
19
+ maplist(row_json(Variables), Rows, JsonRows),
20
+ with_output_to(string(JsonString), json_write_dict(current_output, _{rows: JsonRows}, [])).
21
+
22
+ validate_non_empty_atom(Name, Value) :-
23
+ must_be(atom, Value),
24
+ ( Value == ''
25
+ -> throw(error(domain_error(non_empty_atom, Name), _))
26
+ ; true
27
+ ).
28
+
29
+ validate_remote_endpoint(Endpoint) :-
30
+ ( sub_atom(Endpoint, 0, _, _, 'http://')
31
+ ; sub_atom(Endpoint, 0, _, _, 'https://')
32
+ ),
33
+ endpoint_host(Endpoint, Host),
34
+ \+ local_or_private_host(Host),
35
+ !.
36
+ validate_remote_endpoint(Endpoint) :-
37
+ ( sub_atom(Endpoint, 0, _, _, 'http://')
38
+ ; sub_atom(Endpoint, 0, _, _, 'https://')
39
+ ),
40
+ !,
41
+ throw(error(domain_error(public_remote_endpoint, Endpoint), _)).
42
+ validate_remote_endpoint(Endpoint) :-
43
+ throw(error(domain_error(remote_http_endpoint, Endpoint), _)).
44
+
45
+ validate_select_query(Query) :-
46
+ normalize_space(atom(Normalized), Query),
47
+ downcase_atom(Normalized, Lower),
48
+ sub_atom(Lower, 0, 6, After, 'select'),
49
+ ( After =:= 0
50
+ ; sub_atom(Lower, 6, 1, _, ' ')
51
+ ; sub_atom(Lower, 6, 1, _, '*')
52
+ ),
53
+ !.
54
+ validate_select_query(Query) :-
55
+ throw(error(domain_error(sparql_select_query, Query), _)).
56
+
57
+ endpoint_host(Endpoint, Host) :-
58
+ strip_endpoint_scheme(Endpoint, WithoutScheme),
59
+ segment_before(WithoutScheme, '/', WithoutPath),
60
+ segment_before(WithoutPath, '?', WithoutQuery),
61
+ segment_before(WithoutQuery, '#', Authority),
62
+ strip_userinfo(Authority, HostWithPort),
63
+ strip_port(HostWithPort, Host0),
64
+ downcase_atom(Host0, Host).
65
+
66
+ strip_endpoint_scheme(Endpoint, WithoutScheme) :-
67
+ sub_atom(Endpoint, 0, 7, _, 'http://'),
68
+ !,
69
+ sub_atom(Endpoint, 7, _, 0, WithoutScheme).
70
+ strip_endpoint_scheme(Endpoint, WithoutScheme) :-
71
+ sub_atom(Endpoint, 0, 8, _, 'https://'),
72
+ sub_atom(Endpoint, 8, _, 0, WithoutScheme).
73
+
74
+ segment_before(Atom, Delimiter, Segment) :-
75
+ sub_atom(Atom, Before, _, _, Delimiter),
76
+ !,
77
+ sub_atom(Atom, 0, Before, _, Segment).
78
+ segment_before(Atom, _Delimiter, Atom).
79
+
80
+ strip_userinfo(Authority, HostWithPort) :-
81
+ sub_atom(Authority, _, _, After, '@'),
82
+ !,
83
+ sub_atom(Authority, _, After, 0, HostWithPort).
84
+ strip_userinfo(Authority, Authority).
85
+
86
+ strip_port(HostWithPort, Host) :-
87
+ sub_atom(HostWithPort, 0, 1, _, '['),
88
+ sub_atom(HostWithPort, 1, Length, _, ']'),
89
+ !,
90
+ sub_atom(HostWithPort, 1, Length, _, Host).
91
+ strip_port(HostWithPort, Host) :-
92
+ segment_before(HostWithPort, ':', Host).
93
+
94
+ local_or_private_host(localhost).
95
+ local_or_private_host('0.0.0.0').
96
+ local_or_private_host('::1').
97
+ local_or_private_host(Host) :-
98
+ sub_atom(Host, _, _, 0, '.localhost').
99
+ local_or_private_host(Host) :-
100
+ sub_atom(Host, 0, _, _, '127.').
101
+ local_or_private_host(Host) :-
102
+ sub_atom(Host, 0, _, _, '10.').
103
+ local_or_private_host(Host) :-
104
+ sub_atom(Host, 0, _, _, '192.168.').
105
+ local_or_private_host(Host) :-
106
+ sub_atom(Host, 0, _, _, '169.254.').
107
+ local_or_private_host(Host) :-
108
+ sub_atom(Host, 0, _, _, 'fe80:').
109
+ local_or_private_host(Host) :-
110
+ sub_atom(Host, 0, 2, _, Prefix),
111
+ memberchk(Prefix, ['fc', 'fd']).
112
+ local_or_private_host(Host) :-
113
+ sub_atom(Host, 0, _, _, '172.'),
114
+ atomic_list_concat([_, SecondAtom|_], '.', Host),
115
+ atom_number(SecondAtom, Second),
116
+ Second >= 16,
117
+ Second =< 31.
118
+
119
+ validate_sparql_options([], []).
120
+ validate_sparql_options([result_format(json)|Rest], SparqlOptions) :-
121
+ !,
122
+ validate_sparql_options(Rest, SparqlOptions).
123
+ validate_sparql_options([timeout(Timeout)|Rest], [timeout(Timeout)|SparqlOptions]) :-
124
+ !,
125
+ validate_sparql_options(Rest, SparqlOptions).
126
+ validate_sparql_options([Option|_], _) :-
127
+ throw(error(domain_error(sparql_client_option, Option), _)).
128
+
129
+ row_json(Variables, Row, Dict) :-
130
+ Row =.. [row|Values],
131
+ pairs_json(Variables, Values, Pairs),
132
+ dict_create(Dict, row, Pairs).
133
+
134
+ pairs_json([], [], []).
135
+ pairs_json([Variable|Variables], [Value|Values], [Variable-JsonValue|Pairs]) :-
136
+ sparql_value_json(Value, JsonValue),
137
+ pairs_json(Variables, Values, Pairs).
138
+
139
+ sparql_value_json(Value, JsonValue) :-
140
+ atomic(Value),
141
+ !,
142
+ JsonValue = Value.
143
+ sparql_value_json(literal(Value), JsonValue) :-
144
+ !,
145
+ sparql_value_json(Value, JsonValue).
146
+ sparql_value_json(type(_Type, Value), JsonValue) :-
147
+ !,
148
+ sparql_value_json(Value, JsonValue).
149
+ sparql_value_json(Value, JsonValue) :-
150
+ term_string(Value, JsonValue).