├── .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 | [
](https://github.com/kbknapp) |[
](https://github.com/homu) |[
](https://github.com/m-n) |[
](https://github.com/nabijaczleweli) |[
](https://github.com/Aaronepower) |[
](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 | [
](https://github.com/DenisKolodin) |[
](https://github.com/pascalw) |[
](https://github.com/gitter-badger) |[
](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 | [](https://gitter.im/kbknapp/cargo-count?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 | Linux: [](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 | 
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 |
--------------------------------------------------------------------------------