Skip to content

Commit 2f4b62d

Browse files
authored
feat: add possibility to create general html report from reports files
- Logic for create general html report from reports files; - Add language setting; - Extends examples of using the util in the README file; - Test coverage of html report generation logic. Refs: #179
1 parent 6a81f73 commit 2f4b62d

23 files changed

+958
-186
lines changed

Makefile

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
UTIL_VERSION := 0.3.7
1+
UTIL_VERSION := 0.4.0
22
UTIL_NAME := codeplag
33
PWD := $(shell pwd)
44

55
USER_UID ?= $(shell id --user)
66
USER_GID ?= $(shell id --group)
77

8-
BASE_DOCKER_VERSION := 1.2
8+
BASE_DOCKER_VERSION := 1.3
99
BASE_DOCKER_TAG := $(shell echo $(UTIL_NAME)-base-ubuntu20.04:$(BASE_DOCKER_VERSION) | tr A-Z a-z)
1010
TEST_DOCKER_TAG := $(shell echo $(UTIL_NAME)-test-ubuntu20.04:$(UTIL_VERSION) | tr A-Z a-z)
1111
DOCKER_TAG ?= $(shell echo $(UTIL_NAME)-ubuntu20.04:$(UTIL_VERSION) | tr A-Z a-z)
@@ -16,6 +16,7 @@ PYTHONPATH := $(PWD)/src/:$(PWD)/test/auto
1616
LOGS_PATH := /var/log/$(UTIL_NAME)
1717
CODEPLAG_LOG_PATH := $(LOGS_PATH)/$(UTIL_NAME).log
1818
CONFIG_PATH := /etc/$(UTIL_NAME)/settings.conf
19+
LIB_PATH := /var/lib/$(UTIL_NAME)
1920
DEBIAN_PACKAGES_PATH := debian/deb
2021

2122
SOURCE_SUB_FILES := src/$(UTIL_NAME)/consts.py
@@ -44,6 +45,7 @@ substitute = @sed \
4445
-e "s|@DEVEL_SUFFIX@|${DEVEL_SUFFIX}|g" \
4546
-e "s|@PYTHON_REQUIRED_LIBS@|${PYTHON_REQUIRED_LIBS}|g" \
4647
-e "s|@LOGS_PATH@|${LOGS_PATH}|g" \
48+
-e "s|@LIB_PATH@|${LIB_PATH}|g" \
4749
-e "s|@CONFIG_PATH@|${CONFIG_PATH}|g" \
4850
-e "s|@BASE_DOCKER_TAG@|${BASE_DOCKER_TAG}|g" \
4951
-e "s|@DEBIAN_PACKAGES_PATH@|${DEBIAN_PACKAGES_PATH}|g" \
@@ -82,6 +84,10 @@ install: substitute-sources man
8284

8385
install -D -d -m 0755 $(DESTDIR)/$(LOGS_PATH)
8486
install -D -m 0666 /dev/null $(DESTDIR)/$(CODEPLAG_LOG_PATH)
87+
install -D -d -m 0755 $(DESTDIR)/$(LIB_PATH)
88+
89+
install -D -m 0666 src/templates/report_ru.templ $(DESTDIR)/$(LIB_PATH)/report_ru.templ
90+
install -D -m 0666 src/templates/report_en.templ $(DESTDIR)/$(LIB_PATH)/report_en.templ
8591

8692
if [ ! -f $(DESTDIR)/$(CONFIG_PATH) ]; then \
8793
install -D -d -m 0755 $(DESTDIR)/etc/$(UTIL_NAME); \
@@ -102,11 +108,11 @@ package: substitute-debian
102108
)
103109

104110
test: substitute-sources
105-
pytest test/unit -q
111+
pytest test/unit -vv
106112
make clean-cache
107113

108114
autotest:
109-
pytest test/auto -q
115+
pytest test/auto -vv
110116
make clean-cache
111117

112118
pre-commit:

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,19 @@
118118

119119
## 4. Demo examples (works in the project directory and with an installed codeplag package)
120120

