Skip to content

Commit

Permalink
Merge pull request #90 from roskakori/73-add-rich-formatting
Browse files Browse the repository at this point in the history
#73 Added percentages to API and cleaned up visual design of summary.
  • Loading branch information
roskakori authored Apr 9, 2022
2 parents 958d8ca + 13dd7dc commit 49ca668
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 201 deletions.
24 changes: 17 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,27 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
poetry run coveralls --service=github
check-style:
runs-on: ubuntu-latest
# Disable pre-commit check on main and production to prevent
# pull request merges to fail with don't commit to branch".
if: github.ref != 'refs/heads/main'
steps:
- uses: actions/checkout@v2
- name: Install pre-commit
run: |
sudo apt-get install python3
python -m pip install --upgrade pip
pip install pre-commit
- name: Load cached pre-commit
id: cached-pre-commit
uses: actions/cache@v2
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
- name: Install pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- name: Install pre-commit hooks
if: steps.cached-pre-commit.outputs.cache-hit != 'true'
run: |
poetry run pre-commit install --install-hooks
run: pre-commit install --install-hooks
- name: Check coding style
if: ${{ matrix.python-version }} == $MAIN_PYTHON_VERSION
run: |
poetry run pre-commit run --all-files
run: pre-commit run --all-files
9 changes: 8 additions & 1 deletion .idea/pygount.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ repos:
- id: isort

- repo: https://github.com/ambv/black
rev: 21.12b0
rev: 22.3.0
hooks:
- id: black

- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.5.1
rev: v2.6.2
hooks:
- id: prettier

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.2.0
hooks:
- id: fix-byte-order-marker
- id: trailing-whitespace
Expand Down
8 changes: 8 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ Changes

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

Version 1.4.0, 2022-04-09

* Added progress bar during scan phase and improved visual design of
``--format=summary`` (contributed by Stanislav Zmiev, issue.
`#73 <https://github.com/roskakori/pygount/issues/73>`_).
* Added percentages to API. For example in addition to
``code_count`` now there also is ``code_percentage``.

Version 1.3.0, 2022-01-06

* Fixed computation of "lines per second", which was a copy and paste of
Expand Down
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Sphinx==4.3.2
Sphinx==4.5.0
sphinx_rtd_theme==1.0.0
251 changes: 123 additions & 128 deletions poetry.lock

Large diffs are not rendered by default.

89 changes: 83 additions & 6 deletions pygount/summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ def __init__(self, language: str):
self._documentation_count = 0
self._empty_count = 0
self._file_count = 0
self._file_percentage = 0.0
self._string_count = 0
self._is_pseudo_language = _PSEUDO_LANGUAGE_REGEX.match(self.language) is not None
self._has_up_to_date_percentages = False

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

@property
def code_percentage(self) -> float:
"""percentage of lines containing code for this language across entire project"""
return _percentage_or_0(self.code_count, self.line_count)

def _assert_has_up_to_date_percentages(self):
assert self._has_up_to_date_percentages, "update_percentages() must be called first"

@property
def documentation_count(self) -> int:
"""sum lines of documentation for this language"""
return self._documentation_count

@property
def documentation_percentage(self) -> float:
"""percentage of lines containing documentation for this language across entire project"""
return _percentage_or_0(self.documentation_count, self.line_count)

@property
def empty_count(self) -> int:
"""sum empty lines for this language"""
return self._empty_count

@property
def empty_percentage(self) -> float:
"""percentage of empty lines for this language across entire project"""
return _percentage_or_0(self.empty_count, self.line_count)

@property
def file_count(self) -> int:
"""number of source code files for this language"""
return self._file_count

@property
def file_percentage(self) -> float:
"""percentage of files in project"""
self._assert_has_up_to_date_percentages()
return self._file_percentage

@property
def line_count(self) -> int:
"""sum count of all lines of any kind for this language"""
return self.code_count + self.documentation_count + self.empty_count + self.string_count

