shacl2code 0.0.11__py3-none-any.whl

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.
shacl2code/main.py ADDED
@@ -0,0 +1,139 @@
1
+ #! /usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2024 Joshua Watt
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+
7
+ import argparse
8
+ import json
9
+ import sys
10
+ import urllib.request
11
+ import rdflib
12
+ from pathlib import Path
13
+
14
+ from . import Model, UrlContext, ContextData
15
+ from .version import VERSION
16
+ from .lang import LANGUAGES
17
+
18
+
19
+ def main(args=None):
20
+ def handle_generate(parser, args):
21
+ graph = rdflib.Graph()
22
+ for inmodel in args.input:
23
+ if inmodel == "-":
24
+ if args.input_format == "auto":
25
+ print("ERROR: Input format must be specified with stdin")
26
+ parser.print_help()
27
+ return 1
28
+
29
+ graph.parse(sys.stdin, format=args.input_format)
30
+ else:
31
+ if args.input_format == "auto":
32
+ graph.parse(inmodel)
33
+ else:
34
+ graph.parse(inmodel, format=args.input_format)
35
+
36
+ contexts = []
37
+ for c in args.context:
38
+ with urllib.request.urlopen(c) as f:
39
+ data = json.load(f)
40
+ contexts.append(ContextData(data, c))
41
+
42
+ for location, url in args.context_url:
43
+ if "://" in location:
44
+ with urllib.request.urlopen(location) as f:
45
+ data = json.load(f)
46
+ else:
47
+ with Path(location).open("r") as f:
48
+ data = json.load(f)
49
+ contexts.append(ContextData(data, url))
50
+
51
+ m = Model(graph, UrlContext(contexts))
52
+
53
+ render = args.lang(args)
54
+ render.output(m)
55
+ return 0
56
+
57
+ def handle_list(parser, args):
58
+ width = max(len(lang) for lang in LANGUAGES)
59
+ for k, v in LANGUAGES.items():
60
+ if args.short:
61
+ print(k)
62
+ else:
63
+ print(f"{k:{width}} - {v.HELP}")
64
+
65
+ return 0
66
+
67
+ def handle_version(parser, args):
68
+ print(VERSION)
69
+ return 0
70
+
71
+ parser = argparse.ArgumentParser(
72
+ description=f"Convert JSON-LD model to python. Version {VERSION}"
73
+ )
74
+ command_subparser = parser.add_subparsers(
75
+ title="command",
76
+ description="Command to execute",
77
+ required=True,
78
+ )
79
+ generate_parser = command_subparser.add_parser(
80
+ "generate",
81
+ help="Generate language bindings",
82
+ )
83
+ generate_parser.add_argument(
84
+ "--input",
85
+ "-i",
86
+ help="Input model (path, URL, or '-')",
87
+ action="append",
88
+ default=[],
89
+ required=True,
90
+ )
91
+ generate_parser.add_argument(
92
+ "--input-format",
93
+ "-t",
94
+ help="Input file format, or 'auto' to attempt to determine automatically. Default is %(default)s",
95
+ default="auto",
96
+ )
97
+ generate_parser.add_argument(
98
+ "--context",
99
+ "-x",
100
+ help="Require context for output (URL)",
101
+ action="append",
102
+ default=[],
103
+ )
104
+ generate_parser.add_argument(
105
+ "--context-url",
106
+ "-u",
107
+ help="Require context from LOCATION (path or URL), but report as URL in generated code",
108
+ nargs=2,
109
+ metavar=("LOCATION", "URL"),
110
+ action="append",
111
+ default=[],
112
+ )
113
+ generate_parser.set_defaults(func=handle_generate)
114
+
115
+ lang_subparser = generate_parser.add_subparsers(
116
+ title="language",
117
+ description="Language to generate",
118
+ required=True,
119
+ )
120
+ for k, v in LANGUAGES.items():
121
+ p = lang_subparser.add_parser(k, help=v.HELP)
122
+ v.get_arguments(p)
123
+ p.set_defaults(lang=v)
124
+
125
+ list_parser = command_subparser.add_parser("list", help="List languages")
126
+ list_parser.add_argument(
127
+ "--short",
128
+ "-s",
129
+ action="store_true",
130
+ help="Only list languages without descriptions",
131
+ )
132
+ list_parser.set_defaults(func=handle_list)
133
+
134
+ version_parser = command_subparser.add_parser("version", help="Show version")
135
+ version_parser.set_defaults(func=handle_version)
136
+
137
+ parsed_args = parser.parse_args(args)
138
+
139
+ return parsed_args.func(parser, parsed_args)
shacl2code/model.py ADDED
@@ -0,0 +1,293 @@
1
+ #! /usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2024 Joshua Watt
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+
7
+ import typing
8
+ from dataclasses import dataclass
9
+
10
+ from rdflib import URIRef
11
+ from rdflib.namespace import RDF, RDFS, OWL, SH, XSD, DefinedNamespace, Namespace
12
+
13
+ PATTERN_DATATYPES = [
14
+ str(XSD.string),
15
+ str(XSD.dateTime),
16
+ str(XSD.dateTimeStamp),
17
+ str(XSD.anyURI),
18
+ ]
19
+
20
+
21
+ class SHACL2CODE(DefinedNamespace):
22
+ idPropertyName: URIRef
23
+ isExtensible: URIRef
24
+ isAbstract: URIRef
25
+
26
+ _NS = Namespace("https://jpewdev.github.io/shacl2code/schema#")
27
+
28
+
29
+ class ModelException(Exception):
30
+ pass
31
+
32
+
33
+ def common_prefix(*s):
34
+ if not s:
35
+ return ""
36
+
37
+ if len(s) == 1:
38
+ return s[0]
39
+
40
+ p1 = common_prefix(*s[: len(s) // 2])
41
+ p2 = common_prefix(*s[len(s) // 2 :])
42
+ for idx in range(len(p1)):
43
+ if idx >= len(p2):
44
+ return p2
45
+
46
+ if p1[idx] != p2[idx]:
47
+ return p2[:idx]
48
+
49
+ return p1
50
+
51
+
52
+ def remove_common_prefix(val, *cmp):
53
+ prefix = common_prefix(val, *cmp)
54
+ return val[len(prefix) :]
55
+
56
+
57
+ @dataclass
58
+ class Individual:
59
+ _id: str
60
+ varname: str
61
+ comment: str = ""
62
+
63
+
64
+ @dataclass
65
+ class Property:
66
+ path: str
67
+ varname: str
68
+ comment: str = ""
69
+ max_count: int = None
70
+ min_count: int = None
71
+ enum_values: list = None
72
+ class_id: str = ""
73
+ datatype: str = ""
74
+ pattern: str = ""
75
+
76
+
77
+ @dataclass
78
+ class Class:
79
+ _id: str
80
+ clsname: str
81
+ parent_ids: typing.List[str]
82
+ derived_ids: list
83
+ properties: typing.List[Property]
84
+ comment: str = ""
85
+ id_property: str = ""
86
+ node_kind: str = None
87
+ is_extensible: bool = False
88
+ is_abstract: bool = False
89
+ named_individuals: list = None
90
+
91
+
92
+ class Model(object):
93
+ def __init__(self, graph, context=None):
94
+ self.model = graph
95
+ self.context = context
96
+ self.compact_ids = {}
97
+ self.objects = {}
98
+ self.enums = []
99
+ self.classes = []
100
+ class_iris = set()
101
+ classes_by_iri = {}
102
+
103
+ def int_val(v):
104
+ if not v:
105
+ return None
106
+ return int(v)
107
+
108
+ def str_val(v):
109
+ if v is None:
110
+ return v
111
+ return str(v)
112
+
113
+ def get_inherited_value(subject, predicate, default=None):
114
+ def get_value(subject, predicate):
115
+ value = self.model.value(subject, predicate)
116
+ if value is not None:
117
+ return value
118
+
119
+ for parent in self.model.objects(subject, RDFS.subClassOf):
120
+ value = get_value(parent, predicate)
121
+ if value is not None:
122
+ return value
123
+
124
+ return None
125
+
126
+ value = get_value(subject, predicate)
127
+ if value is not None:
128
+ return value
129
+ return default
130
+
131
+ def set_prop_range(p, range_id):
132
+ nonlocal class_iris
133
+
134
+ if range_id in class_iris:
135
+ p.class_id = str(range_id)
136
+ return True
137
+
138
+ return False
139
+
140
+ def get_named_individuals(cls_iri):
141
+ members = []
142
+ for member_iri in self.model.subjects(RDF.type, cls_iri):
143
+ if (member_iri, RDF.type, OWL.NamedIndividual) not in self.model:
144
+ continue
145
+
146
+ members.append(
147
+ Individual(
148
+ _id=str(member_iri),
149
+ varname=remove_common_prefix(member_iri, cls_iri).lstrip("/"),
150
+ comment=str(
151
+ self.model.value(member_iri, RDFS.comment, default="")
152
+ ),
153
+ )
154
+ )
155
+ return members
156
+
157
+ def is_abstract(s):
158
+ if (
159
+ s,
160
+ RDF.type,
161
+ URIRef("http://spdx.invalid./AbstractClass"),
162
+ ) in self.model:
163
+ return True
164
+
165
+ return bool(self.model.value(s, SHACL2CODE.isAbstract, default=False))
166
+
167
+ class_iris = set(self.model.subjects(RDF.type, OWL.Class))
168
+ for cls_iri in class_iris:
169
+ c = Class(
170
+ _id=str(cls_iri),
171
+ parent_ids=[
172
+ str(parent_iri)
173
+ for parent_iri in self.model.objects(cls_iri, RDFS.subClassOf)
174
+ if parent_iri in class_iris
175
+ ],
176
+ derived_ids=[],
177
+ clsname=self.get_class_name(cls_iri),
178
+ comment=str(self.model.value(cls_iri, RDFS.comment, default="")),
179
+ properties=[],
180
+ id_property=str_val(
181
+ get_inherited_value(cls_iri, SHACL2CODE.idPropertyName)
182
+ ),
183
+ node_kind=get_inherited_value(cls_iri, SH.nodeKind, SH.BlankNodeOrIRI),
184
+ is_extensible=bool(self.model.value(cls_iri, SHACL2CODE.isExtensible)),
185
+ is_abstract=is_abstract(cls_iri),
186
+ named_individuals=get_named_individuals(cls_iri),
187
+ )
188
+
189
+ if c.node_kind not in (SH.IRI, SH.BlankNode, SH.BlankNodeOrIRI):
190
+ raise ModelException(
191
+ f"Class {c._id} has unsupported '{SH.nodeKind}' value '{c.node_kind}'"
192
+ )
193
+
194
+ for obj_prop in self.model.objects(cls_iri, SH.property):
195
+ prop = self.model.value(obj_prop, SH.path)
196
+
197
+ p = Property(
198
+ varname=self.model.value(
199
+ obj_prop,
200
+ SH.name,
201
+ default=self.get_compact_id(
202
+ prop,
203
+ fallback=remove_common_prefix(prop, cls_iri).lstrip("/"),
204
+ ),
205
+ ),
206
+ path=str(prop),
207
+ comment=str(self.model.value(prop, RDFS.comment, default="")),
208
+ max_count=int_val(self.model.value(obj_prop, SH.maxCount)),
209
+ min_count=int_val(self.model.value(obj_prop, SH.minCount)),
210
+ )
211
+
212
+ if in_list := self.model.value(obj_prop, SH["in"]):
213
+ p.enum_values = sorted(tuple(self.model.items(in_list)))
214
+
215
+ if range_id := self.model.value(obj_prop, SH["class"]):
216
+ if not set_prop_range(p, range_id):
217
+ raise ModelException(
218
+ f"Prop {prop} has unknown class restriction {range_id}"
219
+ )
220
+
221
+ elif range_id := self.model.value(obj_prop, SH.datatype):
222
+ p.datatype = str(range_id)
223
+
224
+ elif range_id := self.model.value(prop, RDFS.range):
225
+ if not set_prop_range(p, range_id):
226
+ p.datatype = str(range_id)
227
+
228
+ else:
229
+ raise ModelException(f"Prop '{prop}' is missing range")
230
+
231
+ if pattern := self.model.value(obj_prop, SH.pattern):
232
+ if not p.datatype:
233
+ raise ModelException(
234
+ f"Property '{prop}' is not a datatype and may not have a pattern"
235
+ )
236
+ if p.datatype not in PATTERN_DATATYPES:
237
+ raise ModelException(
238
+ f"Property '{prop}' of type '{p.datatype}' cannot have a pattern. Must be one of type {' '.join(PATTERN_DATATYPES)}"
239
+ )
240
+ p.pattern = str(pattern)
241
+
242
+ c.properties.append(p)
243
+
244
+ c.properties.sort(key=lambda p: p.path)
245
+
246
+ self.classes.append(c)
247
+ classes_by_iri[str(cls_iri)] = c
248
+
249
+ for c in self.classes:
250
+ for p in c.parent_ids:
251
+ classes_by_iri[p].derived_ids.append(c._id)
252
+
253
+ for c in self.classes:
254
+ c.derived_ids.sort()
255
+
256
+ self.enums.sort(key=lambda e: e._id)
257
+ self.classes.sort(key=lambda c: c._id)
258
+
259
+ tmp_classes = self.classes
260
+ done_ids = set()
261
+ self.classes = []
262
+
263
+ while tmp_classes:
264
+ c = tmp_classes.pop(0)
265
+
266
+ # If any parent classes of this class are outstanding, then push it
267
+ # back on the end of the class list and try again. This ensures that
268
+ # derived classes are always written after any parent classes
269
+ if not all(p in done_ids for p in c.parent_ids):
270
+ tmp_classes.append(c)
271
+ continue
272
+
273
+ self.classes.append(c)
274
+ done_ids.add(c._id)
275
+
276
+ def get_compact_id(self, _id, *, fallback=None):
277
+ """
278
+ Returns the "compacted" name of an object, that is the name of the
279
+ object with the context applied
280
+ """
281
+ _id = str(_id)
282
+ if _id not in self.compact_ids:
283
+ self.compact_ids[_id] = self.context.compact(_id)
284
+
285
+ if self.compact_ids[_id] == _id and fallback is not None:
286
+ return fallback
287
+ return self.compact_ids[_id]
288
+
289
+ def get_class_name(self, c):
290
+ """
291
+ Returns the name for a class that should be used in Code
292
+ """
293
+ return self.get_compact_id(c).split(":")
@@ -0,0 +1,23 @@
1
+ #! /usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2024 Joshua Watt
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+
7
+ import typing
8
+ from dataclasses import dataclass
9
+ from .context import Context
10
+
11
+
12
+ @dataclass
13
+ class ContextData:
14
+ context: typing.Dict
15
+ url: str
16
+
17
+
18
+ class UrlContext(Context):
19
+ def __init__(self, contexts=[]):
20
+ super().__init__([c.context.get("@context", {}) for c in contexts])
21
+ self.urls = []
22
+ for ctx in contexts:
23
+ self.urls.append(ctx.url)
shacl2code/version.py ADDED
@@ -0,0 +1 @@
1
+ VERSION = "0.0.11"
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.3
2
+ Name: shacl2code
3
+ Version: 0.0.11
4
+ Summary: Convert SHACL model file to code bindings
5
+ Project-URL: Homepage, https://github.com/JPEWdev/shacl2code
6
+ Project-URL: Repository, https://github.com/JPEWdev/shacl2code.git
7
+ Project-URL: Issues, https://github.com/JPEWdev/shacl2code/issues
8
+ Author-email: Joshua Watt <JPEWhacker@gmail.com>
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: >=3.8
21
+ Requires-Dist: jinja2>=3.1.2
22
+ Requires-Dist: rdflib>=7.0.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: flake8>=7.0.0; extra == 'dev'
25
+ Requires-Dist: jsonschema>=4.21.1; extra == 'dev'
26
+ Requires-Dist: pyshacl>=0.25.0; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
28
+ Requires-Dist: pytest-server-fixtures>=1.7; extra == 'dev'
29
+ Requires-Dist: pytest>=7.4; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Convert SHACL Model to code bindings
33
+ [![Coverage Report](https://raw.githubusercontent.com/JPEWdev/shacl2code/python-coverage-comment-action-data/badge.svg)](https://htmlpreview.github.io/?https://github.com/JPEWdev/shacl2code/blob/python-coverage-comment-action-data/htmlcov/index.html)
34
+
35
+ This tool can be used to convert a SHACL model into various code bindings
36
+
37
+ ## Installation
38
+
39
+ `shacl2code` can be installed using pip:
40
+
41
+ ```shell
42
+ python3 -m pip install shacl2code
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ `shacl2code` can generate bindings from either a local file:
48
+ ```shell
49
+ shacl2code generate -i model.jsonld python -o out.py
50
+ ```
51
+ Or from a URL:
52
+ ```shell
53
+ shacl2code generate -i https://example.com/rdf/model.jsonld python -o out.py
54
+ ```
55
+ Or from stdin:
56
+ ```shell
57
+ cat model.jsonld | shacl2code generate -i - python -o - > out.py
58
+ ```
59
+
60
+ For more information, run:
61
+ ```shell
62
+ shacl2code --help
63
+ ```
64
+
65
+ The available language bindings can be viewed by running:
66
+ ```shell
67
+ shacl2code list
68
+ ```
69
+
70
+ ## Developing
71
+
72
+ Developing on `shacl2code` is best done using a virtual environment. You can
73
+ configure one and install shacl2code in editable mode with all necessary
74
+ development dependencies by running:
75
+
76
+ ```shell
77
+ python3 -m venv .venv
78
+ . .venv/bin/activate
79
+ pip install -e ".[dev]"
80
+ ```
81
+
82
+ ## Testing
83
+
84
+ `shacl2code` has a test suite written in [pytest][pytest]. To run it, setup a
85
+ virtual environment as shown above, then run:
86
+ ```shell
87
+ pytest
88
+ ```
89
+
90
+ In addition to the test results, a test coverage report will also be generated
91
+ using [pytest-cov][pytest-cov]
92
+
93
+
94
+ ## Custom Annotations
95
+
96
+ `shacl2code` supports a number of custom annotations that can be specified in a
97
+ SHACL model to give hints about the generated code. All of these annotations
98
+ live in the `https://jpewdev.github.io/shacl2code/schema#` namespace, and
99
+ commonly are given the `sh-to-code` prefix to make it easier to reference them.
100
+ For example, in Turtle one would add the prefix mapping:
101
+
102
+ ```ttl
103
+ @prefix sh-to-code: <https://jpewdev.github.io/shacl2code/schema#> .
104
+ ```
105
+
106
+ ### ID Property Name
107
+
108
+ The `idPropertyName` annotation allows a class to specify what the name of the
109
+ "property" that specifies the RDF subject of an object is for serializations
110
+ that support it. For example, in JSON-LD, the `@id` property indicates the
111
+ subject in RDF. If you wanted to alias the `@id` property to another name, the
112
+ `idPropertyName` annotation will let you do this. For example, the following
113
+ turtle will use `MyId` instead of `@id` when writing JSON-LD bindings:
114
+
115
+ ```ttl
116
+ <MyClass> a owl:Class, sh:NodeShape ;
117
+ sh-to-code:idPropertyName "MyId"
118
+ .
119
+ ```
120
+
121
+ When doing this, the class would then look like this in JSON-LD:
122
+ ```json
123
+ {
124
+ "@type": "MyClass",
125
+ "MyId": "http://example.com/id"
126
+ }
127
+ ```
128
+
129
+ The `idProperyName` annotation is inherited by derived classes, so for example
130
+ any class that derived from `MyClass` would also use `MyId` as the subject
131
+ property.
132
+
133
+ **Note:** This only specifies what the name of the field should be in generated
134
+ bindings and has no bearing on how an RDF parser would interpret the property.
135
+ In order to still be parsed by RDF, you would also need context file that maps
136
+ `MyId` to `@id`, for example:
137
+
138
+ ```json
139
+ {
140
+ "@context": {
141
+ "MyId": "@id"
142
+ }
143
+ }
144
+ ```
145
+
146
+ `shacl2code` doesn't do this for you, nor does it validate that you have done
147
+ it.
148
+
149
+ ### Extensible Classes
150
+
151
+ Most bindings generated from `shacl2code` are "closed" in that they do not
152
+ allow extra properties to be added to object outside of what is specified in
153
+ model. This ensures that field name typos and other unintended properties are
154
+ not added to an object. However, in some cases a class may be specifically
155
+ intended to be extended such that arbitrary fields can be added to it, which
156
+ can be done using the `isExtensible` property. This is a boolean property that
157
+ indicates if a class can be extended, and defaults to `false`. For example, the
158
+ following turtle will declare a class as extensible:
159
+
160
+ ```ttl
161
+
162
+ <MyClass> a owl:Class, sh:NodeShape ;
163
+ sh-to-code:isExtensible true
164
+ .
165
+ ```
166
+
167
+ The `isExtensible` property is _not_ inherited by derived classes, meaning it
168
+ is possible to have a class derived from `MyClass` which is itself not
169
+ extensible.
170
+
171
+ The mechanism for dealing with extensible classes will vary between the
172
+ different bindings, but in general it means that they will not be very picky
173
+ about object types and properties in any location where an extensible class is
174
+ allowed.
175
+
176
+ **Note**: You may want to be careful about where and how many extensible
177
+ classes are allowed in your model. If there are too many and they are allowed
178
+ anywhere, it may mean that typos in object types (e.g. `@type` in JSON-LD) are
179
+ not caught by validation as they will have to be assumed to be a derived class
180
+ from an extensible type.
181
+
182
+ ### Abstract Classes
183
+
184
+ By default, classes generated by `shacl2code` are all instantiable (i.e. they
185
+ can be created). In some instances, it may be desirable to declare a class as
186
+ abstract (meaning that it cannot be instantiated, but non-abstract derived
187
+ classes can). This can be done with the boolean `isAbstract` property. For
188
+ example, the following turtle will declare a class as abstract:
189
+
190
+ ```ttl
191
+
192
+ <MyClass> a owl:Class, sh:NodeShape ;
193
+ sh-to-code:isAbstract true
194
+ .
195
+ ```
196
+
197
+ The `isAbstract` property is _not_ inherited by derived classes, so any derived
198
+ classes are automatically concrete unless they indicate otherwise.
199
+
200
+ Note that for compatibility reasons, it is also possible to define a class as
201
+ abstract by declaring it to be of type: `http://spdx.invalid./AbstractClass`,
202
+ but this is not preferred.
203
+
204
+ [pytest]: https://www.pytest.org
205
+ [pytest-cov]: https://pytest-cov.readthedocs.io/en/latest/
@@ -0,0 +1,20 @@
1
+ shacl2code/__init__.py,sha256=aq5je2i1vkTFCIhUEEt1RsbJlsXpg2rzPLEK_PJh3ac,197
2
+ shacl2code/__main__.py,sha256=xWmYOo4S8cDiXgIQFhBg1fdHi1ANIbviMSrbx4LXOAw,173
3
+ shacl2code/context.py,sha256=pzu_GseOZVz4gR0JC5LjuKigOn5LrlS9rlwyu9wfgDA,5368
4
+ shacl2code/main.py,sha256=eQ5bWu7pGKWb2DAEOc4brMapzPlghtuW6hOZ9M85lMo,4005
5
+ shacl2code/model.py,sha256=ESABSh4xHh5e5qfmeqQqLqdI1G1EWVsoLQMJ-sTBqgY,9077
6
+ shacl2code/urlcontext.py,sha256=cOlNXX1gdbhtmV4hnPTwEdMOPNn8iP0l3sH6pEr3pwQ,472
7
+ shacl2code/version.py,sha256=wvhEP9_tqxjTVjTcNW0lZOeHOeP3yZH_m-zQShg9NXI,19
8
+ shacl2code/lang/__init__.py,sha256=yy-nNrvlRM_O--MpBZ55OO2HoU6W9ICN6H_CE75JJnA,314
9
+ shacl2code/lang/common.py,sha256=pGftHigCCq_MEYny6EqtBnvLxnDEOfrljT0sIKw2YNs,3932
10
+ shacl2code/lang/jinja.py,sha256=MlxZUMoBSaD76ahwBSmGTBJilGiecdvA__WxUqfRZUI,607
11
+ shacl2code/lang/jsonschema.py,sha256=PfffswQ-QY0rV0aGR9qzVzJ-P78UKFvMh8yiuCA2RDo,1292
12
+ shacl2code/lang/lang.py,sha256=4TbEsktsHCrMwLlT4bkhh2fhMWkIxFdwhT1vwML8Rx8,296
13
+ shacl2code/lang/python.py,sha256=umiZx0p2J_vBaoTnPx3ldx4e5yKmQ0hGd7qN6lTVsy8,1414
14
+ shacl2code/lang/templates/jsonschema.j2,sha256=cuTGYvQ1pLj6aMnMwtwTVRxyQMRWdY7YOt-6kzJqCaw,11340
15
+ shacl2code/lang/templates/python.j2,sha256=oXQqL2KoRsf974l-jk4ImOCXYn49iyy5h1Svevkbalg,57340
16
+ shacl2code-0.0.11.dist-info/METADATA,sha256=XjDPB8Wn1q928vJardZ4P0JTRD-ACuRIdvrdP339eYA,6927
17
+ shacl2code-0.0.11.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
18
+ shacl2code-0.0.11.dist-info/entry_points.txt,sha256=xMo3DqLqOpZfEwDXLL3lqQZoXUXKUUhgn-ABzH4nOEg,47
19
+ shacl2code-0.0.11.dist-info/licenses/LICENSE,sha256=npUT32k_O0lU4UHJ8ZZ2_Ti0GR1oSbNGRzID9lSWkxs,1068
20
+ shacl2code-0.0.11.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.24.2
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ shacl2code = shacl2code:main