121-
- Python analyzer
121+
- Show help: `$ codeplag --help`
122+
- Show help of subcommands (and further along the chain similarly): `$ codeplag check --help`
123+
- Setting up the util:
124+
```
125+
# Setup check threshold to 70
126+
# Language to English
127+
# Show check progress
128+
# Extension of reports 'csv'
129+
# Reports path to '/usr/src/works'
130+
# Path to environment variables '/usr/src/works/.env'
131+
$ codeplag settings modify --threshold 70 --language en --show_progress 1 --reports_extension csv --reports /usr/src/works --environment /usr/src/works/.env
132+
```
133+
- Python analyzer:
122134
```
123135
$ codeplag check --extension py --files src/codeplag/pyplag/astwalkers.py --directories src/codeplag/pyplag
124136
$ codeplag check --extension py --directories src/codeplag/algorithms src
@@ -129,11 +141,11 @@
129141
$ codeplag check --extension py --github-project-folders https://github.com/OSLL/code-plagiarism/blob/main/src/codeplag/pyplag --github-user OSLL --repo-regexp code-
130142
$ codeplag check --extension py --github-project-folders https://github.com/OSLL/code-plagiarism/blob/main/src/codeplag/pyplag --directories src/codeplag/pyplag/
131143
```
132-
133-
- C++/C analyzer
144+
- C++/C analyzer:
134145
```
135146
$ codeplag check --extension cpp --directories src/codeplag/cplag/tests/data src/ --files test/codeplag/cplag/data/sample1.cpp test/codeplag/cplag/data/sample2.cpp
136147
$ codeplag check --extension cpp --github-files https://github.com/OSLL/code-plagiarism/blob/main/test/codeplag/cplag/data/sample3.cpp https://github.com/OSLL/code-plagiarism/blob/main/test/codeplag/cplag/data/sample4.cpp
137148
$ codeplag check --extension cpp --github-project-folders https://github.com/OSLL/code-plagiarism/tree/main/test
138149
$ codeplag check --extension cpp --github-user OSLL --repo-regexp "code-plag"
139150
```
151+
- Create html report: `codeplag report create --path /usr/src/works`

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"requests~=2.31.0",
1717
"typing-extensions~=4.3.0",
1818
"aiohttp~=3.8.5",
19+
"Jinja2~=3.1.2",
1920
"cachetools==5.3.1",
2021
"gidgethub~=5.3.0",
2122
]

src/codeplag/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def main() -> Literal[0, 1, 2]:
2020
logger = get_logger(__name__, LOG_PATH, verbose=parsed_args["verbose"])
2121
codeplag_util = CodeplagEngine(logger, parsed_args)
2222
try:
23-
codeplag_util.run()
23+
code = codeplag_util.run()
2424
except KeyboardInterrupt:
2525
logger.warning("The util stopped by keyboard interrupt.")
2626
return 1
@@ -33,4 +33,4 @@ def main() -> Literal[0, 1, 2]:
3333
logger.debug("Trace:", exc_info=True)
3434
return 2
3535

36-
return 0
36+
return code

src/codeplag/codeplagcli.py

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
from webparsers.types import GitHubContentUrl
1010

