diff --git a/ext/LaTeXStringsExtension.jl b/ext/LaTeXStringsExtension.jl index 095568c..607a3ac 100644 --- a/ext/LaTeXStringsExtension.jl +++ b/ext/LaTeXStringsExtension.jl @@ -5,8 +5,6 @@ import Typstry: show_typst using LaTeXStrings: LaTeXString, @L_str using Typstry: compile_workload, show_raw -# Strings - """ show_typst(io, ::LaTeXString) @@ -18,8 +16,6 @@ Print in Typst format for LaTeXStrings.jl. """ show_typst(io, tc, x::LaTeXString) = show_raw(print, io, tc, x, "latex") -# Internals - const examples = [L"a" => LaTeXString] compile_workload(examples) diff --git a/ext/MarkdownExtension.jl b/ext/MarkdownExtension.jl index 4f6c9c7..d1298af 100644 --- a/ext/MarkdownExtension.jl +++ b/ext/MarkdownExtension.jl @@ -5,8 +5,6 @@ import Typstry: show_typst using Markdown: MD, @md_str using Typstry: compile_workload, show_raw -# Strings - """ show_typst(io, ::Markdown.MD) @@ -25,8 +23,6 @@ show_typst(io, tc, x::MD) = show_raw(io, tc, x, "markdown") do io, x print(io, read(buffer, String)[begin:end - 1]) end -# Internals - const examples = [md"# A" => MD] compile_workload(examples) diff --git a/src/Typstry.jl b/src/Typstry.jl index 9fce05f..7912880 100644 --- a/src/Typstry.jl +++ b/src/Typstry.jl @@ -25,6 +25,42 @@ export @typst_cmd, @typst_str, code, context, julia_mono, markup, math, render, set_context, show_typst, typst +""" + examples + +A constant `Vector` of Julia values and their corresponding +`Type`s implemented for [`show_typst`](@ref). +""" +const examples = [ + Any[true, 1, 1.2, 1 // 2] => AbstractArray + 'a' => AbstractChar + 1.2 => AbstractFloat + Any[true 1; 1.2 1 // 2] => AbstractMatrix + "a" => AbstractString + true => Bool + im => Complex{Bool} + 1 + 2im => Complex + π => Irrational + nothing => Nothing + 0:2:6 => OrdinalRange{<:Integer, <:Integer} + 1 // 2 => Rational + r"[a-z]" => Regex + 1 => Signed + StepRangeLen(0, 2, 4) => StepRangeLen{<:Integer, <:Integer, <:Integer} + (true, 1, 1.2, 1 // 2) => Tuple + Typst(1) => Typst + typst"[\"a\"]" => TypstString + TypstText([1, 2, 3, 4]) => TypstText + 0xff => Unsigned + v"1.2.3" => VersionNumber + html"

a

