From 188e64fa301475033938310d18319c1fd2380bf2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 20 Mar 2023 14:50:35 +0100 Subject: [PATCH] Documentation for counters --- docs/src/lib.rs | 27 +++-- library/src/meta/counter.rs | 231 +++++++++++++++++++++++++++++++++++- 2 files changed, 247 insertions(+), 11 deletions(-) diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 97535b1a2..491695757 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -303,6 +303,7 @@ pub struct FuncModel { pub details: Html, pub params: Vec, pub returns: Vec<&'static str>, + pub methods: Vec, } /// Details about a group of functions. @@ -332,14 +333,17 @@ fn function_page( /// Produce a function's model. fn func_model(resolver: &dyn Resolver, func: &Func, info: &FuncInfo) -> FuncModel { + let mut s = unscanny::Scanner::new(info.docs); + let docs = s.eat_until("\n## Methods").trim(); FuncModel { name: info.name.into(), display: info.display, - oneliner: oneliner(info.docs), + oneliner: oneliner(docs), showable: func.element().is_some(), - details: Html::markdown(resolver, info.docs), + details: Html::markdown(resolver, docs), params: info.params.iter().map(|param| param_model(resolver, param)).collect(), returns: info.returns.clone(), + methods: method_models(resolver, info.docs), } } @@ -501,7 +505,18 @@ fn type_model(resolver: &dyn Resolver, part: &'static str) -> TypeModel { let mut s = unscanny::Scanner::new(part); let display = s.eat_until('\n').trim(); let docs = s.eat_until("\n## Methods").trim(); + TypeModel { + name: display.to_lowercase(), + oneliner: oneliner(docs), + details: Html::markdown(resolver, docs), + methods: method_models(resolver, part), + } +} +/// Produce multiple methods' models. +fn method_models(resolver: &dyn Resolver, docs: &'static str) -> Vec { + let mut s = unscanny::Scanner::new(docs); + s.eat_until("\n## Methods"); s.eat_whitespace(); let mut methods = vec![]; @@ -512,12 +527,7 @@ fn type_model(resolver: &dyn Resolver, part: &'static str) -> TypeModel { } } - TypeModel { - name: display.to_lowercase(), - oneliner: oneliner(docs), - details: Html::markdown(resolver, docs), - methods, - } + methods } /// Produce a method's model. @@ -741,6 +751,7 @@ const TYPE_ORDER: &[&str] = &[ "dictionary", "function", "arguments", + "location", "dir", "alignment", "2d alignment", diff --git a/library/src/meta/counter.rs b/library/src/meta/counter.rs index 3cfc2cd5c..e5f03e539 100644 --- a/library/src/meta/counter.rs +++ b/library/src/meta/counter.rs @@ -11,12 +11,237 @@ use crate::prelude::*; /// Count through pages, elements, and more. /// +/// With the counter function, you can access and modify counters for pages, +/// headings, figures, and more. Moreover, you can define custom counters for +/// other things you want to count. +/// +/// ## Displaying a counter +/// To display the current value of the heading counter, you call the `counter` +/// function with the `key` set to `heading` and then call the `display` method +/// on the counter. To see any output, you also have to enable heading +/// [numbering]($func/heading.numbering). +/// +/// The display function optionally takes an argument telling it how to +/// format the counter. This can be a +/// [numbering pattern or a function]($func/numbering). +/// +/// ```example +/// #set heading(numbering: "1.") +/// +/// = Introduction +/// Some text here. +/// +/// = Background +/// The current value is: +/// #counter(heading).display() +/// +/// Or in roman numerals: +/// #counter(heading).display("I") +/// ``` +/// +/// ## Modifying a counter +/// To modify a counter, you can use the `step` and `update` methods: +/// +/// - The `step` method increases the value of the counter by one. Because +/// counters can have multiple levels (in the case of headings for sections, +/// subsections, and so on), the `step` method optionally takes a `level` +/// argument. If given, the counter steps at the given depth. +/// +/// - The `update` method allows you to arbitrarily modify the counter. In its +/// basic form, you give it an integer (or multiple for multiple levels). For +/// more flexibility, you can instead also give it a function that gets the +/// current value and returns a new value. +/// +/// The heading counter is stepped before the heading is displayed, so +/// `Analysis` gets the number seven even though the counter is at six after the +/// second update. +/// +/// ```example +/// #set heading(numbering: "1.") +/// +/// = Introduction +/// #counter(heading).step() +/// +/// = Background +/// #counter(heading).update(3) +/// #counter(heading).update(n => n * 2) +/// +/// = Analysis +/// Let's skip 7.1. +/// #counter(heading).step(level: 2) +/// +/// == Analysis +/// Still at #counter(heading).display(). +/// ``` +/// +/// ## Page counter +/// The page counter is special. It is automatically stepped at each pagebreak. +/// But like other counters, you can also step it manually. For example, you +/// could have Roman page numbers for your preface, then switch to Arabic page +/// numbers for your main content and reset the page counter to one. +/// +/// ```example +/// >>> #set page( +/// >>> height: 100pt, +/// >>> margin: (bottom: 24pt, rest: 16pt), +/// >>> ) +/// #set page(numbering: "(i)") +/// +/// = Preface +/// The preface is numbered with +/// roman numerals. +/// +/// #set page(numbering: "1 / 1") +/// #counter(page).update(1) +/// +/// = Main text +/// Here, the counter is reset to one. +/// We also display both the current +/// page and total number of pages in +/// Arabic numbers. +/// ``` +/// +/// ## Custom counters +/// To define your own counter, call the `counter` function with a string as a +/// key. This key identifies the counter globally. +/// +/// ```example +/// #let mine = counter("mycounter") +/// #mine.display() \ +/// #mine.step() +/// #mine.display() \ +/// #mine.update(c => c * 3) +/// #mine.display() \ +/// ``` +/// +/// ## Time travel +/// Counters can travel through time! You can find out the final value of the +/// counter before it is reached and even determine what the value was at any +/// particular location in the document. +/// +/// ```example +/// #let mine = counter("mycounter") +/// +/// = Values +/// #locate(loc => { +/// let start-val = mine.at(loc) +/// let elements = query(, loc) +/// let intro-val = mine.at( +/// elements.first().location() +/// ) +/// let final-val = mine.final(loc) +/// [Starts as: #start-val \ +/// Value at intro is: #intro-val \ +/// Final value is: #final-val \ ] +/// }) +/// +/// #mine.update(n => n + 3) +/// +/// = Introduction +/// #lorem(10) +/// +/// #mine.step() +/// #mine.step() +/// ``` +/// +/// Let's disect what happens in the example above: +/// +/// - We call [`locate`]($func/locate) to get access to the current location in +/// the document. We then pass this location to our counter's `at` method to +/// get its value at the current location. The `at` method always returns an +/// array because counters can have multiple levels. As the counter starts at +/// one, the first value is thus `{(1,)}`. +/// +/// - We now [`query`]($func/query) the document for all elements with the +/// `{}` label. The result is an array from which we extract the first +/// (and only) element's [location]($type/content.location). We then look up +/// the value of the counter at that location. The first update to the counter +/// sets it to `{1 + 3 = 4}`. At the introduction heading, the value is thus +/// `{(4,)}`. +/// +/// - Last but not least, we call the `final` method on the counter. It tells us +/// what the counter's value will be at the end of the document. We also need +/// to give it a location to prove that we are inside of a `locate` call, but +/// which one doesn't matter. After the heading follow two calls to `step()`, +/// so the final value is `{(6,)}`. +/// +/// ## Methods +/// ### display() +/// Display the value of the counter. +/// +/// - numbering: string or function (positional) +/// A [numbering pattern or a function]($func/numbering), which specifies how +/// to display the counter. If given a function, that function receives each +/// number of the counter as a separate argument. If the amount of numbers +/// varies, e.g. for the heading argument, you can use an +/// [argument sink]($type/arguments). +/// +/// - returns: content +/// +/// ### step() +/// Increase the value of the counter by one. +/// +/// The update will be in effect at the position where the returned content is +/// inserted into the document. If you don't put the output into the document, +/// nothing happens! This would be the case, for example, if you write +/// `{let _ = counter(page).step()}`. Counter updates are always applied in +/// layout order and in that case, Typst wouldn't know when to step the counter. +/// +/// - level: integer (named) +/// The depth at which to step the counter. Defaults to `{1}`. +/// +/// - returns: content +/// +/// ### update() +/// Update the value of the counter. +/// +/// Just like `step()`, the update only occurs if you put the resulting +/// content into the document. +/// +/// - value: integer or array or function (positional, required) +/// If given an integer or array of integers, sets the counter to that value. +/// If given a function, that function receives the previous counter value +/// (with each number as a separate argument) and has to return the new +/// value (integer or array). +/// +/// - returns: content +/// +/// ### at() +/// Get the value of the counter at the given location. Always returns an +/// array of integers, even if the counter has just one number. +/// +/// - location: location (positional, required) +/// The location at which the counter value should be retrieved. A suitable +/// location can be retrieved from [`locate`]($func/locate) or +/// [`query`]($func/query). +/// +/// - returns: array +/// +/// ### final() +/// Get the value of the counter at the end of the document. Always returns an +/// array of integers, even if the counter has just one number. +/// +/// - location: location (positional, required) +/// Can be any location. Why is it required then? Typst has to evaluate parts +/// of your code multiple times to find out all counter's values. By only +/// allowing this method in [`locate`]($func/locate) calls, the amount of code +/// that can depend on the method's result is reduced. If you could call +/// `final` directly at the top level of a module, the evaluation of the whole +/// module and its exports could depend on the counter's value. +/// +/// - returns: array +/// /// Display: Counter /// Category: meta /// Returns: counter #[func] pub fn counter( /// The key that identifies this counter. + /// + /// - If this is the [`page`]($func/page) function, counts through pages. + /// - If this is any other element function, counts through its elements. + /// - If it is a string, creates a custom counter that is only affected by + /// manual updates. key: CounterKey, ) -> Value { Value::dynamic(Counter::new(key)) @@ -53,14 +278,14 @@ impl Counter { args.named("both")?.unwrap_or(false), ) .into(), - "at" => self.at(&mut vm.vt, args.expect("location")?)?.into(), - "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into(), - "update" => self.update(args.expect("value or function")?).into(), "step" => self .update(CounterUpdate::Step( args.named("level")?.unwrap_or(NonZeroUsize::ONE), )) .into(), + "update" => self.update(args.expect("value or function")?).into(), + "at" => self.at(&mut vm.vt, args.expect("location")?)?.into(), + "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into(), _ => bail!(span, "type counter has no method `{}`", method), }; args.finish()?;