mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Rewrote head (#1911)
See https://github.com/uutils/coreutils/pull/1911 for the details
This commit is contained in:
@@ -15,7 +15,7 @@ edition = "2018"
|
||||
path = "src/head.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.42"
|
||||
clap = "2.33"
|
||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
|
||||
+615
-213
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ParseError {
|
||||
Syntax,
|
||||
Overflow,
|
||||
}
|
||||
/// Parses obsolete syntax
|
||||
/// head -NUM[kmzv]
|
||||
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
|
||||
let mut chars = src.char_indices();
|
||||
if let Some((_, '-')) = chars.next() {
|
||||
let mut num_end = 0usize;
|
||||
let mut has_num = false;
|
||||
let mut last_char = 0 as char;
|
||||
while let Some((n, c)) = chars.next() {
|
||||
if c.is_numeric() {
|
||||
has_num = true;
|
||||
num_end = n;
|
||||
} else {
|
||||
last_char = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if has_num {
|
||||
match src[1..=num_end].parse::<usize>() {
|
||||
Ok(num) => {
|
||||
let mut quiet = false;
|
||||
let mut verbose = false;
|
||||
let mut zero_terminated = false;
|
||||
let mut multiplier = None;
|
||||
let mut c = last_char;
|
||||
loop {
|
||||
// not that here, we only match lower case 'k', 'c', and 'm'
|
||||
match c {
|
||||
// we want to preserve order
|
||||
// this also saves us 1 heap allocation
|
||||
'q' => {
|
||||
quiet = true;
|
||||
verbose = false
|
||||
}
|
||||
'v' => {
|
||||
verbose = true;
|
||||
quiet = false
|
||||
}
|
||||
'z' => zero_terminated = true,
|
||||
'c' => multiplier = Some(1),
|
||||
'b' => multiplier = Some(512),
|
||||
'k' => multiplier = Some(1024),
|
||||
'm' => multiplier = Some(1024 * 1024),
|
||||
'\0' => {}
|
||||
_ => return Some(Err(ParseError::Syntax)),
|
||||
}
|
||||
if let Some((_, next)) = chars.next() {
|
||||
c = next
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut options = Vec::new();
|
||||
if quiet {
|
||||
options.push(OsString::from("-q"))
|
||||
}
|
||||
if verbose {
|
||||
options.push(OsString::from("-v"))
|
||||
}
|
||||
if zero_terminated {
|
||||
options.push(OsString::from("-z"))
|
||||
}
|
||||
if let Some(n) = multiplier {
|
||||
options.push(OsString::from("-c"));
|
||||
let num = match num.checked_mul(n) {
|
||||
Some(n) => n,
|
||||
None => return Some(Err(ParseError::Overflow)),
|
||||
};
|
||||
options.push(OsString::from(format!("{}", num)));
|
||||
} else {
|
||||
options.push(OsString::from("-n"));
|
||||
options.push(OsString::from(format!("{}", num)));
|
||||
}
|
||||
Some(Ok(options.into_iter()))
|
||||
}
|
||||
Err(_) => Some(Err(ParseError::Overflow)),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Parses an -c or -n argument,
|
||||
/// the bool specifies whether to read from the end
|
||||
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> {
|
||||
let mut num_start = 0;
|
||||
let mut chars = src.char_indices();
|
||||
let (mut chars, all_but_last) = match chars.next() {
|
||||
Some((_, c)) => {
|
||||
if c == '-' {
|
||||
num_start += 1;
|
||||
(chars, true)
|
||||
} else {
|
||||
(src.char_indices(), false)
|
||||
}
|
||||
}
|
||||
None => return Err(ParseError::Syntax),
|
||||
};
|
||||
let mut num_end = 0usize;
|
||||
let mut last_char = 0 as char;
|
||||
let mut num_count = 0usize;
|
||||
while let Some((n, c)) = chars.next() {
|
||||
if c.is_numeric() {
|
||||
num_end = n;
|
||||
num_count += 1;
|
||||
} else {
|
||||
last_char = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let num = if num_count > 0 {
|
||||
match src[num_start..=num_end].parse::<usize>() {
|
||||
Ok(n) => Some(n),
|
||||
Err(_) => return Err(ParseError::Overflow),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if last_char == 0 as char {
|
||||
if let Some(n) = num {
|
||||
Ok((n, all_but_last))
|
||||
} else {
|
||||
Err(ParseError::Syntax)
|
||||
}
|
||||
} else {
|
||||
let base: u128 = match chars.next() {
|
||||
Some((_, c)) => {
|
||||
let b = match c {
|
||||
'B' if last_char != 'b' => 1000,
|
||||
'i' if last_char != 'b' => {
|
||||
if let Some((_, 'B')) = chars.next() {
|
||||
1024
|
||||
} else {
|
||||
return Err(ParseError::Syntax);
|
||||
}
|
||||
}
|
||||
_ => return Err(ParseError::Syntax),
|
||||
};
|
||||
if chars.next().is_some() {
|
||||
return Err(ParseError::Syntax);
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
None => 1024,
|
||||
};
|
||||
let mul = match last_char.to_lowercase().next().unwrap() {
|
||||
'b' => 512,
|
||||
'k' => base.pow(1),
|
||||
'm' => base.pow(2),
|
||||
'g' => base.pow(3),
|
||||
't' => base.pow(4),
|
||||
'p' => base.pow(5),
|
||||
'e' => base.pow(6),
|
||||
'z' => base.pow(7),
|
||||
'y' => base.pow(8),
|
||||
_ => return Err(ParseError::Syntax),
|
||||
};
|
||||
let mul = match usize::try_from(mul) {
|
||||
Ok(n) => n,
|
||||
Err(_) => return Err(ParseError::Overflow),
|
||||
};
|
||||
match num.unwrap_or(1).checked_mul(mul) {
|
||||
Some(n) => Ok((n, all_but_last)),
|
||||
None => Err(ParseError::Overflow),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn obsolete(src: &str) -> Option<Result<Vec<String>, ParseError>> {
|
||||
let r = parse_obsolete(src);
|
||||
match r {
|
||||
Some(s) => match s {
|
||||
Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())),
|
||||
Err(e) => Some(Err(e)),
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
fn obsolete_result(src: &[&str]) -> Option<Result<Vec<String>, ParseError>> {
|
||||
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
|
||||
}
|
||||
#[test]
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
fn test_parse_overflow_x64() {
|
||||
assert_eq!(parse_num("1Y"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1Z"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("100E"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("100000P"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1000000000T"), Err(ParseError::Overflow));
|
||||
assert_eq!(
|
||||
parse_num("10000000000000000000000"),
|
||||
Err(ParseError::Overflow)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn test_parse_overflow_x32() {
|
||||
assert_eq!(parse_num("1T"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1000G"), Err(ParseError::Overflow));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_bad_syntax() {
|
||||
assert_eq!(parse_num("5MiB nonsense"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("Nonsense string"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("5mib"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("biB"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("-"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num(""), Err(ParseError::Syntax));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_numbers() {
|
||||
assert_eq!(parse_num("k"), Ok((1024, false)));
|
||||
assert_eq!(parse_num("MiB"), Ok((1024 * 1024, false)));
|
||||
assert_eq!(parse_num("-5"), Ok((5, true)));
|
||||
assert_eq!(parse_num("b"), Ok((512, false)));
|
||||
assert_eq!(parse_num("-2GiB"), Ok((2 * 1024 * 1024 * 1024, true)));
|
||||
assert_eq!(parse_num("5M"), Ok((5 * 1024 * 1024, false)));
|
||||
assert_eq!(parse_num("5MB"), Ok((5 * 1000 * 1000, false)));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_numbers_obsolete() {
|
||||
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
|
||||
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
|
||||
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"]));
|
||||
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"]));
|
||||
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"]));
|
||||
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"]));
|
||||
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"]));
|
||||
assert_eq!(
|
||||
obsolete("-1vzqvq"),
|
||||
obsolete_result(&["-q", "-z", "-n", "1"])
|
||||
);
|
||||
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"]));
|
||||
assert_eq!(
|
||||
obsolete("-105kzm"),
|
||||
obsolete_result(&["-z", "-c", "110100480"])
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_errors_obsolete() {
|
||||
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax)));
|
||||
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax)));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_obsolete_nomatch() {
|
||||
assert_eq!(obsolete("-k"), None);
|
||||
assert_eq!(obsolete("asd"), None);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn test_parse_obsolete_overflow_x64() {
|
||||
assert_eq!(
|
||||
obsolete("-1000000000000000m"),
|
||||
Some(Err(ParseError::Overflow))
|
||||
);
|
||||
assert_eq!(
|
||||
obsolete("-10000000000000000000000"),
|
||||
Some(Err(ParseError::Overflow))
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn test_parse_obsolete_overflow_x32() {
|
||||
assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow)));
|
||||
assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Event<'a> {
|
||||
Data(&'a [u8]),
|
||||
Line,
|
||||
}
|
||||
/// Loops over the lines read from a BufRead.
|
||||
/// # Arguments
|
||||
/// * `input` the ReadBuf to read from
|
||||
/// * `zero` whether to use 0u8 as a line delimiter
|
||||
/// * `on_event` a closure receiving some bytes read in a slice, or
|
||||
/// event signalling a line was just read.
|
||||
/// this is guaranteed to be signalled *directly* after the
|
||||
/// slice containing the (CR on win)LF / 0 is passed
|
||||
///
|
||||
/// Return whether to continue
|
||||
pub fn walk_lines<F>(
|
||||
input: &mut impl std::io::BufRead,
|
||||
zero: bool,
|
||||
mut on_event: F,
|
||||
) -> std::io::Result<()>
|
||||
where
|
||||
F: FnMut(Event) -> std::io::Result<bool>,
|
||||
{
|
||||
let mut buffer = [0u8; super::BUF_SIZE];
|
||||
loop {
|
||||
let read = loop {
|
||||
match input.read(&mut buffer) {
|
||||
Ok(n) => break n,
|
||||
Err(e) => match e.kind() {
|
||||
std::io::ErrorKind::Interrupted => {}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
};
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut base = 0usize;
|
||||
for (i, byte) in buffer[..read].iter().enumerate() {
|
||||
match byte {
|
||||
b'\n' if !zero => {
|
||||
on_event(Event::Data(&buffer[base..=i]))?;
|
||||
base = i + 1;
|
||||
if !on_event(Event::Line)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
0u8 if zero => {
|
||||
on_event(Event::Data(&buffer[base..=i]))?;
|
||||
base = i + 1;
|
||||
if !on_event(Event::Line)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
on_event(Event::Data(&buffer[base..read]))?;
|
||||
}
|
||||
}
|
||||
Regular → Executable
+63
-42
@@ -86,88 +86,74 @@ fn test_verbose() {
|
||||
.stdout_is_fixture("lorem_ipsum_verbose.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_terminated() {
|
||||
new_ucmd!()
|
||||
.args(&["-z", "zero_terminated.txt"])
|
||||
.run()
|
||||
.stdout_is_fixture("zero_terminated.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_spams_newline() {
|
||||
//this test is does not mirror what GNU does
|
||||
new_ucmd!().pipe_in("a").succeeds().stdout_is("a\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_unsupported_byte_syntax() {
|
||||
fn test_byte_syntax() {
|
||||
new_ucmd!()
|
||||
.args(&["-1c"])
|
||||
.pipe_in("abc")
|
||||
.fails()
|
||||
//GNU head returns "a"
|
||||
.stdout_is("")
|
||||
.stderr_is("head: error: Unrecognized option: \'1\'");
|
||||
.run()
|
||||
.stdout_is("a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_unsupported_line_syntax() {
|
||||
fn test_line_syntax() {
|
||||
new_ucmd!()
|
||||
.args(&["-n", "2048m"])
|
||||
.pipe_in("a\n")
|
||||
.fails()
|
||||
//.stdout_is("a\n"); What GNU head returns.
|
||||
.stdout_is("")
|
||||
.stderr_is("head: error: invalid line count \'2048m\': invalid digit found in string");
|
||||
.run()
|
||||
.stdout_is("a\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_unsupported_zero_terminated_syntax() {
|
||||
fn test_zero_terminated_syntax() {
|
||||
new_ucmd!()
|
||||
.args(&["-z -n 1"])
|
||||
.args(&["-z", "-n", "1"])
|
||||
.pipe_in("x\0y")
|
||||
.fails()
|
||||
//GNU Head returns "x\0"
|
||||
.stderr_is("head: error: Unrecognized option: \'z\'");
|
||||
.run()
|
||||
.stdout_is("x\0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_unsupported_zero_terminated_syntax_2() {
|
||||
fn test_zero_terminated_syntax_2() {
|
||||
new_ucmd!()
|
||||
.args(&["-z -n 2"])
|
||||
.args(&["-z", "-n", "2"])
|
||||
.pipe_in("x\0y")
|
||||
.fails()
|
||||
//GNU Head returns "x\0y"
|
||||
.stderr_is("head: error: Unrecognized option: \'z\'");
|
||||
.run()
|
||||
.stdout_is("x\0y");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_unsupported_negative_byte_syntax() {
|
||||
fn test_negative_byte_syntax() {
|
||||
new_ucmd!()
|
||||
.args(&["--bytes=-2"])
|
||||
.pipe_in("a\n")
|
||||
.fails()
|
||||
//GNU Head returns ""
|
||||
.stderr_is("head: error: invalid byte count \'-2\': invalid digit found in string");
|
||||
.run()
|
||||
.stdout_is("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_bug_in_negative_zero_lines() {
|
||||
fn test_negative_zero_lines() {
|
||||
new_ucmd!()
|
||||
.args(&["--lines=-0"])
|
||||
.pipe_in("a\nb\n")
|
||||
.succeeds()
|
||||
//GNU Head returns "a\nb\n"
|
||||
.stdout_is("");
|
||||
.stdout_is("a\nb\n");
|
||||
}
|
||||
#[test]
|
||||
fn test_negative_zero_bytes() {
|
||||
new_ucmd!()
|
||||
.args(&["--bytes=-0"])
|
||||
.pipe_in("qwerty")
|
||||
.succeeds()
|
||||
.stdout_is("qwerty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_such_file_or_directory() {
|
||||
let result = new_ucmd!().arg("no_such_file.toml").run();
|
||||
@@ -179,3 +165,38 @@ fn test_no_such_file_or_directory() {
|
||||
.contains("cannot open 'no_such_file.toml' for reading: No such file or directory")
|
||||
)
|
||||
}
|
||||
|
||||
// there was a bug not caught by previous tests
|
||||
// where for negative n > 3, the total amount of lines
|
||||
// was correct, but it would eat from the second line
|
||||
#[test]
|
||||
fn test_sequence_fixture() {
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-10", "sequence"])
|
||||
.run()
|
||||
.stdout_is_fixture("sequence.expected");
|
||||
}
|
||||
#[test]
|
||||
fn test_file_backwards() {
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-10", "lorem_ipsum.txt"])
|
||||
.run()
|
||||
.stdout_is_fixture("lorem_ipsum_backwards_file.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_terminated() {
|
||||
new_ucmd!()
|
||||
.args(&["-z", "zero_terminated.txt"])
|
||||
.run()
|
||||
.stdout_is_fixture("zero_terminated.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obsolete_extras() {
|
||||
new_ucmd!()
|
||||
.args(&["-5zv"])
|
||||
.pipe_in("1\02\03\04\05\06")
|
||||
.succeeds()
|
||||
.stdout_is("==> standard input <==\n1\02\03\04\05\0");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit.
|
||||
Nunc interdum suscipit sem vel ornare.
|
||||
Proin euismod,
|
||||
justo sed mollis dictum,
|
||||
eros urna ultricies augue,
|
||||
eu pharetra mi ex id ante.
|
||||
Duis convallis porttitor aliquam.
|
||||
Nunc vitae tincidunt ex.
|
||||
Suspendisse iaculis ligula ac diam consectetur lacinia.
|
||||
Donec vel velit dui.
|
||||
Etiam fringilla,
|
||||
dolor quis tempor vehicula,
|
||||
lacus turpis bibendum velit,
|
||||
et pellentesque elit odio a magna.
|
||||
Cras vulputate tortor non libero vehicula euismod.
|
||||
Aliquam tincidunt nisl eget enim cursus,
|
||||
viverra sagittis magna commodo.
|
||||
Cras rhoncus egestas leo nec blandit.
|
||||
Suspendisse potenti.
|
||||
Etiam ullamcorper leo vel lacus vestibulum,
|
||||
cursus semper eros efficitur.
|
||||
In hac habitasse platea dictumst.
|
||||
Phasellus scelerisque vehicula f
|
||||
Vendored
+100
@@ -0,0 +1,100 @@
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
30
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
47
|
||||
48
|
||||
49
|
||||
50
|
||||
51
|
||||
52
|
||||
53
|
||||
54
|
||||
55
|
||||
56
|
||||
57
|
||||
58
|
||||
59
|
||||
60
|
||||
61
|
||||
62
|
||||
63
|
||||
64
|
||||
65
|
||||
66
|
||||
67
|
||||
68
|
||||
69
|
||||
70
|
||||
71
|
||||
72
|
||||
73
|
||||
74
|
||||
75
|
||||
76
|
||||
77
|
||||
78
|
||||
79
|
||||
80
|
||||
81
|
||||
82
|
||||
83
|
||||
84
|
||||
85
|
||||
86
|
||||
87
|
||||
88
|
||||
89
|
||||
90
|
||||
91
|
||||
92
|
||||
93
|
||||
94
|
||||
95
|
||||
96
|
||||
97
|
||||
98
|
||||
99
|
||||
100
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
30
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
47
|
||||
48
|
||||
49
|
||||
50
|
||||
51
|
||||
52
|
||||
53
|
||||
54
|
||||
55
|
||||
56
|
||||
57
|
||||
58
|
||||
59
|
||||
60
|
||||
61
|
||||
62
|
||||
63
|
||||
64
|
||||
65
|
||||
66
|
||||
67
|
||||
68
|
||||
69
|
||||
70
|
||||
71
|
||||
72
|
||||
73
|
||||
74
|
||||
75
|
||||
76
|
||||
77
|
||||
78
|
||||
79
|
||||
80
|
||||
81
|
||||
82
|
||||
83
|
||||
84
|
||||
85
|
||||
86
|
||||
87
|
||||
88
|
||||
89
|
||||
90
|
||||
Reference in New Issue
Block a user