" => HTML + text"[\"a\"]" => Text + Date(1) => Date + DateTime(1) => DateTime + Day(1) => Period + Time(0) => Time +] + compile_workload(examples) end # Typstry diff --git a/src/commands/commands.jl b/src/commands/commands.jl index f1a29dd..7806795 100644 --- a/src/commands/commands.jl +++ b/src/commands/commands.jl @@ -1,60 +1,7 @@ -""" - typst_command_error(tc) -""" -typst_command_error(tc) = TypstCommandError(tc) - include("typst_command.jl") include("typst_command_error.jl") -# Internals - -""" - format(::Union{MIME"application/pdf", MIME"image/png", MIME"image/svg+xml"}) - -Return the image format acronym corresponding to the given `MIME`. - -# Examples - -```jldoctest -julia> Typstry.format(MIME"application/pdf"()) -"pdf" - -julia> Typstry.format(MIME"image/png"()) -"png" - -julia> Typstry.format(MIME"image/svg+xml"()) -"svg" -``` -""" -format(::MIME"application/pdf") = "pdf" -format(::MIME"image/png") = "png" -format(::MIME"image/svg+xml") = "svg" - -# `Typstry` - -""" - @typst_cmd("s") - typst`s` - -Construct a [`TypstCommand`](@ref) where each parameter is separated by a space. - -This does not support interpolation; use the constructor instead. - -# Examples - -```jldoctest -julia> typst`help` -typst`help` - -julia> typst`compile input.typ output.typ` -typst`compile input.typ output.typ` -``` -""" -macro typst_cmd(parameters::String) - :(TypstCommand($(isempty(parameters) ? String[] : map(string, split(parameters, " "))))) -end - """ julia_mono @@ -103,7 +50,7 @@ function render(value; ) Base.open(input; truncate = true) do file tc = render!(context) - print(file, unwrap(tc, TypstString, :preamble)) + print(file, preamble(tc)) _show_typst(file, tc, value) println(file) end @@ -112,32 +59,6 @@ function render(value; ignorestatus)) end -""" - typst(::AbstractString; catch_interrupt = true, ignorestatus = true) - -Convenience function intended for interactive use, emulating the typst -command line interface. Be aware, however, that it strictly splits -on spaces and does not provide any shell-style escape mechanism, -so it will not work if there are, e.g., filenames with spaces. - -When `catch_interrupt` is true, CTRL-C quietly quits the command. -When [`ignorestatus`](@ref) is true, a Typst failure will not imply a julia error. - -If the `"TYPST_FONT_PATHS"` environment variable is not set, -it is temporarily set to [`julia_mono`](@ref). -""" -function typst(parameters::AbstractString; catch_interrupt = true, ignorestatus = true) - tc = addenv(TypstCommand(TypstCommand(split(parameters)); ignorestatus), - "TYPST_FONT_PATHS" => get(ENV, "TYPST_FONT_PATHS", julia_mono)) - if catch_interrupt - try run(tc) - catch e e isa InterruptException || rethrow() - end - else run(tc) - end - nothing -end - """ show(::IO, ::Union{ MIME"application/pdf", MIME"image/png", MIME"image/svg+xml" @@ -163,9 +84,34 @@ function show(io::IO, m::Union{ input = tempname() output = input * "." * format(m) - render(t; input, output, open = false, ignorestatus = false, - context = unwrap(io, :typst_context, TypstContext())) + render(t; input, output, open = false, ignorestatus = false, context = typst_context(io)) write(io, read(output)) nothing end + +""" + typst(::AbstractString; catch_interrupt = true, ignorestatus = true) + +Convenience function intended for interactive use, emulating the typst +command line interface. Be aware, however, that it strictly splits +on spaces and does not provide any shell-style escape mechanism, +so it will not work if there are, e.g., filenames with spaces. + +When `catch_interrupt` is true, CTRL-C quietly quits the command. +When [`ignorestatus`](@ref) is true, a Typst failure will not imply a julia error. + +If the `"TYPST_FONT_PATHS"` environment variable is not set, +it is temporarily set to [`julia_mono`](@ref). +""" +function typst(parameters::AbstractString; catch_interrupt = true, ignorestatus = true) + tc = addenv(TypstCommand(TypstCommand(split(parameters)); ignorestatus), + "TYPST_FONT_PATHS" => get(ENV, "TYPST_FONT_PATHS", julia_mono)) + if catch_interrupt + try run(tc) + catch e e isa InterruptException || rethrow() + end + else run(tc) + end + nothing +end diff --git a/src/commands/typst_command.jl b/src/commands/typst_command.jl index a93da78..10f15cd 100644 --- a/src/commands/typst_command.jl +++ b/src/commands/typst_command.jl @@ -1,13 +1,4 @@ -""" - apply(f, tc, args...; kwargs...) -""" -function apply(f, tc, args...; kwargs...) - _tc = deepcopy(tc) - _tc.compiler = f(_tc.compiler, args...; kwargs...) - _tc -end - """ TypstCommand(::AbstractVector{<:AbstractString}) TypstCommand(::TypstCommand; kwargs...) @@ -21,31 +12,31 @@ Keyword parameters have the same semantics as for a `Cmd`. This type implements the `Cmd` interface. However, the interface is undocumented, which may result in unexpected behavior. -- `addenv(::TypstCommand, env...; ::Bool = true)` +- `addenv(::TypstCommand,\u00A0env...;\u00A0::Bool\u00A0=\u00A0true)` - Can be used with [`julia_mono`](@ref) - - `addenv(::TypstCommand, "TYPST_FONT_PATHS" => julia_mono)` + - `addenv(::TypstCommand,\u00A0"TYPST_FONT_PATHS"\u00A0=>\u00A0julia_mono)` - `detach(::TypstCommand)` - `eltype(::Type{TypstCommand})` - `firstindex(::TypstCommand)` -- `getindex(::TypstCommand, i)` -- `hash(::TypstCommand, ::UInt)` +- `getindex(::TypstCommand,\u00A0i)` +- `hash(::TypstCommand,\u00A0::UInt)` - `ignorestatus(::TypstCommand)` - Do not throw a [`TypstCommandError`](@ref) if the Typst compiler throws an error. Errors thrown by the Typst compiler are printed to `stderr` regardless. -- `iterate(::TypstCommand, i)` +- `iterate(::TypstCommand,\u00A0i)` - `iterate(::TypstCommand)` - `keys(::TypstCommand)` - `lastindex(::TypstCommand)` - `length(::TypstCommand)` -- `run(::TypstCommand, args...; ::Bool = true)` +- `run(::TypstCommand,\u00A0args...;\u00A0::Bool\u00A0=\u00A0true)` - Errors thrown by the Typst compiler will be printed to `stderr`. Then, a Julia [`TypstCommandError`](@ref) will be thrown unless the [`ignorestatus`](@ref) flag is set. -- `setcpuaffinity(::TypstCommand, cpus)` -- `setenv(::TypstString, env...; kwargs...)` +- `setcpuaffinity(::TypstCommand,\u00A0cpus)` +- `setenv(::TypstString,\u00A0env...;\u00A0kwargs...)` - Can be used with [`julia_mono`](@ref) - - `setenv(::TypstCommand, "TYPST_FONT_PATHS" => julia_mono)` -- `show(::IO, ::MIME"text/plain", ::TypstCommand)` + - `setenv(::TypstCommand,\u00A0"TYPST_FONT_PATHS"\u00A0=>\u00A0julia_mono)` +- `show(::IO,\u00A0::MIME"text/plain",\u00A0::TypstCommand)` # Examples @@ -67,6 +58,28 @@ mutable struct TypstCommand new(tc.parameters, ignorestatus, Cmd(tc.compiler; kwargs...)) end +""" + @typst_cmd("s") + typst`s` + +Construct a [`TypstCommand`](@ref) where each parameter is separated by a space. + +This does not support interpolation; use the constructor instead. + +# Examples + +```jldoctest +julia> typst`help` +typst`help` + +julia> typst`compile input.typ output.typ` +typst`compile input.typ output.typ` +``` +""" +macro typst_cmd(parameters::String) + :(TypstCommand($(isempty(parameters) ? String[] : map(string, split(parameters, " "))))) +end + tc::TypstCommand == _tc::TypstCommand = tc.compiler == _tc.compiler && tc.parameters == _tc.parameters && @@ -103,7 +116,7 @@ length(tc::TypstCommand) = length(tc.parameters) + 1 function run(tc::TypstCommand, args...; wait::Bool = true) process = run(ignorestatus(Cmd(`$(tc.compiler) $(tc.parameters)`)), args...; wait) - tc.ignore_status || success(process) || throw(typst_command_error(tc)) + tc.ignore_status || success(process) || throw(TypstCommandError(tc)) process end diff --git a/src/commands/typst_command_error.jl b/src/commands/typst_command_error.jl index f0376d9..ccc73a7 100644 --- a/src/commands/typst_command_error.jl +++ b/src/commands/typst_command_error.jl @@ -9,8 +9,8 @@ An `Exception` indicating a failure to [`run`](@ref) a [`TypstCommand`](@ref). Implements the `Exception` interface. -- `showerror(::IO, ::TypstCommandError)` -- `show(::IO, ::MIME"text/plain", ::TypstCommandError)` +- `showerror(::IO,\u00A0::TypstCommandError)` +- `show(::IO,\u00A0::MIME"text/plain",\u00A0::TypstCommandError)` # Examples ```jldoctest diff --git a/src/strings/show_typst.jl b/src/strings/show_typst.jl index 0f11162..d1ad3fb 100644 --- a/src/strings/show_typst.jl +++ b/src/strings/show_typst.jl @@ -1,159 +1,4 @@ -""" - code_mode(io, tc) - -Print the number sign, unless `mode(tc) == code`. - -See also [`Mode`](@ref) and [`mode`](@ref Typstry.mode). -""" -code_mode(io, tc) = if mode(tc) ≠ code print(io, "#") end - -""" - indent(tc) -""" -indent(tc) = " " ^ tab_size(tc) - -""" - math_mode(f, io, tc, x; kwargs...) -""" -math_mode(f, io, tc, x; kwargs...) = enclose(f, io, x, math_pad(tc); kwargs...) - -""" - math_pad(tc) - -Return `""`, `"\\\$"`, or `"\\\$ "` depending on the -[`block`](@ref Typstry.block) and [`mode`](@ref Typstry.mode) settings. -""" -math_pad(tc) = - if mode(tc) == math "" - else block(tc) ? "\$ " : "\$" - end - -""" - show_parameters(io, tc, f, keys, final) -""" -function show_parameters(io, tc, f, keys, final) - pairs = map(key -> key => unwrap(tc, TypstString, key), filter(key -> haskey(tc, key), keys)) - - println(io, f, "(") - join_with(io, pairs, ",\n") do io, (key, value) - print(io, indent(tc) ^ (depth(tc) + 1), key, ": ") - _show_typst(io, value) - end - - if !isempty(pairs) - final && print(io, ",") - println(io) - end -end - -""" - show_array(io, x) -""" -show_array(io, x) = enclose(io, x, "(", ")") do io, x - join_with((io, x) -> _show_typst(io, x; parenthesize = false, mode = code), io, x, ", ") - if length(x) == 1 print(io, ",") end -end - -""" - show_raw(f, io, tc, x, language) -""" -function show_raw(f, io, tc, x, language) - _backticks, _block = "`" ^ backticks(tc), block(tc) - - mode(tc) == math && print(io, "#") - print(io, _backticks, language) - - if _block - _indent, _depth = indent(tc), depth(tc) - - print(io, "\n") - - for line in eachsplit(sprint(f, x), "\n") - println(io, _indent ^ (_depth + 1), line) - end - - print(io, _indent ^ _depth) - else enclose(f, io, x, " ") - end - - print(io, _backticks) -end - -""" - show_vector(io, tc, x) -""" -show_vector(io, tc, x) = math_mode(io, tc, x) do io, x - _depth, _indent = depth(tc), indent(tc) - __depth = _depth + 1 - - show_parameters(io, tc, "vec", [:delim, :gap], true) - print(io, _indent ^ __depth) - join_with((io, x) -> _show_typst(io, TypstContext(; depth = __depth, mode = math, parenthesize = false), x), io, x, ", "), - print(io, "\n", _indent ^ _depth, ")") -end - -for (key, value) in pairs(default_context) - @eval begin - $key(context) = unwrap(context, $(QuoteNode(key)), $value) - @doc "$($key)" $key - end -end - -## Dates.jl - -""" - date_time(::Union{Dates.Date, Dates.Time, Dates.DateTime}) -""" -date_time(::Date) = year, month, day -date_time(::Time) = hour, minute, second -date_time(::DateTime) = year, month, day, hour, minute, second - -""" - duration(::Dates.Period) - -# Examples - -```jldoctest -julia> Typstry.duration(Dates.Day(1)) -:days - -julia> Typstry.duration(Dates.Hour(1)) -:hours -``` -""" -duration(::Day) = :days -duration(::Hour) = :hours -duration(::Minute) = :minutes -duration(::Second) = :seconds -duration(::Week) = :weeks - -""" - dates(::Union{Dates.Date, Dates.DateTime, Dates.Period, Dates.Time}) - -# Examples - -```jldoctest -julia> Typstry.dates(Dates.Date(1)) -("datetime", (:year, :month, :day), (1, 1, 1)) - -julia> Typstry.dates(Dates.Day(1)) -("duration", (:days,), (TypstText{String}("1"),)) -``` -""" -function dates(x::Union{Date, DateTime, Time}) - fs = date_time(x) - "datetime", map(Symbol, fs), map(f -> f(x), fs) -end -function dates(x::Period) - buffer = IOBuffer() - - print(buffer, x) - seekstart(buffer) - - "duration", (duration(x),), (TypstText(readuntil(buffer, " ")),) -end - function _show_typst(io, tc, x) _tc = TypstContext(x) merge!(merge_contexts!(_tc, context), tc) @@ -174,21 +19,21 @@ show_typst(io, tc, x::AbstractFloat) = end show_typst(io, tc, x::AbstractMatrix) = mode(tc) == code ? show_array(io, x) : - math_mode((io, x; indent, depth) -> begin - _depth = depth + 1 + math_mode((io, tc, x) -> begin + _depth = depth(tc) + 1 show_parameters(io, tc, "mat", [:augment, :column_gap, :delim, :gap, :row_gap], true) - join_with((io, x; indent) -> begin - print(io, indent ^ _depth) + join_with((io, x) -> begin + print(io, indent(tc) ^ _depth) join_with((io, x) -> _show_typst(io, x; depth = _depth, mode = math, parenthesize = false), io, x, ", ") - end, io, eachrow(x), ";\n"; indent) - print(io, "\n", indent ^ depth, ")") - end, io, TypstContext(; mode = mode(tc)), x; indent = indent(tc), depth = depth(tc)) + end, io, eachrow(x), ";\n") + print(io, "\n", indent(tc) ^ depth(tc), ")") + end, io, tc, x) show_typst(io, tc, x::AbstractString) = enclose((io, x) -> escape_string(io, x, "\""), io, x, "\"", mode(tc) == math && length(x) == 1 ? "\\u{200b}\"" : "\"") show_typst(io, tc, x::Bool) = mode(tc) == math ? enclose(print, io, x, "\"") : print(io, x) show_typst(io, tc, x::Complex{Bool}) = _show_typst(io, tc, Complex(Int(real(x)), Int(imag(x)))) -show_typst(io, tc, x::Complex) = math_mode(io, tc, x) do io, x +show_typst(io, tc, x::Complex) = math_mode(io, tc, x) do io, tc, x imaginary = imag(x) _real, _imaginary = real(x), abs(imaginary) __real, __imaginary = _real == 0, _imaginary == 0 @@ -245,9 +90,7 @@ function show_typst(io, tc, x::Text) code_mode(io, tc) _show_typst(io, string(x)) end -show_typst(io, tc, x::Typst) = show_typst(io, tc, x.value) show_typst(io, tc, x::TypstString) = print(io, x) -show_typst(io, tc, x::TypstText) = print(io, x.value) function show_typst(io, tc, x::Unsigned) code_mode(io, tc) show(io, x) @@ -279,7 +122,7 @@ function show_typst(io, tc, x::Union{Date, DateTime, Period, Time}) _values = map(value -> TypstString(value; mode = code), values) code_mode(io, tc) - show_parameters(io, TypstContext(; zip(keys, _values)...), f, keys, false) + show_parameters(io, merge_contexts!(TypstContext(; zip(keys, _values)...), tc), f, keys, false) print(io, indent(tc) ^ depth(tc), ")") end show_typst(tc, x) = _show_typst(stdout, tc, x) diff --git a/src/strings/strings.jl b/src/strings/strings.jl index 28e93fb..89dc05f 100644 --- a/src/strings/strings.jl +++ b/src/strings/strings.jl @@ -1,168 +1,9 @@ -""" - Mode - -An `Enum`erated type used to specify that the current Typst syntactical -context is [`code`](@ref), [`markup`](@ref), or [`math`](@ref). - -# Examples - -```jldoctest -julia> Mode -Enum Mode: -code = 0 -markup = 1 -math = 2 -``` -""" -@enum Mode code markup math - -""" - Typst{T} - Typst(::T) - -A wrapper used to pass values to -[`show(::IO,\u00A0::MIME"text/typst",\u00A0::Typst)`](@ref). - -# Examples - -```jldoctest -julia> Typst(1) -Typst{Int64}(1) - -julia> Typst("a") -Typst{String}("a") -``` -""" -struct Typst{T} - value::T -end - -""" - TypstText{T} - TypstText(::Any) - -A wrapper whose [`show_typst`](@ref) method uses `print`. - -# Examples - -```jldoctest -julia> TypstText(1) -TypstText{Int64}(1) - -julia> TypstText("a") -TypstText{String}("a") -``` -""" -struct TypstText{T} - value::T -end - +include("types.jl") include("typst_context.jl") include("typst_string.jl") include("show_typst.jl") -""" - @typst_str("s") - typst"s" - -Construct a [`TypstString`](@ref). - -Control characters are escaped, -except double quotation marks and backslashes in the same manner as `@raw_str`. -Values may be interpolated by calling the `TypstString` constructor, -except using a backslash instead of the type name. -Interpolation syntax may be escaped in the same manner as quotation marks. - -!!! tip - Print directly to an `IO` using - [`show(::IO,\u00A0::MIME"text/typst",\u00A0::Typst)`](@ref). - - See also the performance tip to [Avoid string interpolation for I/O] - (https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-string-interpolation-for-I/O). - -# Examples - -```jldoctest -julia> x = 1; - -julia> typst"\$ \\(x; mode = math) / \\(x + 1; mode = math) \$" -typst"\$ 1 / 2 \$" - -julia> typst"\\(x//2)" -typst"\$1 / 2\$" - -julia> typst"\\(x // 2; mode = math)" -typst"(1 / 2)" - -julia> typst"\\\\(x)" -typst"\\\\(x)" -``` -""" -macro typst_str(s::String) - filename = __source__.file - current, final = firstindex(s), lastindex(s) - _s = Expr(:string) - args = _s.args - - while (regex_match = match(r"(\\+)(\()", s, current)) ≢ nothing - backslashes, start = length(first(regex_match.captures)), last(regex_match.offsets) - interpolate, previous = isodd(backslashes), prevind(s, start) - - current < previous && push!(args, s[current:prevind(s, previous, interpolate + backslashes ÷ 2)]) - - if interpolate - x, current = parse(s, start; filename, greedy = false) - isexpr(x, :incomplete) && throw(first(x.args)) - interpolation = :($TypstString()) - - append!(interpolation.args, parse(s[previous:prevind(s, current)]; filename).args[2:end]) - push!(args, esc(interpolation)) - else current = start - end - end - - current > final || push!(args, s[current:final]) - :(TypstString(TypstText($_s))) -end - -# `Typstry` - -@doc """ - code - -A Typst syntactical [`Mode`](@ref) prefixed by the number sign. - -# Examples - -```jldoctest -julia> code -code::Mode = 0 -``` -""" code - -@doc """ - markup - -A Typst syntactical [`Mode`](@ref) at the top-level of source text and enclosed within square brackets. - -```jldoctest -julia> markup -markup::Mode = 1 -``` -""" markup - -@doc """ - math - -A Typst syntactical [`Mode`](@ref) enclosed within dollar signs. - -```jldoctest -julia> math -math::Mode = 2 -``` -""" math - """ show(::IO, ::MIME"text/typst", ::Union{Typst, TypstString, TypstText}) @@ -179,57 +20,22 @@ See also [`TypstString`](@ref) and [`TypstText`](@ref). show(io::IO, ::MIME"text/typst", t::Union{Typst, TypstString, TypstText}) = _show_typst(io, t) show(io::IOContext, ::MIME"text/typst", t::Union{Typst, TypstString, TypstText}) = - _show_typst(io, unwrap(io, :typst_context, TypstContext()), t) - -# Internals - -""" - compile_workload(examples) + _show_typst(io, typst_context(io), t) -Given an iterable of value-type pairs, interpolate each value into -a `@typst_str` within a `PrecompileTools.@compile_workload` block. -""" -compile_workload(examples) = @compile_workload for (example, _) in examples - TypstString(example) +function show(io::IO, x::T) where T <: Union{TypstText, Typst} + print(io, nameof(T), "(") + show(io, x.value) + print(io, ")") end -""" - examples - -A constant `Vector` of Julia values and their corresponding -`Type`s implemented for [`show_typst`](@ref). -""" -const examples = [ - Any[true, 1, 1.2, 1 // 2] => AbstractArray - 'a' => AbstractChar - 1.2 => AbstractFloat - Any[true 1; 1.2 1 // 2] => AbstractMatrix - "a" => AbstractString - true => Bool - im => Complex{Bool} - 1 + 2im => Complex - π => Irrational - nothing => Nothing - 0:2:6 => OrdinalRange{<:Integer, <:Integer} - 1 // 2 => Rational - r"[a-z]" => Regex - 1 => Signed - StepRangeLen(0, 2, 4) => StepRangeLen{<:Integer, <:Integer, <:Integer} - (true, 1, 1.2, 1 // 2) => Tuple - Typst(1) => Typst - typst"[\"a\"]" => TypstString - TypstText([1, 2, 3, 4]) => TypstText - 0xff => Unsigned - v"1.2.3" => VersionNumber - html"

