mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Document meta and data loading categories
This commit is contained in:
parent
b4b022940b
commit
b8ffd3ad3d
@ -6,9 +6,28 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
/// Read structured data from a CSV file.
|
/// Read structured data from a CSV file.
|
||||||
///
|
///
|
||||||
|
/// The CSV file will be read and parsed into a 2-dimensional array of strings:
|
||||||
|
/// Each row in the CSV file will be represented as an array of strings, and all
|
||||||
|
/// rows will be collected into a single array. Header rows will not be
|
||||||
|
/// stripped.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #let results = csv("/data.csv")
|
||||||
|
///
|
||||||
|
/// #table(
|
||||||
|
/// columns: 2,
|
||||||
|
/// [*Condition*], [*Result*],
|
||||||
|
/// ..results.flatten(),
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - path: EcoString (positional, required)
|
/// - path: EcoString (positional, required)
|
||||||
/// Path to a CSV file.
|
/// Path to a CSV file.
|
||||||
|
/// - delimiter: Delimiter (named)
|
||||||
|
/// The delimiter that separates columns in the CSV file.
|
||||||
|
/// Must be a single ASCII character.
|
||||||
|
/// Defaults to a comma.
|
||||||
///
|
///
|
||||||
/// # Tags
|
/// # Tags
|
||||||
/// - data-loading
|
/// - data-loading
|
||||||
@ -23,6 +42,10 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
|||||||
let mut builder = csv::ReaderBuilder::new();
|
let mut builder = csv::ReaderBuilder::new();
|
||||||
builder.has_headers(false);
|
builder.has_headers(false);
|
||||||
|
|
||||||
|
if let Some(delimiter) = args.named::<Delimiter>("delimiter")? {
|
||||||
|
builder.delimiter(delimiter.0);
|
||||||
|
}
|
||||||
|
|
||||||
let mut reader = builder.from_reader(data.as_slice());
|
let mut reader = builder.from_reader(data.as_slice());
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
|
|
||||||
@ -35,6 +58,26 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
|||||||
Ok(Value::Array(Array::from_vec(vec)))
|
Ok(Value::Array(Array::from_vec(vec)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The delimiter to use when parsing CSV files.
|
||||||
|
struct Delimiter(u8);
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Delimiter,
|
||||||
|
v: EcoString => {
|
||||||
|
let mut chars = v.chars();
|
||||||
|
let first = chars.next().ok_or("delimiter must not be empty")?;
|
||||||
|
if chars.next().is_some() {
|
||||||
|
Err("delimiter must be a single character")?
|
||||||
|
}
|
||||||
|
|
||||||
|
if !first.is_ascii() {
|
||||||
|
Err("delimiter must be an ASCII character")?
|
||||||
|
}
|
||||||
|
|
||||||
|
Self(first as u8)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// Format the user-facing CSV error message.
|
/// Format the user-facing CSV error message.
|
||||||
fn format_csv_error(error: csv::Error) -> String {
|
fn format_csv_error(error: csv::Error) -> String {
|
||||||
match error.kind() {
|
match error.kind() {
|
||||||
@ -54,6 +97,43 @@ fn format_csv_error(error: csv::Error) -> String {
|
|||||||
|
|
||||||
/// Read structured data from a JSON file.
|
/// Read structured data from a JSON file.
|
||||||
///
|
///
|
||||||
|
/// The file must contain a valid JSON object or array. JSON objects will be
|
||||||
|
/// converted into Typst dictionaries, and JSON arrays will be converted into
|
||||||
|
/// Typst arrays. Strings and booleans will be converted into the Typst
|
||||||
|
/// equivalents, `null` will be converted into `{none}`, and numbers will be
|
||||||
|
/// converted to floats or integers depending on whether they are whole numbers.
|
||||||
|
///
|
||||||
|
/// The function returns a dictionary or an array, depending on the JSON file.
|
||||||
|
///
|
||||||
|
/// The JSON files in the example contain a object with the keys `temperature`,
|
||||||
|
/// `unit`, and `weather`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #let forecast(day) = block[
|
||||||
|
/// #square(
|
||||||
|
/// width: 2cm,
|
||||||
|
/// inset: 8pt,
|
||||||
|
/// fill: if day.weather == "sunny" {
|
||||||
|
/// yellow
|
||||||
|
/// } else {
|
||||||
|
/// aqua
|
||||||
|
/// },
|
||||||
|
/// align(
|
||||||
|
/// bottom + right,
|
||||||
|
/// strong(day.weather),
|
||||||
|
/// ),
|
||||||
|
/// )
|
||||||
|
/// #h(6pt)
|
||||||
|
/// #set text(22pt, baseline: -8pt)
|
||||||
|
/// {day.temperature}
|
||||||
|
/// °{day.unit}
|
||||||
|
/// ]
|
||||||
|
///
|
||||||
|
/// #forecast(json("/monday.json"))
|
||||||
|
/// #forecast(json("/tuesday.json"))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - path: EcoString (positional, required)
|
/// - path: EcoString (positional, required)
|
||||||
/// Path to a JSON file.
|
/// Path to a JSON file.
|
||||||
@ -97,11 +177,61 @@ fn convert_json(value: serde_json::Value) -> Value {
|
|||||||
/// Format the user-facing JSON error message.
|
/// Format the user-facing JSON error message.
|
||||||
fn format_json_error(error: serde_json::Error) -> String {
|
fn format_json_error(error: serde_json::Error) -> String {
|
||||||
assert!(error.is_syntax() || error.is_eof());
|
assert!(error.is_syntax() || error.is_eof());
|
||||||
format!("failed to parse json file: syntax error in line {}", error.line())
|
format!(
|
||||||
|
"failed to parse json file: syntax error in line {}",
|
||||||
|
error.line()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read structured data from an XML file.
|
/// Read structured data from an XML file.
|
||||||
///
|
///
|
||||||
|
/// The XML file is parsed into an array of dictionaries and strings. XML nodes
|
||||||
|
/// can be elements or strings. Elements are represented as dictionaries with
|
||||||
|
/// the the following keys:
|
||||||
|
///
|
||||||
|
/// - `tag`: The name of the element as a string.
|
||||||
|
/// - `attrs`: A dictionary of the element's attributes as strings.
|
||||||
|
/// - `children`: An array of the element's child nodes.
|
||||||
|
///
|
||||||
|
/// The XML file in the example contains a root `news` tag with multiple
|
||||||
|
/// `article` tags. Each article has a `title`, `author`, and `content` tag. The
|
||||||
|
/// `content` tag contains one or more paragraphs, which are represented as `p`
|
||||||
|
/// tags.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #let findChild(elem, tag) = {
|
||||||
|
/// elem.children
|
||||||
|
/// .find(e => "tag" in e and e.tag == tag)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #let article(elem) = {
|
||||||
|
/// let title = findChild(elem, "title")
|
||||||
|
/// let author = findChild(elem, "author")
|
||||||
|
/// let pars = findChild(elem, "content")
|
||||||
|
///
|
||||||
|
/// heading((title.children)(0))
|
||||||
|
/// text(10pt, weight: "medium")[
|
||||||
|
/// Published by
|
||||||
|
/// {(author.children)(0)}
|
||||||
|
/// ]
|
||||||
|
///
|
||||||
|
/// for p in pars.children {
|
||||||
|
/// if (type(p) == "dictionary") {
|
||||||
|
/// parbreak()
|
||||||
|
/// (p.children)(0)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #let file = xml("/example.xml")
|
||||||
|
/// #for child in file(0).children {
|
||||||
|
/// if (type(child) == "dictionary") {
|
||||||
|
/// article(child)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - path: EcoString (positional, required)
|
/// - path: EcoString (positional, required)
|
||||||
/// Path to an XML file.
|
/// Path to an XML file.
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
use crate::layout::{LayoutRoot, PageNode};
|
use crate::layout::{LayoutRoot, PageNode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// The root node that represents a full document.
|
/// The root element of a document and its metadata.
|
||||||
|
///
|
||||||
|
/// All documents are automatically wrapped in a `document` element. The main
|
||||||
|
/// use of this element is to use it in `set` rules to specify document
|
||||||
|
/// metadata.
|
||||||
|
///
|
||||||
|
/// The metadata set with this function is not rendered within the document.
|
||||||
|
/// Instead, it is embedded in the compiled PDF file.
|
||||||
///
|
///
|
||||||
/// # Tags
|
/// # Tags
|
||||||
/// - meta
|
/// - meta
|
||||||
@ -12,7 +19,8 @@ pub struct DocumentNode(pub StyleVec<PageNode>);
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl DocumentNode {
|
impl DocumentNode {
|
||||||
/// The document's title.
|
/// The document's title. This is often rendered as the title of the
|
||||||
|
/// PDF viewer window.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const TITLE: Option<EcoString> = None;
|
pub const TITLE: Option<EcoString> = None;
|
||||||
|
|
||||||
|
@ -1,14 +1,47 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// Link text and other elements to a destination.
|
/// Link to a URL or another location in the document.
|
||||||
|
///
|
||||||
|
/// The link function makes its positional `body` argument clickable and links
|
||||||
|
/// it to the destination specified by the `dest` argument.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #show link: underline
|
||||||
|
///
|
||||||
|
/// #link("https://example.com") \
|
||||||
|
/// #link("https://example.com")[
|
||||||
|
/// See example.com
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - dest: Destination (positional, required)
|
/// - dest: Destination (positional, required)
|
||||||
/// The destination the link points to.
|
/// The destination the link points to.
|
||||||
///
|
///
|
||||||
|
/// - To link to web pages, `dest` should be a valid URL string. If the URL is
|
||||||
|
/// in the `mailto:` or `tel:` scheme and the `body` parameter is omitted,
|
||||||
|
/// the email address or phone number will be the link's body, without the
|
||||||
|
/// scheme.
|
||||||
|
///
|
||||||
|
/// - To link to another part of the document, `dest` must contain a
|
||||||
|
/// dictionary with a `page` key of type `integer` and `x` and `y`
|
||||||
|
/// coordinates of type `length`. Pages are counted from one, and the
|
||||||
|
/// coordinates are relative to the page's top left corner.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #link("mailto:hello@typst.app") \
|
||||||
|
/// #link((page: 1, x: 0pt, y: 0pt))[
|
||||||
|
/// Go to top
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// - body: Content (positional)
|
/// - body: Content (positional)
|
||||||
/// How the link is represented. Defaults to the destination if it is a link.
|
///
|
||||||
|
/// The content that should become a link. If `dest` is an URL string, the
|
||||||
|
/// parameter can be omitted. In this case, the URL will be shown as the link.
|
||||||
///
|
///
|
||||||
/// # Tags
|
/// # Tags
|
||||||
/// - meta
|
/// - meta
|
||||||
|
@ -3,7 +3,22 @@ use crate::layout::{BlockNode, HNode, HideNode, RepeatNode, Spacing};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{LinebreakNode, SpaceNode, TextNode};
|
use crate::text::{LinebreakNode, SpaceNode, TextNode};
|
||||||
|
|
||||||
/// A section outline (table of contents).
|
/// Generate a section outline / table of contents.
|
||||||
|
///
|
||||||
|
/// This function generates a list of all headings in the
|
||||||
|
/// document, up to a given depth. The [@heading] numbering will be reproduced
|
||||||
|
/// within the outline.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #outline()
|
||||||
|
///
|
||||||
|
/// = Introduction
|
||||||
|
/// #lorem(5)
|
||||||
|
///
|
||||||
|
/// = Prior work
|
||||||
|
/// #lorem(10)
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Tags
|
/// # Tags
|
||||||
/// - meta
|
/// - meta
|
||||||
@ -15,18 +30,52 @@ pub struct OutlineNode;
|
|||||||
#[node]
|
#[node]
|
||||||
impl OutlineNode {
|
impl OutlineNode {
|
||||||
/// The title of the outline.
|
/// The title of the outline.
|
||||||
|
///
|
||||||
|
/// - When set to `{auto}`, an appropriate title for the [@text] language will
|
||||||
|
/// be used. This is the default.
|
||||||
|
/// - When set to `{none}`, the outline will not have a title.
|
||||||
|
/// - A custom title can be set by passing content.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const TITLE: Option<Smart<Content>> = Some(Smart::Auto);
|
pub const TITLE: Option<Smart<Content>> = Some(Smart::Auto);
|
||||||
|
|
||||||
/// The maximum depth up to which headings are included in the outline.
|
/// The maximum depth up to which headings are included in the outline. When
|
||||||
|
/// this arguement is `{none}`, all headings are included.
|
||||||
pub const DEPTH: Option<NonZeroUsize> = None;
|
pub const DEPTH: Option<NonZeroUsize> = None;
|
||||||
|
|
||||||
/// Whether to indent the subheadings to match their parents.
|
/// Whether to indent the subheadings to align the start of their numbering
|
||||||
|
/// with the title of their parents. This will only have an effect if a
|
||||||
|
/// [@heading] numbering is set.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #set heading(numbering: "1.a.")
|
||||||
|
///
|
||||||
|
/// #outline(indent: true)
|
||||||
|
///
|
||||||
|
/// = About ACME Corp.
|
||||||
|
///
|
||||||
|
/// == History
|
||||||
|
/// #lorem(10)
|
||||||
|
///
|
||||||
|
/// == Products
|
||||||
|
/// #lorem(10)
|
||||||
|
/// ```
|
||||||
pub const INDENT: bool = false;
|
pub const INDENT: bool = false;
|
||||||
|
|
||||||
/// The fill symbol.
|
/// The symbol used to fill the space between the title and the page
|
||||||
|
/// number. Can be set to `none` to disable filling. The default is a
|
||||||
|
/// single dot.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #outline(
|
||||||
|
/// fill: pad(x: -1.5pt)[―]
|
||||||
|
/// )
|
||||||
|
///
|
||||||
|
/// = A New Beginning
|
||||||
|
/// ```
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const FILL: Option<EcoString> = Some('.'.into());
|
pub const FILL: Option<Content> = Some(TextNode::packed("."));
|
||||||
|
|
||||||
fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self.pack())
|
Ok(Self.pack())
|
||||||
@ -132,7 +181,7 @@ impl Show for OutlineNode {
|
|||||||
// Add filler symbols between the section name and page number.
|
// Add filler symbols between the section name and page number.
|
||||||
if let Some(filler) = styles.get(Self::FILL) {
|
if let Some(filler) = styles.get(Self::FILL) {
|
||||||
seq.push(SpaceNode.pack());
|
seq.push(SpaceNode.pack());
|
||||||
seq.push(RepeatNode(TextNode::packed(filler.clone())).pack());
|
seq.push(RepeatNode(filler.clone()).pack());
|
||||||
seq.push(SpaceNode.pack());
|
seq.push(SpaceNode.pack());
|
||||||
} else {
|
} else {
|
||||||
let amount = Spacing::Fractional(Fr::one());
|
let amount = Spacing::Fractional(Fr::one());
|
||||||
|
@ -3,6 +3,18 @@ use crate::text::TextNode;
|
|||||||
|
|
||||||
/// A reference to a label.
|
/// A reference to a label.
|
||||||
///
|
///
|
||||||
|
/// *Note: This function is currently unimplemented.*
|
||||||
|
///
|
||||||
|
/// The reference function produces a textual reference to a label. For example,
|
||||||
|
/// a reference to a heading will yield an appropriate string such as "Section
|
||||||
|
/// 1" for a reference to the first heading's label. The references are also
|
||||||
|
/// links to the respective labels.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// This function also has dedicated syntax: A reference to a label can be
|
||||||
|
/// created by typing an `@` followed by the name of the label (e.g. `[=
|
||||||
|
/// Introduction <intro>]` can be referenced by typing `[@intro]`).
|
||||||
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - target: Label (positional, required)
|
/// - target: Label (positional, required)
|
||||||
/// The label that should be referenced.
|
/// The label that should be referenced.
|
||||||
|
@ -112,7 +112,6 @@ pub fn example(docs: &mut String) -> Option<String> {
|
|||||||
.skip_while(|line| !line.contains("```"))
|
.skip_while(|line| !line.contains("```"))
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.take_while(|line| !line.contains("```"))
|
.take_while(|line| !line.contains("```"))
|
||||||
.map(|s| s.trim())
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
)
|
)
|
||||||
|
@ -91,7 +91,10 @@ fn documentation(attrs: &[syn::Attribute]) -> String {
|
|||||||
|
|
||||||
/// Dedent documentation text.
|
/// Dedent documentation text.
|
||||||
fn dedent(text: &str) -> String {
|
fn dedent(text: &str) -> String {
|
||||||
text.lines().map(str::trim).collect::<Vec<_>>().join("\n")
|
text.lines()
|
||||||
|
.map(|s| s.strip_prefix(" ").unwrap_or(s))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Quote an optional value.
|
/// Quote an optional value.
|
||||||
|
@ -476,7 +476,10 @@ impl Eval for ast::Frac {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok((vm.items.math_frac)(self.num().eval(vm)?, self.denom().eval(vm)?))
|
Ok((vm.items.math_frac)(
|
||||||
|
self.num().eval(vm)?,
|
||||||
|
self.denom().eval(vm)?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -778,7 +781,11 @@ impl Eval for ast::FieldAccess {
|
|||||||
.field(&field)
|
.field(&field)
|
||||||
.ok_or_else(|| format!("unknown field {field:?}"))
|
.ok_or_else(|| format!("unknown field {field:?}"))
|
||||||
.at(span)?,
|
.at(span)?,
|
||||||
v => bail!(self.target().span(), "cannot access field on {}", v.type_name()),
|
v => bail!(
|
||||||
|
self.target().span(),
|
||||||
|
"cannot access field on {}",
|
||||||
|
v.type_name()
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user