Решение на упр.06 задача 1 от Калоян Стоянов

Обратно към всички решения

Към профила на Калоян Стоянов

Резултати

  • 4 точки от тестове
  • 1 бонус точка
  • 5 точки общо
  • 4 успешни тест(а)
  • 0 неуспешни тест(а)

Код

use std::collections::HashMap;
use std::error::Error;
use std::ffi::OsStr;
use std::fs::{read_dir, File};
use std::io::{self, BufRead, BufReader, Read};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum LogLevel {
Error,
Warn,
Info,
Debug,
}
impl LogLevel {
fn as_str(&self) -> &'static str {
match self {
LogLevel::Error => "Error",
LogLevel::Warn => "Warn",
LogLevel::Info => "Info",
LogLevel::Debug => "Debug",
}
}
}
struct AggregateInfo {
log_counts: HashMap<LogLevel, usize>,
skipped_files: Vec<String>,
}
#[derive(Debug)]
enum ParseLogError {
Read(io::Error),
ParseLine,
}
impl From<io::Error> for ParseLogError {
fn from(e: io::Error) -> Self {
ParseLogError::Read(e)
}
}
impl std::fmt::Display for ParseLogError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseLogError::Read(e) => write!(f, "I/O error: {}", e),
ParseLogError::ParseLine => write!(f, "Parse error on line"),
}
}
}
impl Error for ParseLogError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ParseLogError::Read(e) => Some(e),
ParseLogError::ParseLine => None,
}
}
}
fn parse_log_file<R>(mut file: R, map: &mut HashMap<LogLevel, usize>) -> Result<(), ParseLogError>
where
R: Read,
{
let mut s = String::new();
file.read_to_string(&mut s).map_err(ParseLogError::Read)?;
for line in s.lines() {
let level = parse_line_level(line).map_err(|_| ParseLogError::ParseLine)?;
*map.entry(level).or_insert(0) += 1;
}
Ok(())
}
fn parse_line_level(line: &str) -> Result<LogLevel, ()> {
let mut parts = line.split_whitespace();
match parts.next() {
Some(tok) => match tok.to_ascii_uppercase().as_str() {
"ERROR" => Ok(LogLevel::Error),
"WARN" => Ok(LogLevel::Warn),
"INFO" => Ok(LogLevel::Info),
"DEBUG" => Ok(LogLevel::Debug),
_ => Err(()),
},
None => Err(()),
}
}
fn aggregate_logs(dir: &Path) -> Result<AggregateInfo, Box<dyn Error>> {
let mut log_counts: HashMap<LogLevel, usize> = HashMap::new();
let mut skipped_files: Vec<String> = Vec::new();
let rd = read_dir(dir)?;
for entry_res in rd {
let entry = match entry_res {
Ok(e) => e,
Err(e) => {
eprintln!("Warning: failed to read directory entry: {}", e);
continue;
}
};
let path = entry.path();
eprintln!("DEBUG: found entry {:?}", path);
if !path.is_file() {
continue;
}
if !has_log_extension(&path) {
skipped_files.push(display_path(&path));
eprintln!("DEBUG: skipped non-log file {:?}", path);
continue;
}
let metadata = match entry.metadata() {
Ok(m) => m,
Err(e) => {
eprintln!("Warning: failed to stat file {:?}: {}", path, e);
skipped_files.push(display_path(&path));
continue;
}
};
// If file > 10KB, stream it line-by-line
const TEN_KB: u64 = 10 * 1024;
if metadata.len() > TEN_KB {
match File::open(&path) {
Ok(f) => {
let reader = BufReader::new(f);
if let Err(e) = parse_reader_lines(reader, &mut log_counts) {
eprintln!("Warning: failed to parse (stream) file {:?}: {}", path, e);
skipped_files.push(display_path(&path));
continue;
} else {
eprintln!("DEBUG: parsed (stream) file {:?}", path);
}
}
Err(e) => {
eprintln!("Warning: failed to open file {:?}: {}", path, e);
skipped_files.push(display_path(&path));
continue;
}
}
} else {
match File::open(&path) {
Ok(f) => {
if let Err(e) = parse_log_file(f, &mut log_counts) {
eprintln!("Warning: failed to parse file {:?}: {}", path, e);
skipped_files.push(display_path(&path));
continue;
} else {
eprintln!("DEBUG: parsed file {:?}", path);
}
}
Err(e) => {
eprintln!("Warning: failed to open file {:?}: {}", path, e);
skipped_files.push(display_path(&path));
continue;
}
}
}
}
Ok(AggregateInfo { log_counts, skipped_files })
}
fn parse_reader_lines<R>(reader: R, map: &mut HashMap<LogLevel, usize>) -> Result<(), ParseLogError>
where
R: BufRead,
{
for line_res in reader.lines() {
let line = line_res.map_err(ParseLogError::Read)?;
let level = parse_line_level(&line).map_err(|_| ParseLogError::ParseLine)?;
*map.entry(level).or_insert(0) += 1;
}
Ok(())
}
fn has_log_extension(path: &Path) -> bool {
match path.extension().and_then(OsStr::to_str) {
Some(ext) => ext.eq_ignore_ascii_case("log"),
None => false,
}
}
fn display_path(p: &Path) -> String {
p.to_string_lossy().into_owned()
}
fn print_stats(info: &AggregateInfo) {
let order = [LogLevel::Debug, LogLevel::Info, LogLevel::Warn, LogLevel::Error];
for lvl in &order {
let cnt = info.log_counts.get(lvl).copied().unwrap_or(0);
println!("{}: {}", lvl.as_str(), cnt);
}
if !info.skipped_files.is_empty() {
println!("\nSkipped files:");
for s in &info.skipped_files {
println!(" {}", s);
}
}
}

