rhai/src/packages/blob_basic.rs

615 lines
20 KiB
Rust
Raw Normal View History

2021-11-23 07:58:54 +01:00
#![cfg(not(feature = "no_index"))]
#![allow(non_snake_case)]
use crate::eval::calc_offset_len;
2021-11-23 07:58:54 +01:00
use crate::plugin::*;
2021-12-15 05:06:17 +01:00
use crate::{
2021-12-27 05:27:31 +01:00
def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position,
2021-12-27 15:28:11 +01:00
RhaiResultOf, INT,
2021-12-15 05:06:17 +01:00
};
2021-11-23 07:58:54 +01:00
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::TypeId, mem};
2021-12-12 09:29:54 +01:00
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
2021-12-20 04:42:39 +01:00
def_package! {
/// Package of basic BLOB utilities.
crate::BasicBlobPackage => |lib| {
lib.standard = true;
2021-11-23 07:58:54 +01:00
2021-12-20 04:42:39 +01:00
combine_with_exported_module!(lib, "blob", blob_functions);
2021-11-23 07:58:54 +01:00
2021-12-20 04:42:39 +01:00
// Register blob iterator
lib.set_iterable::<Blob>();
}
}
2021-11-23 07:58:54 +01:00
#[export_module]
2021-12-22 12:59:48 +01:00
pub mod blob_functions {
2021-12-18 16:03:35 +01:00
pub const fn blob() -> Blob {
2021-11-23 07:58:54 +01:00
Blob::new()
}
#[rhai_fn(name = "blob", return_raw)]
2021-12-25 16:49:14 +01:00
pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf<Blob> {
2021-11-23 07:58:54 +01:00
blob_with_capacity_and_value(ctx, len, 0)
}
#[rhai_fn(name = "blob", return_raw)]
pub fn blob_with_capacity_and_value(
2021-11-23 11:10:01 +01:00
ctx: NativeCallContext,
2021-11-23 07:58:54 +01:00
len: INT,
value: INT,
2021-12-25 16:49:14 +01:00
) -> RhaiResultOf<Blob> {
2021-11-23 07:58:54 +01:00
let len = if len < 0 { 0 } else { len as usize };
2021-11-23 11:10:01 +01:00
let _ctx = ctx;
2021-11-23 07:58:54 +01:00
// Check if blob will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() {
2021-12-27 15:28:11 +01:00
return Err(
crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(),
);
2021-11-23 07:58:54 +01:00
}
2021-11-23 11:10:01 +01:00
let mut blob = Blob::new();
2021-12-18 16:03:35 +01:00
blob.resize(len, (value & 0x000000ff) as u8);
2021-11-23 07:58:54 +01:00
Ok(blob)
}
#[rhai_fn(name = "len", get = "len", pure)]
pub fn len(blob: &mut Blob) -> INT {
blob.len() as INT
}
pub fn push(blob: &mut Blob, item: INT) {
2021-12-18 16:03:35 +01:00
let item = (item & 0x000000ff) as u8;
2021-11-23 07:58:54 +01:00
blob.push(item);
}
pub fn append(blob: &mut Blob, y: Blob) {
if !y.is_empty() {
if blob.is_empty() {
*blob = y;
} else {
blob.extend(y);
}
}
}
2021-12-22 12:59:48 +01:00
#[rhai_fn(name = "+")]
pub fn concat(blob1: Blob, blob2: Blob) -> Blob {
if !blob2.is_empty() {
if blob1.is_empty() {
blob2
} else {
let mut blob = blob1;
blob.extend(blob2);
blob
}
} else {
blob1
}
}
pub fn insert(blob: &mut Blob, index: INT, item: INT) {
2021-12-18 16:03:35 +01:00
let item = (item & 0x000000ff) as u8;
2021-11-23 07:58:54 +01:00
if blob.is_empty() {
blob.push(item);
return;
}
let (index, _) = calc_offset_len(blob.len(), index, 0);
if index >= blob.len() {
2021-11-23 07:58:54 +01:00
blob.push(item);
} else {
blob.insert(index, item);
2021-11-23 07:58:54 +01:00
}
}
#[rhai_fn(return_raw)]
2021-12-27 04:43:11 +01:00
pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, item: INT) -> RhaiResultOf<()> {
2021-11-23 07:58:54 +01:00
if len <= 0 {
return Ok(());
}
2021-12-18 16:03:35 +01:00
let item = (item & 0x000000ff) as u8;
2021-11-23 11:10:01 +01:00
let _ctx = ctx;
2021-11-23 07:58:54 +01:00
// Check if blob will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
2021-12-27 15:28:11 +01:00
return Err(
crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(),
);
2021-11-23 07:58:54 +01:00
}
if len as usize > blob.len() {
blob.resize(len as usize, item);
}
Ok(())
}
pub fn pop(blob: &mut Blob) -> INT {
if blob.is_empty() {
0
} else {
blob.pop().map_or_else(|| 0, |v| v as INT)
}
}
pub fn shift(blob: &mut Blob) -> INT {
if blob.is_empty() {
0
} else {
blob.remove(0) as INT
}
}
pub fn remove(blob: &mut Blob, len: INT) -> INT {
if len < 0 || (len as usize) >= blob.len() {
0
} else {
blob.remove(len as usize) as INT
}
}
pub fn clear(blob: &mut Blob) {
if !blob.is_empty() {
blob.clear();
}
}
pub fn truncate(blob: &mut Blob, len: INT) {
if !blob.is_empty() {
if len >= 0 {
blob.truncate(len as usize);
} else {
blob.clear();
}
}
}
pub fn chop(blob: &mut Blob, len: INT) {
2022-01-14 03:04:24 +01:00
if !blob.is_empty() {
if len <= 0 {
2021-11-23 07:58:54 +01:00
blob.clear();
2022-01-14 03:04:24 +01:00
} else if (len as usize) < blob.len() {
blob.drain(0..blob.len() - len as usize);
2021-11-23 07:58:54 +01:00
}
}
}
pub fn reverse(blob: &mut Blob) {
if !blob.is_empty() {
blob.reverse();
}
}
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "splice")]
pub fn splice_range(blob: &mut Blob, range: ExclusiveRange, replace: Blob) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
splice(blob, start, end - start, replace)
}
#[rhai_fn(name = "splice")]
pub fn splice_range_inclusive(blob: &mut Blob, range: InclusiveRange, replace: Blob) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
splice(blob, start, end - start + 1, replace)
}
2021-11-23 07:58:54 +01:00
pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) {
if blob.is_empty() {
*blob = replace;
return;
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-11-23 07:58:54 +01:00
if len == 0 {
blob.extend(replace);
2021-11-23 07:58:54 +01:00
} else {
blob.splice(start..start + len, replace);
}
2021-11-23 07:58:54 +01:00
}
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "extract")]
pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
extract(blob, start, end - start)
}
#[rhai_fn(name = "extract")]
pub fn extract_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
extract(blob, start, end - start + 1)
}
2021-11-23 07:58:54 +01:00
pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob {
if blob.is_empty() || len <= 0 {
return Blob::new();
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-11-23 07:58:54 +01:00
if len == 0 {
Blob::new()
2021-11-23 07:58:54 +01:00
} else {
blob[start..start + len].to_vec()
}
2021-11-23 07:58:54 +01:00
}
#[rhai_fn(name = "extract")]
pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob {
2021-12-12 10:26:15 +01:00
extract(blob, start, INT::MAX)
2021-11-23 07:58:54 +01:00
}
#[rhai_fn(name = "split")]
pub fn split_at(blob: &mut Blob, start: INT) -> Blob {
if blob.is_empty() {
return Blob::new();
}
let (start, len) = calc_offset_len(blob.len(), start, INT::MAX);
if start == 0 {
if len > blob.len() {
2021-11-23 07:58:54 +01:00
mem::take(blob)
} else {
let mut result = Blob::new();
result.extend(blob.drain(blob.len() - len..));
result
2021-11-23 07:58:54 +01:00
}
} else if start >= blob.len() {
2021-11-23 07:58:54 +01:00
Blob::new()
} else {
let mut result = Blob::new();
result.extend(blob.drain(start as usize..));
result
}
}
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "drain")]
pub fn drain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
drain(blob, start, end - start)
}
#[rhai_fn(name = "drain")]
pub fn drain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
drain(blob, start, end - start + 1)
}
2021-11-23 16:01:14 +01:00
pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob {
2021-11-23 07:58:54 +01:00
if blob.is_empty() || len <= 0 {
return Blob::new();
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-11-23 07:58:54 +01:00
if len == 0 {
Blob::new()
2021-11-23 07:58:54 +01:00
} else {
blob.drain(start..start + len).collect()
}
2021-11-23 07:58:54 +01:00
}
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "retain")]
pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
retain(blob, start, end - start)
}
#[rhai_fn(name = "retain")]
pub fn retain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
retain(blob, start, end - start + 1)
}
2021-11-23 16:01:14 +01:00
pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob {
2021-11-23 07:58:54 +01:00
if blob.is_empty() || len <= 0 {
return Blob::new();
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-11-23 07:58:54 +01:00
if len == 0 {
mem::take(blob)
2021-11-23 07:58:54 +01:00
} else {
let mut drained: Blob = blob.drain(..start).collect();
drained.extend(blob.drain(len..));
2021-11-23 07:58:54 +01:00
drained
}
2021-11-23 07:58:54 +01:00
}
2021-12-12 09:29:54 +01:00
#[inline]
fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT {
if blob.is_empty() || len <= 0 {
return 0;
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-12-12 09:29:54 +01:00
if len == 0 {
2021-12-12 09:29:54 +01:00
return 0;
}
2021-12-12 09:29:54 +01:00
const INT_BYTES: usize = mem::size_of::<INT>();
let len = usize::min(len, INT_BYTES);
let mut buf = [0_u8; INT_BYTES];
buf[..len].copy_from_slice(&blob[start..][..len]);
if is_le {
INT::from_le_bytes(buf)
} else {
INT::from_be_bytes(buf)
}
}
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "parse_le_int")]
pub fn parse_le_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
parse_le_int(blob, start, end - start)
}
#[rhai_fn(name = "parse_le_int")]
pub fn parse_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
parse_le_int(blob, start, end - start + 1)
}
2021-12-12 09:29:54 +01:00
pub fn parse_le_int(blob: &mut Blob, start: INT, len: INT) -> INT {
parse_int(blob, start, len, true)
}
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "parse_be_int")]
pub fn parse_be_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
parse_be_int(blob, start, end - start)
}
#[rhai_fn(name = "parse_be_int")]
pub fn parse_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
parse_be_int(blob, start, end - start + 1)
}
2021-12-12 09:29:54 +01:00
pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT {
parse_int(blob, start, len, false)
}
#[inline]
fn write_int(blob: &mut Blob, start: INT, len: INT, value: INT, is_le: bool) {
if blob.is_empty() || len <= 0 {
return;
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-12-12 09:29:54 +01:00
if len == 0 {
return;
}
2021-12-12 09:29:54 +01:00
const INT_BYTES: usize = mem::size_of::<INT>();
let len = usize::min(len, INT_BYTES);
2021-12-18 16:03:35 +01:00
let buf = if is_le {
2021-12-12 09:29:54 +01:00
value.to_le_bytes()
} else {
value.to_be_bytes()
2021-12-18 16:03:35 +01:00
};
2021-12-12 09:29:54 +01:00
blob[start..][..len].copy_from_slice(&buf[..len]);
}
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "write_le_int")]
pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_le_int(blob, start, end - start, value)
}
#[rhai_fn(name = "write_le_int")]
pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_le_int(blob, start, end - start + 1, value)
}
2021-12-12 09:29:54 +01:00
#[rhai_fn(name = "write_le")]
pub fn write_le_int(blob: &mut Blob, start: INT, len: INT, value: INT) {
write_int(blob, start, len, value, true)
}
#[rhai_fn(name = "write_be")]
2021-12-15 05:06:17 +01:00
pub fn write_be_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_be_int(blob, start, end - start, value)
}
#[rhai_fn(name = "write_be")]
pub fn write_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_be_int(blob, start, end - start + 1, value)
}
#[rhai_fn(name = "write_be")]
2021-12-12 09:29:54 +01:00
pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) {
write_int(blob, start, len, value, false)
}
#[cfg(not(feature = "no_float"))]
#[inline]
fn parse_float(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> FLOAT {
if blob.is_empty() || len <= 0 {
return 0.0;
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-12-12 09:29:54 +01:00
if len == 0 {
return 0.0;
}
2021-12-12 09:29:54 +01:00
const FLOAT_BYTES: usize = mem::size_of::<FLOAT>();
let len = usize::min(len, FLOAT_BYTES);
let mut buf = [0_u8; FLOAT_BYTES];
buf[..len].copy_from_slice(&blob[start..][..len]);
if is_le {
FLOAT::from_le_bytes(buf)
} else {
FLOAT::from_be_bytes(buf)
}
}
2021-12-15 05:06:17 +01:00
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "parse_le_float")]
pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
parse_le_float(blob, start, end - start)
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "parse_le_float")]
pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
parse_le_float(blob, start, end - start + 1)
}
2021-12-12 09:29:54 +01:00
#[cfg(not(feature = "no_float"))]
pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT {
parse_float(blob, start, len, true)
}
#[cfg(not(feature = "no_float"))]
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "parse_be_float")]
pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
parse_be_float(blob, start, end - start)
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "parse_be_float")]
pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
parse_be_float(blob, start, end - start + 1)
}
#[cfg(not(feature = "no_float"))]
2021-12-12 09:29:54 +01:00
pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT {
parse_float(blob, start, len, false)
}
#[cfg(not(feature = "no_float"))]
#[inline]
fn write_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT, is_le: bool) {
if blob.is_empty() || len <= 0 {
return;
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-12-12 09:29:54 +01:00
if len == 0 {
return;
}
2021-12-12 09:29:54 +01:00
const FLOAT_BYTES: usize = mem::size_of::<FLOAT>();
let len = usize::min(len, FLOAT_BYTES);
2021-12-18 16:03:35 +01:00
let buf = if is_le {
2021-12-12 09:29:54 +01:00
value.to_le_bytes()
} else {
value.to_be_bytes()
2021-12-18 16:03:35 +01:00
};
2021-12-12 09:29:54 +01:00
blob[start..][..len].copy_from_slice(&buf[..len]);
}
#[cfg(not(feature = "no_float"))]
2021-12-15 05:06:17 +01:00
#[rhai_fn(name = "write_le_float")]
pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_le_float(blob, start, end - start, value)
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_le_float")]
pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_le_float(blob, start, end - start + 1, value)
}
#[cfg(not(feature = "no_float"))]
2021-12-12 09:29:54 +01:00
#[rhai_fn(name = "write_le")]
pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
write_float(blob, start, len, value, true)
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_be")]
2021-12-15 05:06:17 +01:00
pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_be_float(blob, start, end - start, value)
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_be")]
pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_be_float(blob, start, end - start + 1, value)
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_be")]
2021-12-12 09:29:54 +01:00
pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
write_float(blob, start, len, value, false)
}
#[inline]
fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) {
2021-12-18 16:03:35 +01:00
if len <= 0 || blob.is_empty() || string.is_empty() {
return;
}
let (start, len) = calc_offset_len(blob.len(), start, len);
2021-12-18 16:03:35 +01:00
if len == 0 {
return;
}
2021-12-18 16:03:35 +01:00
let len = usize::min(len, string.len());
if ascii_only {
string
.chars()
.filter(char::is_ascii)
.take(len)
.map(|ch| ch as u8)
.enumerate()
.for_each(|(i, x)| blob[start + i] = x);
} else {
blob[start..][..len].copy_from_slice(&string.as_bytes()[..len]);
}
2021-12-18 16:03:35 +01:00
}
#[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string(blob: &mut Blob, start: INT, len: INT, string: &str) {
write_string(blob, start, len, string, false)
}
#[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
2021-12-18 16:03:35 +01:00
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, false)
}
#[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string_range_inclusive(blob: &mut Blob, range: InclusiveRange, string: &str) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_string(blob, start, end - start + 1, string, false)
}
#[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string(blob: &mut Blob, start: INT, len: INT, string: &str) {
write_string(blob, start, len, string, true)
2021-12-18 16:03:35 +01:00
}
#[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, true)
}
#[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string_range_inclusive(
blob: &mut Blob,
range: InclusiveRange,
string: &str,
) {
2021-12-18 16:03:35 +01:00
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_string(blob, start, end - start + 1, string, true)
2021-12-18 16:03:35 +01:00
}
2021-11-23 07:58:54 +01:00
}