Skip to content

Commit 49ca668

Browse files
authored
Merge pull request #90 from roskakori/73-add-rich-formatting
#73 Added percentages to API and cleaned up visual design of summary.
2 parents 958d8ca + 13dd7dc commit 49ca668

File tree

11 files changed

+330
-201
lines changed

11 files changed

+330
-201
lines changed

.github/workflows/build.yml

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,27 @@ jobs:
4545
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4646
run: |
4747
poetry run coveralls --service=github
48+
49+
check-style:
50+
runs-on: ubuntu-latest
51+
# Disable pre-commit check on main and production to prevent
52+
# pull request merges to fail with don't commit to branch".
53+
if: github.ref != 'refs/heads/main'
54+
steps:
55+
- uses: actions/checkout@v2
56+
- name: Install pre-commit
57+
run: |
58+
sudo apt-get install python3
59+
python -m pip install --upgrade pip
60+
pip install pre-commit
4861
- name: Load cached pre-commit
4962
id: cached-pre-commit
5063
uses: actions/cache@v2
5164
with:
5265
path: ~/.cache/pre-commit
53-
key: pre-commit-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
54-
- name: Install pre-commit
66+
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
67+
- name: Install pre-commit hooks
5568
if: steps.cached-pre-commit.outputs.cache-hit != 'true'
56-
run: |
57-
poetry run pre-commit install --install-hooks
69+
run: pre-commit install --install-hooks
5870
- name: Check coding style
59-
if: ${{ matrix.python-version }} == $MAIN_PYTHON_VERSION
60-
run: |
61-
poetry run pre-commit run --all-files
71+
run: pre-commit run --all-files

.idea/pygount.iml

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ repos:
77
- id: isort
88

99
- repo: https://github.com/ambv/black
10-
rev: 21.12b0
10+
rev: 22.3.0
1111
hooks:
1212
- id: black
1313

1414
- repo: https://github.com/pre-commit/mirrors-prettier
15-
rev: v2.5.1
15+
rev: v2.6.2
1616
hooks:
1717
- id: prettier
1818

1919
- repo: https://github.com/pre-commit/pre-commit-hooks
20-
rev: v4.1.0
20+
rev: v4.2.0
2121
hooks:
2222
- id: fix-byte-order-marker
2323
- id: trailing-whitespace

docs/changes.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ Changes
55

66
This chapter describes the changes coming with each new version of pygount.
77

8+
Version 1.4.0, 2022-04-09
9+
10+
* Added progress bar during scan phase and improved visual design of
11+
``--format=summary`` (contributed by Stanislav Zmiev, issue.
12+
`#73 <https://github.com/roskakori/pygount/issues/73>`_).
13+
* Added percentages to API. For example in addition to
14+
``code_count`` now there also is ``code_percentage``.
15+
816
Version 1.3.0, 2022-01-06
917

1018
* Fixed computation of "lines per second", which was a copy and paste of

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Sphinx==4.3.2
1+
Sphinx==4.5.0
22
sphinx_rtd_theme==1.0.0

poetry.lock

Lines changed: 123 additions & 128 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pygount/summary.py

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ def __init__(self, language: str):
2424
self._documentation_count = 0
2525
self._empty_count = 0
2626
self._file_count = 0
27+
self._file_percentage = 0.0
2728
self._string_count = 0
2829
self._is_pseudo_language = _PSEUDO_LANGUAGE_REGEX.match(self.language) is not None
30+
self._has_up_to_date_percentages = False
2931

3032
@property
3133
def language(self) -> str:
@@ -37,31 +39,70 @@ def code_count(self) -> int:
3739
"""sum lines of code for this language"""
3840
return self._code_count
3941

42+
@property
43+
def code_percentage(self) -> float:
44+
"""percentage of lines containing code for this language across entire project"""
45+
return _percentage_or_0(self.code_count, self.line_count)
46+
47+
def _assert_has_up_to_date_percentages(self):
48+
assert self._has_up_to_date_percentages, "update_percentages() must be called first"
49+
4050
@property
4151
def documentation_count(self) -> int:
4252
"""sum lines of documentation for this language"""
4353
return self._documentation_count
4454

55+
@property
56+
def documentation_percentage(self) -> float:
57+
"""percentage of lines containing documentation for this language across entire project"""
58+
return _percentage_or_0(self.documentation_count, self.line_count)
59+
4560
@property
4661
def empty_count(self) -> int:
4762
"""sum empty lines for this language"""
4863
return self._empty_count
4964

65+
@property
66+
def empty_percentage(self) -> float:
67+
"""percentage of empty lines for this language across entire project"""
68+
return _percentage_or_0(self.empty_count, self.line_count)
69+
5070
@property
5171
def file_count(self) -> int:
5272
"""number of source code files for this language"""
5373
return self._file_count
5474

