Coverage for python/pyshacl_turbo/cli.py: 100.0%
130 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 12:25 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 12:25 +0000
1"""Command-line I/O for the native validator and rule runner."""
3import argparse 1Ob
4import logging 1Ob
5import sys 1Ob
6from contextlib import ExitStack 1Ob
7from importlib.metadata import version 1Ob
9from rdflib import Dataset, Graph 1Ob
10from rdflib.namespace import SH 1Ob
12from . import shacl_rules, validate, validate_each 1Ob
15def table(headers, rows): 1Ob
16 def split(value): 1Nac
17 text = str(value) 1Nac
18 return [text[i : i + 25] for i in range(0, len(text), 25)] or [""] 1Nac
20 cells = [[split(value) for value in row] for row in [headers, *rows]] 1Nac
21 widths = [max(len(line) for row in cells for line in row[i]) for i in range(len(headers))] 1Nac
22 border = "+" + "+".join("-" * (width + 2) for width in widths) + "+" 1Nac
23 result = [border] 1Nac
24 for index, row in enumerate(cells): 1Nac
25 for line in range(max(map(len, row))): 1Nac
26 result.append( 1Nac
27 "| "
28 + " | ".join(
29 (column[line] if line < len(column) else "").ljust(width)
30 for column, width in zip(row, widths)
31 )
32 + " |"
33 )
34 if index == 0: 1Nac
35 result.append(border) 1Nac
36 result.append(border) 1Nac
37 return "\n".join(result) + "\n" 1Nac
40def report_table(conforms, report): 1Ob
41 output = table(["Conforms"], [[conforms]]) 1Nac
42 if conforms: 1Nac
43 return output 1c
44 rows = [] 1Na
45 fields = [ 1Na
46 SH.resultSeverity,
47 SH.focusNode,
48 SH.resultPath,
49 SH.resultMessage,
50 SH.sourceConstraintComponent,
51 SH.sourceShape,
52 SH.value,
53 ]
54 for index, result in enumerate(report.objects(None, SH.result), 1): 1Na
55 values = [ 1a
56 str(report.value(result, predicate) or "-").replace(str(SH), "") for predicate in fields
57 ]
58 rows.extend([[index, *values], [""] * 8]) 1a
59 return ( 1Na
60 output
61 + "\n"
62 + table(
63 [
64 "No.",
65 "Severity",
66 "Focus Node",
67 "Result Path",
68 "Message",
69 "Component",
70 "Shape",
71 "Value",
72 ],
73 rows,
74 )
75 )
78def serialize_graph(graph, rdf_format): 1Ob
79 if rdf_format in {"trig", "nquads"} and not graph.context_aware: 1fgCDEhjkdlemnFopqrsitu
80 dataset = Dataset() 1ghde
81 dataset.default_graph += graph 1ghde
82 for prefix, namespace in graph.namespaces(): 1ghde
83 dataset.bind(prefix, namespace) 1ghde
84 graph = dataset 1ghde
85 return graph.serialize(format=rdf_format) 1fgCDEhjkdlemnFopqrsitu
88def main(argv=None): 1Ob
89 parser = argparse.ArgumentParser(prog="pyshacl-turbo") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
90 parser.add_argument("data", nargs="*") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
91 parser.add_argument("-s", "--shapes", "--shacl", dest="shacl_graph") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
92 parser.add_argument("-e", "--ont-graph", dest="ont_graph") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
93 parser.add_argument( 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
94 "-i", "--inference", choices=["none", "rdfs", "owlrl", "both"], default="none"
95 )
96 parser.add_argument("-a", "--advanced", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
97 parser.add_argument("-m", "--metashacl", dest="meta_shacl", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
98 parser.add_argument("-j", "--js", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
99 parser.add_argument("-im", "--imports", dest="do_owl_imports", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
100 parser.add_argument("-it", "--iterate-rules", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
101 parser.add_argument("--abort", dest="abort_on_first", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
102 parser.add_argument("--allow-info", "--allow-infos", dest="allow_infos", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
103 parser.add_argument( 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
104 "-w", "--allow-warning", "--allow-warnings", dest="allow_warnings", action="store_true"
105 )
106 parser.add_argument("--max-depth", dest="max_validation_depth", type=int) 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
107 parser.add_argument("--validate-each", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
108 parser.add_argument("--focus") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
109 parser.add_argument("--shape") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
110 parser.add_argument("-d", "--debug", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
111 parser.add_argument("--rules", action="store_true") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
112 parser.add_argument("-df", "--data-file-format", dest="data_graph_format") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
113 parser.add_argument("-sf", "--shacl-file-format", dest="shacl_graph_format") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
114 parser.add_argument("-ef", "--ont-file-format", dest="ont_graph_format") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
115 parser.add_argument( 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
116 "-f",
117 "--format",
118 default="human",
119 choices=["human", "table", "turtle", "trig", "xml", "json-ld", "nt", "n3", "nquads"],
120 )
121 parser.add_argument("-o", "--output") 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
122 parser.add_argument("-V", "--version", action="version", version=version("pyshacl-turbo")) 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
123 args = parser.parse_args(argv) 1xGHJyzAfIbvgwCDEhjkdlemnacLKFMBopqrsitu
124 if not args.data: 1xGHJyzAfIbvgwCDEhjkdlemnacLKFBopqrsitu
125 print("Input Error. No DataGraph file or endpoint supplied.", file=sys.stderr) 1L
126 parser.print_usage(sys.stderr) 1L
127 return 1 1L
128 if args.debug: 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
129 logging.basicConfig(level=logging.DEBUG) 1f
130 options = { 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
131 name: getattr(args, name)
132 for name in [
133 "shacl_graph",
134 "ont_graph",
135 "inference",
136 "advanced",
137 "js",
138 "do_owl_imports",
139 "iterate_rules",
140 "data_graph_format",
141 "shacl_graph_format",
142 "ont_graph_format",
143 "debug",
144 "max_validation_depth",
145 ]
146 }
147 for name in ("data_graph_format", "shacl_graph_format", "ont_graph_format"): 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
148 if options[name] == "auto": 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
149 options[name] = None 1i
150 options.update( 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
151 focus_nodes=[name.strip() for name in args.focus.split(",")] if args.focus else None,
152 use_shapes=[name.strip() for name in args.shape.split(",")] if args.shape else None,
153 )
154 with ExitStack() as files: 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
155 try: 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
156 data = [ 1xGHJyzAfIbvgwCDEhjkdlemnacKFBopqrsitu
157 sys.stdin.buffer.read()
158 if source == "-"
159 else files.enter_context(open(source, "rb"))
160 for source in args.data
161 ]
162 except FileNotFoundError: 1JIK
163 print("Input Error. DataGraph file not found.", file=sys.stderr) 1JK
164 return 1 1JK
165 except PermissionError: 1I
166 print("Input Error. DataGraph file not readable.", file=sys.stderr) 1I
167 return 1 1I
168 return _run(args, data, options) 1xGHyzAfbvgwCDEhjkdlemnacFBopqrsitu
171def _validation_output(result, rdf_format): 1Ob
172 conforms, report, text = result 1xyzAfbvwjkdlemnacBopqrsitu
173 if rdf_format == "table" and isinstance(report, Graph): 1xyzAfbvwjkdlemnacBopqrsitu
174 return report_table(conforms, report) 1ac
175 if rdf_format != "human" and isinstance(report, Graph): 1xyzAfbvwjkdlemnBopqrsitu
176 return serialize_graph(report, rdf_format) 1fjkdlemnopqrsitu
177 return text 1xyzAbvwB
180def _run(args, inputs, options): 1Ob
181 data = inputs[0] if len(inputs) == 1 else inputs 1xGHyzAfbvgwCDEhjkdlemnacFBopqrsitu
182 try: 1xGHyzAfbvgwCDEhjkdlemnacFBopqrsitu
183 if args.rules: 1xGHyzAfbvgwCDEhjkdlemnacFBopqrsitu
184 options["advanced"] = True 1gCDEhF
185 report = shacl_rules(data, **options) 1gCDEhF
186 output_format = args.format if args.format not in {"human", "table"} else "trig" 1gCDEhF
187 text = serialize_graph(report, output_format) 1gCDEhF
188 conforms = True 1gCDEhF
189 else:
190 options.update( 1xGHyzAfbvwjkdlemnacBopqrsitu
191 {
192 name: getattr(args, name)
193 for name in ["meta_shacl", "abort_on_first", "allow_infos", "allow_warnings"]
194 }
195 )
196 if args.validate_each: 1xGHyzAfbvwjkdlemnacBopqrsitu
197 results = validate_each(inputs, **options) 1B
198 conforms = all(result[0] for result in results.values()) 1B
199 text = "".join( 1B
200 f"=== Validation result for {index} ===\n"
201 + _validation_output(result, args.format)
202 + "\n"
203 for index, result in results.items()
204 )
205 else:
206 result = validate(data, **options) 1xGHyzAfbvwjkdlemnacopqrsitu
207 conforms = result[0] 1xyzAfbvwjkdlemnacopqrsitu
208 text = _validation_output(result, args.format) 1xyzAfbvwjkdlemnacopqrsitu
209 if args.output: 1xyzAfbvgwCDEhjkdlemnacFBopqrsitu
210 with open(args.output, "w", encoding="utf-8") as output: 1f
211 output.write(text) 1f
212 else:
213 sys.stdout.write(text) 1xyzAbvgwCDEhjkdlemnacFBopqrsitu
214 return 0 if conforms else 1 1xyzAfbvgwCDEhjkdlemnacFBopqrsitu
215 except NotImplementedError as error: 1GH
216 print(error, file=sys.stderr) 1H
217 return 3 1H
218 except (ValueError, RuntimeError, OSError) as error: 1G
219 print(error, file=sys.stderr) 1G
220 return 2 1G
223def entrypoint(): 1Ob
224 raise SystemExit(main()) 1bvw
227def rules_entrypoint(): 1Ob
228 raise SystemExit(main(["--rules", *sys.argv[1:]])) 1g
231if __name__ == "__main__": 1Ob
232 entrypoint() 1b