@property
def string_count(self) -> int:
"""sum number of lines containing only strings for this language"""
"""sum number of lines containing strings for this language"""
return self._string_count

@property
def string_percentage(self) -> float:
"""percentage of lines containing strings for this language across entire project"""
return _percentage_or_0(self.string_count, self.line_count)

@property
def source_count(self) -> int:
"""sum number of source lines of code"""
return self.code_count + self.string_count

@property
def source_percentage(self) -> float:
"""percentage of source lines for code for this language across entire project"""
return _percentage_or_0(self.source_count, self.line_count)

@property
def is_pseudo_language(self) -> bool:
"""``True`` if the language is not a real programming language"""
Expand All @@ -84,13 +125,18 @@ def add(self, source_analysis: SourceAnalysis) -> None:
assert source_analysis is not None
assert source_analysis.language == self.language

self._has_up_to_date_percentages = False
self._file_count += 1
if source_analysis.is_countable:
self._code_count += source_analysis.code_count
self._documentation_count += source_analysis.documentation_count
self._empty_count += source_analysis.empty_count
self._string_count += source_analysis.string_count

def update_file_percentage(self, project_summary: "ProjectSummary"):
self._file_percentage = _percentage_or_0(self.file_count, project_summary.total_file_count)
self._has_up_to_date_percentages = True

def __repr__(self):
result = "{0}(language={1!r}, file_count={2}".format(self.__class__.__name__, self.language, self.file_count)
if not self.is_pseudo_language:
Expand All @@ -101,6 +147,12 @@ def __repr__(self):
return result


def _percentage_or_0(partial_count: int, total_count: int) -> float:
assert partial_count >= 0
assert total_count >= 0
return 100 * partial_count / total_count if total_count != 0 else 0.0