75+
@property
76+
def file_percentage(self) -> float:
77+
"""percentage of files in project"""
78+
self._assert_has_up_to_date_percentages()
79+
return self._file_percentage
80+
81+
@property
82+
def line_count(self) -> int:
83+
"""sum count of all lines of any kind for this language"""
84+
return self.code_count + self.documentation_count + self.empty_count + self.string_count
85+
5586
@property
5687
def string_count(self) -> int:
57-
"""sum number of lines containing only strings for this language"""
88+
"""sum number of lines containing strings for this language"""
5889
return self._string_count
5990

91+
@property
92+
def string_percentage(self) -> float:
93+
"""percentage of lines containing strings for this language across entire project"""
94+
return _percentage_or_0(self.string_count, self.line_count)
95+
6096
@property
6197
def source_count(self) -> int:
6298
"""sum number of source lines of code"""
6399
return self.code_count + self.string_count
64100

101+
@property
102+
def source_percentage(self) -> float:
103+
"""percentage of source lines for code for this language across entire project"""
104+
return _percentage_or_0(self.source_count, self.line_count)
105+
65106
@property
66107
def is_pseudo_language(self) -> bool:
67108
"""``True`` if the language is not a real programming language"""
@@ -84,13 +125,18 @@ def add(self, source_analysis: SourceAnalysis) -> None:
84125
assert source_analysis is not None
85126
assert source_analysis.language == self.language
86127

128+
self._has_up_to_date_percentages = False
87129
self._file_count += 1
88130
if source_analysis.is_countable:
89131
self._code_count += source_analysis.code_count
90132
self._documentation_count += source_analysis.documentation_count
91133
self._empty_count += source_analysis.empty_count
92134
self._string_count += source_analysis.string_count
93135

136+
def update_file_percentage(self, project_summary: "ProjectSummary"):
137+
self._file_percentage = _percentage_or_0(self.file_count, project_summary.total_file_count)
138+
self._has_up_to_date_percentages = True
139+
94140
def __repr__(self):
95141
result = "{0}(language={1!r}, file_count={2}".format(self.__class__.__name__, self.language, self.file_count)
96142
if not self.is_pseudo_language:
@@ -101,6 +147,12 @@ def __repr__(self):
101147
return result
102148

103149

