shex_compact/
shex_compact_printer.rs

1use colored::*;
2use iri_s::IriS;
3use prefixmap::{IriRef, PrefixMap};
4use pretty::{Arena, DocAllocator, DocBuilder, RefDoc};
5use rust_decimal::Decimal;
6/// This file converts ShEx AST to ShEx compact syntax
7use 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/// Struct that can be used to pretty print ShEx schemas
18///
19/// Example:
20/// ```
21/// use shex_compact::ShExFormatter;
22/// use shex_ast::{Schema, ShapeExprLabel, ShapeExpr};
23/// use iri_s::IriS;
24///
25/// let mut schema = Schema::new();
26/// schema.add_prefix("ex", &IriS::new_unchecked("http://example.org/"));
27/// schema.add_shape(ShapeExprLabel::iri_unchecked("http://example.org/S"), ShapeExpr::empty_shape(), false);
28///
29/// let expected = r#"prefix ex: <http://example.org/>
30/// ex:S {  }"#;
31///
32/// assert_eq!(ShExFormatter::default().format_schema(&schema), expected);
33/// ```
34#[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    /// Changes the formatter to avoid showing colors
85    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    /// Pretty print to a String
202    pub fn pretty_print(&self) -> String {
203        let doc = self.pp_schema();
204        doc.pretty(self.width).to_string()
205    }
206
207    /// Writes a ShEx schema to a `std::io::Write` object
208    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    // type DB<'a, A> = DocBuilder<'a, Arena<'a, A>, A>;
424
425    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), // TODO: Review
697        }
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}