Лог от изпълнението

Updating crates.io index
     Locking 17 packages to latest compatible versions
   Compiling proc-macro2 v1.0.103
   Compiling quote v1.0.42
   Compiling unicode-ident v1.0.22
   Compiling futures-core v0.3.31
   Compiling futures-sink v0.3.31
   Compiling futures-channel v0.3.31
   Compiling futures-io v0.3.31
   Compiling futures-task v0.3.31
   Compiling slab v0.4.11
   Compiling syn v2.0.110
   Compiling memchr v2.7.6
   Compiling pin-project-lite v0.2.16
   Compiling pin-utils v0.1.0
   Compiling solution v0.1.0 (/tmp/d20251120-1757769-118kgaa/solution)
warning: unused import: `PathBuf`
 --> src/lib.rs:6:23
  |
6 | use std::path::{Path, PathBuf};
  |                       ^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: enum `LogLevel` is never used
 --> src/lib.rs:9:6
  |
9 | enum LogLevel {
  |      ^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: method `as_str` is never used
  --> src/lib.rs:17:8
   |
16 | impl LogLevel {
   | ------------- method in this implementation
17 |     fn as_str(&self) -> &'static str {
   |        ^^^^^^

warning: struct `AggregateInfo` is never constructed
  --> src/lib.rs:27:8
   |
27 | struct AggregateInfo {
   |        ^^^^^^^^^^^^^

warning: variant `ParseLine` is never constructed
  --> src/lib.rs:35:5
   |
33 | enum ParseLogError {
   |      ------------- variant in this enum
34 |     Read(io::Error),
35 |     ParseLine,
   |     ^^^^^^^^^
   |
   = note: `ParseLogError` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis

warning: function `parse_log_file` is never used
  --> src/lib.rs:62:4
   |
62 | fn parse_log_file<R>(mut file: R, map: &mut HashMap<LogLevel, usize>) -> Result<(), ParseLogError>
   |    ^^^^^^^^^^^^^^

warning: function `parse_line_level` is never used
  --> src/lib.rs:77:4
   |
77 | fn parse_line_level(line: &str) -> Result<LogLevel, ()> {
   |    ^^^^^^^^^^^^^^^^

warning: function `aggregate_logs` is never used
  --> src/lib.rs:91:4
   |
91 | fn aggregate_logs(dir: &Path) -> Result<AggregateInfo, Box<dyn Error>> {
   |    ^^^^^^^^^^^^^^

warning: function `parse_reader_lines` is never used
   --> src/lib.rs:170:4
    |
170 | fn parse_reader_lines<R>(reader: R, map: &mut HashMap<LogLevel, usize>) -> Result<(), ParseLogError>
    |    ^^^^^^^^^^^^^^^^^^

warning: function `has_log_extension` is never used
   --> src/lib.rs:182:4
    |
182 | fn has_log_extension(path: &Path) -> bool {
    |    ^^^^^^^^^^^^^^^^^

warning: function `display_path` is never used
   --> src/lib.rs:189:4
    |
189 | fn display_path(p: &Path) -> String {
    |    ^^^^^^^^^^^^

warning: function `print_stats` is never used
   --> src/lib.rs:193:4
    |
193 | fn print_stats(info: &AggregateInfo) {
    |    ^^^^^^^^^^^

warning: `solution` (lib) generated 12 warnings (run `cargo fix --lib -p solution` to apply 1 suggestion)
   Compiling futures-macro v0.3.31
   Compiling futures-util v0.3.31
   Compiling futures-executor v0.3.31
   Compiling futures v0.3.31
warning: unused import: `PathBuf`
 --> tests/../src/lib.rs:6:23
  |
6 | use std::path::{Path, PathBuf};
  |                       ^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: method `as_str` is never used
  --> tests/../src/lib.rs:17:8
   |
16 | impl LogLevel {
   | ------------- method in this implementation
17 |     fn as_str(&self) -> &'static str {
   |        ^^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: function `print_stats` is never used
   --> tests/../src/lib.rs:193:4
    |
193 | fn print_stats(info: &AggregateInfo) {
    |    ^^^^^^^^^^^

warning: `solution` (test "solution_test") generated 3 warnings (run `cargo fix --test "solution_test"` to apply 1 suggestion)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 8.84s
     Running tests/solution_test.rs (target/debug/deps/solution_test-f75e629a1d90e17c)

running 4 tests
test solution_test::test_parse_log_basic ... ok
test solution_test::test_aggregate ... ok
test solution_test::test_parse_log_big_data ... ok
test solution_test::test_parse_log_invalid ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

История (1 версия и 0 коментара)

Калоян качи първо решение на 16.11.2025 19:03 (преди 19 дена)