1use colored::*;
2use iri_s::IriS;
3use prefixmap::{IriRef, PrefixMap};
4use pretty::{Arena, DocAllocator, DocBuilder, RefDoc};
5use rust_decimal::Decimal;
6use shex_ast::{
8 value_set_value::ValueSetValue, Annotation, BNode, IriOrStr, NodeConstraint, NodeKind,
9 NumericFacet, ObjectValue, Pattern, Schema, SemAct, Shape, ShapeDecl, ShapeExpr,
10 ShapeExprLabel, StringFacet, TripleExpr, XsFacet,
11};
12use srdf::{lang::Lang, literal::Literal, numeric_literal::NumericLiteral};
13use std::{borrow::Cow, io, marker::PhantomData};
14
15use crate::pp_object_value;
16
17#[derive(Debug, Clone)]
35pub struct ShExFormatter {
36 keyword_color: Option<Color>,
37 string_color: Option<Color>,
38 prefix_color: Option<Color>,
39 semicolon_color: Option<Color>,
40 localname_color: Option<Color>,
41}
42
43impl ShExFormatter {
44 pub fn keyword_color(&self) -> Option<Color> {
45 self.keyword_color
46 }
47
48 pub fn prefix_color(&self) -> Option<Color> {
49 self.prefix_color
50 }
51
52 pub fn semicolon_color(&self) -> Option<Color> {
53 self.semicolon_color
54 }
55
56 pub fn localname_color(&self) -> Option<Color> {
57 self.localname_color
58 }
59
60 pub fn with_keyword_color(mut self, color: Option<Color>) -> ShExFormatter {
61 self.keyword_color = color;
62 self
63 }
64 pub fn with_prefix_color(mut self, color: Option<Color>) -> ShExFormatter {
65 self.prefix_color = color;
66 self
67 }
68
69 pub fn with_semicolon_color(mut self, color: Option<Color>) -> ShExFormatter {
70 self.semicolon_color = color;
71 self
72 }
73
74 pub fn with_string_color(mut self, color: Option<Color>) -> ShExFormatter {
75 self.string_color = color;
76 self
77 }
78
79 pub fn with_localname_color(mut self, color: Option<Color>) -> ShExFormatter {
80 self.localname_color = color;
81 self
82 }
83
84 pub fn without_colors(self) -> ShExFormatter {
86 self.with_keyword_color(None)
87 .with_localname_color(None)
88 .with_prefix_color(None)
89 .with_string_color(None)
90 .with_semicolon_color(None)
91 }
92
93 pub fn format_schema(&self, schema: &Schema) -> String {
94 let arena = Arena::<()>::new();
95 let mut printer = ShExCompactPrinter::new(schema, &arena);
96 printer = printer.with_keyword_color(self.keyword_color);
97 printer = printer.with_string_color(self.string_color);
98 printer = printer.with_qualify_localname_color(self.localname_color);
99 printer = printer.with_qualify_prefix_color(self.prefix_color);
100 printer = printer.with_qualify_semicolon_color(self.semicolon_color);
101 printer.pretty_print()
102 }
103
104 pub fn write_schema<W: std::io::Write>(
105 &self,
106 schema: &Schema,
107 writer: &mut W,
108 ) -> Result<(), std::io::Error> {
109 let arena = Arena::<()>::new();
110 let mut printer = ShExCompactPrinter::new(schema, &arena);
111 printer = printer.with_keyword_color(self.keyword_color);
112 printer = printer.with_string_color(self.string_color);
113 printer = printer.with_qualify_localname_color(self.localname_color);
114 printer = printer.with_qualify_prefix_color(self.prefix_color);
115 printer = printer.with_qualify_semicolon_color(self.semicolon_color);
116 printer.pretty_print_write(writer)
117 }
118}
119
120impl Default for ShExFormatter {
121 fn default() -> Self {
122 Self {
123 keyword_color: DEFAULT_KEYWORD_COLOR,
124 prefix_color: DEFAULT_QUALIFY_ALIAS_COLOR,
125 semicolon_color: DEFAULT_QUALIFY_SEMICOLON_COLOR,
126 string_color: DEFAULT_STRING_COLOR,
127 localname_color: DEFAULT_QUALIFY_LOCALNAME_COLOR,
128 }
129 }
130}
131
132struct ShExCompactPrinter<'a, A>
133where
134 A: Clone,
135{
136 width: usize,
137 indent: isize,
138 keyword_color: Option<Color>,
139 string_color: Option<Color>,
140 schema: &'a Schema,
141 doc: &'a Arena<'a, A>,
142 marker: PhantomData<A>,
143 prefixmap: PrefixMap,
144}
145
146const DEFAULT_WIDTH: usize = 100;
147const DEFAULT_INDENT: isize = 4;
148const DEFAULT_QUALIFY_ALIAS_COLOR: Option<Color> = Some(Color::Blue);
149const DEFAULT_QUALIFY_SEMICOLON_COLOR: Option<Color> = Some(Color::BrightGreen);
150const DEFAULT_QUALIFY_LOCALNAME_COLOR: Option<Color> = Some(Color::Black);
151const DEFAULT_KEYWORD_COLOR: Option<Color> = Some(Color::BrightBlue);
152const DEFAULT_STRING_COLOR: Option<Color> = Some(Color::Red);
153
154impl<'a, A> ShExCompactPrinter<'a, A>
155where
156 A: Clone,
157{
158 pub fn new(schema: &'a Schema, doc: &'a Arena<'a, A>) -> ShExCompactPrinter<'a, A> {
159 ShExCompactPrinter {
160 width: DEFAULT_WIDTH,
161 indent: DEFAULT_INDENT,
162 keyword_color: DEFAULT_KEYWORD_COLOR,
163 string_color: DEFAULT_STRING_COLOR,
164 schema,
165 doc,
166 marker: PhantomData,
167 prefixmap: schema
168 .prefixmap()
169 .unwrap_or_default()
170 .with_qualify_localname_color(DEFAULT_QUALIFY_LOCALNAME_COLOR)
171 .with_qualify_prefix_color(DEFAULT_QUALIFY_ALIAS_COLOR)
172 .with_qualify_semicolon_color(DEFAULT_QUALIFY_SEMICOLON_COLOR),
173 }
174 }
175
176 pub fn with_keyword_color(mut self, color: Option<Color>) -> Self {
177 self.keyword_color = color;
178 self
179 }
180
181 pub fn with_string_color(mut self, color: Option<Color>) -> Self {
182 self.string_color = color;
183 self
184 }
185
186 pub fn with_qualify_prefix_color(mut self, color: Option<Color>) -> Self {
187 self.prefixmap = self.prefixmap.with_qualify_prefix_color(color);
188 self
189 }
190
191 pub fn with_qualify_semicolon_color(mut self, color: Option<Color>) -> Self {
192 self.prefixmap = self.prefixmap.with_qualify_semicolon_color(color);
193 self
194 }
195
196 pub fn with_qualify_localname_color(mut self, color: Option<Color>) -> Self {
197 self.prefixmap = self.prefixmap.with_qualify_localname_color(color);
198 self
199 }
200
201 pub fn pretty_print(&self) -> String {
203 let doc = self.pp_schema();
204 doc.pretty(self.width).to_string()
205 }
206
207 pub fn pretty_print_write<W: io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
209 let doc = self.pp_schema();
210 doc.render(self.width, writer)
211 }
212
213 fn pp_schema(&self) -> DocBuilder<'a, Arena<'a, A>, A> {
214 self.opt_pp(self.schema.prefixmap(), self.pp_prefix_map())
215 .append(self.opt_pp(self.schema.base(), self.pp_base()))
216 .append(self.pp_imports(self.schema.imports()))
217 .append(self.opt_pp(self.schema.start_actions(), self.pp_actions()))
218 .append(self.opt_pp(self.schema.start(), self.pp_start()))
219 .append(self.opt_pp(self.schema.shapes(), self.pp_shape_decls()))
220 }
221
222 fn pp_imports(&self, imports: Vec<IriOrStr>) -> DocBuilder<'a, Arena<'a, A>, A> {
223 if imports.is_empty() {
224 self.doc.nil()
225 } else {
226 let mut docs = Vec::new();
227 for import in imports {
228 docs.push(
229 self.keyword("import")
230 .append(self.space())
231 .append(self.pp_iri_or_str(import)),
232 )
233 }
234 self.doc
235 .intersperse(docs, self.doc.hardline())
236 .append(self.doc.hardline())
237 }
238 }
239
240 fn pp_iri_or_str(&self, iri_or_str: IriOrStr) -> DocBuilder<'a, Arena<'a, A>, A> {
241 match iri_or_str {
242 IriOrStr::IriS(iri) => self.pp_iri(&iri),
243 IriOrStr::String(str) => self.pp_str(format!("<{}>", str.as_str()).as_str()),
244 }
245 }
246
247 fn pp_shape_decls(
248 &self,
249 ) -> impl Fn(&Vec<ShapeDecl>, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A>
250 {
251 move |shape_decls, printer| {
252 let mut docs = Vec::new();
253 for sd in shape_decls {
254 docs.push(printer.pp_shape_decl(sd))
255 }
256 printer.doc.intersperse(docs, printer.doc.hardline())
257 }
258 }
259
260 fn pp_shape_decl(&self, sd: &ShapeDecl) -> DocBuilder<'a, Arena<'a, A>, A> {
261 self.pp_label(&sd.id)
262 .append(self.space())
263 .append(self.pp_shape_expr(&sd.shape_expr))
264 }
265
266 fn pp_start(
267 &self,
268 ) -> impl Fn(&ShapeExpr, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
269 move |se, printer| {
270 printer
271 .keyword("start")
272 .append(printer.space())
273 .append("=")
274 .append(printer.space())
275 .append(printer.pp_shape_expr(se))
276 .append(printer.doc.hardline())
277 }
278 }
279
280 fn pp_shape_expr(&self, se: &ShapeExpr) -> DocBuilder<'a, Arena<'a, A>, A> {
281 match se {
282 ShapeExpr::Ref(ref_) => self.doc.text("@").append(self.pp_label(ref_)),
283 ShapeExpr::Shape(s) => self.pp_shape(s),
284 ShapeExpr::NodeConstraint(nc) => self.pp_node_constraint(nc),
285 ShapeExpr::External => self.pp_external(),
286 ShapeExpr::ShapeAnd { shape_exprs } => {
287 let mut docs = Vec::new();
288 for sew in shape_exprs {
289 docs.push(self.pp_shape_expr(&sew.se))
290 }
291 self.doc
292 .intersperse(docs, self.keyword(" AND "))
293 .group()
294 .nest(self.indent)
295 }
296 ShapeExpr::ShapeOr { shape_exprs } => {
297 let mut docs = Vec::new();
298 for sew in shape_exprs {
299 docs.push(self.pp_shape_expr(&sew.se))
300 }
301 self.doc
302 .intersperse(docs, self.keyword(" OR "))
303 .group()
304 .nest(self.indent)
305 }
306 ShapeExpr::ShapeNot { shape_expr } => self
307 .doc
308 .nil()
309 .append(self.keyword("NOT "))
310 .append(self.pp_shape_expr(&shape_expr.se)),
311 }
312 }
313
314 fn space(&self) -> DocBuilder<'a, Arena<'a, A>, A> {
315 self.doc.space()
316 }
317
318 fn pp_shape(&self, s: &Shape) -> DocBuilder<'a, Arena<'a, A>, A> {
319 let closed = if s.is_closed() {
320 self.keyword("CLOSED ")
321 } else {
322 self.doc.nil()
323 };
324 let extra = self.opt_pp1(&s.extra, self.pp_extra());
325 let extends = self.opt_pp1(&s.extends, self.pp_extends());
326 let annotations = self.opt_pp1(&s.annotations, self.pp_annotations());
327 closed
328 .append(extra)
329 .append(extends)
330 .append(self.doc.text("{"))
331 .append(self.doc.line())
332 .append(self.opt_pp(s.triple_expr(), self.pp_triple_expr()))
333 .nest(self.indent)
334 .append(self.doc.line())
335 .append(self.doc.text("}"))
336 .append(annotations)
337 .group()
338 }
339
340 fn pp_extends(
341 &self,
342 ) -> impl Fn(&Vec<ShapeExprLabel>, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A>
343 {
344 move |vs, printer| {
345 let mut docs = Vec::new();
346 for v in vs {
347 docs.push(printer.pp_reference(v))
348 }
349 printer
350 .doc
351 .nil()
352 .append(printer.keyword("EXTENDS"))
353 .append(printer.space())
354 .append(printer.doc.intersperse(docs, printer.doc.space()))
355 .append(printer.space())
356 }
357 }
358
359 fn pp_annotations(
360 &self,
361 ) -> impl Fn(&Vec<Annotation>, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A>
362 {
363 move |vs, printer| {
364 let mut docs = Vec::new();
365 for a in vs {
366 docs.push(printer.pp_annotation(a))
367 }
368 printer
369 .doc
370 .nil()
371 .append(printer.space())
372 .append(printer.doc.intersperse(docs, printer.doc.softline()))
373 }
374 }
375
376 fn pp_triple_expr(
377 &self,
378 ) -> impl Fn(&TripleExpr, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> + '_
379 {
380 move |te, printer| match te {
381 TripleExpr::EachOf { expressions, .. } => {
382 let mut docs = Vec::new();
383 for e in expressions {
384 let pp_te = printer.pp_triple_expr()(&e.te, printer);
385 docs.push(pp_te)
386 }
387 printer
388 .doc
389 .intersperse(docs, printer.doc.text(";").append(printer.doc.line()))
390 }
391 TripleExpr::OneOf { .. } => todo!(),
392 TripleExpr::TripleConstraint {
393 negated,
394 inverse,
395 predicate,
396 value_expr,
397 min,
398 max,
399 sem_acts,
400 annotations,
401 ..
402 } => {
403 let doc_expr = match value_expr {
404 Some(se) => printer.pp_shape_expr(se),
405 None => printer.doc.text("."),
406 };
407 printer
408 .doc
409 .nil()
410 .append(self.pp_negated(negated))
411 .append(self.pp_inverse(inverse))
412 .append(self.pp_iri_ref(predicate))
413 .append(self.doc.space())
414 .append(doc_expr)
415 .append(self.pp_cardinality(min, max))
416 .append(self.opt_pp1(sem_acts, self.pp_actions()))
417 .append(self.opt_pp1(annotations, self.pp_annotations()))
418 }
419 TripleExpr::TripleExprRef(_) => todo!(),
420 }
421 }
422
423 fn pp_negated(&self, negated: &Option<bool>) -> DocBuilder<'a, Arena<'a, A>, A> {
426 match negated {
427 Some(true) => self.doc.text("!"),
428 _ => self.doc.nil(),
429 }
430 }
431
432 fn pp_inverse(&self, inverse: &Option<bool>) -> DocBuilder<'a, Arena<'a, A>, A> {
433 match inverse {
434 Some(true) => self.doc.text("^"),
435 _ => self.doc.nil(),
436 }
437 }
438
439 fn pp_cardinality(
440 &self,
441 min: &Option<i32>,
442 max: &Option<i32>,
443 ) -> DocBuilder<'a, Arena<'a, A>, A> {
444 match (min, max) {
445 (Some(1), Some(1)) => self.doc.nil(),
446 (Some(0), Some(1)) => self.doc.space().append(self.doc.text("?")),
447 (Some(0), Some(-1)) => self.doc.space().append(self.doc.text("*")),
448 (Some(1), Some(-1)) => self.doc.space().append(self.doc.text("+")),
449 (Some(1), None) => self.doc.space().append(self.doc.text("+")),
450 (Some(m), Some(n)) => self.doc.space().append(
451 self.enclose_space(
452 "{",
453 self.doc
454 .text(m.to_string())
455 .append(self.doc.text(","))
456 .append(self.doc.text(n.to_string())),
457 "}",
458 ),
459 ),
460 (Some(m), None) => self
461 .doc
462 .space()
463 .append(self.doc.text("{"))
464 .append(self.doc.text(m.to_string()))
465 .append(self.doc.text(",}")),
466 (None, Some(-1)) => self.doc.space().append(self.doc.text("*")),
467 (None, Some(n)) => self
468 .doc
469 .space()
470 .append(self.doc.text("{,"))
471 .append(self.doc.text(n.to_string()))
472 .append(self.doc.text("}")),
473 (None, None) => self.doc.nil(),
474 }
475 }
476
477 fn pp_extra(
478 &self,
479 ) -> impl Fn(&Vec<IriRef>, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
480 move |es, printer| {
481 let mut docs = Vec::new();
482 for e in es {
483 docs.push(printer.pp_iri_ref(e))
484 }
485 printer
486 .doc
487 .nil()
488 .append(printer.keyword("EXTRA "))
489 .append(printer.doc.intersperse(docs, printer.doc.space()))
490 }
491 }
492
493 fn pp_annotation(&self, annotation: &Annotation) -> DocBuilder<'a, Arena<'a, A>, A> {
494 let predicate = self.pp_iri_ref(&annotation.predicate());
495 let object = self.pp_object_value(&annotation.object());
496 self.keyword("//")
497 .append(self.space())
498 .append(predicate)
499 .append(self.space())
500 .append(object)
501 }
502
503 fn pp_object_value(&self, object_value: &ObjectValue) -> DocBuilder<'a, Arena<'a, A>, A> {
504 match object_value {
505 ObjectValue::IriRef(iri_ref) => self.pp_iri_ref(iri_ref),
506 ObjectValue::Literal(lit) => self.pp_literal(lit),
507 }
508 }
509
510 fn pp_literal(&self, literal: &Literal) -> DocBuilder<'a, Arena<'a, A>, A> {
511 match literal {
512 Literal::StringLiteral { lexical_form, lang } => {
513 self.pp_string_literal(lexical_form, lang)
514 }
515 Literal::DatatypeLiteral {
516 lexical_form: _,
517 datatype: _,
518 } => todo!(),
519 Literal::NumericLiteral(lit) => self.pp_numeric_literal(lit),
520 Literal::BooleanLiteral(_) => todo!(),
521 }
522 }
523
524 fn pp_string_literal(
525 &self,
526 lexical_form: &str,
527 lang: &Option<Lang>,
528 ) -> DocBuilder<'a, Arena<'a, A>, A> {
529 match lang {
530 Some(_) => todo!(),
531 None => self.pp_string(lexical_form),
532 }
533 }
534
535 fn pp_string(&self, str: &str) -> DocBuilder<'a, Arena<'a, A>, A> {
536 let s = format!("\"{str}\"");
537 if let Some(color) = self.string_color {
538 self.doc.text(s.as_str().color(color).to_string())
539 } else {
540 self.doc.text(s)
541 }
542 }
543
544 fn pp_node_constraint(&self, nc: &NodeConstraint) -> DocBuilder<'a, Arena<'a, A>, A> {
545 self.opt_pp(nc.node_kind(), self.pp_node_kind())
546 .append(self.opt_pp(nc.datatype(), self.pp_datatype()))
547 .append(self.opt_pp(nc.values(), self.pp_value_set()))
548 .append(self.opt_pp(nc.xs_facet(), self.pp_xsfacets()))
549 }
550
551 fn pp_node_kind(
552 &self,
553 ) -> impl Fn(&NodeKind, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
554 move |nk, printer| match nk {
555 NodeKind::Iri => printer.keyword("IRI"),
556 NodeKind::BNode => printer.keyword("BNODE"),
557 NodeKind::NonLiteral => printer.keyword("NONLITERAL"),
558 NodeKind::Literal => printer.keyword("LITERAL"),
559 }
560 }
561
562 fn pp_value_set(
563 &self,
564 ) -> impl Fn(&Vec<ValueSetValue>, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A>
565 {
566 move |values, printer| {
567 let mut docs = Vec::new();
568 for v in values {
569 docs.push(printer.pp_value_set_value(v))
570 }
571 printer.doc.space().append(printer.enclose_space(
572 "[",
573 printer.doc.intersperse(docs, printer.doc.space()),
574 "]",
575 ))
576 }
577 }
578
579 fn pp_xsfacets(
580 &self,
581 ) -> impl Fn(&Vec<XsFacet>, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
582 move |xsfacets, printer| {
583 let mut docs = Vec::new();
584 for v in xsfacets {
585 docs.push(printer.pp_xsfacet(v))
586 }
587 printer
588 .doc
589 .space()
590 .append(printer.doc.intersperse(docs, printer.doc.space()))
591 }
592 }
593
594 fn pp_xsfacet(&self, xsfacet: &XsFacet) -> DocBuilder<'a, Arena<'a, A>, A> {
595 match xsfacet {
596 XsFacet::NumericFacet(nf) => self.pp_numericfacet(nf),
597 XsFacet::StringFacet(sf) => self.pp_stringfacet(sf),
598 }
599 }
600
601 fn pp_numericfacet(&self, nf: &NumericFacet) -> DocBuilder<'a, Arena<'a, A>, A> {
602 match nf {
603 NumericFacet::FractionDigits(fd) => self
604 .keyword("FractionDigits")
605 .append(self.space())
606 .append(self.pp_usize(fd)),
607 NumericFacet::TotalDigits(td) => self
608 .keyword("TotalDigits")
609 .append(self.space())
610 .append(self.pp_usize(td)),
611 NumericFacet::MinInclusive(m) => self
612 .keyword("MinInclusive")
613 .append(self.space())
614 .append(self.pp_numeric_literal(m)),
615 NumericFacet::MaxInclusive(m) => self
616 .keyword("MaxInclusive")
617 .append(self.space())
618 .append(self.pp_numeric_literal(m)),
619 NumericFacet::MinExclusive(m) => self
620 .keyword("MinExclusive")
621 .append(self.space())
622 .append(self.pp_numeric_literal(m)),
623 NumericFacet::MaxExclusive(m) => self
624 .keyword("MaxExclusive")
625 .append(self.space())
626 .append(self.pp_numeric_literal(m)),
627 }
628 }
629
630 fn pp_stringfacet(&self, sf: &StringFacet) -> DocBuilder<'a, Arena<'a, A>, A> {
631 match sf {
632 StringFacet::Length(l) => self
633 .keyword("Length")
634 .append(self.space())
635 .append(self.pp_usize(l)),
636 StringFacet::MinLength(l) => self
637 .keyword("MinLength")
638 .append(self.space())
639 .append(self.pp_usize(l)),
640 StringFacet::MaxLength(l) => self
641 .keyword("MaxLength")
642 .append(self.space())
643 .append(self.pp_usize(l)),
644 StringFacet::Pattern(pat) => self.pp_pattern(pat),
645 }
646 }
647
648 fn pp_pattern(&self, pattern: &Pattern) -> DocBuilder<'a, Arena<'a, A>, A> {
649 let flags = match &pattern.flags {
650 Some(flags) => flags.clone(),
651 None => "".to_string(),
652 };
653 let str = format!("/{}/{}", pattern.str, flags);
654 self.doc.text(str)
655 }
656
657 fn pp_datatype(
658 &self,
659 ) -> impl Fn(&IriRef, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
660 move |dt, printer| printer.pp_iri_ref(dt)
661 }
662
663 fn pp_external(&self) -> DocBuilder<'a, Arena<'a, A>, A> {
664 self.keyword("EXTERNAL")
665 }
666
667 fn pp_value_set_value(&self, v: &ValueSetValue) -> DocBuilder<'a, Arena<'a, A>, A> {
668 match v {
669 ValueSetValue::LanguageStem { .. } => todo!(),
670 ValueSetValue::LanguageStemRange { .. } => todo!(),
671 ValueSetValue::ObjectValue(ov) => pp_object_value(ov, self.doc, &self.prefixmap),
672 ValueSetValue::IriStem { .. } => todo!(),
673 ValueSetValue::IriStemRange { .. } => todo!(),
674 ValueSetValue::LiteralStem { .. } => todo!(),
675 ValueSetValue::LiteralStemRange { .. } => todo!(),
676 ValueSetValue::Language { .. } => todo!(),
677 }
678 }
679
680 fn pp_label(&self, ref_: &ShapeExprLabel) -> DocBuilder<'a, Arena<'a, A>, A> {
681 match ref_ {
682 ShapeExprLabel::BNode { value } => self.pp_bnode(value),
683 ShapeExprLabel::IriRef { value } => self.pp_iri_ref(value),
684 ShapeExprLabel::Start => self.keyword("START"),
685 }
686 }
687
688 fn pp_reference(&self, ref_: &ShapeExprLabel) -> DocBuilder<'a, Arena<'a, A>, A> {
689 self.doc.text("@").append(self.pp_label(ref_))
690 }
691
692 fn pp_numeric_literal(&self, value: &NumericLiteral) -> DocBuilder<'a, Arena<'a, A>, A> {
693 match value {
694 NumericLiteral::Integer(n) => self.pp_isize(n),
695 NumericLiteral::Decimal(d) => self.pp_decimal(d),
696 NumericLiteral::Double(d) => self.pp_double(d), }
698 }
699
700 fn pp_bnode(&self, value: &BNode) -> DocBuilder<'a, Arena<'a, A>, A> {
701 self.doc.text(format!("{value}"))
702 }
703
704 fn pp_isize(&self, value: &isize) -> DocBuilder<'a, Arena<'a, A>, A> {
705 self.doc.text(value.to_string())
706 }
707
708 fn pp_decimal(&self, value: &Decimal) -> DocBuilder<'a, Arena<'a, A>, A> {
709 self.doc.text(value.to_string())
710 }
711
712 fn pp_double(&self, value: &f64) -> DocBuilder<'a, Arena<'a, A>, A> {
713 self.doc.text(value.to_string())
714 }
715
716 fn pp_usize(&self, value: &usize) -> DocBuilder<'a, Arena<'a, A>, A> {
717 self.doc.text(value.to_string())
718 }
719
720 fn pp_iri_ref(&self, value: &IriRef) -> DocBuilder<'a, Arena<'a, A>, A> {
721 match value {
722 IriRef::Iri(iri) => self.pp_iri(iri),
723 IriRef::Prefixed { prefix, local } => self
724 .doc
725 .text(prefix.clone())
726 .append(self.doc.text(":"))
727 .append(self.doc.text(local.clone())),
728 }
729 }
730
731 fn keyword<U>(&self, s: U) -> DocBuilder<'a, Arena<'a, A>, A>
732 where
733 U: Into<Cow<'a, str>>,
734 {
735 if let Some(color) = self.keyword_color {
736 let data: Cow<str> = s.into();
737 let s: String = match data {
738 Cow::Owned(t) => t,
739 Cow::Borrowed(t) => t.into(),
740 };
741 self.doc.text(s.as_str().color(color).to_string())
742 } else {
743 let s: String = s.into().into();
744 self.doc.text(s)
745 }
746 }
747
748 fn pp_actions(
749 &self,
750 ) -> impl Fn(&Vec<SemAct>, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
751 move |actions, printer| {
752 let mut docs = Vec::new();
753 for a in actions {
754 docs.push(printer.pp_action(a))
755 }
756 printer
757 .doc
758 .intersperse(docs, printer.doc.hardline())
759 .append(printer.doc.hardline())
760 }
761 }
762
763 fn pp_action(&self, a: &SemAct) -> DocBuilder<'a, Arena<'a, A>, A> {
764 self.doc
765 .text("%")
766 .append(self.pp_iri_ref(&a.name()))
767 .append(match a.code() {
768 None => self.doc.text("%"),
769 Some(str) => self
770 .doc
771 .text("{")
772 .append(self.doc.text(str))
773 .append(self.doc.text("%}")),
774 })
775 }
776
777 fn pp_base(
778 &self,
779 ) -> impl Fn(&IriS, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
780 move |base, printer| {
781 printer
782 .keyword("base")
783 .append(printer.doc.space())
784 .append(printer.pp_iri_unqualified(base))
785 .append(printer.doc.hardline())
786 }
787 }
788
789 fn pp_prefix_map(
790 &self,
791 ) -> impl Fn(&PrefixMap, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> {
792 move |pm, printer| {
793 let mut pms: Vec<DocBuilder<'a, Arena<'a, A>, A>> = Vec::new();
794 for (alias, iri) in pm.map.clone().into_iter() {
795 pms.push(
796 printer
797 .doc
798 .nil()
799 .append(printer.keyword("prefix"))
800 .append(printer.doc.space())
801 .append(printer.doc.text(alias))
802 .append(printer.doc.text(":"))
803 .append(printer.doc.space())
804 .append(printer.pp_iri_unqualified(&iri)),
805 )
806 }
807 printer
808 .doc
809 .intersperse(pms, printer.doc.hardline())
810 .append(printer.doc.hardline())
811 }
812 }
813
814 fn pp_iri_unqualified(&self, iri: &IriS) -> DocBuilder<'a, Arena<'a, A>, A> {
815 let str = format!("<{iri}>");
816 self.doc.text(str)
817 }
818
819 fn pp_iri(&self, iri: &IriS) -> DocBuilder<'a, Arena<'a, A>, A> {
820 self.doc.text(self.prefixmap.qualify(iri))
821 }
822
823 fn pp_str(&self, str: &str) -> DocBuilder<'a, Arena<'a, A>, A> {
824 self.doc.text(str.to_string())
825 }
826
827 fn opt_pp<V>(
828 &self,
829 maybe: Option<V>,
830 pp: impl Fn(&V, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A>,
831 ) -> DocBuilder<'a, Arena<'a, A>, A> {
832 match maybe {
833 None => self.doc.nil(),
834 Some(ref v) => pp(v, self),
835 }
836 }
837
838 fn opt_pp1<V>(
839 &self,
840 maybe: &Option<V>,
841 pp: impl Fn(&V, &ShExCompactPrinter<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A>,
842 ) -> DocBuilder<'a, Arena<'a, A>, A> {
843 match maybe {
844 None => self.doc.nil(),
845 Some(ref v) => pp(v, self),
846 }
847 }
848
849 fn is_empty(&self, d: &DocBuilder<'a, Arena<'a, A>, A>) -> bool {
850 use pretty::Doc::*;
851 match &**d {
852 Nil => true,
853 FlatAlt(t1, t2) => Self::is_empty_ref(t1) && Self::is_empty_ref(t2),
854 Group(t) => Self::is_empty_ref(t),
855 Nest(_, t) => Self::is_empty_ref(t),
856 Union(t1, t2) => Self::is_empty_ref(t1) && Self::is_empty_ref(t2),
857 Annotated(_, t) => Self::is_empty_ref(t),
858 _ => false,
859 }
860 }
861
862 fn is_empty_ref(rd: &RefDoc<'a, A>) -> bool {
863 use pretty::Doc::*;
864 match &**rd {
865 Nil => true,
866 FlatAlt(t1, t2) => Self::is_empty_ref(t1) && Self::is_empty_ref(t2),
867 Group(t) => Self::is_empty_ref(t),
868 Nest(_, t) => Self::is_empty_ref(t),
869 Union(t1, t2) => Self::is_empty_ref(t1) && Self::is_empty_ref(t2),
870 Annotated(_, t) => Self::is_empty_ref(t),
871 _ => false,
872 }
873 }
874
875 pub fn enclose_space(
876 &self,
877 left: &'a str,
878 middle: DocBuilder<'a, Arena<'a, A>, A>,
879 right: &'a str,
880 ) -> DocBuilder<'a, Arena<'a, A>, A> {
881 if self.is_empty(&middle) {
882 self.doc.text(left).append(right)
883 } else {
884 self.doc
885 .text(left)
886 .append(self.doc.line())
887 .append(middle)
888 .nest(self.indent)
889 .append(self.doc.line())
890 .append(right)
891 .group()
892 }
893 }
894}
895
896#[cfg(test)]
897mod tests {
898 use iri_s::IriS;
899 use prefixmap::PrefixMap;
900
901 use super::*;
902
903 #[test]
904 fn empty_schema() {
905 let mut pm = PrefixMap::new();
906 pm.insert("", &IriS::new_unchecked("http://example.org/"))
907 .unwrap();
908 pm.insert("schema", &IriS::new_unchecked("https://schema.org/"))
909 .unwrap();
910 let schema = Schema::new().with_prefixmap(Some(pm));
911 let s = ShExFormatter::default()
912 .without_colors()
913 .format_schema(&schema);
914 assert_eq!(
915 s,
916 "prefix : <http://example.org/>\nprefix schema: <https://schema.org/>\n"
917 );
918 }
919}