Syntax and example sections

This commit is contained in:
Laurenz 2022-12-19 01:16:35 +01:00
parent ba384e5bb6
commit b4b022940b
31 changed files with 199 additions and 37 deletions

View File

@ -664,7 +664,7 @@ impl FontSearcher {
let path = path.as_ref();
if let Ok(file) = File::open(path) {
if let Ok(mmap) = unsafe { Mmap::map(&file) } {
for (i, info) in FontInfo::from_data(&mmap).enumerate() {
for (i, info) in FontInfo::iter(&mmap).enumerate() {
self.book.push(info);
self.fonts.push(FontSlot {
path: path.into(),

View File

@ -7,9 +7,23 @@ use crate::text::{SpaceNode, TextNode, TextSize};
/// A section heading.
///
/// # Example
/// ```
/// #set heading(numbering: "I.")
///
/// = Introduction
/// In recent years, ...
/// ```
///
/// # Syntax
/// Headings can be created by starting a line with one or multiple equals
/// signs. The number of equals signs determines the heading's logical nesting
/// depth.
///
/// # Parameters
/// - body: Content (positional, required)
/// The heading's contents.
///
/// - level: NonZeroUsize (named)
/// The logical nesting depth of the heading, starting from one.
///
@ -29,10 +43,31 @@ pub struct HeadingNode {
#[node]
impl HeadingNode {
/// How to number the heading.
///
/// # Example
/// ```
/// #set heading(numbering: "1.a.")
///
/// = A section
/// == A subsection
/// === A sub-subsection
/// ```
#[property(referenced)]
pub const NUMBERING: Option<NumberingPattern> = None;
/// Whether the heading should appear in the outline.
///
/// # Example
/// ```
/// #outline()
///
/// #heading[Normal]
/// This is a normal heading.
///
/// #heading(outlined: false)[Hidden]
/// This heading does not appear
/// in the outline.
/// ```
pub const OUTLINED: bool = true;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {

View File

@ -8,12 +8,22 @@ use crate::text::{SpaceNode, TextNode};
/// # Parameters
/// - items: Content (positional, variadic)
/// The contents of the list items.
///
/// - start: NonZeroUsize (named)
/// Which number to start the enumeration with.
///
/// - tight: bool (named)
/// Makes the list more compact, if enabled. This looks better if the items
/// fit into a single line each.
///
/// # Example
/// ```
/// #show columns.with(2)
/// #list(tight: true)[Tight][List]
/// #colbreak()
/// #list(tight: false)[Wide][List]
/// ```
///
/// # Tags
/// - basics
#[func]

View File

@ -6,14 +6,19 @@ use crate::prelude::*;
/// # Parameters
/// - cells: Content (positional, variadic)
/// The contents of the table cells.
///
/// - rows: TrackSizings (named)
/// Defines the row sizes.
///
/// - columns: TrackSizings (named)
/// Defines the column sizes.
///
/// - gutter: TrackSizings (named)
/// Defines the gaps between rows & columns.
///
/// - column-gutter: TrackSizings (named)
/// Defines the gaps between columns. Takes precedence over `gutter`.
///
/// - row-gutter: TrackSizings (named)
/// Defines the gaps between rows. Takes precedence over `gutter`.
///

View File

@ -105,6 +105,7 @@ pub fn odd(args: &mut Args) -> SourceResult<Value> {
/// # Parameters
/// - dividend: ToMod (positional, required)
/// The dividend of the modulus.
///
/// - divisor: ToMod (positional, required)
/// The divisor of the modulus.
///

View File

@ -77,12 +77,21 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
///
/// If this string is given, the individual components should not be given.
///
/// # Example
/// ```
/// #let color = rgb("#239dad")
/// #text(16pt, color)[*Typst*]
/// ```
///
/// - red: Component (positional)
/// The red component.
///
/// - green: Component (positional)
/// The green component.
///
/// - blue: Component (positional)
/// The blue component.
///
/// - alpha: Component (positional)
/// The alpha component.
///
@ -125,10 +134,13 @@ castable! {
/// # Parameters
/// - cyan: RatioComponent (positional, required)
/// The cyan component.
///
/// - magenta: RatioComponent (positional, required)
/// The magenta component.
///
/// - yellow: RatioComponent (positional, required)
/// The yellow component.
///
/// - key: RatioComponent (positional, required)
/// The key component.
///

View File

@ -22,6 +22,7 @@ pub fn lorem(args: &mut Args) -> SourceResult<Value> {
/// # Parameters
/// - pattern: NumberingPattern (positional, required)
/// A string that defines how the numbering works.
///
/// - numbers: NonZeroUsize (positional, variadic)
/// The numbers to apply the pattern to.
///

View File

@ -5,6 +5,7 @@ use crate::prelude::*;
/// # Parameters
/// - body: Content (positional, required)
/// The content to align.
///
/// - alignment: Axes<Option<GenAlign>> (positional, settable)
/// The alignment along both axes.
///

View File

@ -6,6 +6,7 @@ use crate::text::TextNode;
/// # Parameters
/// - count: usize (positional, required)
/// The number of columns.
///
/// - body: Content (positional, required)
/// The content that should be layouted into the columns.
///

View File

@ -7,8 +7,10 @@ use crate::prelude::*;
/// # Parameters
/// - body: Content (positional)
/// The contents of the box.
///
/// - width: Rel<Length> (named)
/// The width of the box.
///
/// - height: Rel<Length> (named)
/// The height of the box.
///
@ -78,11 +80,14 @@ impl Inline for BoxNode {}
/// # Parameters
/// - body: Content (positional)
/// The contents of the block.
///
/// - spacing: Spacing (named, settable)
/// The spacing around this block.
///
/// - above: Spacing (named, settable)
/// The spacing between the previous and this block. Takes precedence over
/// `spacing`.
///
/// - below: Spacing (named, settable)
/// The spacing between this block and the following one. Takes precedence
/// over `spacing`.

View File

@ -7,14 +7,19 @@ use super::Spacing;
/// # Parameters
/// - cells: Content (positional, variadic)
/// The contents of the table cells.
///
/// - rows: TrackSizings (named)
/// Defines the row sizes.
///
/// - columns: TrackSizings (named)
/// Defines the column sizes.
///
/// - gutter: TrackSizings (named)
/// Defines the gaps between rows & columns.
///
/// - column-gutter: TrackSizings (named)
/// Defines the gaps between columns. Takes precedence over `gutter`.
///
/// - row-gutter: TrackSizings (named)
/// Defines the gaps between rows. Takes precedence over `gutter`.
///

View File

@ -5,18 +5,25 @@ use crate::prelude::*;
/// # Parameters
/// - body: Content (positional, required)
/// The content to pad at the sides.
///
/// - left: Rel<Length> (named)
/// The padding at the left side.
///
/// - right: Rel<Length> (named)
/// The padding at the right side.
///
/// - top: Rel<Length> (named)
/// The padding at the top side.
///
/// - bottom: Rel<Length> (named)
/// The padding at the bottom side.
///
/// - x: Rel<Length> (named)
/// The horizontal padding. Both `left` and `right` take precedence over this.
///
/// - y: Rel<Length> (named)
/// The vertical padding. Both `top` and `bottom` take precedence over this.
///
/// - rest: Rel<Length> (named)
/// The padding for all sides. All other parameters take precedence over this.
///

View File

@ -9,6 +9,7 @@ use crate::text::TextNode;
/// # Parameters
/// - body: Content (positional, required)
/// The contents of the page(s).
///
/// - paper: Paper (positional, settable)
/// The paper size.
///

View File

@ -5,10 +5,13 @@ use crate::prelude::*;
/// # Parameters
/// - alignment: Axes<Option<GenAlign>> (positional)
/// Relative to which position in the parent container to place the content.
///
/// - body: Content (positional, required)
/// The content to place.
///
/// - dx: Rel<Length> (named)
/// The horizontal displacement of the placed content.
///
/// - dy: Rel<Length> (named)
/// The vertical displacement of the placed content.
///

View File

@ -7,6 +7,7 @@ use crate::prelude::*;
/// # Parameters
/// - amount: Spacing (positional, required)
/// How much spacing to insert.
///
/// - weak: bool (named)
/// If true, the spacing collapses at the start or end of a paragraph.
/// Moreover, from multiple adjacent weak spacings all but the largest one
@ -67,6 +68,7 @@ impl Behave for HNode {
/// # Parameters
/// - amount: Spacing (positional, required)
/// How much spacing to insert.
///
/// - weak: bool (named)
/// If true, the spacing collapses at the start or end of a flow.
/// Moreover, from multiple adjacent weak spacings all but the largest one

View File

@ -8,8 +8,10 @@ use crate::prelude::*;
/// # Parameters
/// - items: StackChild (positional, variadic)
/// The items to stack along an axis.
///
/// - dir: Dir (named)
/// The direction along which the items are stacked.
///
/// - spacing: Spacing (named)
/// Spacing to insert between items where no explicit spacing was provided.
///

View File

@ -7,8 +7,10 @@ use crate::prelude::*;
/// # Parameters
/// - body: Content (positional, required)
/// The content to move.
///
/// - dx: Rel<Length> (named)
/// The horizontal displacement of the content.
///
/// - dy: Rel<Length> (named)
/// The vertical displacement of the content.
///
@ -61,10 +63,13 @@ impl Inline for MoveNode {}
/// # Parameters
/// - body: Content (positional, required)
/// The content to transform.
///
/// - angle: Angle (named)
/// The amount of rotation.
///
/// - x: Ratio (named)
/// The horizontal scaling factor.
///
/// - y: Ratio (named)
/// The vertical scaling factor.
///

View File

@ -19,6 +19,7 @@ use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode};
/// # Parameters
/// - items: Content (positional, variadic)
/// The individual parts of the formula.
///
/// - block: bool (named)
/// Whether the formula is displayed as a separate block.
///
@ -309,6 +310,7 @@ impl Texify for AtomNode {
/// # Parameters
/// - base: Content (positional, required)
/// The base to which the accent is applied.
///
/// - accent: Content (positional, required)
/// The accent to apply to the base.
///
@ -393,6 +395,7 @@ impl Texify for AccNode {
/// # Parameters
/// - num: Content (positional, required)
/// The fraction's numerator.
///
/// - denom: Content (positional, required)
/// The fraction's denominator.
///
@ -433,6 +436,7 @@ impl Texify for FracNode {
/// # Parameters
/// - upper: Content (positional, required)
/// The binomial's upper index.
///
/// - lower: Content (positional, required)
/// The binomial's lower index.
///
@ -473,8 +477,10 @@ impl Texify for BinomNode {
/// # Parameters
/// - base: Content (positional, required)
/// The base to which the applies the sub- and/or superscript.
///
/// - sub: Content (named)
/// The subscript.
///
/// - sup: Content (named)
/// The superscript.
///

View File

@ -6,6 +6,7 @@ use crate::text::TextNode;
/// # Parameters
/// - dest: Destination (positional, required)
/// The destination the link points to.
///
/// - body: Content (positional)
/// How the link is represented. Defaults to the destination if it is a link.
///

View File

@ -30,6 +30,7 @@ use crate::prelude::*;
/// # Parameters
/// - family: EcoString (positional, variadic, settable)
/// A prioritized sequence of font families.
///
/// - body: Content (positional, required)
/// Content in which all text is styled according to the other arguments.
///

View File

@ -11,6 +11,7 @@ use crate::prelude::*;
/// # Parameters
/// - text: EcoString (positional, required)
/// The raw text.
///
/// - block: bool (named)
/// Whether the raw text is displayed as a separate block.
///

View File

@ -7,10 +7,13 @@ use crate::prelude::*;
/// # Parameters
/// - origin: Axes<Rel<Length>> (named)
/// The start point of the line.
///
/// - to: Axes<Rel<Length>> (named)
/// The end point of the line.
///
/// - length: Rel<Length> (named)
/// The line's length.
///
/// - angle: Angle (named)
/// The angle at which the line points away from the origin.
///

View File

@ -7,14 +7,19 @@ use crate::prelude::*;
/// # Parameters
/// - body: Content (positional)
/// The content to place into the shape.
///
/// - width: Rel<Length> (named)
/// The shape's width.
///
/// - height: Rel<Length> (named)
/// The shape's height.
///
/// - size: Length (named)
/// The square's side length.
///
/// - radius: Length (named)
/// The circle's radius.
///
/// - stroke: Smart<Sides<Option<PartialStroke>>> (named)
/// How to stroke the shape.
///

View File

@ -1,4 +1,3 @@
use proc_macro2::Span;
use unscanny::Scanner;
use super::*;
@ -14,12 +13,21 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
let tags = tags(&mut docs);
let params = params(&mut docs)?;
let example = quote_option(example(&mut docs));
let syntax = quote_option(section(&mut docs, "Syntax"));
let docs = docs.trim();
if docs.contains("# ") {
bail!(item, "Documentation heading not recognized");
}
let info = quote! {
::typst::model::FuncInfo {
name,
docs: #docs,
tags: &[#(#tags),*],
docs: #docs,
example: #example,
syntax: #syntax,
params: ::std::vec![#(#params),*],
}
};
@ -76,18 +84,18 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
/// Extract a section.
pub fn section(docs: &mut String, title: &str) -> Option<String> {
let needle = format!("# {title}\n");
let needle = format!("\n# {title}\n");
let start = docs.find(&needle)?;
let rest = &docs[start..];
let len = rest[1..].find('#').map(|x| 1 + x).unwrap_or(rest.len());
let len = rest[1..].find("\n# ").map(|x| 1 + x).unwrap_or(rest.len());
let end = start + len;
let section = docs[start + needle.len()..].to_owned();
let section = docs[start + needle.len()..end].trim().to_owned();
docs.replace_range(start..end, "");
Some(section)
}
/// Parse the tag section.
pub fn tags(docs: &mut String) -> Vec<String> {
fn tags(docs: &mut String) -> Vec<String> {
section(docs, "Tags")
.unwrap_or_default()
.lines()
@ -96,24 +104,40 @@ pub fn tags(docs: &mut String) -> Vec<String> {
.collect()
}
/// Parse the example section.
pub fn example(docs: &mut String) -> Option<String> {
Some(
section(docs, "Example")?
.lines()
.skip_while(|line| !line.contains("```"))
.skip(1)
.take_while(|line| !line.contains("```"))
.map(|s| s.trim())
.collect::<Vec<_>>()
.join("\n"),
)
}
/// Parse the parameter section.
pub fn params(docs: &mut String) -> Result<Vec<TokenStream>> {
fn params(docs: &mut String) -> Result<Vec<TokenStream>> {
let Some(section) = section(docs, "Parameters") else { return Ok(vec![]) };
let mut s = Scanner::new(&section);
let mut infos = vec![];
while s.eat_if('-') {
s.eat_whitespace();
let name = s.eat_until(':');
s.expect(": ");
let ty: syn::Type = syn::parse_str(s.eat_until(char::is_whitespace))?;
s.eat_whitespace();
let mut named = false;
let mut positional = false;
let mut required = false;
let mut variadic = false;
let mut settable = false;
s.eat_whitespace();
let name = s.eat_until(':');
s.expect(": ");
let ty: syn::Type = syn::parse_str(s.eat_until(char::is_whitespace))?;
s.eat_whitespace();
s.expect('(');
for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) {
match part {
"named" => named = true,
@ -121,12 +145,7 @@ pub fn params(docs: &mut String) -> Result<Vec<TokenStream>> {
"required" => required = true,
"variadic" => variadic = true,
"settable" => settable = true,
_ => {
return Err(syn::Error::new(
Span::call_site(),
format!("unknown parameter flag {:?}", part),
))
}
_ => bail!(callsite, "unknown parameter flag {:?}", part),
}
}
@ -135,18 +154,20 @@ pub fn params(docs: &mut String) -> Result<Vec<TokenStream>> {
|| (named && variadic)
|| (required && variadic)
{
return Err(syn::Error::new(
Span::call_site(),
"invalid combination of parameter flags",
));
bail!(callsite, "invalid combination of parameter flags");
}
s.expect(')');
let docs = dedent(s.eat_until("\n-").trim());
let mut docs = dedent(s.eat_until("\n-").trim());
let example = quote_option(example(&mut docs));
let docs = docs.trim();
infos.push(quote! {
::typst::model::ParamInfo {
name: #name,
docs: #docs,
example: #example,
cast: <#ty as ::typst::model::Cast<
::typst::syntax::Spanned<::typst::model::Value>
>>::describe(),

View File

@ -4,12 +4,18 @@ extern crate proc_macro;
/// Return an error at the given item.
macro_rules! bail {
(callsite, $fmt:literal $($tts:tt)*) => {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
format!(concat!("typst: ", $fmt) $($tts)*)
))
};
($item:expr, $fmt:literal $($tts:tt)*) => {
return Err(Error::new_spanned(
return Err(syn::Error::new_spanned(
&$item,
format!(concat!("typst: ", $fmt) $($tts)*)
))
}
};
}
mod capable;
@ -19,9 +25,8 @@ mod node;
use proc_macro::TokenStream as BoundaryStream;
use proc_macro2::{TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use syn::parse_quote;
use syn::{Error, Ident, Result};
use quote::{quote, quote_spanned, ToTokens};
use syn::{parse_quote, Ident, Result};
/// Implement `FuncType` for a type or function.
#[proc_macro_attribute]
@ -88,3 +93,11 @@ fn documentation(attrs: &[syn::Attribute]) -> String {
fn dedent(text: &str) -> String {
text.lines().map(str::trim).collect::<Vec<_>>().join("\n")
}
/// Quote an optional value.
fn quote_option<T: ToTokens>(option: Option<T>) -> TokenStream {
match option {
Some(value) => quote! { Some(#value) },
None => quote! { None },
}
}

View File

@ -333,13 +333,18 @@ fn create_node_set_func(node: &Node) -> syn::ImplItemMethod {
fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod {
let infos = node.properties.iter().filter(|p| !p.skip).map(|property| {
let name = property.name.to_string().replace('_', "-").to_lowercase();
let docs = documentation(&property.attrs);
let value_ty = &property.value_ty;
let shorthand = matches!(property.shorthand, Some(Shorthand::Positional));
let mut docs = documentation(&property.attrs);
let example = quote_option(super::func::example(&mut docs));
let docs = docs.trim();
quote! {
::typst::model::ParamInfo {
name: #name,
docs: #docs,
example: #example,
cast: <#value_ty as ::typst::model::Cast<
::typst::syntax::Spanned<::typst::model::Value>
>>::describe(),

View File

@ -170,7 +170,7 @@ bitflags::bitflags! {
impl FontInfo {
/// Compute metadata for all fonts in the given data.
pub fn from_data(data: &[u8]) -> impl Iterator<Item = FontInfo> + '_ {
pub fn iter(data: &[u8]) -> impl Iterator<Item = FontInfo> + '_ {
let count = ttf_parser::fonts_in_collection(data).unwrap_or(1);
(0..count).filter_map(move |index| {
let ttf = ttf_parser::Face::parse(data, index).ok()?;
@ -179,7 +179,7 @@ impl FontInfo {
}
/// Compute metadata for a single ttf-parser face.
pub fn from_ttf(ttf: &ttf_parser::Face) -> Option<Self> {
pub(super) fn from_ttf(ttf: &ttf_parser::Face) -> Option<Self> {
// We cannot use Name ID 16 "Typographic Family", because for some
// fonts it groups together more than just Style / Weight / Stretch
// variants (e.g. Display variants of Noto fonts) and then some

View File

@ -69,6 +69,12 @@ impl Font {
})))
}
/// Parse all fonts in the given data.
pub fn iter(data: Buffer) -> impl Iterator<Item = Self> {
let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
(0..count).filter_map(move |index| Self::new(data.clone(), index))
}
/// The underlying buffer.
pub fn data(&self) -> &Buffer {
&self.0.data

View File

@ -214,6 +214,10 @@ pub struct FuncInfo {
pub tags: &'static [&'static str],
/// Documentation for the function.
pub docs: &'static str,
/// The source code of an example, if any.
pub example: Option<&'static str>,
/// Documentation about this function's syntax, if it has syntax.
pub syntax: Option<&'static str>,
/// Details about the function's parameters.
pub params: Vec<ParamInfo>,
}
@ -232,6 +236,8 @@ pub struct ParamInfo {
pub name: &'static str,
/// Documentation for the parameter.
pub docs: &'static str,
/// The source code of an example, if any.
pub example: Option<&'static str>,
/// Valid values for the parameter.
pub cast: CastInfo,
/// Is the parameter positional?

View File

@ -122,7 +122,7 @@ pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new();
/// break incremental, but only when different sets of lang items are used in
/// the same program. For this reason, if this function is called multiple
/// times, the items must be the same.
pub(crate) fn set_lang_items(items: LangItems) {
pub fn set_lang_items(items: LangItems) {
if let Err(items) = LANG_ITEMS.set(items) {
let first = hash128(LANG_ITEMS.get().unwrap());
let second = hash128(&items);

View File

@ -213,10 +213,8 @@ impl TestWorld {
.filter_map(|e| e.ok())
.filter(|entry| entry.file_type().is_file())
{
let buffer: Buffer = fs::read(entry.path()).unwrap().into();
for index in 0..ttf_parser::fonts_in_collection(&buffer).unwrap_or(1) {
fonts.push(Font::new(buffer.clone(), index).unwrap())
}
let data = std::fs::read(entry.path()).unwrap();
fonts.extend(Font::iter(data.into()));
}
Self {