Skip to content

Commit f1323ba

Browse files
authored
feat: exit with positive exit code in the end when found similarity. (#210)
Refs: #210, #153.
1 parent 9875f8a commit f1323ba

File tree

14 files changed

+155
-59
lines changed

14 files changed

+155
-59
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
UTIL_VERSION := 0.5.11
1+
UTIL_VERSION := 0.5.12
22
UTIL_NAME := codeplag
33
PWD := $(shell pwd)
44

locales/codeplag.pot

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
#, fuzzy
66
msgid ""
77
msgstr ""
8-
"Project-Id-Version: codeplag 0.5.11\n"
9-
"POT-Creation-Date: 2025-01-02 18:42+0300\n"
8+
"Project-Id-Version: codeplag 0.5.12\n"
9+
"POT-Creation-Date: 2025-01-03 14:06+0300\n"
1010
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1111
"Last-Translator: Artyom Semidolin\n"
1212
"Language-Team: LANGUAGE <[email protected]>\n"
@@ -216,6 +216,14 @@ msgid ""
216216
"user', or 'github-project-folder' options."
217217
msgstr ""
218218

219+
#: src/codeplag/codeplagcli.py:421 src/codeplag/handlers/report.py:95
220+
msgid "All paths must be provided."
221+
msgstr ""
222+
223+
#: src/codeplag/handlers/report.py:92
224+
msgid "Invalid report type."
225+
msgstr ""
226+
219227
#: src/templates/general.templ:5 src/templates/sources.templ:5
220228
msgid "Comparative report"
221229
msgstr ""

locales/translations/en/LC_MESSAGES/codeplag.po

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
msgid ""
66
msgstr ""
7-
"Project-Id-Version: codeplag 0.5.11\n"
7+
"Project-Id-Version: codeplag 0.5.12\n"
88
"POT-Creation-Date: 2024-05-21 09:28+0300\n"
99
"PO-Revision-Date: 2024-05-16 19:15+0300\n"
1010
"Last-Translator: Artyom Semidolin\n"
@@ -242,6 +242,14 @@ msgstr ""
242242
"The'path-regexp' option requires the provided 'directories', 'github-"
243243
"user', or 'github-project-folder' options."
244244

245+
#: src/codeplag/codeplagcli.py:421 src/codeplag/handlers/report.py:95
246+
msgid "All paths must be provided."
247+
msgstr "All or none of the root paths must be specified."
248+
249+
#: src/codeplag/handlers/report.py:92
250+
msgid "Invalid report type."
251+
msgstr "Invalid report type."
252+
245253
#: src/templates/general.templ:5 src/templates/sources.templ:5
246254
msgid "Comparative report"
247255
msgstr "Comparative report"

locales/translations/ru/LC_MESSAGES/codeplag.po

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
msgid ""
66
msgstr ""
7-
"Project-Id-Version: codeplag 0.5.11\n"
7+
"Project-Id-Version: codeplag 0.5.12\n"
88
"POT-Creation-Date: 2024-05-21 09:28+0300\n"
99
"PO-Revision-Date: 2024-05-11 12:05+0300\n"
1010
"Last-Translator: Artyom Semidolin\n"
@@ -256,6 +256,14 @@ msgstr ""
256256
"Аргумент 'path-regexp' требует заданного параметра 'directories', "
257257
"'github-user' или 'github-project-folder'."
258258

259+
#: src/codeplag/codeplagcli.py:421 src/codeplag/handlers/report.py:95
260+
msgid "All paths must be provided."
261+
msgstr "Необходимо указать все корневые пути или не указывать ни одного."
262+
263+
#: src/codeplag/handlers/report.py:92
264+
msgid "Invalid report type."
265+
msgstr "Некорректный тип отчёта."
266+
259267
#: src/templates/general.templ:5 src/templates/sources.templ:5
260268
msgid "Comparative report"
261269
msgstr "Сравнительный отчёт"

src/codeplag/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Literal
1+
from codeplag.types import ExitCode
22

33

4-
def main() -> Literal[0, 1, 2]:
4+
def main() -> ExitCode:
55
import argcomplete
66
import pandas as pd
77

@@ -29,14 +29,14 @@ def main() -> Literal[0, 1, 2]:
2929
code = codeplag_util.run()
3030
except KeyboardInterrupt:
3131
logger.warning("The util stopped by keyboard interrupt.")
32-
return 1
32+
return ExitCode.EXIT_KEYBOARD
3333
except Exception:
3434
logger.error(
3535
"An unexpected error occurred while running the utility. "
3636
"For getting more information, check file '%s'.",
3737
LOG_PATH,
3838
)
3939
logger.debug("Trace:", exc_info=True)
40-
return 2
40+
return ExitCode.EXIT_UNKNOWN
4141

4242
return code

src/codeplag/codeplagcli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,13 @@ def validate_args(self: Self, parsed_args: argparse.Namespace) -> None:
412412
"'github-user', or 'github-project-folder' options."
413413
)
414414
)
415+
elif (
416+
root == "report"
417+
and command == "create"
418+
and not all([parsed_args.first_root_path, parsed_args.second_root_path])
419+
and any([parsed_args.first_root_path, parsed_args.second_root_path])
420+
):
421+
self.error(_("All paths must be provided."))
415422

