├── .clog.toml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── cargo-count.png ├── justfile ├── rustfmt.toml └── src ├── comment.rs ├── config.rs ├── count ├── counts.rs └── mod.rs ├── error.rs ├── fmt.rs ├── fsutil.rs ├── language.rs ├── macros.rs └── main.rs /.clog.toml: -------------------------------------------------------------------------------- 1 | [clog] 2 | repository = "https://github.com/kbknapp/cargo-count" 3 | outfile = "CHANGELOG.md" 4 | from-latest-tag = true 5 | 6 | [sections] 7 | Performance = ["perf"] 8 | Improvements = ["impr", "im", "imp"] 9 | Documentation = ["docs"] 10 | Deprecations = ["depr"] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | 13 | # temp files 14 | .*~ 15 | 16 | # Backup Files 17 | *.orig 18 | *.bk 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - nightly-2016-09-04 5 | - beta 6 | - stable 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | script: 11 | - | 12 | cargo build && 13 | cargo test 14 | 15 | env: 16 | global: 17 | secure: JLBlgHY6OEmhJ8woewNJHmuBokTNUv7/WvLkJGV8xk0t6bXBwSU0jNloXwlH7FiQTc4TccX0PumPDD4MrMgxIAVFPmmmlQOCmdpYP4tqZJ8xo189E5zk8lKF5OyaVYCs5SMmFC3cxCsKjfwGIexNu3ck5Uhwe9jI0tqgkgM3URA= 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ### v0.2.3 (2016-09-06) 3 | 4 | 5 | #### Bug Fixes 6 | 7 | * fixes the thousands separator option ([f255de98](https://github.com/kbknapp/cargo-count/commit/f255de986ba884d6055b29cff39975ed9819bf2f)) 8 | 9 | 10 | 11 | ### v0.2.2 (2016-09-06) 12 | 13 | 14 | #### Bug Fixes 15 | 16 | * fix division by zero ([d6b2978b](https://github.com/kbknapp/cargo-count/commit/d6b2978b5135ba587e1eb6bc9005e4dcd3d45ec0)) 17 | * Batch results by language, not file extension ([1828538a](https://github.com/kbknapp/cargo-count/commit/1828538a9b30a8aec9fb059127d90721b4f9c29d), closes [#19](https://github.com/kbknapp/cargo-count/issues/19)) 18 | 19 | #### Features 20 | 21 | * **languages:** Add ASM, Shell, D and Nim ([350790c7](https://github.com/kbknapp/cargo-count/commit/350790c7f1e352fb21d7c274abcd30c10bb73a52)) 22 | 23 | #### Improvements 24 | 25 | * updates clap and uses new features ([4251fda2](https://github.com/kbknapp/cargo-count/commit/4251fda2473e6eda3630c777d0a674d7ee13449d)) 26 | 27 | 28 | 29 | 30 | ### v0.2.1 (2015-12-24) 31 | 32 | 33 | #### Improvements 34 | 35 | * adds C, h++, cc, cxx, cp, and htm extensions ([ea4f540a](https://github.com/kbknapp/cargo-count/commit/ea4f540ac356dc946c42aaebe683f8c6d70fd362), closes [#19](https://github.com/kbknapp/cargo-count/issues/19)) 36 | 37 | 38 | 39 | ## v0.2.0 (2015-12-24) 40 | 41 | 42 | #### Improvements 43 | 44 | * Ignore files in accordance with .gitignore ([a0c30706](https://github.com/kbknapp/cargo-count/commit/a0c307061413972b973f148802abe06e80a01099), Closes [#8](https://github.com/kbknapp/cargo-count/issues/8), [#9](https://github.com/kbknapp/cargo-count/issues/9)) 45 | 46 | #### Bug Fixes 47 | 48 | * fixes building on windows due to upstream dep ([3333f252](https://github.com/kbknapp/cargo-count/commit/3333f252f4c7e5e1324d5a178b9f020823283bc7)) 49 | 50 | 51 | 52 | 53 | ### v0.1.4 (2015-11-14) 54 | 55 | 56 | #### Bug Fixes 57 | 58 | * fixes building on windows due to upstream dep ([3333f252](https://github.com/kbknapp/cargo-count/commit/3333f252f4c7e5e1324d5a178b9f020823283bc7)) 59 | 60 | 61 | 62 | 63 | ### v0.1.3 (2015-11-04) 64 | 65 | 66 | #### Documentation 67 | 68 | * adds cargo install instructions ([467dd945](https://github.com/kbknapp/cargo-count/commit/467dd9456e6b605e1cbf48e033db9053bcfe1735)) 69 | 70 | #### Features 71 | 72 | * uses clippy to lint dev builds ([e02b9d9b](https://github.com/kbknapp/cargo-count/commit/e02b9d9b7381385721466677f6c80bf340aae9ae)) 73 | 74 | #### Improvements 75 | 76 | * better comparison of f64 values thanks to clippy ([6c3a1362](https://github.com/kbknapp/cargo-count/commit/6c3a13625fc93038dc6ab799dc023f03d2a4bfe9)) 77 | 78 | #### Bug Fixes 79 | 80 | * fixes features declarations ([de98dde6](https://github.com/kbknapp/cargo-count/commit/de98dde6e4d207f88130b9668c4517adf719dac7)) 81 | 82 | 83 | 84 | 85 | ### v0.1.2 (2015-08-25) 86 | 87 | 88 | #### Bug Fixes 89 | 90 | * **Symlinks:** adds ability to optionally follow symlinks while counting ([d265980e](https://github.com/kbknapp/cargo-count/commit/d265980e8e06101c07dd3265dd2d66d834b09c58), closes [#6](https://github.com/kbknapp/cargo-count/issues/6), [#7](https://github.com/kbknapp/cargo-count/issues/7)) 91 | 92 | 93 | 94 | 95 | ### v0.1.1 (2015-08-24) 96 | 97 | 98 | #### Bug Fixes 99 | 100 | * fixes unsafe code count bug in C and C++ files ([1c1e01d6](https://github.com/kbknapp/cargo-count/commit/1c1e01d67c0f5ad717b3842295c5fb597db65656), closes [#1](https://github.com/kbknapp/cargo-count/issues/1)) 101 | * fixes single line block comment bug ([d896412b](https://github.com/kbknapp/cargo-count/commit/d896412bf81da6271c762ab5168d40e27e8eb988), closes [#2](https://github.com/kbknapp/cargo-count/issues/2)) 102 | * **Unsafe Counter:** fixes a bug in the unsafe counter for Rust giving incorrect numbers ([317d2fc9](https://github.com/kbknapp/cargo-count/commit/317d2fc9964d131dbdc28fa93a6e29230143cb94), closes [#5](https://github.com/kbknapp/cargo-count/issues/5)) 103 | 104 | 105 | 106 | 107 | ## v0.1.0 (2015-08-21) 108 | 109 | 110 | #### Documentation 111 | 112 | * adds a changelog using clog ([34fdc52b](https://github.com/kbknapp/cargo-count/commit/34fdc52b8dac02b5668a0cd9daca57ae3dd9de17)) 113 | * updates the readme ([d302d65d](https://github.com/kbknapp/cargo-count/commit/d302d65da7614c609120011858cee0cc4e32bcc3)) 114 | 115 | #### Features 116 | 117 | * initial implementation ([b6e968fb](https://github.com/kbknapp/cargo-count/commit/b6e968fb2c1ff0bc5af6b21a11f83099c6fe6e68)) 118 | 119 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | Contributions are always welcome! Please use the following guidelines when contributing to `cargo-count` 4 | 5 | 1. Fork `cargo-count` 6 | 2. Clone your fork (`git clone https://github.com/$YOUR_USERNAME/cargo-count && cd cargo-count`) 7 | 3. Create new branch (`git checkout -b new-branch`) 8 | 4. Make your changes, and commit (`git commit -am "your message"`) 9 | * I use a [conventional](https://github.com/ajoslin/conventional-changelog/blob/a5505865ff3dd710cf757f50530e73ef0ca641da/conventions/angular.md) changelog format so I can update my changelog using [clog](https://github.com/clog-tool/clog-cli) 10 | * In addition to the conventions defined above, I also use `imp`, `wip`, `examples`. 11 | * Format your commit subject line using the following format: `TYPE(COMPONENT): MESSAGE` where `TYPE` is one of the following: 12 | - `feat` - A new feature 13 | - `imp` - An improvement to an existing feature 14 | - `perf` - A performance improvement 15 | - `docs` - Changes to documentation only 16 | - `tests` - Changes to the testing framework or tests only 17 | - `fix` - A bug fix 18 | - `refactor` - Code functionality doesn't change, but underlying structure may 19 | - `style` - Stylistic changes only, no functionality changes 20 | - `wip` - A work in progress commit (Should typically be `git rebase`'ed away) 21 | - `chore` - Catch all or things that have to do with the build system, etc 22 | - `examples` - Changes to existing example, or a new example 23 | * The `COMPONENT` is optional, and may be a single file, directory, or logical component. Can be omitted if commit applies globally 24 | 5. Run the tests (`cargo test `) 25 | 6. `git rebase` into concise commits and remove `--fixup`s (`git rebase -i HEAD~NUM` where `NUM` is number of commits back) 26 | 7. Push your changes back to your fork (`git push origin $your-branch`) 27 | 8. Create a pull request! (You can also create the pull request first, and we'll merge when ready. This a good way to discuss proposed changes.) 28 | 29 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | the following is a list of contributors: 2 | 3 | 4 | [kbknapp](https://github.com/kbknapp) |[homu](https://github.com/homu) |[m-n](https://github.com/m-n) |[nabijaczleweli](https://github.com/nabijaczleweli) |[Aaronepower](https://github.com/Aaronepower) |[Vinatorul](https://github.com/Vinatorul) | 5 | :---: |:---: |:---: |:---: |:---: |:---: | 6 | [kbknapp](https://github.com/kbknapp) |[homu](https://github.com/homu) |[m-n](https://github.com/m-n) |[nabijaczleweli](https://github.com/nabijaczleweli) |[Aaronepower](https://github.com/Aaronepower) |[Vinatorul](https://github.com/Vinatorul) | 7 | 8 | [DenisKolodin](https://github.com/DenisKolodin) |[pascalw](https://github.com/pascalw) |[gitter-badger](https://github.com/gitter-badger) |[0x1997](https://github.com/0x1997) | 9 | :---: |:---: |:---: |:---: | 10 | [DenisKolodin](https://github.com/DenisKolodin) |[pascalw](https://github.com/pascalw) |[gitter-badger](https://github.com/gitter-badger) |[0x1997](https://github.com/0x1997) | 11 | 12 | 13 | 14 | 15 | This list was generated by [mgechev/github-contributors-list](https://github.com/mgechev/github-contributors-list) 16 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.5.2" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "ansi_term" 11 | version = "0.9.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "0.7.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "cargo-count" 21 | version = "0.2.4" 22 | dependencies = [ 23 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "clap 2.11.2 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "clippy 0.0.88 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "gitignore 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 27 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "regex 0.1.75 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "tabwriter 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", 30 | ] 31 | 32 | [[package]] 33 | name = "clap" 34 | version = "2.11.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "clippy" 49 | version = "0.0.88" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | dependencies = [ 52 | "clippy_lints 0.0.88 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "clippy_lints" 57 | version = "0.0.88" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "gitignore" 71 | version = "1.0.4" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 75 | ] 76 | 77 | [[package]] 78 | name = "glob" 79 | version = "0.2.11" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | 82 | [[package]] 83 | name = "kernel32-sys" 84 | version = "0.2.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | dependencies = [ 87 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "libc" 93 | version = "0.2.15" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | 96 | [[package]] 97 | name = "matches" 98 | version = "0.1.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | 101 | [[package]] 102 | name = "memchr" 103 | version = "0.1.11" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "nom" 111 | version = "1.2.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "quine-mc_cluskey" 116 | version = "0.2.4" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | 119 | [[package]] 120 | name = "regex" 121 | version = "0.1.75" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 129 | ] 130 | 131 | [[package]] 132 | name = "regex-syntax" 133 | version = "0.3.5" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | 136 | [[package]] 137 | name = "rustc-serialize" 138 | version = "0.3.19" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | 141 | [[package]] 142 | name = "semver" 143 | version = "0.2.3" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | dependencies = [ 146 | "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 147 | ] 148 | 149 | [[package]] 150 | name = "strsim" 151 | version = "0.5.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | 154 | [[package]] 155 | name = "tabwriter" 156 | version = "0.1.25" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | dependencies = [ 159 | "unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "term_size" 164 | version = "0.2.1" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | dependencies = [ 167 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 170 | ] 171 | 172 | [[package]] 173 | name = "thread-id" 174 | version = "2.0.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | dependencies = [ 177 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 179 | ] 180 | 181 | [[package]] 182 | name = "thread_local" 183 | version = "0.2.6" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | dependencies = [ 186 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 187 | ] 188 | 189 | [[package]] 190 | name = "toml" 191 | version = "0.1.30" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | dependencies = [ 194 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 195 | ] 196 | 197 | [[package]] 198 | name = "unicode-normalization" 199 | version = "0.1.2" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | 202 | [[package]] 203 | name = "unicode-segmentation" 204 | version = "0.1.2" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | [[package]] 208 | name = "unicode-width" 209 | version = "0.1.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "utf8-ranges" 214 | version = "0.1.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [[package]] 218 | name = "vec_map" 219 | version = "0.6.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | 222 | [[package]] 223 | name = "winapi" 224 | version = "0.2.8" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | 227 | [[package]] 228 | name = "winapi-build" 229 | version = "0.1.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | 232 | [metadata] 233 | "checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" 234 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 235 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 236 | "checksum clap 2.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8888b7b29ca3f7a23bc8b8526537f9e233a94c836140ffcfc6fdef1f320a40f7" 237 | "checksum clippy 0.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc502e63a10b490d8b445c728cfba3ea778a38a14432cf392b132e655cdf029" 238 | "checksum clippy_lints 0.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "28e4d47b13273289faf3e7a0d10e93e9ad5226c21e133e2e38ed09ca44a879fb" 239 | "checksum gitignore 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8cd9bd3a32e762ced4ea984bef04bb63700f0ac7964250eb16b25be637d39298" 240 | "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" 241 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 242 | "checksum libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23e3757828fa702a20072c37ff47938e9dd331b92fac6e223d26d4b7a55f7ee2" 243 | "checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" 244 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 245 | "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" 246 | "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" 247 | "checksum regex 0.1.75 (registry+https://github.com/rust-lang/crates.io-index)" = "f62414f9d3b0f53e827ac46d6f8ce2ff6a91afd724225a5986e54e81e170693c" 248 | "checksum regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279401017ae31cf4e15344aa3f085d0e2e5c1e70067289ef906906fdbe92c8fd" 249 | "checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" 250 | "checksum semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d5b7638a1f03815d94e88cb3b3c08e87f0db4d683ef499d1836aaf70a45623f" 251 | "checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e" 252 | "checksum tabwriter 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "85dbf563da2891d55ef4b00ef08c5b5f160143f67691ff1f97ad89e77824ed3b" 253 | "checksum term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7f5f3f71b0040cecc71af239414c23fd3c73570f5ff54cf50e03cef637f2a0" 254 | "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 255 | "checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" 256 | "checksum toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "0590d72182e50e879c4da3b11c6488dae18fccb1ae0c7a3eda18e16795844796" 257 | "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" 258 | "checksum unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b905d0fc2a1f0befd86b0e72e31d1787944efef9d38b9358a9e92a69757f7e3b" 259 | "checksum unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6722facc10989f63ee0e20a83cd4e1714a9ae11529403ac7e0afd069abc39e" 260 | "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 261 | "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" 262 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 263 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 264 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-count" 3 | version = "0.2.4" 4 | authors = ["Kevin K "] 5 | exclude = ["*.png"] 6 | description = "Cargo subcommand for displaying statistics about projects, such as code, comments, and unsafe counters" 7 | repository = "https://github.com/kbknapp/cargo-count.git" 8 | readme = "README.md" 9 | license = "MIT" 10 | keywords = ["cargo", "subcommand", "statistics", "count", "lines"] 11 | categories = ["development-tools", "development-tools::cargo-plugins"] 12 | 13 | [[bin]] 14 | name = "cargo-count" 15 | 16 | [badges] 17 | travis-ci = {repository = "kbknapp/cargo-count"} 18 | 19 | [dependencies] 20 | clap = "~2.11.2" 21 | glob = "~0.2" 22 | tabwriter = "~0.1" 23 | regex = "~0.1" 24 | gitignore = "~1" 25 | ansi_term = {version = "~0.9", optional = true} 26 | clippy = {version = "=0.0.88", optional = true} 27 | 28 | [features] 29 | default = ["color"] 30 | color = ["ansi_term"] 31 | debug = [] 32 | nightly = [] 33 | lints = ["clippy", "nightly"] 34 | unstable = ["lints"] 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kevin K. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cargo-count 2 | 3 | [![Join the chat at https://gitter.im/kbknapp/cargo-count](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kbknapp/cargo-count?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Linux: [![Build Status](https://travis-ci.org/kbknapp/cargo-count.svg?branch=master)](https://travis-ci.org/kbknapp/cargo-count) 6 | 7 | A cargo subcommand for displaying line counts of source code in projects, including a niave `unsafe` counter for Rust source files. This subcommand was originally based off and inspired by the project [tokei](https://github.com/aaronepower/tokei) by [Aaronepower](https://github.com/aaronepower) 8 | 9 | ## Demo 10 | 11 | To count the source code in the [Rust](https://github.com/rust-lang/rust) repository (checkout `4c99649`), and print some naive statistics on how much "unsafe" code exists. 12 | 13 | **NOTE:** The Rust repository is quite large, if you're on a slow internet connect consider using a smaller repository, such as the `cargo-count` repo. 14 | 15 | ``` 16 | $ git clone https://github.com/rust-lang/rust 17 | $ cd rust 18 | $ cargo count --separator , --unsafe-statistics 19 | Gathering information... 20 | Language Files Lines Blanks Comments Code Unsafe (%) 21 | -------- ----- ----- ------ -------- ---- ---------- 22 | Rust 6,018 528,510 66,984 133,698 327,792 3,163 (0.96%) 23 | C 54 9,962 1,445 1,492 7,025 7,025 (100.00%) 24 | CSS 4 1,266 149 52 1,065 25 | JavaScript 4 1,118 131 166 821 26 | Python 31 4,797 843 585 3,369 27 | C Header 13 1,865 284 585 996 996 (100.00%) 28 | C++ 4 1,611 185 81 1,345 1,345 (100.00%) 29 | -------- ----- ----- ------ -------- ---- ---------- 30 | Totals: 6,128 549,129 70,021 136,659 342,413 12,529 (3.66%) 31 | 32 | ``` 33 | 34 | The `--separator ,` sets a `,` character as the thousands separator, and `--unsafe-statistics` looks for, and counts lines of `unsafe`. 35 | 36 | ## Installing 37 | 38 | `cargo-count` can be installed with `cargo install` 39 | 40 | ``` 41 | $ cargo install cargo-count 42 | ``` 43 | 44 | This may require a nightly version of `cargo` if you get an error about the `install` command not being found. You may also compile and install the traditional way by followin the instructions below. 45 | 46 | 47 | ## Compiling 48 | 49 | Follow these instructions to compile `cargo-count`, then skip down to Installation. 50 | 51 | 1. Ensure you have current version of `cargo` and [Rust](https://www.rust-lang.org) installed 52 | 2. Clone the project `$ git clone https://github.com/kbknapp/cargo-count && cd cargo-count` 53 | 3. Build the project `$ cargo build --release` (**NOTE:** There is a large performance differnce when compiling without optimizations, so I recommend alwasy using `--release` to enable to them) 54 | 4. Once complete, the binary will be located at `target/release/cargo-count` 55 | 56 | ## Installation and Usage 57 | 58 | All you need to do is place `cargo-count` somewhere in your `$PATH`. Then run `cargo count` anywhere in your project directory. For full details see below. 59 | 60 | ### Linux / OS X 61 | 62 | You have two options, place `cargo-count` into a directory that is already located in your `$PATH` variable (To see which directories those are, open a terminal and type `echo "${PATH//:/\n}"`, the quotation marks are important), or you can add a custom directory to your `$PATH` 63 | 64 | **Option 1** 65 | If you have write permission to a directory listed in your `$PATH` or you have root permission (or via `sudo`), simply copy the `cargo-count` to that directory `# sudo cp cargo-count /usr/local/bin` 66 | 67 | **Option 2** 68 | If you do not have root, `sudo`, or write permission to any directory already in `$PATH` you can create a directory inside your home directory, and add that. Many people use `$HOME/.bin` to keep it hidden (and not clutter your home directory), or `$HOME/bin` if you want it to be always visible. Here is an example to make the directory, add it to `$PATH`, and copy `cargo-count` there. 69 | 70 | Simply change `bin` to whatever you'd like to name the directory, and `.bashrc` to whatever your shell startup file is (usually `.bashrc`, `.bash_profile`, or `.zshrc`) 71 | 72 | ```sh 73 | $ mkdir ~/bin 74 | $ echo "export PATH=$PATH:$HOME/bin" >> ~/.bashrc 75 | $ cp cargo-count ~/bin 76 | $ source ~/.bashrc 77 | ``` 78 | 79 | ### Windows 80 | 81 | On Windows 7/8 you can add directory to the `PATH` variable by opening a command line as an administrator and running 82 | 83 | ```sh 84 | C:\> setx path "%path%;C:\path\to\cargo-count\binary" 85 | ``` 86 | 87 | Otherwise, ensure you have the `cargo-count` binary in the directory which you operating in the command line from, because Windows automatically adds your current directory to PATH (i.e. if you open a command line to `C:\my_project\` to use `cargo-count` ensure `cargo-count.exe` is inside that directory as well). 88 | 89 | 90 | ### Options 91 | 92 | There are a few options for using `cargo-count` which should be somewhat self explanitory. 93 | 94 | ``` 95 | USAGE: 96 | cargo count [FLAGS] [OPTIONS] [--] [ARGS] 97 | 98 | FLAGS: 99 | -S, --follow-symlinks Follows symlinks and counts source files it finds 100 | -a, --all Do not ignore .gitignored paths 101 | (Defaults to false when omitted) 102 | -h, --help Prints help information 103 | --unsafe-statistics Displays lines and percentages of "unsafe" code 104 | -V, --version Prints version information 105 | -v, --verbose Print verbose output 106 | 107 | OPTIONS: 108 | -l, --language ... Only count these languges (by source code extension) 109 | (i.e. '-l js py cpp') 110 | -e, --exclude ... Files or directories to exclude (automatically includes '.git') 111 | --utf8-rule Sets the UTF-8 parsing rule (Defaults to 'strict') 112 | [values: ignore lossy strict] 113 | -s, --separator Set the thousands separator for pretty printing 114 | 115 | ARGS: 116 | to_count... The files or directories (including children) to count 117 | (defaults to current working directory when omitted) 118 | 119 | When using '--exclude ' the path given can either be relative to the current 120 | directory, or absolute. When '' is a file, it must be relative to the current 121 | directory or it will not be found. Example, if the current directory has a child 122 | directory named 'target' with a child fild 'test.rs' and you use `--exclude target/test.rs' 123 | 124 | Globs are also supported. For example, to exclude 'test.rs' files from all child directories 125 | of the current directory you could do '--exclude */test.rs'. 126 | ``` 127 | 128 | ## License 129 | 130 | `cargo-count` is released under the terms of the MIT. See the LICENSE-MIT file for the details. 131 | 132 | ## Dependencies Tree 133 | ![cargo-count dependencies](cargo-count.png) 134 | -------------------------------------------------------------------------------- /cargo-count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbknapp/cargo-count/eebe6f8772af5ca1add8a770fd1ac8bb4a4edec5/cargo-count.png -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | @update-contributors: 2 | echo 'Removing old CONTRIBUTORS.md' 3 | mv CONTRIBUTORS.md CONTRIBUTORS.md.bak 4 | echo 'Downloading a list of new contributors' 5 | echo "the following is a list of contributors:" > CONTRIBUTORS.md 6 | echo "" >> CONTRIBUTORS.md 7 | echo "" >> CONTRIBUTORS.md 8 | githubcontrib --owner kbknapp --repo cargo-count --sha master --cols 6 --format md --showlogin true --sortBy contributions --sortOrder desc >> CONTRIBUTORS.md 9 | echo "" >> CONTRIBUTORS.md 10 | echo "" >> CONTRIBUTORS.md 11 | echo "This list was generated by [mgechev/github-contributors-list](https://github.com/mgechev/github-contributors-list)" >> CONTRIBUTORS.md 12 | rm CONTRIBUTORS.md.bak 13 | 14 | run-test TEST: 15 | cargo test --test {{TEST}} 16 | 17 | debug TEST: 18 | cargo test --test {{TEST}} --features debug 19 | 20 | run-tests: 21 | cargo test --features "yaml unstable" 22 | 23 | nightly: 24 | rustup override add nightly 25 | 26 | rm-nightly: 27 | rustup override remove 28 | 29 | @lint: nightly 30 | cargo build --features lints && just rm-nightly 31 | 32 | clean: 33 | cargo clean 34 | find . -type f -name "*.orig" -exec rm {} \; 35 | find . -type f -name "*.bk" -exec rm {} \; 36 | find . -type f -name ".*~" -exec rm {} \; 37 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_strings = false 2 | reorder_imports = true 3 | -------------------------------------------------------------------------------- /src/comment.rs: -------------------------------------------------------------------------------- 1 | /// Defines comment styles for any language 2 | pub trait Comment { 3 | /// The type of the comment (`String`, `&'static str`, etc.) 4 | type Rep; 5 | /// Returns the single line comment style, if any 6 | fn single(&self) -> Option::Rep>>; 7 | /// Returns the start of a multi-line comment style, if any 8 | fn multi_start(&self) -> Option<::Rep>; 9 | /// Returns the end of a multi-line comment style, if any 10 | fn multi_end(&self) -> Option<::Rep>; 11 | } 12 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use clap::ArgMatches; 4 | 5 | use error::{CliError, CliResult}; 6 | use language::Language; 7 | use std::env; 8 | use std::path::{Path, PathBuf}; 9 | 10 | arg_enum! { 11 | #[derive(Debug)] 12 | pub enum Utf8Rule { 13 | Ignore, 14 | Lossy, 15 | Strict 16 | } 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct Config<'a> { 21 | pub verbose: bool, 22 | pub all: bool, 23 | pub thousands: Option, 24 | pub utf8_rule: Utf8Rule, 25 | pub usafe: bool, 26 | pub exclude: Vec, 27 | pub exts: Option>, 28 | pub to_count: Vec, 29 | pub follow_links: bool, 30 | } 31 | 32 | impl<'a> Config<'a> { 33 | pub fn from_matches(m: &'a ArgMatches<'a>) -> CliResult { 34 | if let Some(ext_vec) = m.values_of("language") { 35 | for e in ext_vec { 36 | if let None = Language::from_ext(e) { 37 | return Err(CliError::UnknownExt(format!("unsupported source code extension \ 38 | '{}'", 39 | e.to_owned()))); 40 | } 41 | } 42 | } 43 | Ok(Config { 44 | verbose: m.is_present("verbose"), 45 | all: m.is_present("all"), 46 | thousands: m.value_of("separator").map(|s| s.chars().nth(0).unwrap()), 47 | usafe: m.is_present("unsafe-statistics"), 48 | utf8_rule: value_t!(m.value_of("utf8-rule"), Utf8Rule).unwrap_or(Utf8Rule::Strict), 49 | exclude: if let Some(v) = m.values_of("exclude") { 50 | debugln!("There are some"); 51 | let mut ret = vec![]; 52 | for p in v { 53 | let pb = Path::new(p); 54 | if pb.is_relative() { 55 | ret.push(cli_try!(env::current_dir()).join(p)); 56 | } else { 57 | ret.push(pb.to_path_buf()); 58 | } 59 | } 60 | debugln!("found files or dirs: {:?}", ret); 61 | ret.push(cli_try!(env::current_dir()).join(".git")); 62 | ret 63 | } else { 64 | debugln!("There aren't any, adding .git"); 65 | vec![cli_try!(env::current_dir()).join(".git")] 66 | }, 67 | to_count: if let Some(v) = m.values_of("PATH") { 68 | debugln!("There are some"); 69 | let mut ret = vec![]; 70 | for p in v { 71 | ret.push(PathBuf::from(p)); 72 | } 73 | debugln!("found files or dirs: {:?}", ret); 74 | ret 75 | } else { 76 | debugln!("There aren't any, using cwd"); 77 | vec![cli_try!(env::current_dir())] 78 | }, 79 | exts: m.values_of("language").map(|v| v.collect()), 80 | follow_links: m.is_present("follow-symlinks"), 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/count/counts.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use comment::Comment; 4 | use config::{Config, Utf8Rule}; 5 | use count::Count; 6 | use error::{CliError, CliResult}; 7 | use fmt::{self, Format}; 8 | use fsutil; 9 | use gitignore; 10 | use language::Language; 11 | use regex::Regex; 12 | use std::env; 13 | use std::f64; 14 | use std::fs::File; 15 | use std::io::{self, Read, Write}; 16 | use std::path::{Path, PathBuf}; 17 | 18 | use tabwriter::TabWriter; 19 | 20 | pub struct Counts<'c> { 21 | cfg: &'c Config<'c>, 22 | counts: Vec, 23 | tot: usize, 24 | tot_lines: u64, 25 | tot_comments: u64, 26 | tot_blanks: u64, 27 | tot_code: u64, 28 | tot_usafe: u64, 29 | } 30 | 31 | impl<'c> Counts<'c> { 32 | pub fn new(cfg: &'c Config) -> Self { 33 | Counts { 34 | cfg: cfg, 35 | counts: vec![], 36 | tot: 0, 37 | tot_lines: 0, 38 | tot_comments: 0, 39 | tot_blanks: 0, 40 | tot_code: 0, 41 | tot_usafe: 0, 42 | } 43 | } 44 | 45 | pub fn fill_from(&mut self) { 46 | debugln!("executing; fill_from; cfg={:?}", self.cfg); 47 | let cd; 48 | let gitignore = if self.cfg.all { 49 | None 50 | } else { 51 | cd = env::current_dir().unwrap().join(".gitignore"); 52 | gitignore::File::new(&cd).ok() 53 | }; 54 | for path in &self.cfg.to_count { 55 | debugln!("iter; path={:?};", path); 56 | let mut files = vec![]; 57 | fsutil::get_all_files(&mut files, 58 | path, 59 | &self.cfg.exclude, 60 | self.cfg.follow_links, 61 | &gitignore); 62 | 63 | for file in files { 64 | debugln!("iter; file={:?};", file); 65 | let extension = match Path::new(&file).extension() { 66 | Some(result) => { 67 | if let Some(ref exts) = self.cfg.exts { 68 | if !exts.contains(&result.to_str().unwrap_or("")) { 69 | continue; 70 | } 71 | } 72 | result.to_str().unwrap() 73 | } 74 | None => continue, 75 | }; 76 | 77 | debugln!("found extension: {:?}", extension); 78 | if let Some(pos_lang) = Language::from_ext(extension) { 79 | debugln!("Extension is valid"); 80 | let mut found = false; 81 | debugln!("Searching for previous entries of that type"); 82 | for l in self.counts.iter_mut() { 83 | if l.lang == pos_lang { 84 | debugln!("Found"); 85 | found = true; 86 | l.add_file(PathBuf::from(&file)); 87 | break; 88 | } 89 | } 90 | if !found { 91 | debugln!("Not found, creating new"); 92 | let mut c = Count::new(pos_lang, self.cfg.thousands); 93 | c.add_file(PathBuf::from(&file)); 94 | self.counts.push(c); 95 | } 96 | } else { 97 | debugln!("extension wasn't valid"); 98 | } 99 | } 100 | } 101 | } 102 | 103 | #[cfg_attr(feature = "lints", allow(cyclomatic_complexity, trivial_regex))] 104 | pub fn count(&mut self) -> CliResult<()> { 105 | for count in self.counts.iter_mut() { 106 | debugln!("iter; count={:?};", count); 107 | let re = if let Some(kw) = count.lang.unsafe_keyword() { 108 | Regex::new(&*format!("(.*?)([:^word:]{}[:^word:])(.*)", kw)).unwrap() 109 | } else { 110 | Regex::new("").unwrap() 111 | }; 112 | for file in count.files.iter() { 113 | debugln!("iter; file={:?};", file); 114 | let mut buffer = String::new(); 115 | 116 | let mut file_ref = cli_try!(File::open(&file)); 117 | 118 | match self.cfg.utf8_rule { 119 | Utf8Rule::Ignore => { 120 | if let Err(..) = file_ref.read_to_string(&mut buffer) { 121 | continue; 122 | } 123 | } 124 | Utf8Rule::Lossy => { 125 | let mut vec_buf = vec![]; 126 | cli_try!(file_ref.read_to_end(&mut vec_buf)); 127 | buffer = String::from_utf8_lossy(&vec_buf).into_owned(); 128 | } 129 | Utf8Rule::Strict => { 130 | cli_try!(file_ref.read_to_string(&mut buffer)); 131 | } 132 | } 133 | let mut is_in_comments = false; 134 | let mut is_in_unsafe = false; 135 | let mut bracket_count: i64 = 0; 136 | 137 | 'new_line: for line in buffer.lines() { 138 | let line = line.trim(); 139 | debugln!("iter; line={:?};", line); 140 | count.lines += 1; 141 | 142 | if is_in_comments { 143 | debugln!("still in comments"); 144 | if line.contains(count.multi_end().unwrap()) { 145 | debugln!("line contained ending comment, stopping comments"); 146 | is_in_comments = false; 147 | } 148 | count.comments += 1; 149 | continue; 150 | } 151 | debugln!("not in comments"); 152 | 153 | if line.trim().is_empty() { 154 | debugln!("line was empty"); 155 | count.blanks += 1; 156 | continue; 157 | } 158 | debugln!("Line isn't empty"); 159 | 160 | if let Some(ms) = count.multi_start() { 161 | debugln!("This file type has a multi start of: {:?}", ms); 162 | if line.starts_with(ms) { 163 | debugln!("line starts with multi comment"); 164 | count.comments += 1; 165 | is_in_comments = !line.contains(count.multi_end().unwrap()); 166 | debugln!("line also contained a multi end: {:?}", is_in_comments); 167 | continue; 168 | } else if line.contains(ms) { 169 | debugln!("line contains a multi start"); 170 | is_in_comments = !line.contains(count.multi_end().unwrap()); 171 | debugln!("line also contained a multi end: {:?}", is_in_comments); 172 | if is_in_comments { 173 | continue; 174 | } 175 | } 176 | } else { 177 | debugln!("No multi line comments for this type"); 178 | } 179 | debugln!("No multi line comments for this line"); 180 | 181 | if let Some(single_comments) = count.single() { 182 | debugln!("This type has single line comments: {:?}", single_comments); 183 | for single in single_comments { 184 | if line.starts_with(single) { 185 | debugln!("Line started with a comment"); 186 | count.comments += 1; 187 | continue 'new_line; 188 | } else { 189 | debugln!("Line dind't start with a comment"); 190 | } 191 | } 192 | } else { 193 | debugln!("No single line comments for this type"); 194 | } 195 | 196 | if self.cfg.usafe && count.lang.is_unsafe() { 197 | debugln!("Calculating --unsafe-statistics"); 198 | debugln!("The language is not safe"); 199 | if let Some(..) = count.lang.unsafe_keyword() { 200 | debugln!("There is a keyword"); 201 | debugln!("line={:?}", line); 202 | if is_in_unsafe { 203 | debugln!("It didn't contain the keyword, but we are still in \ 204 | unsafe"); 205 | count.usafe += 1; 206 | bracket_count = Counts::count_brackets(line, Some(bracket_count)); 207 | is_in_unsafe = bracket_count > 0; 208 | debugln!("after counting brackets; is_in_unsafe={:?}; \ 209 | bracket_count={:?}", 210 | is_in_unsafe, 211 | bracket_count); 212 | } else if let Some(caps) = re.captures(line) { 213 | let mut should_count = true; 214 | if let Some(before) = caps.at(1) { 215 | if let Some(single_v) = count.lang.single() { 216 | for s in single_v { 217 | if before.contains(s) { 218 | should_count = false; 219 | break; 220 | } 221 | } 222 | } 223 | if let Some(multi) = count.lang.multi_start() { 224 | if before.contains(multi) && 225 | !before.contains(count.lang.multi_end().unwrap()) { 226 | should_count = false; 227 | } 228 | } 229 | } 230 | if should_count { 231 | debugln!("It contained the keyword; usafe_line={:?}", line); 232 | count.usafe += 1; 233 | if let Some(after) = caps.at(3) { 234 | debugln!("after_usafe={:?}", after); 235 | bracket_count = Counts::count_brackets(after, None); 236 | is_in_unsafe = bracket_count > 0; 237 | debugln!("after counting brackets; is_in_unsafe={:?}; \ 238 | bracket_count={:?}", 239 | is_in_unsafe, 240 | bracket_count); 241 | } 242 | } 243 | } else { 244 | debugln!("It didn't contain the keyword, and we are not in unsafe"); 245 | } 246 | 247 | if bracket_count < 0 { 248 | debugln!("bracket_count < 0; resetting"); 249 | bracket_count = 0 250 | } 251 | } else { 252 | debugln!("Language is unsafe, incing the count"); 253 | count.usafe += 1; 254 | } 255 | } 256 | count.code += 1; 257 | } 258 | } 259 | self.tot += count.files.len(); 260 | self.tot_lines += count.lines; 261 | self.tot_comments += count.comments; 262 | self.tot_blanks += count.blanks; 263 | self.tot_code += count.code; 264 | self.tot_usafe += count.usafe; 265 | } 266 | 267 | Ok(()) 268 | } 269 | 270 | pub fn write_results(&mut self) -> CliResult<()> { 271 | let mut w = TabWriter::new(vec![]); 272 | cli_try!(write!(w, 273 | "\tLanguage\tFiles\tLines\tBlanks\tComments\tCode{}\n", 274 | if self.cfg.usafe { "\tUnsafe (%)" } else { "" })); 275 | cli_try!(write!(w, 276 | "\t--------\t-----\t-----\t------\t--------\t----{}\n", 277 | if self.cfg.usafe { "\t----------" } else { "" })); 278 | for count in &self.counts { 279 | if self.cfg.usafe { 280 | let usafe_per = if count.code != 0 { 281 | (count.usafe as f64 / count.code as f64) * 100.00f64 282 | } else { 283 | 0f64 284 | }; 285 | cli_try!(write!(w, 286 | "\t{}\t{}\t{}\t{}\t{}\t{}\t{}\n", 287 | count.lang.name(), 288 | count.total_files(), 289 | count.lines(), 290 | count.blanks(), 291 | count.comments(), 292 | count.code(), 293 | if (usafe_per - 00f64).abs() < f64::EPSILON { 294 | "".to_owned() 295 | } else { 296 | format!("{} ({:.2}%)", count.usafe(), usafe_per) 297 | })); 298 | } else { 299 | cli_try!(write!(w, "\t{}\n", count)); 300 | } 301 | } 302 | cli_try!(write!(w, 303 | "\t--------\t-----\t-----\t------\t--------\t----{}\n", 304 | if self.cfg.usafe { "\t----------" } else { "" })); 305 | cli_try!(write!(w, 306 | "{}\t\t{}\t{}\t{}\t{}\t{}{}\n", 307 | "Totals:", 308 | fmt::format_number(self.tot as u64, self.cfg.thousands), 309 | fmt::format_number(self.tot_lines, self.cfg.thousands), 310 | fmt::format_number(self.tot_blanks, self.cfg.thousands), 311 | fmt::format_number(self.tot_comments, self.cfg.thousands), 312 | fmt::format_number(self.tot_code, self.cfg.thousands), 313 | if self.cfg.usafe { 314 | format!("\t{} ({:.2}%)", 315 | fmt::format_number(self.tot_usafe, self.cfg.thousands), 316 | (self.tot_usafe as f64 / self.tot_code as f64) * 100.00f64) 317 | } else { 318 | "".to_owned() 319 | })); 320 | 321 | cli_try!(w.flush()); 322 | 323 | verboseln!(self.cfg, 324 | "{} {}", 325 | Format::Good("Displaying"), 326 | "the results:"); 327 | if self.tot > 0 { 328 | write!(io::stdout(), 329 | "{}", 330 | String::from_utf8(w.unwrap()).ok().expect("failed to get valid UTF-8 String")) 331 | .expect("failed to write output"); 332 | } else { 333 | println!("\n\tNo source files were found matching the specified criteria"); 334 | } 335 | Ok(()) 336 | } 337 | 338 | fn count_brackets(line: &str, count: Option) -> i64 { 339 | let mut b: i64 = count.unwrap_or(0); 340 | for c in line.chars() { 341 | match c { 342 | '{' => b += 1, 343 | '}' => b -= 1, 344 | _ => (), 345 | } 346 | } 347 | b 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/count/mod.rs: -------------------------------------------------------------------------------- 1 | mod counts; 2 | 3 | 4 | use fmt; 5 | use language::Language; 6 | pub use self::counts::Counts; 7 | 8 | use std::fmt as StdFmt; 9 | use std::ops::Deref; 10 | use std::path::PathBuf; 11 | 12 | #[derive(Debug)] 13 | pub struct Count { 14 | pub lang: Language, 15 | pub files: Vec, 16 | pub code: u64, 17 | pub comments: u64, 18 | pub blanks: u64, 19 | pub lines: u64, 20 | pub usafe: u64, 21 | pub sep: Option, 22 | } 23 | 24 | impl Count { 25 | pub fn new(lang: Language, sep: Option) -> Self { 26 | Count { 27 | lang: lang, 28 | files: vec![], 29 | code: 0, 30 | comments: 0, 31 | blanks: 0, 32 | lines: 0, 33 | usafe: 0, 34 | sep: sep, 35 | } 36 | } 37 | 38 | pub fn add_file(&mut self, f: PathBuf) { 39 | self.files.push(f); 40 | } 41 | 42 | pub fn lines(&self) -> String { 43 | fmt::format_number(self.lines, self.sep) 44 | } 45 | 46 | pub fn code(&self) -> String { 47 | fmt::format_number(self.code, self.sep) 48 | } 49 | 50 | pub fn blanks(&self) -> String { 51 | fmt::format_number(self.blanks, self.sep) 52 | } 53 | 54 | pub fn usafe(&self) -> String { 55 | fmt::format_number(self.usafe, self.sep) 56 | } 57 | 58 | pub fn comments(&self) -> String { 59 | fmt::format_number(self.comments, self.sep) 60 | } 61 | 62 | pub fn total_files(&self) -> String { 63 | fmt::format_number(self.files.len() as u64, self.sep) 64 | } 65 | } 66 | 67 | impl Deref for Count { 68 | type Target = Language; 69 | fn deref(&self) -> &::Target { 70 | &self.lang 71 | } 72 | } 73 | 74 | impl StdFmt::Display for Count { 75 | fn fmt(&self, f: &mut StdFmt::Formatter) -> StdFmt::Result { 76 | write!(f, 77 | "{}\t{}\t{}\t{}\t{}\t{}", 78 | self.lang, 79 | self.total_files(), 80 | self.lines(), 81 | self.blanks(), 82 | self.comments(), 83 | self.code()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use fmt::Format;use std::error::Error; 4 | use std::fmt::{Display, Formatter}; 5 | use std::fmt::Result as FmtResult; 6 | 7 | pub type CliResult = Result; 8 | 9 | #[derive(Debug)] 10 | #[allow(dead_code)] 11 | pub enum CliError { 12 | Generic(String), 13 | UnknownExt(String), 14 | Unknown, 15 | } 16 | 17 | // Copies clog::error::Error; 18 | impl CliError { 19 | /// Return whether this was a fatal error or not. 20 | #[allow(dead_code)] 21 | pub fn is_fatal(&self) -> bool { 22 | // For now all errors are fatal 23 | true 24 | } 25 | 26 | /// Print this error and immediately exit the program. 27 | /// 28 | /// If the error is non-fatal then the error is printed to stdout and the 29 | /// exit status will be `0`. Otherwise, when the error is fatal, the error 30 | /// is printed to stderr and the exit status will be `1`. 31 | pub fn exit(&self) -> ! { 32 | if self.is_fatal() { 33 | wlnerr!("{}", self); 34 | ::std::process::exit(1) 35 | } else { 36 | println!("{}", self); 37 | ::std::process::exit(0) 38 | } 39 | } 40 | } 41 | 42 | impl Display for CliError { 43 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 44 | write!(f, "{} {}", Format::Error("error:"), self.description()) 45 | } 46 | } 47 | 48 | impl Error for CliError { 49 | #[cfg_attr(feature = "lints", allow(match_same_arms))] 50 | fn description(&self) -> &str { 51 | match *self { 52 | CliError::Generic(ref d) => &*d, 53 | CliError::UnknownExt(ref d) => &*d, 54 | CliError::Unknown => "An unknown fatal error has occurred, please consider filing a bug-report!", 55 | } 56 | } 57 | 58 | fn cause(&self) -> Option<&Error> { 59 | None 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/fmt.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(all(feature = "color", not(target_os = "windows")))] 3 | use ansi_term::ANSIString; 4 | 5 | #[cfg(all(feature = "color", not(target_os = "windows")))] 6 | use ansi_term::Colour::{Green, Red, Yellow};use std::fmt; 7 | 8 | #[allow(dead_code)] 9 | pub enum Format { 10 | Error(T), 11 | Warning(T), 12 | Good(T), 13 | } 14 | 15 | #[cfg(all(feature = "color", not(target_os = "windows")))] 16 | impl> Format { 17 | fn format(&self) -> ANSIString { 18 | match *self { 19 | Format::Error(ref e) => Red.bold().paint(e.as_ref()), 20 | Format::Warning(ref e) => Yellow.paint(e.as_ref()), 21 | Format::Good(ref e) => Green.paint(e.as_ref()), 22 | } 23 | } 24 | } 25 | 26 | #[cfg(all(feature = "color", not(target_os = "windows")))] 27 | impl> fmt::Display for Format { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | write!(f, "{}", &self.format()) 30 | } 31 | } 32 | 33 | #[cfg(any(not(feature = "color"), target_os = "windows"))] 34 | impl Format { 35 | fn format(&self) -> &T { 36 | match *self { 37 | Format::Error(ref e) => e, 38 | Format::Warning(ref e) => e, 39 | Format::Good(ref e) => e, 40 | } 41 | } 42 | } 43 | 44 | #[cfg(any(not(feature = "color"), target_os = "windows"))] 45 | impl fmt::Display for Format { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | write!(f, "{}", &self.format()) 48 | } 49 | } 50 | 51 | pub fn format_number(n: u64, sep: Option) -> String { 52 | debugln!("executing; format_number; n={}", n); 53 | let s = format!("{}", n); 54 | if let Some(sep) = sep { 55 | debugln!("There was a separator {}", sep); 56 | let mut ins_sep = s.len() % 3; 57 | ins_sep = if ins_sep == 0 { 3 } else { ins_sep }; 58 | let mut ret = vec![]; 59 | for (i, c) in s.chars().enumerate() { 60 | debugln!("iter; c={}; ins_sep={}; ret={:?}", c, ins_sep, ret); 61 | if ins_sep == 0 && i != 0 { 62 | debugln!("Inserting the separator"); 63 | ret.push(sep); 64 | ins_sep = 3; 65 | } 66 | ret.push(c); 67 | ins_sep -= 1; 68 | } 69 | debugln!("returning; ret={}", ret.iter().cloned().collect::()); 70 | ret.iter().cloned().collect() 71 | } else { 72 | debugln!("There was not a separator"); 73 | s 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/fsutil.rs: -------------------------------------------------------------------------------- 1 | 2 | use gitignore::File; 3 | 4 | use glob;use std::fs; 5 | use std::io::Result; 6 | use std::path::PathBuf; 7 | 8 | pub fn get_all_files(v: &mut Vec, 9 | path: &PathBuf, 10 | exclude: &[PathBuf], 11 | follow_links: bool, 12 | gitignore: &Option) { 13 | debugln!("executing; get_all_files; path={:?}; exclude={:?}; all={:?}", 14 | path, 15 | exclude, 16 | all); 17 | if exclude.contains(path) { 18 | return; 19 | } 20 | 21 | if let Some(ref f) = *gitignore { 22 | if f.is_excluded(path).unwrap() { 23 | return; 24 | } 25 | } 26 | 27 | debugln!("Getting metadata"); 28 | if let Ok(result) = get_metadata(path, follow_links) { 29 | debugln!("Found"); 30 | if result.is_dir() { 31 | debugln!("It's a dir"); 32 | let dir = fs::read_dir(&path).unwrap(); 33 | for entry in dir { 34 | let entry = entry.unwrap(); 35 | let file_path = entry.path(); 36 | get_all_files(v, 37 | &file_path.to_path_buf(), 38 | exclude, 39 | follow_links, 40 | gitignore); 41 | } 42 | } else { 43 | debugln!("It's a file"); 44 | v.push(path.clone()); 45 | } 46 | } else { 47 | for path_buf in glob::glob(path.to_str().unwrap_or("")) 48 | .expect("failed to get files from glob") { 49 | if let Ok(file_path) = path_buf { 50 | if let Ok(result) = get_metadata(&file_path, follow_links) { 51 | if result.is_dir() { 52 | debugln!("It's a dir"); 53 | let dir = fs::read_dir(&path).unwrap(); 54 | for entry in dir { 55 | let entry = entry.unwrap(); 56 | let file_path = entry.path(); 57 | get_all_files(v, 58 | &file_path.to_path_buf(), 59 | exclude, 60 | follow_links, 61 | gitignore); 62 | } 63 | } else { 64 | debugln!("It's a file"); 65 | v.push(path.clone()); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | fn get_metadata(path: &PathBuf, follow_links: bool) -> Result { 74 | if follow_links { 75 | fs::metadata(path) 76 | } else { 77 | fs::symlink_metadata(path) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/language.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use comment::Comment; 4 | use std::fmt as StdFmt; 5 | 6 | #[derive(Debug, Eq, PartialEq)] 7 | pub enum Language { 8 | C, 9 | Header, 10 | Hpp, 11 | Cpp, 12 | Css, 13 | Html, 14 | Java, 15 | JavaScript, 16 | Perl, 17 | Php, 18 | Python, 19 | Ruby, 20 | Rust, 21 | Xml, 22 | Toml, 23 | Go, 24 | Assembly, 25 | Shell, 26 | D, 27 | Nim, 28 | } 29 | 30 | impl Language { 31 | pub fn from_ext(ext: &str) -> Option { 32 | match ext { 33 | "cpp" | "cp" | "cc" | "cxx" | "c++" | "C" => Some(Language::Cpp), 34 | "hpp" | "h++" => Some(Language::Hpp), 35 | "c" => Some(Language::C), 36 | "h" => Some(Language::Header), 37 | "css" => Some(Language::Css), 38 | "java" => Some(Language::Java), 39 | "js" => Some(Language::JavaScript), 40 | "rs" => Some(Language::Rust), 41 | "xml" => Some(Language::Xml), 42 | "html" | "htm" => Some(Language::Html), 43 | "py" => Some(Language::Python), 44 | "rb" => Some(Language::Ruby), 45 | "php" => Some(Language::Php), 46 | "toml" => Some(Language::Toml), 47 | "pl" => Some(Language::Perl), 48 | "go" => Some(Language::Go), 49 | "agc" | "asm" | "a51" | "inc" | "nasm" | "s" | "ms" => Some(Language::Assembly), 50 | "ps1" | "psd1" | "psm1" | "sh" | "bash" | "bats" | "cgi" | "command" | "fcgi" | 51 | "ksh" | "sh.in" | "tmux" | "tool" | "zsh" | "tcsh" | "csh" | "fish" => { 52 | Some(Language::Shell) 53 | } 54 | "d" | "di" => Some(Language::D), 55 | "nim" | "nimrod" => Some(Language::Nim), 56 | _ => None, 57 | } 58 | } 59 | 60 | pub fn name(&self) -> &str { 61 | match *self { 62 | Language::Cpp => "C++", 63 | Language::Hpp => "C++ Header", 64 | Language::C => "C", 65 | Language::Header => "C Header", 66 | Language::Css => "CSS", 67 | Language::Java => "Java", 68 | Language::JavaScript => "JavaScript", 69 | Language::Rust => "Rust", 70 | Language::Xml => "XML", 71 | Language::Html => "HTML", 72 | Language::Python => "Python", 73 | Language::Ruby => "Ruby", 74 | Language::Php => "PHP", 75 | Language::Toml => "TOML", 76 | Language::Perl => "Perl", 77 | Language::Go => "Go", 78 | Language::Assembly => "Assembly", 79 | Language::Shell => "Shell", 80 | Language::D => "D", 81 | Language::Nim => "Nim", 82 | } 83 | } 84 | 85 | pub fn is_unsafe(&self) -> bool { 86 | match *self { 87 | Language::C | Language::Cpp | Language::Hpp | Language::Header | Language::Rust | 88 | Language::Assembly | Language::Nim => true, 89 | _ => false, 90 | } 91 | } 92 | 93 | pub fn unsafe_keyword(&self) -> Option<&str> { 94 | match *self { 95 | Language::Rust => Some("unsafe"), 96 | _ => None, 97 | } 98 | } 99 | } 100 | 101 | impl StdFmt::Display for Language { 102 | fn fmt(&self, f: &mut StdFmt::Formatter) -> StdFmt::Result { 103 | write!(f, "{}", self.name()) 104 | } 105 | } 106 | 107 | 108 | impl Comment for Language { 109 | type Rep = &'static str; 110 | 111 | fn single(&self) -> Option::Rep>> { 112 | match *self { 113 | Language::C | Language::Cpp | Language::Hpp | Language::Header | Language::Css | 114 | Language::Java | Language::JavaScript | Language::Rust | Language::Go | Language::D => { 115 | Some(vec!["//"]) 116 | } 117 | Language::Php => Some(vec!["//", "#"]), 118 | Language::Xml | Language::Html => Some(vec![""), 143 | Language::Ruby => Some("=end"), 144 | Language::Python => Some("'''"), 145 | Language::Nim => Some("]#"), 146 | Language::Toml | Language::Perl | Language::Assembly | Language::Shell => None, 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! cli_try { 2 | ($t:expr) => ({ 3 | use ::std::error::Error; 4 | match $t { 5 | Ok(o) => o, 6 | Err(e) => return Err(CliError::Generic(e.description().to_owned())) 7 | } 8 | }) 9 | } 10 | macro_rules! wlnerr( 11 | ($($arg:tt)*) => ({ 12 | use std::io::{Write, stderr}; 13 | writeln!(&mut stderr(), $($arg)*).ok(); 14 | }) 15 | ); 16 | 17 | macro_rules! werr( 18 | ($($arg:tt)*) => ({ 19 | use std::io::{Write, stderr}; 20 | write!(&mut stderr(), $($arg)*).ok(); 21 | }) 22 | ); 23 | 24 | macro_rules! verbose( 25 | ($cfg:expr, $($arg:tt)*) => ({ 26 | if $cfg.verbose { 27 | use std::io::{Write, stdout}; 28 | write!(&mut stdout(), $($arg)*).ok(); 29 | } 30 | }) 31 | ); 32 | 33 | macro_rules! verboseln( 34 | ($cfg:expr, $($arg:tt)*) => ({ 35 | if $cfg.verbose { 36 | use std::io::{Write, stdout}; 37 | writeln!(&mut stdout(), $($arg)*).ok(); 38 | } 39 | }) 40 | ); 41 | 42 | #[cfg(feature = "debug")] 43 | macro_rules! debugln { 44 | ($fmt:expr) => (println!(concat!("*DEBUG:cargo-count: ", $fmt))); 45 | ($fmt:expr, $($arg:tt)*) => (println!(concat!("*DEBUG:cargo-count: ",$fmt), $($arg)*)); 46 | } 47 | 48 | #[cfg(feature = "debug")] 49 | macro_rules! debug { 50 | ($fmt:expr) => (print!(concat!("*DEBUG:cargo-count: ", $fmt))); 51 | ($fmt:expr, $($arg:tt)*) => (println!(concat!("*DEBUG:cargo-count: ",$fmt), $($arg)*)); 52 | } 53 | 54 | #[cfg(not(feature = "debug"))] 55 | macro_rules! debugln { 56 | ($fmt:expr) => (); 57 | ($fmt:expr, $($arg:tt)*) => (); 58 | } 59 | 60 | #[cfg(not(feature = "debug"))] 61 | macro_rules! debug { 62 | ($fmt:expr) => (); 63 | ($fmt:expr, $($arg:tt)*) => (); 64 | } 65 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! A cargo subcommand for displaying line counts of source code in projects, 2 | //! including a niave `unsafe` counter for Rust source files. This subcommand 3 | //! was originally based off and inspired by the project 4 | //! [tokei](https://github.com/aaronepower/tokei) by 5 | //! [Aaronepower](https://github.com/aaronepower) 6 | //! 7 | //! ## Demo 8 | //! 9 | //! To count the source code in the [Rust](https://github.com/rust-lang/rust) 10 | //! repository (checkout `4c99649`), and print some naive statistics on how much 11 | //! "unsafe" code exists. 12 | //! 13 | //! **NOTE:** The Rust repository is quite large, if you're on a slow internet 14 | //! connection consider using a smaller repository, such as the `cargo-count` 15 | //! repo. 16 | //! 17 | //! ```ignore 18 | //! $ git clone https://github.com/rust-lang/rust 19 | //! $ cd rust 20 | //! $ cargo count --separator , --unsafe-statistics 21 | //! Gathering information... 22 | //! Language Files Lines Blanks Comments Code Unsafe (%) 23 | //! -------- ----- ----- ------ -------- ---- ---------- 24 | //! Rust 6,018 528,510 66,984 133,698 327,792 3,163 25 | //! (0.96%) 26 | //! C 54 9,962 1,445 1,492 7,025 7,025 27 | //! (100.00%) 28 | //! CSS 4 1,266 149 52 1,065 29 | //! JavaScript 4 1,118 131 166 821 30 | //! Python 31 4,797 843 585 3,369 31 | //! C Header 13 1,865 284 585 996 996 32 | //! (100.00%) 33 | //! C++ 4 1,611 185 81 1,345 1,345 34 | //! (100.00%) 35 | //! -------- ----- ----- ------ -------- ---- ---------- 36 | //! Totals: 6,128 549,129 70,021 136,659 342,413 12,529 37 | //! (3.66%) 38 | //! 39 | //! ``` 40 | //! 41 | //! The `--separator ,` sets a `,` character as the thousands separator, and 42 | //! `--unsafe-statistics` looks for, and counts lines of `unsafe`. 43 | //! 44 | //! ## Compiling 45 | //! 46 | //! Follow these instructions to compile `cargo-count`, then skip down to 47 | //! Installation. 48 | //! 49 | //! 1. Ensure you have current version of `cargo` and 50 | //! [Rust](https://www.rust-lang.org) installed 51 | //! 2. Clone the project 52 | //! `$ git clone https://github.com/kbknapp/cargo-count && cd cargo-count` 53 | //! 3. Build the project `$ cargo build --release` (**NOTE:** There is a large 54 | //! performance differnce when compiling without optimizations, so I 55 | //! recommend alwasy using `--release` to enable to them) 56 | //! 4. Once complete, the binary will be located at 57 | //! `target/release/cargo-count` 58 | //! 59 | //! ## Installation and Usage 60 | //! 61 | //! All you need to do is place `cargo-count` somewhere in your `$PATH`. Then 62 | //! run `cargo count` anywhere in your project directory. For full details see 63 | //! below. 64 | //! 65 | //! ### Linux / OS X 66 | //! 67 | //! You have two options, place `cargo-count` into a directory that is already 68 | //! located in your `$PATH` variable (To see which directories those are, open 69 | //! a terminal and type `echo "${PATH//:/\n}"`, the quotation marks are 70 | //! important), or you can add a custom directory to your `$PATH` 71 | //! 72 | //! **Option 1** 73 | //! If you have write permission to a directory listed in your `$PATH` or you 74 | //! have root permission (or via `sudo`), simply copy the `cargo-count` to that 75 | //! directory `# sudo cp cargo-count /usr/local/bin` 76 | //! 77 | //! **Option 2** 78 | //! If you do not have root, `sudo`, or write permission to any directory 79 | //! already in `$PATH` you can create a directory inside your home directory, 80 | //! and add that. Many people use `$HOME/.bin` to keep it hidden (and not 81 | //! clutter your home directory), or `$HOME/bin` if you want it to be always 82 | //! visible. Here is an example to make the directory, add it to `$PATH`, and 83 | //! copy `cargo-count` there. 84 | //! 85 | //! Simply change `bin` to whatever you'd like to name the directory, and 86 | //! `.bashrc` to whatever your shell startup file is (usually `.bashrc`, 87 | //! `.bash_profile`, or `.zshrc`) 88 | //! 89 | //! ```sh 90 | //! $ mkdir ~/bin 91 | //! $ echo "export PATH=$PATH:$HOME/bin" >> ~/.bashrc 92 | //! $ cp cargo-count ~/bin 93 | //! $ source ~/.bashrc 94 | //! ``` 95 | //! 96 | //! ### Windows 97 | //! 98 | //! On Windows 7/8 you can add directory to the `PATH` variable by opening a 99 | //! command line as an administrator and running 100 | //! 101 | //! ```sh 102 | //! C:\> setx path "%path%;C:\path\to\cargo-count\binary" 103 | //! ``` 104 | //! 105 | //! Otherwise, ensure you have the `cargo-count` binary in the directory which 106 | //! you operating in the command line from, because Windows automatically adds 107 | //! your current directory to PATH (i.e. if you open a command line to 108 | //! `C:\my_project\` to use `cargo-count` ensure `cargo-count.exe` is inside 109 | //! that directory as well). 110 | //! 111 | //! 112 | //! ### Options 113 | //! 114 | //! There are a few options for using `cargo-count` which should be somewhat 115 | //! self explanitory. 116 | //! 117 | //! ```ignore 118 | //! USAGE: 119 | //! cargo count [FLAGS] [OPTIONS] [--] [ARGS] 120 | //! 121 | //! FLAGS: 122 | //! -S, --follow-symlinks Follows symlinks and counts source files it 123 | //! finds 124 | //! -a, --all Do not ignore .gitignored paths 125 | //! (Defaults to false when omitted) 126 | //! -h, --help Prints help information 127 | //! --unsafe-statistics Displays lines and percentages of "unsafe" 128 | //! code 129 | //! -V, --version Prints version information 130 | //! -v, --verbose Print verbose output 131 | //! 132 | //! OPTIONS: 133 | //! -l, --language ... Only count these languges (by source code 134 | //! extension) 135 | //! (i.e. '-l js py cpp') 136 | //! -e, --exclude ... Files or directories to exclude 137 | //! (automatically includes '.git') 138 | //! --utf8-rule Sets the UTF-8 parsing rule (Defaults to 139 | //! 'strict') 140 | //! [values: ignore lossy strict] 141 | //! -s, --separator Set the thousands separator for pretty 142 | //! printing 143 | //! 144 | //! ARGS: 145 | //! to_count... The files or directories (including children) to count 146 | //! (defaults to current working directory when omitted) 147 | //! 148 | //! When using '--exclude ' the path given can either be relative to the 149 | //! current 150 | //! directory, or absolute. When '' is a file, it must be relative to the 151 | //! current 152 | //! directory or it will not be found. Example, if the current directory has a 153 | //! child 154 | //! directory named 'target' with a child fild 'test.rs' and you use `--exclude 155 | //! target/test.rs' 156 | //! 157 | //! Globs are also supported. For example, to exclude 'test.rs' files from all 158 | //! child directories 159 | //! of the current directory you could do '--exclude */test.rs'. 160 | //! ``` 161 | //! 162 | //! ## License 163 | //! 164 | //! `cargo-count` is released under the terms of the MIT. See the LICENSE-MIT 165 | //! file for the details. 166 | #![cfg_attr(feature = "nightly", feature(plugin))] 167 | #![cfg_attr(feature = "lints", plugin(clippy))] 168 | #![cfg_attr(feature = "lints", allow(explicit_iter_loop))] 169 | #![cfg_attr(feature = "lints", allow(should_implement_trait))] 170 | #![cfg_attr(feature = "lints", allow(unstable_features))] 171 | #![cfg_attr(feature = "lints", deny(warnings))] 172 | #![cfg_attr(not(any(feature = "nightly", feature = "unstable")), deny(unstable_features))] 173 | #![deny(missing_docs, 174 | missing_debug_implementations, 175 | missing_copy_implementations, 176 | trivial_casts, trivial_numeric_casts, 177 | unsafe_code, 178 | unused_import_braces, 179 | unused_qualifications)] 180 | 181 | #[macro_use] 182 | extern crate clap; 183 | #[cfg(feature = "color")] 184 | extern crate ansi_term; 185 | extern crate tabwriter; 186 | extern crate glob; 187 | extern crate regex; 188 | extern crate gitignore; 189 | 190 | #[cfg(feature = "debug")] 191 | use std::env; 192 | 193 | use clap::{App, AppSettings, Arg, SubCommand}; 194 | 195 | use config::Config; 196 | use count::Counts; 197 | use error::{CliError, CliResult}; 198 | use fmt::Format; 199 | 200 | #[macro_use] 201 | mod macros; 202 | mod comment; 203 | mod config; 204 | mod count; 205 | mod error; 206 | mod fmt; 207 | mod fsutil; 208 | mod language; 209 | 210 | static UTF8_RULES: [&'static str; 3] = ["strict", "lossy", "ignore"]; 211 | 212 | fn main() { 213 | debugln!("executing; cmd=cargo-count; args={:?}", 214 | env::args().collect::>()); 215 | let m = App::new("cargo-count") 216 | .version(concat!("v", crate_version!())) 217 | // We have to lie about our binary name since this will be a third party 218 | // subcommand for cargo but we want usage strings to generated properly 219 | .bin_name("cargo") 220 | // Global version uses the version we supplied (Cargo.toml) for all subcommands 221 | // as well 222 | .settings(&[AppSettings::GlobalVersion, 223 | AppSettings::SubcommandRequired]) 224 | // We use a subcommand because everything parsed after `cargo` is sent to the 225 | // third party 226 | // plugin which will then be interpreted as a subcommand/positional arg by clap 227 | .subcommand(SubCommand::with_name("count") 228 | .author("Kevin K. ") 229 | .about("Displays line counts of code for cargo projects") 230 | .args_from_usage(" 231 | -e, --exclude [PATH]... 'Files or directories to exclude (automatically includes \'.git\')' 232 | -a, --all 'Do not ignore .gitignore'd paths' 233 | --unsafe-statistics 'Displays lines and percentages of \"unsafe\" code' 234 | -l, --language [EXT]... 'Only count these languges (i.e. \'-l js py cpp\')' 235 | -v, --verbose 'Print verbose output' 236 | -S, --follow-symlinks 'Follows symlinks and counts source files it finds [default: false]' 237 | [PATH]... 'The files or directories (including children) to count (defaults to \ 238 | current working directory when omitted)'") 239 | .arg(Arg::from_usage( 240 | "-s, --separator [CHAR] 'Set the thousands separator for pretty printing'") 241 | .use_delimiter(false) 242 | .validator(single_char)) 243 | .arg(Arg::from_usage("--utf8-rule [RULE] 'Sets the UTF-8 parsing rule'") 244 | .default_value("strict") 245 | .possible_values(&UTF8_RULES)) 246 | .after_help("\ 247 | When using '--exclude ' the path given can either be relative to the current directory, or \ 248 | absolute. When '--exclude ' is a file or path, it must be relative to the current directory \ 249 | or it will not be found. Example, if the current directory has a child directory named 'target' \ 250 | with a child fild 'test.rs' and you use `--exclude target/test.rs' 251 | \n\ 252 | Globs are also supported. For example, to exclude 'test.rs' files from all child directories of \ 253 | the current directory you could do '--exclude */test.rs'.")) 254 | .get_matches(); 255 | 256 | if let Some(m) = m.subcommand_matches("count") { 257 | let cfg = Config::from_matches(m).unwrap_or_else(|e| e.exit()); 258 | println!("Gathering information..."); 259 | if let Err(e) = execute(cfg) { 260 | e.exit(); 261 | } 262 | } 263 | } 264 | 265 | fn execute(cfg: Config) -> CliResult<()> { 266 | debugln!("executing; cmd=execute;"); 267 | verboseln!(cfg, "{}: {:?}", Format::Warning("Excluding"), cfg.exclude); 268 | verbose!(cfg, 269 | "{}", 270 | if cfg.exts.is_some() { 271 | format!("{} including files with extension: {}\n", 272 | Format::Warning("Only"), 273 | cfg.exts 274 | .as_ref() 275 | .unwrap() 276 | .join(", ")) 277 | } else { 278 | "".to_owned() 279 | }); 280 | 281 | debugln!("Checking for files or dirs to count from cli"); 282 | 283 | let mut counts = Counts::new(&cfg); 284 | counts.fill_from(); 285 | cli_try!(counts.count()); 286 | cli_try!(counts.write_results()); 287 | Ok(()) 288 | } 289 | 290 | fn single_char(s: String) -> Result<(), String> { 291 | if s.len() == 1 { 292 | Ok(()) 293 | } else { 294 | Err( 295 | format!( 296 | "the --separator argument option only accepts a single character but found '{}'", 297 | Format::Warning(s))) 298 | } 299 | } 300 | --------------------------------------------------------------------------------