Simplify CLI
This commit is contained in:
parent
36cae88799
commit
db158719d6
77
Cargo.lock
generated
77
Cargo.lock
generated
@ -14,12 +14,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
@ -92,9 +86,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
|
||||
checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@ -219,9 +213,9 @@ checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed3d8a5e20435ff00469e51a0d82049bae66504b5c429920dadf9bb54d47b3f"
|
||||
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror",
|
||||
@ -246,16 +240,6 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b07f5c05414a0d8caba4c17eef8dc8b5c8955fc7c68d324191c7a56d3f3449"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
@ -279,7 +263,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "iai"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/reknih/iai#f57a4452e7ac3b118e695e1feddb6ea02c4015be"
|
||||
source = "git+https://github.com/reknih/iai#3f0f92736408ebce6545808b98e0cb2aea89b7dd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@ -332,9 +316,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.112"
|
||||
version = "0.2.113"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@ -485,9 +469,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -559,15 +543,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "256cc9203115db152290219f35f3362e729301b59e2a391fb2721fe3fa155352"
|
||||
checksum = "d94a32ca845cdda27237a40beba9bd3d3858ac8fc5356eb9442bdeecfe34d9e0"
|
||||
dependencies = [
|
||||
"jpeg-decoder",
|
||||
"log",
|
||||
"pico-args",
|
||||
"png 0.17.2",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"usvg",
|
||||
]
|
||||
@ -632,18 +617,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.133"
|
||||
version = "1.0.135"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
|
||||
checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.133"
|
||||
version = "1.0.135"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
|
||||
checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -661,9 +646,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba1eead9e94aa5a2e02de9e7839f96a007f686ae7a1d57c7797774810d24908a"
|
||||
checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
@ -673,9 +658,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "svg2pdf"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8af8eebab963c97dc4ae380c0adb6063fdaaf586dd961b55205c6a9d646430"
|
||||
checksum = "b7feae49dae1a460ecd13b50e4389204672daac9c7133fd830132f44486ab84d"
|
||||
dependencies = [
|
||||
"image",
|
||||
"miniz_oxide 0.4.4",
|
||||
@ -694,9 +679,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -756,7 +741,7 @@ checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6"
|
||||
name = "typst"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
"codespan-reporting",
|
||||
"dirs",
|
||||
"filedescriptor",
|
||||
@ -831,12 +816,6 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-vo"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
@ -851,28 +830,22 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f472f6f5d41d3eaef059bc893dcd2382eefcdda3e04ebe0b2860c56b538e491e"
|
||||
checksum = "00f064d38f79ff69e3160e2fba884e4ede897061c15178041a3976371c68cab1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"float-cmp",
|
||||
"fontdb",
|
||||
"kurbo",
|
||||
"log",
|
||||
"pico-args",
|
||||
"rctree",
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher",
|
||||
"svgtypes",
|
||||
"ttf-parser",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
"unicode-vo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
96
Cargo.toml
96
Cargo.toml
@ -5,10 +5,63 @@ authors = ["The Typst Project Developers"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["cli", "fs", "layout-cache"]
|
||||
cli = ["anyhow", "codespan-reporting", "fs", "pico-args", "same-file"]
|
||||
default = ["fs", "layout-cache"]
|
||||
cli = ["fs", "pico-args", "codespan-reporting", "same-file"]
|
||||
fs = ["dirs", "memmap2", "same-file", "walkdir"]
|
||||
layout-cache = ["rand"]
|
||||
layout-cache = []
|
||||
|
||||
# Dependency updates:
|
||||
# - Bump ttf-parser when rustybuzz is updated
|
||||
# - Bump usvg and resvg in conjunction with svg2pdf
|
||||
|
||||
[dependencies]
|
||||
# Workspace
|
||||
typst-macros = { path = "./macros" }
|
||||
|
||||
# Utilities
|
||||
bytemuck = "1"
|
||||
fxhash = "0.2"
|
||||
itertools = "0.10"
|
||||
once_cell = "1"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
|
||||
# Text and font handling
|
||||
ttf-parser = "0.12"
|
||||
rustybuzz = "0.4"
|
||||
unicode-bidi = "0.3.5"
|
||||
unicode-segmentation = "1"
|
||||
unicode-xid = "0.2"
|
||||
xi-unicode = "0.3"
|
||||
|
||||
# Raster and vector graphics handling
|
||||
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
||||
resvg = { version = "0.20", default-features = false }
|
||||
usvg = { version = "0.20", default-features = false }
|
||||
|
||||
# PDF export
|
||||
miniz_oxide = "0.4"
|
||||
pdf-writer = "0.4"
|
||||
svg2pdf = "0.2"
|
||||
|
||||
# Command line interface
|
||||
pico-args = { version = "0.4", optional = true }
|
||||
codespan-reporting = { version = "0.11", optional = true }
|
||||
same-file = { version = "1", optional = true }
|
||||
|
||||
# File system loading
|
||||
dirs = { version = "4", optional = true }
|
||||
memmap2 = { version = "0.5", optional = true }
|
||||
walkdir = { version = "2", optional = true }
|
||||
|
||||
# Still here for layout cache evaluation, but must be activated manually.
|
||||
rand = { version = "0.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
filedescriptor = "0.8"
|
||||
iai = { git = "https://github.com/reknih/iai" }
|
||||
resvg = { version = "0.20", default-features = false }
|
||||
tiny-skia = "0.6.2"
|
||||
walkdir = "2"
|
||||
|
||||
[profile.dev]
|
||||
# Faster compilation
|
||||
@ -18,43 +71,6 @@ debug = 0
|
||||
# Faster test execution
|
||||
opt-level = 2
|
||||
|
||||
[dependencies]
|
||||
fxhash = "0.2"
|
||||
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
||||
itertools = "0.10"
|
||||
miniz_oxide = "0.4"
|
||||
once_cell = "1"
|
||||
pdf-writer = "0.4"
|
||||
rustybuzz = "0.4"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
svg2pdf = { version = "0.1", default-features = false, features = ["text", "png", "jpeg"] }
|
||||
ttf-parser = "0.12"
|
||||
typst-macros = { path = "./macros" }
|
||||
unicode-bidi = "0.3.5"
|
||||
unicode-segmentation = "1"
|
||||
unicode-xid = "0.2"
|
||||
usvg = { version = "0.19", default-features = false, features = ["text"] }
|
||||
xi-unicode = "0.3"
|
||||
anyhow = { version = "1", optional = true }
|
||||
codespan-reporting = { version = "0.11", optional = true }
|
||||
dirs = { version = "4", optional = true }
|
||||
memmap2 = { version = "0.5", optional = true }
|
||||
pico-args = { version = "0.4", optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
same-file = { version = "1", optional = true }
|
||||
walkdir = { version = "2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
filedescriptor = "0.8"
|
||||
iai = { git = "https://github.com/reknih/iai" }
|
||||
resvg = { version = "0.19", default-features = false, features = ["text"] }
|
||||
tiny-skia = "0.6.2"
|
||||
walkdir = "2"
|
||||
|
||||
# Dependencies updates:
|
||||
# - Bump ttf-parser when rustybuzz is updated
|
||||
# - Bump usvg and resvg in conjunction with svg2pdf
|
||||
|
||||
[[bin]]
|
||||
name = "typst"
|
||||
required-features = ["cli"]
|
||||
|
@ -150,6 +150,7 @@ impl LayoutCache {
|
||||
entries.retain(|f| f.hits() as f64 / f.age() as f64 > threshold);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "rand")]
|
||||
EvictionPolicy::Random => {
|
||||
// Fraction of items that should be kept.
|
||||
let threshold = self.max_size as f64 / len as f64;
|
||||
@ -340,6 +341,7 @@ pub enum EvictionPolicy {
|
||||
/// Evict the least frequently used item.
|
||||
LeastFrequentlyUsed,
|
||||
/// Evict randomly.
|
||||
#[cfg(feature = "rand")]
|
||||
Random,
|
||||
/// Use the pattern verdicts.
|
||||
Patterns,
|
||||
|
106
src/main.rs
106
src/main.rs
@ -3,7 +3,6 @@ use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::term::{self, termcolor, Config, Styles};
|
||||
use same_file::is_same_file;
|
||||
@ -15,20 +14,34 @@ use typst::loading::FsLoader;
|
||||
use typst::source::SourceStore;
|
||||
use typst::Context;
|
||||
|
||||
const HELP: &'static str = "\
|
||||
typst creates PDF files from .typ files
|
||||
|
||||
USAGE:
|
||||
typst [OPTIONS] <input.typ> [output.pdf]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Print this help
|
||||
|
||||
ARGS:
|
||||
<input.typ> Path input Typst file
|
||||
[output.pdf] Path to output PDF
|
||||
";
|
||||
|
||||
fn main() {
|
||||
if let Err(error) = try_main() {
|
||||
print_error(error).unwrap();
|
||||
let args = parse_args();
|
||||
let ok = args.is_ok();
|
||||
if let Err(msg) = args.and_then(try_main) {
|
||||
print_error(&msg).unwrap();
|
||||
if !ok {
|
||||
println!("\nfor more information, try --help");
|
||||
}
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// The main compiler logic.
|
||||
fn try_main() -> anyhow::Result<()> {
|
||||
let args = Args::from_env().unwrap_or_else(|_| {
|
||||
print_usage().unwrap();
|
||||
process::exit(2);
|
||||
});
|
||||
|
||||
fn try_main(args: Args) -> Result<(), String> {
|
||||
// Create a loader for fonts and files.
|
||||
let mut loader = FsLoader::new();
|
||||
|
||||
@ -51,24 +64,27 @@ fn try_main() -> anyhow::Result<()> {
|
||||
|
||||
// Ensure that the source file is not overwritten.
|
||||
if is_same_file(&args.input, &args.output).unwrap_or(false) {
|
||||
anyhow::bail!("source and destination files are the same");
|
||||
Err("source and destination files are the same")?;
|
||||
}
|
||||
|
||||
// Load the source file.
|
||||
let id = ctx.sources.load(&args.input).context("source file not found")?;
|
||||
let id = ctx
|
||||
.sources
|
||||
.load(&args.input)
|
||||
.map_err(|_| "failed to load source file")?;
|
||||
|
||||
// Typeset.
|
||||
match ctx.typeset(id) {
|
||||
// Export the PDF.
|
||||
Ok(frames) => {
|
||||
let buffer = export::pdf(&ctx, &frames);
|
||||
fs::write(&args.output, buffer).context("failed to write PDF file")?;
|
||||
fs::write(&args.output, buffer).map_err(|_| "failed to write PDF file")?;
|
||||
}
|
||||
|
||||
// Print diagnostics.
|
||||
Err(errors) => {
|
||||
print_diagnostics(&ctx.sources, *errors)
|
||||
.context("failed to print diagnostics")?;
|
||||
.map_err(|_| "failed to print diagnostics")?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,55 +96,41 @@ struct Args {
|
||||
output: PathBuf,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn from_env() -> Result<Self, anyhow::Error> {
|
||||
let mut parser = pico_args::Arguments::from_env();
|
||||
/// Parse command line arguments.
|
||||
fn parse_args() -> Result<Args, String> {
|
||||
let mut args = pico_args::Arguments::from_env();
|
||||
if args.contains(["-h", "--help"]) {
|
||||
print!("{}", HELP);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
// Parse free-standing arguments.
|
||||
let input = parser.free_from_str::<PathBuf>()?;
|
||||
let output = match parser.opt_free_from_str()? {
|
||||
Some(output) => output,
|
||||
None => {
|
||||
let name = input.file_name().context("source path is not a file")?;
|
||||
Path::new(name).with_extension("pdf")
|
||||
}
|
||||
};
|
||||
|
||||
// Don't allow excess arguments.
|
||||
if !parser.finish().is_empty() {
|
||||
anyhow::bail!("too many arguments");
|
||||
let input = args.free_from_str::<PathBuf>().map_err(|_| "missing input file")?;
|
||||
let output = match args.opt_free_from_str().ok().flatten() {
|
||||
Some(output) => output,
|
||||
None => {
|
||||
let name = input.file_name().ok_or("source path does not point to a file")?;
|
||||
Path::new(name).with_extension("pdf")
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self { input, output })
|
||||
// Don't allow excess arguments.
|
||||
if !args.finish().is_empty() {
|
||||
Err("too many arguments")?;
|
||||
}
|
||||
|
||||
Ok(Args { input, output })
|
||||
}
|
||||
|
||||
/// Print a usage message.
|
||||
fn print_usage() -> io::Result<()> {
|
||||
/// Print an application-level error (independent from a source file).
|
||||
fn print_error(msg: &str) -> io::Result<()> {
|
||||
let mut w = StandardStream::stderr(ColorChoice::Always);
|
||||
let styles = Styles::default();
|
||||
|
||||
w.set_color(&styles.header_help)?;
|
||||
write!(w, "usage")?;
|
||||
w.set_color(&styles.header_error)?;
|
||||
write!(w, "error")?;
|
||||
|
||||
w.set_color(&styles.header_message)?;
|
||||
writeln!(w, ": typst <input.typ> [output.pdf]")
|
||||
}
|
||||
|
||||
/// Print an error outside of a source file.
|
||||
fn print_error(error: anyhow::Error) -> io::Result<()> {
|
||||
let mut w = StandardStream::stderr(ColorChoice::Always);
|
||||
let styles = Styles::default();
|
||||
|
||||
for (i, cause) in error.chain().enumerate() {
|
||||
w.set_color(&styles.header_error)?;
|
||||
write!(w, "{}", if i == 0 { "error" } else { "cause" })?;
|
||||
|
||||
w.set_color(&styles.header_message)?;
|
||||
writeln!(w, ": {}", cause)?;
|
||||
}
|
||||
|
||||
w.reset()
|
||||
w.reset()?;
|
||||
writeln!(w, ": {msg}.")
|
||||
}
|
||||
|
||||
/// Print diagnostics messages to the terminal.
|
||||
|
@ -760,7 +760,12 @@ fn draw_image(
|
||||
let w = (scale * view_width.max(aspect * view_height)).ceil() as u32;
|
||||
let h = ((w as f32) / aspect).ceil() as u32;
|
||||
let mut pixmap = sk::Pixmap::new(w, h).unwrap();
|
||||
resvg::render(&tree, FitTo::Size(w, h), pixmap.as_mut());
|
||||
resvg::render(
|
||||
&tree,
|
||||
FitTo::Size(w, h),
|
||||
sk::Transform::identity(),
|
||||
pixmap.as_mut(),
|
||||
);
|
||||
pixmap
|
||||
}
|
||||
};
|
||||
@ -838,7 +843,7 @@ fn convert_usvg_fill(fill: &usvg::Fill) -> (sk::Paint<'static>, sk::FillRule) {
|
||||
let mut paint = sk::Paint::default();
|
||||
paint.anti_alias = true;
|
||||
|
||||
if let usvg::Paint::Color(usvg::Color { red, green, blue, alpha: _ }) = fill.paint {
|
||||
if let usvg::Paint::Color(usvg::Color { red, green, blue }) = fill.paint {
|
||||
paint.set_color_rgba8(red, green, blue, fill.opacity.to_u8())
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user