416423
def parse_args(self: Self, args: list[str] | None = None) -> argparse.Namespace:
417424
parsed_args = super(CodeplagCLI, self).parse_args(args)

src/codeplag/handlers/check.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from codeplag.types import (
3939
ASTFeatures,
4040
CompareInfo,
41+
ExitCode,
4142
Extension,
4243
Flag,
4344
MaxDepth,
@@ -151,7 +152,7 @@ def check(
151152
github_files: list[str] | None = None,
152153
github_project_folders: list[str] | None = None,
153154
github_user: str = "",
154-
) -> None:
155+
) -> ExitCode:
155156
if files is None:
156157
files = []
157158
if directories is None:
@@ -167,16 +168,17 @@ def check(
167168
features_from_gh_files = self.features_getter.get_from_github_files(github_files)
168169

169170
logger.info("Starting searching for plagiarism ...")
171+
exit_code = ExitCode.EXIT_SUCCESS
170172
if self.mode == "many_to_many":
171-
self.__many_to_many_check(
173+
exit_code = self.__many_to_many_check(
172174
features_from_files,
173175
directories,
174176
features_from_gh_files,
175177
github_project_folders,
176178
github_user,
177179
)
178180
elif self.mode == "one_to_one":
179-
self.__one_to_one_check(
181+
exit_code = self.__one_to_one_check(
180182
features_from_files,
181183
directories,
182184
features_from_gh_files,
@@ -187,6 +189,7 @@ def check(
187189
logger.info("Ending searching for plagiarism ...")
188190
if isinstance(self.reporter, CSVReporter):
189191
self.reporter._write_df_to_fs()
192+
return exit_code
190193

191194
def __many_to_many_check(
192195
self: Self,
@@ -195,7 +198,7 @@ def __many_to_many_check(
195198
features_from_gh_files: list[ASTFeatures],
196199
github_project_folders: list[str],
197200
github_user: str,
198-
) -> None:
201+
) -> ExitCode:
199202
works: list[ASTFeatures] = []
200203
works.extend(features_from_files)
201204
works.extend(self.features_getter.get_from_dirs(directories))
@@ -212,15 +215,19 @@ def __many_to_many_check(
212215
iterations,
213216
)
214217
self.progress = Progress(iterations)
218+
exit_code = ExitCode.EXIT_SUCCESS
215219
with ProcessPoolExecutor(max_workers=self.workers) as executor:
216220
processing: list[ProcessingWorks] = []
217221
futures: set[Future] = set()
218222
for i, work1 in enumerate(works):
219223
for j, work2 in enumerate(works):
220224
if i <= j:
221225
continue
222-
self._do_step(executor, processing, futures, work1, work2)
223-
self._handle_completed_futures(processing, futures)
226+
exit_code = ExitCode(
227+
exit_code | self._do_step(executor, processing, futures, work1, work2)
228+
)
229+
exit_code = ExitCode(exit_code | self._handle_completed_futures(processing, futures))
230+
return exit_code
224231

225232
def __one_to_one_check(
226233
self: Self,
@@ -229,7 +236,7 @@ def __one_to_one_check(
229236
features_from_gh_files: list[ASTFeatures],
230237
github_project_folders: list[str],
231238
github_user: str,
232-
) -> None:
239+
) -> ExitCode:
233240
combined_elements = filter(
234241
bool,
235242
(
@@ -253,6 +260,7 @@ def __one_to_one_check(
253260
)
254261
self.progress = ComplexProgress(iterations)
255262
cases = combinations(combined_elements, r=2)
263+
exit_code = ExitCode.EXIT_SUCCESS
256264
with ProcessPoolExecutor(max_workers=self.workers) as executor:
257265
processing: list[ProcessingWorks] = []
258266
futures: set[Future] = set()
@@ -269,8 +277,11 @@ def __one_to_one_check(
269277
self.progress.add_internal_progress(internal_iterations)
270278
for work1 in first_sequence:
271279
for work2 in second_sequence:
272-
self._do_step(executor, processing, futures, work1, work2)
273-
self._handle_completed_futures(processing, futures)
280+
exit_code = ExitCode(
281+
exit_code | self._do_step(executor, processing, futures, work1, work2)
282+
)
283+
exit_code = ExitCode(exit_code | self._handle_completed_futures(processing, futures))
284+
return exit_code
274285

275286
def _do_step(
276287
self: Self,
@@ -279,10 +290,10 @@ def _do_step(
279290
futures: set[Future],
280291
work1: ASTFeatures,
281292
work2: ASTFeatures,
282-
) -> None:
293+
) -> ExitCode:
283294
if work1.filepath == work2.filepath:
284295
_print_pretty_progress_if_need_and_increase(self.progress, self.workers)
285-
return
296+
return ExitCode.EXIT_SUCCESS
286297

287298
work1, work2 = sorted([work1, work2])
288299
metrics = None
@@ -293,23 +304,24 @@ def _do_step(
293304
future.id = len(processing) # type: ignore
294305
futures.add(future)
295306
processing.append(ProcessingWorks(work1, work2))
296-
return
307+
return ExitCode.EXIT_SUCCESS
297308
self._handle_compare_result(work1, work2, metrics)
298309
_print_pretty_progress_if_need_and_increase(self.progress, self.workers)
310+
return ExitCode.EXIT_FOUND_SIM
299311

300312
def _handle_compare_result(
301313
self: Self,
302314
work1: ASTFeatures,
303315
work2: ASTFeatures,
304316
metrics: CompareInfo,
305317
save: bool = False,
306-
) -> None:
318+
) -> ExitCode:
307319
if metrics.structure is None:
308-
return
320+
return ExitCode.EXIT_SUCCESS
309321
if self.reporter and save:
310322
self.reporter.save_result(work1, work2, metrics)
311323
if self.short_output:
312-
return
324+
return ExitCode.EXIT_FOUND_SIM
313325

314326
if self.threshold and (metrics.structure.similarity * 100) <= self.threshold:
315327
print_compare_result(work1, work2, metrics)
@@ -324,19 +336,25 @@ def _handle_compare_result(
324336
work2.head_nodes,
325337
),
326338
)
339+
return ExitCode.EXIT_FOUND_SIM
327340

328341
def _handle_completed_futures(
329342
self: Self,
330343
processing: list[ProcessingWorks],
331344
futures: set[Future],
332-
) -> None:
345+
) -> ExitCode:
346+
exit_code = ExitCode.EXIT_SUCCESS
333347
for future in as_completed(futures):
334348
metrics: CompareInfo = future.result()
335349
proc_works_info = processing[future.id] # type: ignore
336-
self._handle_compare_result(
337-
proc_works_info.work1, proc_works_info.work2, metrics, save=True
350+
exit_code = ExitCode(
351+
exit_code
352+
| self._handle_compare_result(
353+
proc_works_info.work1, proc_works_info.work2, metrics, save=True
354+
)
338355
)
339356
_print_pretty_progress_if_need_and_increase(self.progress, self.workers)
357+
return exit_code
340358

341359
def _create_future_compare(
342360
self: Self,

src/codeplag/handlers/report.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections import defaultdict
44
from copy import deepcopy
55
from pathlib import Path
6-
from typing import Generator, Literal, TypedDict
6+
from typing import Generator, TypedDict
77

88
import jinja2
99
import numpy as np
@@ -25,6 +25,7 @@
2525
from codeplag.translate import get_translations
2626
from codeplag.types import (
2727
CompareInfo,
28+
ExitCode,
2829
Language,
2930
ReportType,
3031
SameFuncs,
@@ -48,7 +49,7 @@ def html_report_create(
4849
report_type: ReportType,
4950
first_root_path: Path | str | None = None,
5051
second_root_path: Path | str | None = None,
51-
) -> Literal[0, 1]:
52+
) -> ExitCode:
5253
"""Creates an HTML report based on the configuration settings.
5354
5455
Args:
@@ -60,7 +61,7 @@ def html_report_create(
6061
6162
Returns:
6263
-------
63-
Literal[0, 1]: 0 if the report was successfully created, 1 otherwise.
64+
ExitCode: 0 if the report was successfully created, 1 otherwise.
6465
6566
Raises:
6667
-------
@@ -76,22 +77,22 @@ def html_report_create(
7677
reports_path = settings_config.get("reports")
7778
if not reports_path:
7879
logger.error("Can't create general report without provided in settings 'report' path.")
79-
return 1
80+
return ExitCode.EXIT_INVAL
8081
if settings_config["reports_extension"] != "csv":
8182
logger.error("Can create report only when 'reports_extension' is csv.")
82-
return 1
83+
return ExitCode.EXIT_INVAL
8384
if not (reports_path / CSV_REPORT_FILENAME).exists():
8485
logger.error(f"There is nothing in '{reports_path}' to create a basic html report from.")
85-
return 1
86+
return ExitCode.EXIT_INVAL
8687
if report_type == "general":
8788
create_report_function = _create_general_report
8889
elif report_type == "sources":
8990
create_report_function = _create_sources_report
9091
else:
91-
raise ValueError("Invalid report type.")
92+
raise ValueError(_("Invalid report type."))
9293
all_paths_provided = all([first_root_path, second_root_path])
9394
if not all_paths_provided and any([first_root_path, second_root_path]):
94-
raise ValueError("All paths must be provided.")
95+
raise ValueError(_("All paths must be provided."))
9596

9697
df = read_df(reports_path / CSV_REPORT_FILENAME)
9798
if all_paths_provided:
@@ -110,7 +111,7 @@ def html_report_create(
110111
settings_config["language"],
111112
paths, # type: ignore
112113
)
113-
return 0
114+
return ExitCode.EXIT_SUCCESS
114115

115116

116117
def calculate_general_total_similarity(

0 commit comments

Comments
 (0)