150+
def _percentage_or_0(partial_count: int, total_count: int) -> float:
151+
assert partial_count >= 0
152+
assert total_count >= 0
153+
return 100 * partial_count / total_count if total_count != 0 else 0.0
154+
155+
104156
class ProjectSummary:
105157
"""
106158
Summary of source code counts for several languages and files.
@@ -126,22 +178,42 @@ def language_to_language_summary_map(self) -> Dict[str, LanguageSummary]:
126178
def total_code_count(self) -> int:
127179
return self._total_code_count
128180

181+
@property
182+
def total_code_percentage(self) -> float:
183+
return _percentage_or_0(self.total_code_count, self.total_line_count)
184+
129185
@property
130186
def total_documentation_count(self) -> int:
131187
return self._total_documentation_count
132188

189+
@property
190+
def total_documentation_percentage(self) -> float:
191+
return _percentage_or_0(self.total_documentation_count, self.total_line_count)
192+
133193
@property
134194
def total_empty_count(self) -> int:
135195
return self._total_empty_count
136196

197+
@property
198+
def total_empty_percentage(self) -> float:
199+
return _percentage_or_0(self.total_empty_count, self.total_line_count)
200+
137201
@property
138202
def total_string_count(self) -> int:
139203
return self._total_string_count
140204

205+
@property
206+
def total_string_percentage(self) -> float:
207+
return _percentage_or_0(self.total_string_count, self.total_line_count)
208+
141209
@property
142210
def total_source_count(self) -> int:
143211
return self.total_code_count + self.total_string_count
144212

213+
@property
214+
def total_source_percentage(self) -> float:
215+
return _percentage_or_0(self.total_source_count, self.total_line_count)
216+
145217
@property
146218
def total_file_count(self) -> int:
147219
return self._total_file_count
@@ -173,10 +245,15 @@ def add(self, source_analysis: SourceAnalysis) -> None:
173245
)
174246
self._total_string_count += source_analysis.string_count
175247

248+
def update_file_percentages(self) -> None:
249+
"""Update percentages for all languages part of the project."""
250+
for language_summary in self._language_to_language_summary_map.values():
251+
language_summary.update_file_percentage(self)
252+
176253
def __repr__(self):
177-
return "{0}(total_file_count={1}, total_line_count={2}, " "languages={3}".format(
178-
self.__class__.__name__,
179-
self.total_file_count,
180-
self.total_line_count,
181-
sorted(self.language_to_language_summary_map.keys()),
254+
return (
255+
f"{self.__class__.__name__}("
256+
f"total_file_count={self.total_file_count}, "
257+
f"total_line_count={self.total_line_count}, "
258+
f"languages={sorted(self.language_to_language_summary_map.keys())})"
182259
)

pygount/write.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def add(self, source_analysis):
4848
self.project_summary.add(source_analysis)
4949

5050
def close(self):
51+
self.project_summary.update_file_percentages()
5152
self.finished_at = datetime.datetime.utcnow()
5253
self.duration = self.finished_at - self.started_at
5354
self.duration_in_seconds = (
@@ -141,9 +142,11 @@ class SummaryWriter(BaseWriter):
141142
_COLUMNS_WITH_JUSTIFY = (
142143
("Language", "left"),
143144
("Files", "right"),
144-
("Blank", "right"),
145-
("Comment", "right"),
145+
("%", "right"),
146146
("Code", "right"),
147+
("%", "right"),
148+
("Comment", "right"),
149+
("%", "right"),
147150
)
148151

149152
def close(self):
@@ -158,17 +161,21 @@ def close(self):
158161
table.add_row(
159162
language_summary.language,
160163
str(language_summary.file_count),
161-
str(language_summary.empty_count),
162-
str(language_summary.documentation_count),
164+
formatted_percentage(language_summary.file_percentage),
163165
str(language_summary.code_count),
166+
formatted_percentage(language_summary.code_percentage),
167+
str(language_summary.documentation_count),
168+
formatted_percentage(language_summary.documentation_percentage),
164169
end_section=(index == len(language_summaries)),
165170
)
166171
table.add_row(
167-
"SUM",
172+
"Sum",
168173
str(self.project_summary.total_file_count),
169-
str(self.project_summary.total_empty_count),
170-
str(self.project_summary.total_documentation_count),
174+
formatted_percentage(100.0),
171175
str(self.project_summary.total_code_count),
176+
formatted_percentage(self.project_summary.total_code_percentage),
177+
str(self.project_summary.total_documentation_count),
178+
formatted_percentage(self.project_summary.total_documentation_percentage),
172179
)
173180
Console(file=self._target_stream, soft_wrap=True).print(table)
174181

@@ -186,20 +193,20 @@ def add(self, source_analysis: SourceAnalysis):
186193
super().add(source_analysis)
187194
self.source_analyses.append(
188195
{
189-
"path": source_analysis.path,
190-
"sourceCount": source_analysis.source_count,
191196
"emptyCount": source_analysis.empty_count,
192197
"documentationCount": source_analysis.documentation_count,
193198
"group": source_analysis.group,
194199
"isCountable": source_analysis.is_countable,
195200
"language": source_analysis.language,
201+
"path": source_analysis.path,
196202
"state": source_analysis.state.name,
197203
"stateInfo": source_analysis.state_info,
204+
"sourceCount": source_analysis.source_count,
198205
}
199206
)
200207

201208
def close(self):
202-
# NOTE: We are using camel case for naming here to follow JSLint's guidelines, see <https://www.jslint.com/>.
209+
# NOTE: JSON names use camel case to follow JSLint's guidelines, see <https://www.jslint.com/>.
203210
super().close()
204211
json_map = {
205212
"formatVersion": JSON_FORMAT_VERSION,
@@ -208,11 +215,15 @@ def close(self):
208215
"languages": [
209216
{
210217
"documentationCount": language_summary.documentation_count,
218+
"documentationPercentage": language_summary.documentation_percentage,
211219
"emptyCount": language_summary.empty_count,
220+
"emptyPercentage": language_summary.empty_percentage,
212221
"fileCount": language_summary.file_count,
222+
"filePercentage": language_summary.file_percentage,
213223
"isPseudoLanguage": language_summary.is_pseudo_language,
214224
"language": language_summary.language,
215225
"sourceCount": language_summary.source_count,
226+
"sourcePercentage": language_summary.source_percentage,
216227
}
217228
for language_summary in self.project_summary.language_to_language_summary_map.values()
218229
],
@@ -225,14 +236,23 @@ def close(self):
225236
},
226237
"summary": {
227238
"totalDocumentationCount": self.project_summary.total_documentation_count,
239+
"totalDocumentationPercentage": self.project_summary.total_documentation_percentage,
228240
"totalEmptyCount": self.project_summary.total_empty_count,
241+
"totalEmptyPercentage": self.project_summary.total_empty_percentage,
229242
"totalFileCount": self.project_summary.total_file_count,
230243
"totalSourceCount": self.project_summary.total_source_count,
244+
"totalSourcePercentage": self.project_summary.total_source_percentage,
231245
},
232246
}
233247
json.dump(json_map, self._target_stream)
234248

235249

236-
def digit_width(line_count):
250+
def digit_width(line_count: int) -> int:
237251
assert line_count >= 0
238252
return math.ceil(math.log10(line_count + 1)) if line_count != 0 else 1
253+
254+
255+
def formatted_percentage(percentage: float) -> str:
256+
assert percentage >= 0.0
257+
assert percentage <= 100.0
258+
return f"{percentage:.01f}"

0 commit comments

Comments
 (0)