How do I dynamically add content from a dictionary defined in a template?

I am using typst for my homework and notes for university. For this I need to reference theorems, lemmas, etc. from the lectures. I want to add a section to the end of my note where all the lecture references, used in that note, are listed and the parts of my note, that refer to these entries, also link to the respective headings.
To avoid always retyping the theorems, I frequently use on different documents, I tried to create a template in which I collected all the lecture references and I dynamically add the parts which I need to the document.

My file template.typ looks like this:

#let lecture_refs = (
  def1_1: (
    lecture: 1,
    index: 1,
    type: "Definition",
    content: [This is my first definition.],
  ),
  the1_2: (
    lecture: 1,
    index: 2,
    type: "Theorem",
    content: [This is the second entry but its a theorem.],
  ),
  the2_1: (
    lecture: 2,
    index: 1,
    type: "Theorem",
    content: [This theorem is relevant but I won't need it for the notes.],
  ),
  def2_2: (
    lecture: 2,
    index: 2,
    type: "Definition",
    content: [Let's define some more items.],
  ),
  lem3_1: (
    lecture: 3,
    index: 1,
    type: "Lemma",
    content: [Now we add a lemma for a change.],
  ),
)

#let lecture_ref(def) = context {
  let state = state("used_literature_refs")
  let array = state.get()
  if array == none {
    state.update((def,))
  }
  else if not array.contains(def) {
    state.update((..array,def))
  }
  ref(label(def))
}

#let used_lecture_refs() = context {
  let used = state("used_literature_refs").final()
  if (used != none and used.len() > 0) {
    text[= Lecture References]
    set heading(numbering: "1.1")
    for (key, value) in lecture_refs {
      if key in used {
        counter(heading).update((value.lecture, value.index - 1))
        text[
          #heading(depth: 2, supplement: value.type)[#value.type]#label(key)
          #value.content
        ]
      }
    }
  }
}

#let template(doc) = {
  doc
  used_lecture_refs()
}

Using this template I am able to render note.typ with the following content:

#import "template.typ": template, lecture_ref

#show: template

= Homework

In my homework I need to reference some content from the lectures like #lecture_ref("def1_1"), #lecture_ref("the1_2"), #lecture_ref("def2_2")

Unused lecture references should not appear in the document

But when I add another reference typst fails to render the document.

My modified note.typ looks like this

#import "template.typ": template, lecture_ref

#show: template

= Homework

In my homework I need to reference some content from the lectures like #lecture_ref("def1_1"), #lecture_ref("the1_2"), #lecture_ref("def2_2"), #lecture_ref("lem3_1")

Unused lecture references should not appear in the document

I get the error warning: layout did not converge within 5 attempts. I assume it’s due to the scope I am using to keep track of the used IDs.

I am not sure how to fix this or what I am doing wrong when modifying the state.

I don’t know if this is enough to resolve but this is a place where get and update can be combined into a single update and in that case it should be combined. As in state.update(old_array => compute_new(old_array))

Hello. You are stepping into the same trap as How to keep exercise and solution together in source, but render them separately? - #3 by Andrew and Why is my random shuffle always producing the same result when using Suiji? - #9 by Andrew.

#let ref-type = (definition: "Definition", theorem: "Theorem", lemma: "Lemma")
#let lecture-refs = (
  def1-1: (
    lecture: 1,
    index: 1,
    type: ref-type.definition,
    content: [This is my first definition.],
  ),
  the1-2: (
    lecture: 1,
    index: 2,
    type: ref-type.theorem,
    content: [This is the second entry but its a theorem.],
  ),
  the2-1: (
    lecture: 2,
    index: 1,
    type: ref-type.theorem,
    content: [This theorem is relevant but I won't need it for the notes.],
  ),
  def2-2: (
    lecture: 2,
    index: 2,
    type: ref-type.definition,
    content: [Let's define some more items.],
  ),
  lem3-1: (
    lecture: 3,
    index: 1,
    type: ref-type.lemma,
    content: [Now we add a lemma for a change.],
  ),
)

#let lecture-ref(def) = context {
  let state = state("used-literature-refs", ())
  let array = state.get()
  if not array.contains(def) {
    state.update(array => array + (def,))
  }
  ref(label(def))
}

#let used-lecture-refs() = context {
  let used = state("used-literature-refs").final()
  if used.len() == 0 { return }
  [= Lecture References]
  set heading(numbering: "1.1")
  for (key, value) in used.map(key => (key, lecture-refs.at(key))) {
    counter(heading).update((value.lecture, value.index - 1))
    [#heading(depth: 2, supplement: value.type)[#value.type]#label(key)]
    value.content
  }
}

#let template(doc) = {
  doc
  used-lecture-refs()
}

#show: template

= Homework
In my homework I need to reference some content from the lectures like
#lecture-ref("def1-1"), #lecture-ref("the1-2"), #lecture-ref("def2-2"),
#lecture-ref("lem3-1")

Thank you very much! This fixed my problem.

1 Like