diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a60ea5850..168b5721f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -89,6 +89,43 @@ syntax. - Does it only contain single line comments? Or only multi-line comments? - Is just C style comments? `/* */, //` +Some languages have a single, standard filename, like Makefile or Dockerfile. +These can be defined with the `filenames` property: + +```json +"Makefile":{ + "filenames":[ + "makefile" + ], + "extensions":[ + "makefile", + "mak", + "mk" + ] +} +``` + +Filenames should be all-lowercase, whether or not the filename typically has capital letters included. + +Note that filenames will *override* extensions, so with the following +configuration: + +```json +"Text":{ + "extensions":[ + "txt" + ] +}, +"CMake":{ + "filenames": [ + "cmakelists.txt" + ] +} + +A file named `CMakeLists.txt` will be detected as a CMake file, not a text file. + +``` + # Bug Reports Please include the error message, and a minimum working example including the file, or file structure. diff --git a/README.md b/README.md index a50e45bb3..e9a3e9eda 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,7 @@ C Header C# C Shell Clojure +CMake CoffeeScript Cogent ColdFusion @@ -269,6 +270,7 @@ CSS D Dart Device Tree +Dockerfile Elixir Elm Erlang @@ -317,6 +319,7 @@ PureScript Python QCL R +Rakefile Razor ReStructuredText Ruby @@ -324,6 +327,7 @@ Ruby HTML Rust Sass Scala +Scons Standard ML SQL Swift diff --git a/build.rs b/build.rs index 45c0ea180..809f5924c 100644 --- a/build.rs +++ b/build.rs @@ -62,26 +62,32 @@ fn generate_tests(out_dir: &OsStr) { let path = path.unwrap(); let path = path.path(); - let name = path.file_stem().unwrap().to_str().unwrap(); + let name = path.file_stem().unwrap().to_str().unwrap().to_lowercase(); string.push_str(&format!(r#" #[test] fn {0}() {{ let mut languages = Languages::new(); languages.get_statistics(vec!["{1}"], Vec::new()); + + if languages.len() != 1 {{ + panic!("wrong languages detected: expected just {0}, found {{:?}}", + languages.into_iter().collect::>()); + }} + + let (name, language) = languages.into_iter().next().unwrap(); + let mut contents = String::new(); File::open("{1}").unwrap().read_to_string(&mut contents).unwrap(); - for (name, language) in languages {{ - assert_eq!(get_digit!(LINES, contents), language.lines); - println!("{{}} LINES MATCH", name); - assert_eq!(get_digit!(CODE, contents), language.code); - println!("{{}} CODE MATCH", name); - assert_eq!(get_digit!(COMMENTS, contents), language.comments); - println!("{{}} COMMENTS MATCH", name); - assert_eq!(get_digit!(BLANKS, contents), language.blanks); - println!("{{}} BLANKS MATCH", name); - }} + assert_eq!(get_digit!(LINES, contents), language.lines); + println!("{{}} LINES MATCH", name); + assert_eq!(get_digit!(CODE, contents), language.code); + println!("{{}} CODE MATCH", name); + assert_eq!(get_digit!(COMMENTS, contents), language.comments); + println!("{{}} COMMENTS MATCH", name); + assert_eq!(get_digit!(BLANKS, contents), language.blanks); + println!("{{}} BLANKS MATCH", name); }} "#, name, path.display())); } diff --git a/languages.json b/languages.json index 8dda94c36..debc0d35f 100644 --- a/languages.json +++ b/languages.json @@ -177,6 +177,23 @@ "clj" ] }, + "CMake": { + "single":[ + "#" + ], + "quotes":[ + [ + "\\\"", + "\\\"" + ] + ], + "extensions":[ + "cmake" + ], + "filenames":[ + "cmakelists.txt" + ] + }, "CoffeeScript":{ "single":[ "#" @@ -351,6 +368,28 @@ "dtsi" ] }, + "Dockerfile":{ + "single":[ + "#" + ], + "extensions":[ + "dockerfile", + "dockerignore" + ], + "filenames":[ + "dockerfile" + ], + "quotes":[ + [ + "\\\"", + "\\\"" + ], + [ + "'", + "'" + ] + ] + }, "Elixir":{ "single":[ "#" @@ -829,6 +868,9 @@ "makefile", "mak", "mk" + ], + "filenames":[ + "makefile" ] }, "Markdown":{ @@ -1048,6 +1090,33 @@ "r" ] }, + "Rakefile":{ + "single":[ + "#" + ], + "multi":[ + [ + "=begin", + "=end" + ] + ], + "quotes":[ + [ + "\\\"", + "\\\"" + ], + [ + "'", + "'" + ] + ], + "filenames":[ + "rakefile" + ], + "extensions":[ + "rake" + ] + }, "Razor":{ "multi":[ [ @@ -1084,8 +1153,7 @@ ] ], "extensions":[ - "rb", - "rake" + "rb" ] }, "RubyHtml":{ @@ -1143,6 +1211,32 @@ "scala" ] }, + "Scons":{ + "base":"hash", + "quotes":[ + [ + "\\\"", + "\\\"" + ], + [ + "'", + "'" + ], + [ + "\\\"\\\"\\\"", + "\\\"\\\"\\\"" + ], + [ + "'''", + "'''" + ] + ], + "filenames":[ + "sconstruct", + "sconscript" + ] + }, + "Sml":{ "name":"Standard ML (SML)", "base":"func", diff --git a/src/language/language_type.hbs.rs b/src/language/language_type.hbs.rs index 34ead2dbe..826e8eded 100644 --- a/src/language/language_type.hbs.rs +++ b/src/language/language_type.hbs.rs @@ -53,22 +53,44 @@ impl LanguageType { ] } - /// Get language from it's file extension. + /// Get language from a file path. May open and read the file. /// /// ```no_run /// # use tokei::*; - /// let rust = LanguageType::from_extension("./main.rs"); + /// let rust = LanguageType::from_path("./main.rs"); /// /// assert_eq!(rust, Some(LanguageType::Rust)); /// ``` - pub fn from_extension>(entry: P) -> Option { - if let Some(extension) = fs::get_extension(entry) { + pub fn from_path>(entry: P) -> Option { + let filename = fs::get_filename(&entry); + + if let Some(filename) = filename { + match &*filename { + {{~#each languages}} + {{~#if this.filenames}} + {{~#each this.filenames}} + "{{~this}}" {{~#unless @last}} | {{~/unless}} + {{~/each}} + => return Some({{~@key}}), + {{~/if}} + {{~/each}} + + _ => () + } + } + + let extension = fs::get_extension(&entry) + .or_else(|| get_filetype_from_shebang(&entry).map(String::from)); + + if let Some(extension) = extension { match &*extension { {{~#each languages}} - {{~#each this.extensions}} - "{{~this}}" {{~#unless @last}} | {{~/unless}} - {{~/each}} - => Some({{~@key}}), + {{~#if this.extensions}} + {{~#each this.extensions}} + "{{~this}}" {{~#unless @last}} | {{~/unless}} + {{~/each}} + => Some({{~@key}}), + {{~/if}} {{~/each}} extension => { warn!("Unknown extension: {}", extension); diff --git a/src/utils/fs.rs b/src/utils/fs.rs index a96c4fd98..266f142f3 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -12,7 +12,6 @@ use ignore::overrides::OverrideBuilder; use ignore::WalkState::*; use language::{Language, Languages, LanguageType}; -use language::LanguageType::*; pub use language::get_filetype_from_shebang; pub fn get_all_files(paths: Vec<&str>, @@ -53,12 +52,7 @@ pub fn get_all_files(paths: Vec<&str>, let entry = entry.path(); - if entry.to_string_lossy().contains("Makefile") { - tx.send((Makefile, entry.to_owned())).unwrap(); - return Continue; - } - - if let Some(language) = LanguageType::from_extension(entry) { + if let Some(language) = LanguageType::from_path(entry) { if let Ok(metadata) = entry.metadata() { if metadata.is_file() { tx.send((language, entry.to_owned())).unwrap(); @@ -82,19 +76,20 @@ pub fn get_extension>(path: P) -> Option { match path.extension() { Some(extension_os) => { Some(extension_os.to_string_lossy().to_lowercase()) - } - None => { - match get_filetype_from_shebang(path) { - // Using String::from here because all file extensions from - // get_filetype_from_shebang are guaranteed to be lowercase. - Some(extension) => Some(String::from(extension)), - None => None, - } - } + }, + None => None } - } +pub fn get_filename>(path: P) -> Option { + let path = path.as_ref(); + match path.file_name() { + Some(filename_os) => { + Some(filename_os.to_string_lossy().to_lowercase()) + }, + None => None + } +} #[cfg(test)] mod test { diff --git a/tests/data/Dockerfile b/tests/data/Dockerfile new file mode 100644 index 000000000..a1aa7dec1 --- /dev/null +++ b/tests/data/Dockerfile @@ -0,0 +1,16 @@ +# 16 lines 6 code 3 comments 7 blanks + +FROM netbsd:7.0.2 + +MAINTAINER Somebody version: 2.2 + +RUN curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- -y + +# this part is important +VOLUME ["/project"] +WORKDIR "/project" + +RUN cargo install tokei # not counted + +# now you do your part + diff --git a/tests/data/Makefile b/tests/data/Makefile new file mode 100644 index 000000000..62354dfc8 --- /dev/null +++ b/tests/data/Makefile @@ -0,0 +1,24 @@ +# 24 lines 11 code 5 comments 8 blanks + +## ## +## IMPORTANT COMMENT ## +## ## + +all: hello + +hello: main.o factorial.o hello.o + g++ main.o factorial.o hello.o -o hello + +# main.o is my favorite +main.o: main.cpp + g++ -c main.cpp + +factorial.o: factorial.cpp + g++ -c factorial.cpp + +hello.o: hello.cpp + g++ -c hello.cpp + +clean: + rm *o hello #not counted + diff --git a/tests/data/Rakefile b/tests/data/Rakefile new file mode 100644 index 000000000..ecbbee683 --- /dev/null +++ b/tests/data/Rakefile @@ -0,0 +1,10 @@ +# 10 lines 4 code 2 comments 4 blanks + +# this is a rakefile + +task default: %w[test] + +task :test do # not counted + ruby "test/unittest.rb" +end + diff --git a/tests/data/SConstruct b/tests/data/SConstruct new file mode 100644 index 000000000..d5a4641cb --- /dev/null +++ b/tests/data/SConstruct @@ -0,0 +1,10 @@ +#!python +# 10 lines 3 code 3 comments 4 blanks + +# this is a comment + +Program('cpp.cpp') # this is a line-ending comment + +env = Environment(CCFLAGS='-O3') +env.Append(CCFLAGS='-O3') + diff --git a/tests/data/cmake.cmake b/tests/data/cmake.cmake new file mode 100644 index 000000000..cb6b08230 --- /dev/null +++ b/tests/data/cmake.cmake @@ -0,0 +1,25 @@ +# 25 lines 16 code 3 comments 6 blanks + +SET(_POSSIBLE_XYZ_INCLUDE include include/xyz) +SET(_POSSIBLE_XYZ_EXECUTABLE xyz) +SET(_POSSIBLE_XYZ_LIBRARY XYZ) + +# this is a comment +IF(XYZ_FIND_VERSION_MAJOR AND XYZ_FIND_VERSION_MINOR) + SET(_POSSIBLE_SUFFIXES "${XYZ_FIND_VERSION_MAJOR}${XYZ_FIND_VERSION_MINOR}" "${XYZ_FIND_VERSION_MAJOR}.${XYZ_FIND_VERSION_MINOR}" "-${XYZ_FIND_VERSION_MAJOR}.${XYZ_FIND_VERSION_MINOR}") # not counted +ELSE(XYZ_FIND_VERSION_MAJOR AND XYZ_FIND_VERSION_MINOR) + SET(_POSSIBLE_SUFFIXES "67" "92" "352.9" "0.0.8z") +ENDIF(XYZ_FIND_VERSION_MAJOR AND XYZ_FIND_VERSION_MINOR) + +FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES}) + LIST(APPEND _POSSIBLE_XYZ_INCLUDE "include/XYZ${_SUFFIX}") + LIST(APPEND _POSSIBLE_XYZ_EXECUTABLE "XYZ${_SUFFIX}") + LIST(APPEND _POSSIBLE_XYZ_LIBRARY "XYZ${_SUFFIX}") +ENDFOREACH(_SUFFIX) # not counted + +FIND_PROGRAM(XYZ_EXECUTABLE + NAMES ${_POSSIBLE_XYZ_EXECUTABLE} +) + +# this is also a comment +