#[macro_use] extern crate clap; use std::fs::File; use std::path::Path; use std::io::*; use std::path::PathBuf; use clap::{App, Arg}; mod config; struct FileTypeStats{ file_type: String, count: i64, } fn omit_line(line: String, in_comment_section: &mut bool, comment_tokens: &config::CommentTokens) -> bool { let s = line.trim(); let begin_comment_section = s.starts_with(comment_tokens.begin_comment_block.as_str()); let end_comment_section = s.starts_with(comment_tokens.end_comment_block.as_str()); if begin_comment_section { *in_comment_section = true; return true; } else if *in_comment_section && !end_comment_section { return true; } else if *in_comment_section && end_comment_section { *in_comment_section = false; return true; } s.starts_with(comment_tokens.single_line.as_str()) || s.is_empty() } fn get_config<'a>(config: &'a Vec, extension: &str) -> Option<&'a config::CommentTokens> { for conf in config.iter() { if conf.file_ending == extension { return Some(conf); } } None } fn count_lines(filename: &str, file_type_stats: &mut Vec, config: &mut Vec) -> Result{ let mut sum = 0; let mut in_comment_section = false; let extension = match Path::new(filename).extension() { Some(ext) => ext.to_str().unwrap(), None => return Err(Error::new(std::io::ErrorKind::Other, "could not get file extension")), }; let comment_tokens = match get_config(&config, extension) { Some(conf) => conf, None => return Err(Error::new(std::io::ErrorKind::NotFound, "config not found")), }; let bufread = BufReader::new(try!(File::open(filename))); for line in bufread.lines() { if omit_line(try!(line), &mut in_comment_section, &comment_tokens) { continue; } sum += 1; } let mut to_add = false; { let file = file_type_stats.iter_mut().find(|x| x.file_type == extension); match file { Some(mut e) => e.count += sum, None => to_add = true, } } if to_add { file_type_stats.push(FileTypeStats { file_type: extension.to_string(), count: sum }); } Ok(sum) } fn count_files_for_dir(dir: PathBuf, mut file_types: &mut Vec, mut config: &mut Vec) { match dir.read_dir() { Ok(iter) => { for entry in iter { match entry { Ok(i) => { if i.path().is_dir() { count_files_for_dir(i.path(), &mut file_types, &mut config); } else { count_lines_for_file(i.path().to_str().unwrap(), &mut file_types, &mut config); } }, Err(e) => println!("error: {}", e), } } }, Err(e) => println!("error: {}", e), } } fn count_lines_for_file(filename: &str, mut file_type_stats: &mut Vec, mut config: &mut Vec) { match count_lines(filename, &mut file_type_stats, &mut config) { Ok(i) => { println!("{}\t: {}", i, filename); }, Err(e) => println!("error for file {}: {}", filename, e), } } fn main() { let m = App::new("loc").about("Count lines of code. Without parameter read recursive all \ files from current directory and count lines of code\ for each") .version(crate_version!()) .author(crate_authors!()) .arg(Arg::with_name("file") .help("files to count lines of code") .short("f") .long("file") .takes_value(true) .multiple(true)) .arg(Arg::with_name("summary") .help("Print a summary for each file type") .short("s") .long("summary")) .get_matches(); let mut config = match config::read_config() { Err(e) => panic!("error reading config file(s): {}", e), Ok(i) => i, }; let mut file_types = Vec::new(); if let Some(files) = m.values_of("file") { for file in files { count_lines_for_file(file, &mut file_types, &mut config) } } else { match std::env::current_dir() { Ok(i) => count_files_for_dir(i, &mut file_types, &mut config), Err(e) => panic!("could not determine current dir: {}", e), } } if m.is_present("summary") { let mut sum = 0; for f in file_types { println!("{}\t: *.{}", f.count, f.file_type); sum += f.count; } println!("{}\t: total", sum); } }