1111
from codeplag.consts import (
12+
DEFAULT_GENERAL_REPORT_NAME,
1213
EXTENSION_CHOICE,
14+
LANGUAGE_CHOICE,
1315
MODE_CHOICE,
1416
REPORTS_EXTENSION_CHOICE,
1517
UTIL_NAME,
@@ -67,44 +69,20 @@ def __new__(cls, *args, **kwargs):
6769
class CodeplagCLI(argparse.ArgumentParser):
6870
"""The argument parser of the codeplag util."""
6971

70-
def __init__(self):
71-
super(CodeplagCLI, self).__init__(
72-
prog=UTIL_NAME,
73-
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
74-
description="Program help to find similar parts of source "
75-
"codes for the different languages.",
76-
)
77-
self.add_argument(
78-
"-v",
79-
"--version",
80-
help="Print current version number and exit.",
81-
action="version",
82-
version=f"{UTIL_NAME} {UTIL_VERSION}",
83-
)
84-
self.add_argument(
85-
"--verbose",
86-
help="Show debug messages.",
87-
action="store_true",
88-
)
89-
90-
subparsers = self.add_subparsers(
91-
help="Commands help.",
92-
parser_class=argparse.ArgumentParser,
93-
required=True,
94-
metavar="COMMAND",
95-
dest="root",
96-
)
97-
72+
def __add_settings_path(self, subparsers: argparse._SubParsersAction) -> None:
9873
settings = subparsers.add_parser(
9974
"settings",
10075
help=f"Modifies and shows static settings of the '{UTIL_NAME}' util.",
10176
)
77+
10278
settings_commands = settings.add_subparsers(
10379
help=f"Settings commands of the '{UTIL_NAME}' util.",
10480
required=True,
10581
metavar="COMMAND",
10682
dest="settings",
10783
)
84+
85+
# settings modify
10886
settings_modify = settings_commands.add_parser(
10987
"modify",
11088
help=f"Manage the '{UTIL_NAME}' util settings.",
@@ -149,12 +127,21 @@ def __init__(self):
149127
choices=range(50, 100),
150128
metavar="{50, 51, ..., 99}",
151129
)
130+
settings_modify.add_argument(
131+
"-l",
132+
"--language",
133+
help="The language of help messages, generated reports, errors.",
134+
type=str,
135+
choices=LANGUAGE_CHOICE,
136+
)
152137

138+
# settings show
153139
settings_commands.add_parser(
154140
"show",
155141
help=f"Show the '{UTIL_NAME}' util settings.",
156142
)
157143

144+
def __add_check_path(self, subparsers: argparse._SubParsersAction) -> None:
158145
check = subparsers.add_parser("check", help="Start searching similar works.")
159146
check.add_argument(
160147
"-d",
@@ -239,3 +226,64 @@ def __init__(self):
239226
action=CheckUniqueStore,
240227
default=[],
241228
)
229+
230+
def __add_report_path(self, subparsers: argparse._SubParsersAction) -> None:
231+
report = subparsers.add_parser(
232+
"report",
233+
help=f"Handling generated by the {UTIL_NAME} reports as creating html "
234+
"report file or show it on console.",
235+
)
236+
237+
report_commands = report.add_subparsers(
238+
help=f"Report commands of the '{UTIL_NAME}' util.",
239+
required=True,
240+
metavar="COMMAND",
241+
dest="report",
242+
)
243+
244+
# report create
245+
report_create = report_commands.add_parser(
246+
"create",
247+
help="Generate general report from created some time ago report files.",
248+
)
249+
report_create.add_argument(
250+
"-p",
251+
"--path",
252+
help="Path to save generated general report. "
253+
"If it's directory, than creates file in it with "
254+
f"name '{DEFAULT_GENERAL_REPORT_NAME}'.",
255+
required=True,
256+
type=Path,
257+
)
258+
259+
def __init__(self):
260+
super(CodeplagCLI, self).__init__(
261+
prog=UTIL_NAME,
262+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
263+
description="Program help to find similar parts of source "
264+
"codes for the different languages.",
265+
)
266+
self.add_argument(
267+
"-v",
268+
"--version",
269+
help="Print current version number and exit.",
270+
action="version",
271+
version=f"{UTIL_NAME} {UTIL_VERSION}",
272+
)
273+
self.add_argument(
274+
"--verbose",
275+
help="Show debug messages.",
276+
action="store_true",
277+
)
278+
279+
subparsers = self.add_subparsers(
280+
help="Commands help.",
281+
parser_class=argparse.ArgumentParser,
282+
required=True,
283+
metavar="COMMAND",
284+
dest="root",
285+
)
286+
287+
self.__add_settings_path(subparsers)
288+
self.__add_check_path(subparsers)
289+
self.__add_report_path(subparsers)

src/codeplag/config.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55

66
from typing_extensions import NotRequired
77

8-
from codeplag.consts import CONFIG_PATH, DEFAULT_THRESHOLD
8+
from codeplag.consts import (
9+
CONFIG_PATH,
10+
DEFAULT_LANGUAGE,
11+
DEFAULT_REPORT_EXTENSION,
12+
DEFAULT_THRESHOLD,
13+
)
914
from codeplag.types import Settings
1015

1116

@@ -74,5 +79,8 @@ def write_settings_conf(settings: Settings) -> None:
7479

7580

7681
DefaultSettingsConfig = Settings(
77-
threshold=DEFAULT_THRESHOLD, show_progress=0, reports_extension="csv"
82+
threshold=DEFAULT_THRESHOLD,
83+
show_progress=0,
84+
reports_extension=DEFAULT_REPORT_EXTENSION,
85+
language=DEFAULT_LANGUAGE,
7886
)

src/codeplag/consts.tmp.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
from pathlib import Path
33
from typing import Dict, Final, List, Tuple
44

5-
from codeplag.types import Extension, Extensions, Mode, ReportsExtension, Threshold
5+
from codeplag.types import (
6+
Extension,
7+
Extensions,
8+
Language,
9+
Mode,
10+
ReportsExtension,
11+
Threshold,
12+
)
613

714
UTIL_NAME: Final[str] = "@UTIL_NAME@"
815
UTIL_VERSION: Final[str] = "@UTIL_VERSION@"
@@ -11,6 +18,17 @@
1118
CONFIG_PATH: Final[Path] = Path("@CONFIG_PATH@")
1219
FILE_DOWNLOAD_PATH: Final[Path] = Path(f"/tmp/{UTIL_NAME}_download.out")
1320
LOG_PATH: Final[Path] = Path("@CODEPLAG_LOG_PATH@")
21+
TEMPLATE_PATH: Final[Dict[Language, Path]] = {
22+
"ru": Path("@LIB_PATH@/report_ru.templ"),
23+
"en": Path("@LIB_PATH@/report_en.templ"),
24+
}
25+
26+
# Default values
27+
DEFAULT_THRESHOLD: Final[Threshold] = 65
28+
DEFAULT_WEIGHTS: Final[Tuple[float, float, float, float]] = (1.0, 0.4, 0.4, 0.4)
29+
DEFAULT_LANGUAGE: Final[Language] = "en"
30+
DEFAULT_REPORT_EXTENSION: Final[ReportsExtension] = "csv"
31+
DEFAULT_GENERAL_REPORT_NAME: Final[str] = "report.html"
1432

1533
GET_FRAZE: Final[str] = "Getting works features from"
1634

@@ -34,10 +52,10 @@
3452
"compliance_matrix",
3553
)
3654