class ProjectSummary:
"""
Summary of source code counts for several languages and files.
Expand All @@ -126,22 +178,42 @@ def language_to_language_summary_map(self) -> Dict[str, LanguageSummary]:
def total_code_count(self) -> int:
return self._total_code_count

@property
def total_code_percentage(self) -> float:
return _percentage_or_0(self.total_code_count, self.total_line_count)

@property
def total_documentation_count(self) -> int:
return self._total_documentation_count

@property
def total_documentation_percentage(self) -> float:
return _percentage_or_0(self.total_documentation_count, self.total_line_count)

@property
def total_empty_count(self) -> int:
return self._total_empty_count

@property
def total_empty_percentage(self) -> float:
return _percentage_or_0(self.total_empty_count, self.total_line_count)

@property
def total_string_count(self) -> int:
return self._total_string_count

@property
def total_string_percentage(self) -> float:
return _percentage_or_0(self.total_string_count, self.total_line_count)

@property
def total_source_count(self) -> int:
return self.total_code_count + self.total_string_count

@property
def total_source_percentage(self) -> float:
return _percentage_or_0(self.total_source_count, self.total_line_count)

@property
def total_file_count(self) -> int:
return self._total_file_count
Expand Down Expand Up @@ -173,10 +245,15 @@ def add(self, source_analysis: SourceAnalysis) -> None:
)
self._total_string_count += source_analysis.string_count

def update_file_percentages(self) -> None:
"""Update percentages for all languages part of the project."""
for language_summary in self._language_to_language_summary_map.values():
language_summary.update_file_percentage(self)

def __repr__(self):
return "{0}(total_file_count={1}, total_line_count={2}, " "languages={3}".format(
self.__class__.__name__,
self.total_file_count,
self.total_line_count,
sorted(self.language_to_language_summary_map.keys()),
return (
f"{self.__class__.__name__}("
f"total_file_count={self.total_file_count}, "
f"total_line_count={self.total_line_count}, "
f"languages={sorted(self.language_to_language_summary_map.keys())})"
)
42 changes: 31 additions & 11 deletions pygount/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def add(self, source_analysis):
self.project_summary.add(source_analysis)

def close(self):
self.project_summary.update_file_percentages()
self.finished_at = datetime.datetime.utcnow()
self.duration = self.finished_at - self.started_at
self.duration_in_seconds = (
Expand Down Expand Up @@ -141,9 +142,11 @@ class SummaryWriter(BaseWriter):
_COLUMNS_WITH_JUSTIFY = (
("Language", "left"),
("Files", "right"),
("Blank", "right"),
("Comment", "right"),
("%", "right"),
("Code", "right"),
("%", "right"),
("Comment", "right"),
("%", "right"),
)

def close(self):
Expand All @@ -158,17 +161,21 @@ def close(self):
table.add_row(
language_summary.language,
str(language_summary.file_count),
str(language_summary.empty_count),
str(language_summary.documentation_count),
formatted_percentage(language_summary.file_percentage),
str(language_summary.code_count),
formatted_percentage(language_summary.code_percentage),
str(language_summary.documentation_count),
formatted_percentage(language_summary.documentation_percentage),
end_section=(index == len(language_summaries)),
)
table.add_row(
"SUM",
"Sum",
str(self.project_summary.total_file_count),
str(self.project_summary.total_empty_count),
str(self.project_summary.total_documentation_count),
formatted_percentage(100.0),
str(self.project_summary.total_code_count),
formatted_percentage(self.project_summary.total_code_percentage),
str(self.project_summary.total_documentation_count),
formatted_percentage(self.project_summary.total_documentation_percentage),
)
Console(file=self._target_stream, soft_wrap=True).print(table)

Expand All @@ -186,20 +193,20 @@ def add(self, source_analysis: SourceAnalysis):
super().add(source_analysis)
self.source_analyses.append(
{
"path": source_analysis.path,
"sourceCount": source_analysis.source_count,
"emptyCount": source_analysis.empty_count,
"documentationCount": source_analysis.documentation_count,
"group": source_analysis.group,
"isCountable": source_analysis.is_countable,
"language": source_analysis.language,
"path": source_analysis.path,
"state": source_analysis.state.name,
"stateInfo": source_analysis.state_info,
"sourceCount": source_analysis.source_count,
}
)

def close(self):
# NOTE: We are using camel case for naming here to follow JSLint's guidelines, see <https://www.jslint.com/>.
# NOTE: JSON names use camel case to follow JSLint's guidelines, see <https://www.jslint.com/>.
super().close()
json_map = {
"formatVersion": JSON_FORMAT_VERSION,
Expand All @@ -208,11 +215,15 @@ def close(self):
"languages": [
{
"documentationCount": language_summary.documentation_count,
"documentationPercentage": language_summary.documentation_percentage,
"emptyCount": language_summary.empty_count,
"emptyPercentage": language_summary.empty_percentage,
"fileCount": language_summary.file_count,
"filePercentage": language_summary.file_percentage,
"isPseudoLanguage": language_summary.is_pseudo_language,
"language": language_summary.language,
"sourceCount": language_summary.source_count,
"sourcePercentage": language_summary.source_percentage,
}
for language_summary in self.project_summary.language_to_language_summary_map.values()
],
Expand All @@ -225,14 +236,23 @@ def close(self):
},
"summary": {
"totalDocumentationCount": self.project_summary.total_documentation_count,
"totalDocumentationPercentage": self.project_summary.total_documentation_percentage,
"totalEmptyCount": self.project_summary.total_empty_count,
"totalEmptyPercentage": self.project_summary.total_empty_percentage,
"totalFileCount": self.project_summary.total_file_count,
"totalSourceCount": self.project_summary.total_source_count,
"totalSourcePercentage": self.project_summary.total_source_percentage,
},
}
json.dump(json_map, self._target_stream)


def digit_width(line_count):
def digit_width(line_count: int) -> int:
assert line_count >= 0
return math.ceil(math.log10(line_count + 1)) if line_count != 0 else 1


def formatted_percentage(percentage: float) -> str:
assert percentage >= 0.0
assert percentage <= 100.0
return f"{percentage:.01f}"
Loading

0 comments on commit 49ca668

Please sign in to comment.