a

" => HTML - text"[\"a\"]" => Text - Date(1) => Date - DateTime(1) => DateTime - Day(1) => Period - Time(0) => Time -] - -get!(context.context, :preamble, TypstString(TypstText(""" +get!(context.context, :preamble, typst""" #set page(margin: 1em, height: auto, width: auto, fill: white) #set text(16pt, font: "JuliaMono") -"""))) +""") + +for (key, value) in pairs(context) + @eval begin + $key(tc) = unwrap(tc, $(typeof(value)), $(QuoteNode(key))) + @doc "$($key)" $key + end +end diff --git a/src/strings/types.jl b/src/strings/types.jl new file mode 100644 index 0000000..0ff8eae --- /dev/null +++ b/src/strings/types.jl @@ -0,0 +1,123 @@ + +""" + TypstText{T} + TypstText(::Any) + +A wrapper whose [`show_typst`](@ref) method uses `print` on the wrapped value. + +# Interface + +- `show_typst(::IO,\u00A0::TypstContext,\u00A0::TypstText)` +- `show(::IO,\u00A0::Union{MIME"application/pdf",\u00A0MIME"image/png",\u00A0MIME"image/svg+xml"},\u00A0::TypstText)` +- `show(::IO,\u00A0::TypstText)` + +# Examples + +```jldoctest +julia> TypstText(1) +TypstText(1) + +julia> TypstText("a") +TypstText("a") +``` +""" +struct TypstText{T} + value::T +end + +""" + Typst{T} + Typst(::T) + +A wrapper used to pass values to [`show_typst`](@ref). + +# Interface + +- `show_typst(::IO,\u00A0::TypstContext,\u00A0::Typst)` +- `show(::IO,\u00A0::Union{MIME"application/pdf",\u00A0MIME"image/png",\u00A0MIME"image/svg+xml"},\u00A0::Typst)` +- `show(::IO,\u00A0::Typst)` + +# Examples + +```jldoctest +julia> Typst(1) +Typst(1) + +julia> Typst("a") +Typst("a") +``` +""" +struct Typst{T} + value::T +end + +""" + Mode + +An `Enum`erated type used to specify that the current Typst syntactical +context is [`code`](@ref), [`markup`](@ref), or [`math`](@ref). + +# Examples + +```jldoctest +julia> Mode +Enum Mode: +code = 0 +markup = 1 +math = 2 +``` +""" +@enum Mode code markup math + +@doc """ + code + +A Typst syntactical [`Mode`](@ref) prefixed by the number sign. + +# Examples + +```jldoctest +julia> code +code::Mode = 0 +``` +""" code + +@doc """ + markup + +A Typst syntactical [`Mode`](@ref) at the top-level of source text and enclosed within square brackets. + +```jldoctest +julia> markup +markup::Mode = 1 +``` +""" markup + +@doc """ + math + +A Typst syntactical [`Mode`](@ref) enclosed within dollar signs. + +```jldoctest +julia> math +math::Mode = 2 +``` +""" math + +""" + show_typst(::IO, ::TypstContext, ::TypstText) + +Call `print` the value wrapped in [`TypstText`](@ref). + +See also [`TypstContext`](@ref). +""" +show_typst(io, _, tt::TypstText) = print(io, tt.value) + +""" + show_typst(::IO, ::TypstContext, ::Typst) + +Call [`show_typst`](@ref) on the value wrapped in [`Typst`](@ref). + +See also [`TypstContext`](@ref). +""" +show_typst(io, tc, x::Typst) = _show_typst(io, tc, x.value) diff --git a/src/strings/typst_context.jl b/src/strings/typst_context.jl index 688eef2..14d4c4a 100644 --- a/src/strings/typst_context.jl +++ b/src/strings/typst_context.jl @@ -1,22 +1,25 @@ """ TypstContext <: AbstractDict{Symbol, Any} + TypstContext(::Any) TypstContext(; kwargs...) Provide formatting data for [`show_typst`](@ref). +Implement a method of this constructor for a custom type to specify its custom settings and parameters. + # Interfaces -This type implements the dictionary, iteration, interfaces. +This type implements the dictionary and iteration interfaces. However, it is immutable such that it does not support inserting, deleting, or setting a key-value pair. - `eltype(::TypstContext)` -- `get(::TypstContext, ::Symbol, default)` -- `get(::Union{Function, Type}, ::TypstContext, ::Symbol)` -- `iterate(::TypstContext, state)` +- `get(::TypstContext,\u00A0::Symbol,\u00A0default)` +- `get(::Union{Function, Type},\u00A0::TypstContext,\u00A0::Symbol)` +- `iterate(::TypstContext,\u00A0state)` - `iterate(::TypstContext)` - `length(::TypstContext)` -- `show(::IO, ::TypstContext)` +- `show(::IO,\u00A0::TypstContext)` """ struct TypstContext <: AbstractDict{Symbol, Any} context::Dict{Symbol, Any} @@ -24,19 +27,7 @@ struct TypstContext <: AbstractDict{Symbol, Any} TypstContext(; kwargs...) = new(Dict(kwargs)) end -""" - TypstContext(::Typst) - -Return the `TypstContext` of the value wrapped in [`Typst`](@ref). -""" -TypstContext(x::Typst) = TypstContext(x.value) - -""" - TypstContext(::Any) - -Implement a method of this constructor for a custom type to specify its custom settings and parameters. -""" -TypstContext(x) = TypstContext() +TypstContext(_) = TypstContext() eltype(tc::TypstContext) = eltype(tc.context) @@ -72,11 +63,6 @@ const default_context = TypstContext(; tab_size = 2 ) -""" - merge_contexts(tc, context) -""" -merge_contexts!(tc, context) = mergewith!((x, _) -> x, tc.context, context) - """ context diff --git a/src/strings/typst_string.jl b/src/strings/typst_string.jl index d226b2d..4199f9d 100644 --- a/src/strings/typst_string.jl +++ b/src/strings/typst_string.jl @@ -1,35 +1,4 @@ -""" - typst_mime - -Equivalent to `MIME"text/typst"()`. - -# Examples - -```jldoctest -julia> Typstry.typst_mime -MIME type text/typst -``` -""" -const typst_mime = MIME"text/typst"() - -""" - escape(io, n) - -Print `\\` to `io` `n` times. - -# Examples - -```jldoctest -julia> Typstry.escape(stdout, 2) -\\\\ -``` -""" -escape(io, n) = - for _ in 1:n - print(io, '\\') - end - """ TypstString <: AbstractString TypstString(::TypstContext, ::Any) @@ -47,18 +16,19 @@ This type implements the `String` interface. However, the interface is undocumented, which may result in unexpected behavior. - `IOBuffer(::TypstString)` -- `codeunit(::TypstString, ::Integer)` +- `codeunit(::TypstString,\u00A0::Integer)` - `codeunit(::TypstString)` -- `isvalid(::TypstString, ::Integer)` -- `iterate(::TypstString, ::Integer)` +- `isvalid(::TypstString,\u00A0::Integer)` +- `iterate(::TypstString,\u00A0::Integer)` - `iterate(::TypstString)` - `ncodeunits(::TypstString)` - `pointer(::TypstString)` -- `repr(::MIME, ::TypstString)` +- `repr(::MIME,\u00A0::TypstString)` - This method patches incorrect output from the assumption in `repr` that the parameter is already in the requested `MIME` type when the `MIME` type satisfies `istextmime` and the parameter is an `AbstractString`. -- `show(::IO, ::TypstString)` +- `show(::IO,\u00A0::Union{MIME"application/pdf",\u00A0MIME"image/png",\u00A0MIME"image/svg+xml"},\u00A0::TypstString)` +- `show(::IO,\u00A0::TypstString)` - Print in [`@typst_str`](@ref) format if each character satisfies `isprint`. Otherwise, print in [`TypstString`](@ref) format. @@ -85,6 +55,70 @@ end TypstString(tc::TypstContext, x) = TypstString(tc, Typst(x)) TypstString(x; context...) = TypstString(TypstContext(; context...), x) +""" + @typst_str("s") + typst"s" + +Construct a [`TypstString`](@ref). + +Control characters are escaped, +except double quotation marks and backslashes in the same manner as `@raw_str`. +Values may be interpolated by calling the `TypstString` constructor, +except using a backslash instead of the type name. +Interpolation syntax may be escaped in the same manner as quotation marks. + +!!! tip + Print directly to an `IO` using + [`show(::IO,\u00A0::MIME"text/typst",\u00A0::Typst)`](@ref). + + See also the performance tip to [Avoid string interpolation for I/O] + (https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-string-interpolation-for-I/O). + +# Examples + +```jldoctest +julia> x = 1; + +julia> typst"\$ \\(x; mode = math) / \\(x + 1; mode = math) \$" +typst"\$ 1 / 2 \$" + +julia> typst"\\(x//2)" +typst"\$1 / 2\$" + +julia> typst"\\(x // 2; mode = math)" +typst"(1 / 2)" + +julia> typst"\\\\(x)" +typst"\\\\(x)" +``` +""" +macro typst_str(s::String) + filename = __source__.file + current, final = firstindex(s), lastindex(s) + _s = Expr(:string) + args = _s.args + + while (regex_match = match(r"(\\+)(\()", s, current)) ≢ nothing + backslashes, start = length(first(regex_match.captures)), last(regex_match.offsets) + interpolate, previous = isodd(backslashes), prevind(s, start) + + current < previous && push!(args, s[current:prevind(s, previous, interpolate + backslashes ÷ 2)]) + + if interpolate + x, current = parse(s, start; filename, greedy = false) + isexpr(x, :incomplete) && throw(first(x.args)) + interpolation = :($TypstString()) + + append!(interpolation.args, parse(s[previous:prevind(s, current)]; filename).args[2:end]) + push!(args, esc(interpolation)) + else current = start + end + end + + current > final || push!(args, s[current:final]) + :(TypstString(TypstText($_s))) +end + IOBuffer(ts::TypstString) = IOBuffer(ts.text) codeunit(ts::TypstString) = codeunit(ts.text) diff --git a/src/utilities/context_error.jl b/src/utilities/context_error.jl index 062d206..558e97e 100644 --- a/src/utilities/context_error.jl +++ b/src/utilities/context_error.jl @@ -9,8 +9,8 @@ An `Exception` indicating that a [`context`](@ref) key returned a value of an in Implements the `Exception` interface. -- `showerror(::IO, ::ContextError)` -- `show(::IO, ::MIME"text/plain", ::ContextError)` +- `showerror(::IO,\u00A0::ContextError)` +- `show(::IO,\u00A0::MIME"text/plain",\u00A0::ContextError)` # Examples @@ -30,15 +30,3 @@ showerror(io::IO, ce::ContextError) = print(io, "ContextError: the context key ` show(io::IO, ::MIME"text/plain", ce::ContextError) = print(io, ContextError, "(", ce.expected, ", ", ce.received, ", :", ce.key, ")") - -_unwrap(type, key, value) = value isa type ? value : throw(ContextError(type, typeof(value), key)) - -""" - unwrap(x, key::Symbol, default) - unwrap(x, type::Type, key) -""" -unwrap(x, key::Symbol, default) = _unwrap(typeof(default), key, get(x, key, default)) -function unwrap(x, type::Type, key) - value = x[key] - _unwrap(type, key, value) -end diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index 0390dee..eff6d65 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -1,6 +1,103 @@ include("context_error.jl") +""" + typst_mime + +Equivalent to `MIME"text/typst"()`. + +# Examples + +```jldoctest +julia> Typstry.typst_mime +MIME type text/typst +``` +""" +const typst_mime = MIME"text/typst"() + +""" + apply(f, tc, args...; kwargs...) +""" +function apply(f, tc, args...; kwargs...) + _tc = deepcopy(tc) + _tc.compiler = f(_tc.compiler, args...; kwargs...) + _tc +end + +""" + compile_workload(examples) + +Given an iterable of value-type pairs, interpolate each value into +a `@typst_str` within a `PrecompileTools.@compile_workload` block. +""" +compile_workload(examples) = @compile_workload for (example, _) in examples + TypstString(example) +end + +""" + code_mode(io, tc) + +Print the number sign, unless `mode(tc) == code`. + +See also [`Mode`](@ref) and [`mode`](@ref Typstry.mode). +""" +code_mode(io, tc) = if mode(tc) ≠ code print(io, "#") end + +date_time(::Date) = year, month, day +date_time(::Time) = hour, minute, second +date_time(::DateTime) = year, month, day, hour, minute, second + +@doc""" + date_time(::Union{Dates.Date, Dates.Time, Dates.DateTime}) +""" date_time + +function dates(x::Union{Date, DateTime, Time}) + fs = date_time(x) + "datetime", map(Symbol, fs), map(f -> f(x), fs) +end +function dates(x::Period) + buffer = IOBuffer() + + print(buffer, x) + seekstart(buffer) + + "duration", (duration(x),), (TypstText(readuntil(buffer, " ")),) +end + +@doc """ + dates(::Union{Dates.Date, Dates.DateTime, Dates.Period, Dates.Time}) + +# Examples + +```jldoctest +julia> Typstry.dates(Dates.Date(1)) +("datetime", (:year, :month, :day), (1, 1, 1)) + +julia> Typstry.dates(Dates.Day(1)) +("duration", (:days,), (TypstText("1"),)) +``` +""" dates + +duration(::Day) = :days +duration(::Hour) = :hours +duration(::Minute) = :minutes +duration(::Second) = :seconds +duration(::Week) = :weeks + +@doc """ + duration(::Dates.Period) + +# Examples + +```jldoctest +julia> Typstry.duration(Dates.Day(1)) +:days + +julia> Typstry.duration(Dates.Hour(1)) +:hours +``` +""" duration + """ enclose(f, io, x, left, right = reverse(left); kwargs...) @@ -19,6 +116,50 @@ function enclose(f, io, x, left, right = reverse(left); context...) print(io, right) end +""" + escape(io, n) + +Print `\\` to `io` `n` times. + +# Examples + +```jldoctest +julia> Typstry.escape(stdout, 2) +\\\\ +``` +""" +escape(io, n) = + for _ in 1:n + print(io, '\\') + end + +""" + indent(tc) +""" +indent(tc) = " " ^ tab_size(tc) + +""" + format(::Union{MIME"application/pdf", MIME"image/png", MIME"image/svg+xml"}) + +Return the image format acronym corresponding to the given `MIME`. + +# Examples + +```jldoctest +julia> Typstry.format(MIME"application/pdf"()) +"pdf" + +julia> Typstry.format(MIME"image/png"()) +"png" + +julia> Typstry.format(MIME"image/svg+xml"()) +"svg" +``` +""" +format(::MIME"application/pdf") = "pdf" +format(::MIME"image/png") = "png" +format(::MIME"image/svg+xml") = "svg" + """ join_with(f, io, xs, delimeter; kwargs...) @@ -39,3 +180,106 @@ function join_with(f, io, xs, delimeter; kwargs...) isempty(_xs) || print(io, delimeter) end end + +""" + math_mode(f, io, tc, x; kwargs...) +""" +math_mode(f, io, tc, x; kwargs...) = + enclose((io, x; kwargs...) -> f(io, tc, x; kwargs...), io, x, math_pad(tc); kwargs...) + +""" + math_pad(tc) + +Return `""`, `"\\\$"`, or `"\\\$ "` depending on the +[`block`](@ref Typstry.block) and [`mode`](@ref Typstry.mode) settings. +""" +math_pad(tc) = + if mode(tc) == math "" + else block(tc) ? "\$ " : "\$" + end + +""" + merge_contexts(tc, context) +""" +merge_contexts!(tc, context) = mergewith!((x, _) -> x, tc.context, context) + +""" + show_array(io, x) +""" +show_array(io, x) = enclose(io, x, "(", ")") do io, x + join_with((io, x) -> _show_typst(io, x; parenthesize = false, mode = code), io, x, ", ") + if length(x) == 1 print(io, ",") end +end + +""" + show_parameters(io, tc, f, keys, final) +""" +function show_parameters(io, tc, f, keys, final) + pairs = map(key -> key => unwrap(tc, TypstString, key), filter(key -> haskey(tc, key), keys)) + + println(io, f, "(") + join_with(io, pairs, ",\n") do io, (key, value) + print(io, indent(tc) ^ (depth(tc) + 1), key, ": ") + _show_typst(io, value) + end + + if !isempty(pairs) + final && print(io, ",") + println(io) + end +end + +""" + show_raw(f, io, tc, x, language) +""" +function show_raw(f, io, tc, x, language) + _backticks, _block = "`" ^ backticks(tc), block(tc) + + mode(tc) == math && print(io, "#") + print(io, _backticks, language) + + if _block + _indent, _depth = indent(tc), depth(tc) + + print(io, "\n") + + for line in eachsplit(sprint(f, x), "\n") + println(io, _indent ^ (_depth + 1), line) + end + + print(io, _indent ^ _depth) + else enclose(f, io, x, " ") + end + + print(io, _backticks) +end + +""" + show_vector(io, tc, x) +""" +show_vector(io, tc, x) = math_mode(io, tc, x) do io, tc, x + _depth, _indent = depth(tc), indent(tc) + __depth = _depth + 1 + + show_parameters(io, tc, "vec", [:delim, :gap], true) + print(io, _indent ^ __depth) + join_with((io, x) -> _show_typst(io, TypstContext(; depth = __depth, mode = math, parenthesize = false), x), io, x, ", "), + print(io, "\n", _indent ^ _depth, ")") +end + +""" + typst_context(io) +""" +typst_context(io) = unwrap(io, :typst_context, TypstContext()) + +_unwrap(type, key, value) = value isa type ? value : throw(ContextError(type, typeof(value), key)) + +""" + unwrap(x, key::Symbol, default) + unwrap(x, type::Type, key) +""" +unwrap(x, key::Symbol, default) = _unwrap(typeof(default), key, get(x, key, default)) +function unwrap(x, type::Type, key) + value = x[key] + _unwrap(type, key, value) +end diff --git a/test/interface/TestStrings.jl b/test/interface/TestStrings.jl index 05b0ea3..f1f15d6 100644 --- a/test/interface/TestStrings.jl +++ b/test/interface/TestStrings.jl @@ -62,7 +62,7 @@ test_equal(f) = test_pairs((ts, s) -> f(ts) == f(s)) @test typst_int == typst_int @test typst_int != Typst(1.0) @test typeof(typst_int) == Typst{Int} - @test string(typst_int) == "Typst{Int64}(1)" + @test string(typst_int) == "Typst(1)" end @testset "`TypstString`" begin