37-
DEFAULT_THRESHOLD: Final[Threshold] = 65
3855
MODE_CHOICE: Final[List[Mode]] = ["many_to_many", "one_to_one"]
3956
REPORTS_EXTENSION_CHOICE: Final[List[ReportsExtension]] = ["csv", "json"]
4057
EXTENSION_CHOICE: Final[List[Extension]] = ["py", "cpp"]
58+
LANGUAGE_CHOICE: Final[List[Language]] = ["en", "ru"]
4159
ALL_EXTENSIONS: Final[Tuple[re.Pattern]] = (re.compile(r"\..*$"),)
4260
# Don't checks changing values by key
4361
SUPPORTED_EXTENSIONS: Final[Dict[Extension, Extensions]] = {

src/codeplag/display.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import sys
22
from enum import Enum
33
from functools import partial
4-
from typing import List
4+
from typing import List, Optional
55

6-
import numpy as np
76
import pandas as pd
87

98
from codeplag.types import ASTFeatures, CompareInfo, NodeCodePlace
@@ -86,7 +85,7 @@ def print_compare_result(
8685
features1: ASTFeatures,
8786
features2: ASTFeatures,
8887
compare_info: CompareInfo,
89-
threshold: int = 60,
88+
compliance_matrix_df: Optional[pd.DataFrame] = None,
9089
) -> None:
9190
"""The function prints the result of comparing two files
9291
@@ -131,24 +130,7 @@ def print_compare_result(
131130
print(additional_metrics_df)
132131
print()
133132

134-
if (compare_info.structure.similarity * 100) > threshold:
135-
data = np.zeros(
136-
(
137-
compare_info.structure.compliance_matrix.shape[0],
138-
compare_info.structure.compliance_matrix.shape[1],
139-
),
140-
dtype=np.float64,
141-
)
142-
for row in range(compare_info.structure.compliance_matrix.shape[0]):
143-
for col in range(compare_info.structure.compliance_matrix.shape[1]):
144-
data[row][col] = (
145-
compare_info.structure.compliance_matrix[row][col][0]
146-
/ compare_info.structure.compliance_matrix[row][col][1]
147-
)
148-
compliance_matrix_df = pd.DataFrame(
149-
data=data, index=features1.head_nodes, columns=features2.head_nodes
150-
)
151-
133+
if compliance_matrix_df is not None:
152134
print(compliance_matrix_df, "\n")
153135

154136
print("+" * 40)

0 commit comments

Comments
 (0)