mirror of
https://github.com/nushell/nushell.git
synced 2024-12-24 07:59:21 +01:00
convert string duration to named duration (#6406)
This commit is contained in:
parent
3f93dc2f1d
commit
5ebfa10495
@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath, Expr},
|
ast::{Call, CellPath, Expr},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Unit, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Unit,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,6 +17,12 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into duration")
|
Signature::build("into duration")
|
||||||
|
.named(
|
||||||
|
"convert",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"convert duration into another duration",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -106,6 +113,15 @@ impl Command for SubCommand {
|
|||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert string to a named duration",
|
||||||
|
example: "'7min' | into duration --convert sec",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "420 sec".to_string(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,17 +133,21 @@ fn into_duration(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |v| {
|
move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
action(&v, head)
|
action(&v, &convert_to_unit, head)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
for path in &column_paths {
|
for path in &column_paths {
|
||||||
let r =
|
let d = convert_to_unit.clone();
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
let r = ret.update_cell_path(
|
||||||
|
&path.members,
|
||||||
|
Box::new(move |old| action(old, &d, head)),
|
||||||
|
);
|
||||||
if let Err(error) = r {
|
if let Err(error) = r {
|
||||||
return Value::Error { error };
|
return Value::Error { error };
|
||||||
}
|
}
|
||||||
@ -140,6 +160,148 @@ fn into_duration(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_str_from_unit_to_unit(
|
||||||
|
val: i64,
|
||||||
|
from_unit: &str,
|
||||||
|
to_unit: &str,
|
||||||
|
span: Span,
|
||||||
|
value_span: Span,
|
||||||
|
) -> Result<i64, ShellError> {
|
||||||
|
match (from_unit, to_unit) {
|
||||||
|
("ns", "ns") => Ok(val),
|
||||||
|
("ns", "us") => Ok(val / 1000),
|
||||||
|
("ns", "ms") => Ok(val / 1000 / 1000),
|
||||||
|
("ns", "sec") => Ok(val / 1000 / 1000 / 1000),
|
||||||
|
("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60),
|
||||||
|
("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60),
|
||||||
|
("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24),
|
||||||
|
("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7),
|
||||||
|
("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30),
|
||||||
|
("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||||
|
("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||||
|
|
||||||
|
("us", "ns") => Ok(val * 1000),
|
||||||
|
("us", "us") => Ok(val),
|
||||||
|
("us", "ms") => Ok(val / 1000),
|
||||||
|
("us", "sec") => Ok(val / 1000 / 1000),
|
||||||
|
("us", "min") => Ok(val / 1000 / 1000 / 60),
|
||||||
|
("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60),
|
||||||
|
("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24),
|
||||||
|
("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7),
|
||||||
|
("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30),
|
||||||
|
("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||||
|
("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||||
|
|
||||||
|
("ms", "ns") => Ok(val * 1000 * 1000),
|
||||||
|
("ms", "us") => Ok(val * 1000),
|
||||||
|
("ms", "ms") => Ok(val),
|
||||||
|
("ms", "sec") => Ok(val / 1000),
|
||||||
|
("ms", "min") => Ok(val / 1000 / 60),
|
||||||
|
("ms", "hr") => Ok(val / 1000 / 60 / 60),
|
||||||
|
("ms", "day") => Ok(val / 1000 / 60 / 60 / 24),
|
||||||
|
("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7),
|
||||||
|
("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30),
|
||||||
|
("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365),
|
||||||
|
("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365),
|
||||||
|
|
||||||
|
("sec", "ns") => Ok(val * 1000 * 1000 * 1000),
|
||||||
|
("sec", "us") => Ok(val * 1000 * 1000),
|
||||||
|
("sec", "ms") => Ok(val * 1000),
|
||||||
|
("sec", "sec") => Ok(val),
|
||||||
|
("sec", "min") => Ok(val / 60),
|
||||||
|
("sec", "hr") => Ok(val / 60 / 60),
|
||||||
|
("sec", "day") => Ok(val / 60 / 60 / 24),
|
||||||
|
("sec", "wk") => Ok(val / 60 / 60 / 24 / 7),
|
||||||
|
("sec", "month") => Ok(val / 60 / 60 / 24 / 30),
|
||||||
|
("sec", "yr") => Ok(val / 60 / 60 / 24 / 365),
|
||||||
|
("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365),
|
||||||
|
|
||||||
|
("min", "ns") => Ok(val * 1000 * 1000 * 1000 * 60),
|
||||||
|
("min", "us") => Ok(val * 1000 * 1000 * 60),
|
||||||
|
("min", "ms") => Ok(val * 1000 * 60),
|
||||||
|
("min", "sec") => Ok(val * 60),
|
||||||
|
("min", "min") => Ok(val),
|
||||||
|
("min", "hr") => Ok(val / 60),
|
||||||
|
("min", "day") => Ok(val / 60 / 24),
|
||||||
|
("min", "wk") => Ok(val / 60 / 24 / 7),
|
||||||
|
("min", "month") => Ok(val / 60 / 24 / 30),
|
||||||
|
("min", "yr") => Ok(val / 60 / 24 / 365),
|
||||||
|
("min", "dec") => Ok(val / 10 / 60 / 24 / 365),
|
||||||
|
|
||||||
|
("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60),
|
||||||
|
("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60),
|
||||||
|
("hr", "ms") => Ok(val * 1000 * 60 * 60),
|
||||||
|
("hr", "sec") => Ok(val * 60 * 60),
|
||||||
|
("hr", "min") => Ok(val * 60),
|
||||||
|
("hr", "hr") => Ok(val),
|
||||||
|
("hr", "day") => Ok(val / 24),
|
||||||
|
("hr", "wk") => Ok(val / 24 / 7),
|
||||||
|
("hr", "month") => Ok(val / 24 / 30),
|
||||||
|
("hr", "yr") => Ok(val / 24 / 365),
|
||||||
|
("hr", "dec") => Ok(val / 10 / 24 / 365),
|
||||||
|
|
||||||
|
("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24),
|
||||||
|
("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24),
|
||||||
|
("day", "ms") => Ok(val * 1000 * 60 * 60 * 24),
|
||||||
|
("day", "sec") => Ok(val * 60 * 60 * 24),
|
||||||
|
("day", "min") => Ok(val * 60 * 24),
|
||||||
|
("day", "hr") => Ok(val * 24),
|
||||||
|
("day", "day") => Ok(val),
|
||||||
|
("day", "wk") => Ok(val / 7),
|
||||||
|
("day", "month") => Ok(val / 30),
|
||||||
|
("day", "yr") => Ok(val / 365),
|
||||||
|
("day", "dec") => Ok(val / 10 / 365),
|
||||||
|
|
||||||
|
("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7),
|
||||||
|
("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7),
|
||||||
|
("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7),
|
||||||
|
("wk", "sec") => Ok(val * 60 * 60 * 24 * 7),
|
||||||
|
("wk", "min") => Ok(val * 60 * 24 * 7),
|
||||||
|
("wk", "hr") => Ok(val * 24 * 7),
|
||||||
|
("wk", "day") => Ok(val * 7),
|
||||||
|
("wk", "wk") => Ok(val),
|
||||||
|
("wk", "month") => Ok(val / 4),
|
||||||
|
("wk", "yr") => Ok(val / 52),
|
||||||
|
("wk", "dec") => Ok(val / 10 / 52),
|
||||||
|
|
||||||
|
("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30),
|
||||||
|
("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30),
|
||||||
|
("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30),
|
||||||
|
("month", "sec") => Ok(val * 60 * 60 * 24 * 30),
|
||||||
|
("month", "min") => Ok(val * 60 * 24 * 30),
|
||||||
|
("month", "hr") => Ok(val * 24 * 30),
|
||||||
|
("month", "day") => Ok(val * 30),
|
||||||
|
("month", "wk") => Ok(val * 4),
|
||||||
|
("month", "month") => Ok(val),
|
||||||
|
("month", "yr") => Ok(val / 12),
|
||||||
|
("month", "dec") => Ok(val / 10 / 12),
|
||||||
|
|
||||||
|
("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365),
|
||||||
|
("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365),
|
||||||
|
("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365),
|
||||||
|
("yr", "sec") => Ok(val * 60 * 60 * 24 * 365),
|
||||||
|
("yr", "min") => Ok(val * 60 * 24 * 365),
|
||||||
|
("yr", "hr") => Ok(val * 24 * 365),
|
||||||
|
("yr", "day") => Ok(val * 365),
|
||||||
|
("yr", "wk") => Ok(val * 52),
|
||||||
|
("yr", "month") => Ok(val * 12),
|
||||||
|
("yr", "yr") => Ok(val),
|
||||||
|
("yr", "dec") => Ok(val / 10),
|
||||||
|
|
||||||
|
_ => Err(ShellError::CantConvertWithValue(
|
||||||
|
"string duration".to_string(),
|
||||||
|
"string duration".to_string(),
|
||||||
|
to_unit.to_string(),
|
||||||
|
span,
|
||||||
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec"
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
|
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
|
||||||
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
@ -168,20 +330,105 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
|||||||
s.to_string(),
|
s.to_string(),
|
||||||
span,
|
span,
|
||||||
value_span,
|
value_span,
|
||||||
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, span: Span) -> Value {
|
fn string_to_unit_duration(
|
||||||
|
s: &str,
|
||||||
|
span: Span,
|
||||||
|
value_span: Span,
|
||||||
|
) -> Result<(&str, i64), ShellError> {
|
||||||
|
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
|
if let Expr::Int(x) = value.expr {
|
||||||
|
match unit.item {
|
||||||
|
Unit::Nanosecond => return Ok(("ns", x)),
|
||||||
|
Unit::Microsecond => return Ok(("us", x)),
|
||||||
|
Unit::Millisecond => return Ok(("ms", x)),
|
||||||
|
Unit::Second => return Ok(("sec", x)),
|
||||||
|
Unit::Minute => return Ok(("min", x)),
|
||||||
|
Unit::Hour => return Ok(("hr", x)),
|
||||||
|
Unit::Day => return Ok(("day", x)),
|
||||||
|
Unit::Week => return Ok(("wk", x)),
|
||||||
|
Unit::Month => return Ok(("month", x)), //30 days to a month
|
||||||
|
Unit::Year => return Ok(("yr", x)), //365 days to a year
|
||||||
|
Unit::Decade => return Ok(("dec", x)), //365 days to a year
|
||||||
|
|
||||||
|
_ => return Ok(("ns", 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ShellError::CantConvertWithValue(
|
||||||
|
"duration".to_string(),
|
||||||
|
"string".to_string(),
|
||||||
|
s.to_string(),
|
||||||
|
span,
|
||||||
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Duration { .. } => input.clone(),
|
Value::Duration {
|
||||||
|
val: _val_num,
|
||||||
|
span: _value_span,
|
||||||
|
} => {
|
||||||
|
if let Some(_to_unit) = convert_to_unit {
|
||||||
|
Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"Cannot convert from a Value::Duration right now. Try making it a string."
|
||||||
|
.into(),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
Value::String {
|
Value::String {
|
||||||
val,
|
val,
|
||||||
span: value_span,
|
span: value_span,
|
||||||
} => match string_to_duration(val, span, *value_span) {
|
} => {
|
||||||
Ok(val) => Value::Duration { val, span },
|
if let Some(to_unit) = convert_to_unit {
|
||||||
Err(error) => Value::Error { error },
|
if let Ok(dur) = string_to_unit_duration(val, span, *value_span) {
|
||||||
},
|
let from_unit = dur.0;
|
||||||
|
let duration = dur.1;
|
||||||
|
match convert_str_from_unit_to_unit(
|
||||||
|
duration,
|
||||||
|
from_unit,
|
||||||
|
&to_unit.item,
|
||||||
|
span,
|
||||||
|
*value_span,
|
||||||
|
) {
|
||||||
|
Ok(d) => Value::String {
|
||||||
|
val: format!("{} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
},
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"'into duration' does not support this string input".into(),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match string_to_duration(val, span, *value_span) {
|
||||||
|
Ok(val) => Value::Duration { val, span },
|
||||||
|
Err(error) => Value::Error { error },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => Value::Error {
|
_ => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
"'into duration' does not support this input".into(),
|
"'into duration' does not support this input".into(),
|
||||||
@ -207,8 +454,9 @@ mod test {
|
|||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let word = Value::test_string("3ns");
|
let word = Value::test_string("3ns");
|
||||||
let expected = Value::Duration { val: 3, span };
|
let expected = Value::Duration { val: 3, span };
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,8 +468,9 @@ mod test {
|
|||||||
val: 4 * 1000,
|
val: 4 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,8 +482,9 @@ mod test {
|
|||||||
val: 5 * 1000 * 1000,
|
val: 5 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,8 +496,9 @@ mod test {
|
|||||||
val: 1000 * 1000 * 1000,
|
val: 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,8 +510,9 @@ mod test {
|
|||||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,8 +524,9 @@ mod test {
|
|||||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,8 +538,9 @@ mod test {
|
|||||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,8 +552,9 @@ mod test {
|
|||||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user