Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add schema for existing typst dictionaries #45

Open
TimeTravelPenguin opened this issue Aug 17, 2024 · 7 comments
Open

Add schema for existing typst dictionaries #45

TimeTravelPenguin opened this issue Aug 17, 2024 · 7 comments

Comments

@TimeTravelPenguin
Copy link
Contributor

TimeTravelPenguin commented Aug 17, 2024

I want to write configurations that accept inset and outset. I am certain there is some docs on what these are, but I cannot seem to find them.

I eventually dived into the source to see that what I effectively want is something like

#let inset = z.dictionary((
	left: z.optional(...),
	right: z.optional(...),
	top: z.optional(...),
	bottom: z.optional(...),
	x: z.optional(...),
	y: z.optional(...),
	rest: z.optional(...),
))

It would be very helpful for template and package developers to have access to basic known dictionaries, where "dictionary" effectively has its own schema known to Typst.

Edit: The main difficulty is that the actual values (the ...) are generic in the source, and vary in each use case. You could set it to z.Any(), but that would not be a great idea.

@TimeTravelPenguin
Copy link
Contributor Author

Rethinking this, I think it would probably be a lot easier to instead have Typst type argument validators instead.

E.g., to validate the args to a Box vs a Block. That would be a fair amount of work though. I would be interested in your opinion of both of these. Perhaps later in the year when I am off university, I can make a pull request with this second idea, should you not have the time or interest. Let me know and I can make a new feature request.

@tingerrr
Copy link
Member

tingerrr commented Aug 17, 2024

When trying to answer this, I ran into a few issues with the built-in schemas, mainly that z.either(z.base-type(name: "auto", types: (type(auto),)), z.relative()) does not accept auto as an input.

I want to preface this with, if you pass this down to block or box, don't validate it, let Typst do that, your API stays consistent without additional maintenance and is less likely to be overly restrictive.

With that said, here's how you can write it today:

#import "@preview/valkyrie:0.2.1" as z

#let func(inset: (:)) = {
  // see the above mentioned issue, the same applies for `z.either(optional: true, ...)` when one of the inner schemas doesn't allow `none`
  let segment = z.base-type.with(
    default: none,
    optional: true,
    name: "relative",
    types: (relative, length, ratio, type(none)),
  )
  let schema = z.dictionary(
    // if it's a single value turn it into a dictionary
    pre-transform: (ctx, it) => if type(it) != dictionary { (rest: it) } else { it },
    // remove all `none` values they are just sentinels
    // could also implement the fallback from `top -> y -> rest` and `left -> x -> rest` here
    post-transform: (ctx, it) => it.pairs().fold((:), (acc, (k, v)) => acc + if v != none { ((k): v) }),
    (
      left: segment(),
      right: segment(),
      top: segment(),
      bottom: segment(),
      x: segment(),
      y: segment(),
      rest: segment(),
    )
  )

  let parsed = z.parse(inset, schema)
  block(inset: parsed, stroke: red, height: 50pt, width: 50pt, [content])
}

#func()
#func(inset: 1pt + 10%)
#func(inset: (x: 20pt, rest: 10pt))

One major downside is, that this accepts (x: none) and simply discards it, unlike the built-in validation. I think we really need to handle the absence of values in a parent schema different to the value being none. This is a problem in many languages that do this, but it's hard to do when both sentinel values we have are valid field values, perhaps we should add a special absent value like metadata("__valkyrie-absent").

cc: @JamesxX

@TimeTravelPenguin
Copy link
Contributor Author

@tingerrr Thanks for your reply.

Your approach is thankfully very similar to my own. My reason for this idea is that I am working on a package that applies a theme to the document. I want to allow the user to provide certain configurations, so that I can extend my package into a more fully-featured template, along with provided defaults that style other elements like tables, code blocks, and so forth.

I want to validate to allow for configurations to be explicitly verified sooner, rather than later so that the user can be informed of any errors, rather than needing to chance down any issues that aren't nicely reported (which can happen if they do their own styling on top!).

One major downside is, that this accepts (x: none) and simply discards it, unlike the built-in validation.

Yeah, I have experienced this issue when implementing my own validation in the past. Having the ability to not only set a value as optional but also allow it to remain absent would be a very powerful addition to the package.

@tingerrr
Copy link
Member

tingerrr commented Aug 20, 2024

Yeah, as noted above, we might need to fundamentally change how valkyrie handles absent values. I personally think we should just leave them absent when declared as optional as they are currently filled with none unless another default is specified.

Specifically, optional should not be of importance for the schema it's used in itself, but only for compound schemas further above.

#import "@preview/valkyrie:0.2.1" as z

#let schema = z.dictionary((
  foo: z.stroke(optional: true),
))

#z.parse((:), schema) // returns (foo: none)

Here stroke.optional should only be used by the dictionary schema, this also brings the benefit of making other compound schemas more sensible. We currently allow the middle element of a triple tuple to be optional, which is sort of nonsensical if the schemas overlap. With a change like this, the tuple schema could allow only trailing optional elements.

@TimeTravelPenguin
Copy link
Contributor Author

I personally think we should just leave them absent when declared as optional as they are currently filled with none unless another default is specified.

That sounds pretty reasonable. It is how I originally interpreted the docs when learning.

I feel that things will become much less rigid!

@jamesrswift
Copy link
Member

I don't think it would be a difficult thing to implement but I'm currently focusing on another project that I'll need to typeset my thesis so I won't have time to contribute here - very open to PRs

@TimeTravelPenguin
Copy link
Contributor Author

Oop, I forgot to reply to this!

I would be more than happy to open a PR, but I will have to wait until after my uni semester has ended. I have a few projects I want to work on when I have free time, so my time is a bit limited, too.

If this isn't closed before then, I will take a look after the semester, if I am able :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants