kdl icon indicating copy to clipboard operation
kdl copied to clipboard

XSLT-style templating spec

Open zkat opened this issue 11 months ago • 10 comments

We already have the equivalent of JSON Schema/XSD in the KDL Schema spec, but we don't have something that would map to XSLT--that is, a templating language for using KDL to transform input KDL into other KDL documents--or even non-kdl documents?

I'm not really sure how big a task this would be. I've never used XSLT in anger but I imagine there's quite a bit to it.

zkat avatar Dec 11 '24 19:12 zkat

With the disclaimer that I have never used XSLTs...

A big pain point we have in Zellij is the repetition created in our layouts. We have pane_templates and tab_templates etc. but they mostly have to live inside the same file.

A long standing TODO I have is to find some sort of mechanism to solve this but I've mostly been putting this off because I don't want to deal with the usual dependency problems. I think these sorts of capabilities (especially if they'll be built in to the existing SDKs) can take away a lot of this work from me.

imsnif avatar Dec 11 '24 21:12 imsnif

@imsnif so, as far as I can tell/imagine, KDL templating would be a lot like your pane_template and tab_template, except it would be "official" and be a little more powerful: it would let you, for example, use KQL selectors to do surgical insertions into more complex templates, for example, rather than limiting you to children. And KQL selectors are about as powerful as CSS selectors (modulo some of the more advanced special CSS selectors, but we should probably add those eventually too). They're mostly the same syntax, too.

And yeah, it would be easy enough to put them in separate files that you pass directly to kdl-rs, and it'll apply the templates to a particular document parse for you, transforming things as needed. And, I would hope, giving you informative errors.

For example:

// my-templates.kdl
$:template match=vertical-sandwich { // KQL selector
    pane split_direction=vertical {
        $:select "[pos = above]"
        $:select "[pos != above][pos != below]"
        $:select "[pos = below]"
    }
}

$:template match=cmd {
    pane command="{$:join $:args ,}"
}

$:template match=watch {
    cmd cargo watch -x $:args
}
// layout.kdl

// This isn't magic. Zellij itself would be responsible for
// loading these files and passing them to kdl-rs
@include "./my-templates.kdl"

vertical-sandwich {
    cmd pos=above fancy-line --color red --width 10
    cmd pos=below htop

    pane split_direction=horizontal {
        watch test
        watch check
    }
}

We could, of course, go further. We could have a whole module system of imports/exports where you can distribute "libraries" of templates and everything. You could also get a lot fancier with how you select arguments and values and interpolate them around. Here's a fun one:

$:template match=group {
    $:select "[]" {
        $:apply $:entries
    }
}

group pos=above {
  cmd foo
  cmd bar
  cmd baz
}
// Expands to:
cmd foo pos=above
cmd bar pos=above
cmd baz pos=above

how does that sound so far?

zkat avatar Dec 11 '24 23:12 zkat

A XSLT-like styling language would, by default, recursively process all nodes (which if empty, would yield nothing) .

So the default behavior of an XSLT processor on:

<foo>
  text1
  <bar>text 2</bar>
 <baz/>
</foo>

would be

  text1
  text2

See: https://www.w3.org/TR/1999/REC-xslt-19991116#built-in-rule

The pattern in XSLT is to define templates for the elements you want to transform (via custom output)

So if your code above would take a KML file, like the one in your example:

group pos=above {
  cmd foo
  cmd bar
  cmd baz
}

and produce, by default

foo
bar
baz 

and, assuming that cmd is contextually the same as an element, then you could extend the implementation by allowing the user to specify templates which match nodes: ie group[pos='above'] in an XPath-y syntax, yielding the expected output.

emceeaich avatar Dec 11 '24 23:12 emceeaich

@emceeaich In my examples, above, the definition for cmd was given:

$:template match=cmd {
    pane command="{$:join $:args ,}"
}

so the final expansion would be:

pane command=foo pos=above
pane command=bar pos=above
pane command=baz pos=above

the pos properties are applied by group itself (with the $:apply node)

zkat avatar Dec 11 '24 23:12 zkat

That makes sense, my example was me thinking in the default behavior of XSLT.

The default behavior in XSLT is to recursively process the tree, applying the default template, unless the stylesheet contains templates that match some expression in the document, if it exists.

emceeaich avatar Dec 12 '24 00:12 emceeaich

yeah. I think in my mind, since KDL isn't a markup language that's intended to be a layer on top of plain text, it makes more sense for us to be more explicit about how the data is passed around and applied. And it'll give us nicer errors to boot, imo.

zkat avatar Dec 12 '24 00:12 zkat

I guess the catch with this is that you couldn't, for example, use this templating system to generate non-KDL templates. In a way, this is more a "function definition" system than a "templating" system.

zkat avatar Dec 12 '24 00:12 zkat

Yes, setting the scope of what you expect it to do is the right thing. I do hope you find some of the ideas in XSLT useful.

emceeaich avatar Dec 12 '24 00:12 emceeaich

Hey @zkat - thanks for the detailed run-down. This indeed looks really cool. I initially thought this might help with the "magic" part you were referring to in the comments (that's the big pain point in this area as I'm not enthusiastic about implementing dependency resolution and all that is involved there).

Would be cool to also have these features though, but one thing at a time :) I still think this is a super useful addition to KDL.

imsnif avatar Dec 13 '24 12:12 imsnif

Yeah I think we could provide some helpers around “including” templates

zkat avatar Dec 14 '24 05:12 zkat