Skip to content
This repository has been archived by the owner on Jul 15, 2024. It is now read-only.

Develop #1

Merged
merged 8 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
cmake_minimum_required(VERSION 3.26)
project(school2024_test_task7)

set(PROJECT_NAME school2024_test_task7)
project(${PROJECT_NAME})

include(FetchContent)

FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
FetchContent_MakeAvailable(json)

set(CMAKE_CXX_STANDARD 20)

add_executable(school2024_test_task7 main.cpp)
set(${PROJECT_NAME}_HEADERS
src/ConfigHandler.hpp
src/Commit.hpp
src/CommitParser.hpp
src/TopContributors.hpp
)

set(${PROJECT_NAME}_SOURCES
src/ConfigHandler.cpp
src/Commit.cpp
src/CommitParser.cpp
src/TopContributors.cpp
src/main.cpp
)

set(${PROJECT_NAME}_SOURCE_LIST
${${PROJECT_NAME}_SOURCES}
${${PROJECT_NAME}_HEADERS}
)

add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_LIST})

target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json)
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,48 @@ CodeKiller777

## Автор решения

### Поляков Леонид
#### Контакты<br>
&nbsp;&nbsp;&nbsp;&nbsp;Telegram: [@Impactor3](https://t.me/Impactor3 "Impactor3")<br>
&nbsp;&nbsp;&nbsp;&nbsp;Почта: [email protected]<br>
---

## Описание реализации

##### <u>Проект имеет следующую структуру</u>:

.
├── **CMakeLists.txt**
├── **README.md**
├── **Resources** - файлы для I/O
├── **cmake-build-debug**
├── **config** - конфиги(в данном случае 1 файл <br> &nbsp;|&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;хранящий пути для файлов commits.txt и result.txt)
└── **src** - исходные файлы с кодом

Для гибкой реализации поставленной задачи было принято решение реализовать 4 класса, каждый из которых отвечает за отдельный функционал:

* **ConfigHandler** - парсит файл из заданному пути(в данном случае config/config.json), в котором описаны пути до файлов commits.txt и result.txt
* **Commit** - абстракция над коммитом. Хранит автора, хэш и время создания коммита.
* **CommitParser** - Парсит переданный файл с коммитами. Он же выполняет валидацию входных данных. Результатом является мапа с информацией о валидных коммитах.
* **TopContributors** - Обрабатывает результат работы CommitParser и записывает результат в файл в заданном формате.

### Особенности реализации

Для повышения надёжности работы утилиты и во избежание получения на выходе неожиданных результатов, было принято решение производить валидацию каждой записи о коммите, а именно:

* Проверяется на соответсвие формату юзернейм автора, хэш коммита и время создание коммита
* Для времени учтено 2 возможных формата: YYYY-MM-ddTHH:mm:ss и YYYY-MM-DDThh:mm:ss[.SSS] - с милесекундами и без
* Проверяется "свежесть" коммита - действительно ли он был сделан во время последнего спринта.
* Все ошибки, возникающие в процессе работы программы, а также сообщения о невалидности того или иного коммита или записи о нём в файле<br>логируются и выводятся в консоль

### Дополнительные возможности

Чтобы сделать утилиту более гибкой было принято решение реализовать возможность задавать некоторые параметры через конструкторы классов, что позволяет не затрагивая исходную реализацию классов менять настройки утилиты под нужое поведение
Возможности:

* Задание кастомного числа "призовых мест" в конструкторе TopContributors, то есть числа лучших контрибьютеров, чьи юзернеймы будут записаны в файл, как лучших за прошедший спринт
* Задание кастомной продолжительности спринта в днях(0 - 365) вторым параметром в конструкторе CommitParser. По умолчанию 28
* Задание своего пути до файла с конфигами в конструкторе ConfigHandler
* Задание пути(и имени) до файлов commits.txt и result.txt - откуда будут считываться данные и куда будет записан результат работы программы

## Инструкция по сборке и запуску решения
26 changes: 26 additions & 0 deletions Resources/commits.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
AIvanov 25ec001 2024-04-25T13:56:39.492
AKalinina 54yd613 2024-04-25T13:56:39.492
CodeKiller777 162ud87 2024-04-26T13:56:39.492
ivan_1petrov kys1189 2024-04-27T13:56:39.492
gopher2009 g00gle3 2024-04-28T13:56:39.492
AIvanov 32shn41 2024-04-29T13:56:39.492
AKalinina 9823jdu 2024-04-30T13:56:39.492
AIvanov djvu892 2024-05-01T13:56:39.492
AKalinina kda92hs 2024-05-02T13:56:39.492
AIvanov 9hd3sus 2024-05-03T13:56:39.492
AIvanov 0wjf23d 2024-05-04T13:56:39.492
CodeKiller777 87heq29 2024-05-05T13:56:39.492
CodeKiller777 9sjn9jw 2024-05-06T13:56:39.492
AKalinina 7by8wqw 2024-05-25T07:56:39.492
ivan_1petrov 7h1yt8j 2024-05-08T13:56:39.492
gopher2009 8hsx7w0 2024-05-09T13:56:39.492
AIvanov 98h8dau 2024-05-10T13:56:39.492
AKalinina h7wsk5g 2024-05-11T13:56:39.492
CodeKiller777 8jwe135 2024-05-12T13:56:39.492
ivan_1petrov 8nwe1sa 2024-05-13T13:56:39.492
gopher2009 9jd21ed 2024-05-14T13:56:39.492
AKalinina 98hdw91 2024-05-15T13:56:39.492
CodeKiller777 v54a5ra 2024-05-16T13:56:39.492
AIvanov x7gsx2q 2024-05-17T13:56:39.492
gopher2009 dnv34id 2024-05-18T13:56:39.492
ttest_case bu3r8hr 2024-05-24T13:56:39
4 changes: 4 additions & 0 deletions config/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"commitsFilePath": "../Resources/commits.txt",
"outputFilePath": "../Resources/result.txt"
}
6 changes: 0 additions & 6 deletions main.cpp

This file was deleted.

61 changes: 61 additions & 0 deletions src/Commit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Created by DJ Tape on 21.05.2024.
//

#include "Commit.hpp"
Parser::Commit::Commit(std::string _username, std::string _commitHash, const std::string& _commitTime):
authorUsername(std::move(_username)), commitHash(std::move(_commitHash))
{
std::istringstream iss(_commitTime);
// парсинг времени в структуру
iss >> std::get_time(&commitTime, "%Y-%m-%dT%H:%M:%S");
if (iss.fail()) {
throw std::string{"Bad time format in commit: " + commitHash};
}
}

bool Parser::Commit::isRecentEnough(int validDayDiff) const {
if (0 > validDayDiff or validDayDiff > 365) {
throw std::string{"Invalid dayDiff argument in isRecentEnough func: must be between 0 and 365"};
}
std::time_t now = std::time(nullptr);
std::tm* currentTime = std::localtime(&now);
int yearDiff = currentTime->tm_year - commitTime.tm_year;
int dayDiff = currentTime->tm_yday - commitTime.tm_yday;
const auto isLeapYear = [](int year) -> bool {
if (year % 4 != 0)
return false;
else if (year % 100 == 0 and year % 400 != 0)
return false;
else
return true;
}(commitTime.tm_year);

if (yearDiff > 1)
return false;
if (yearDiff == 0) {
if (dayDiff <= validDayDiff)
return true;
return false;
}
// если спринт начался ещё в прошлом году
int daysBeforeNY{365 - commitTime.tm_yday}, daysAfterNY{currentTime->tm_yday};
if (isLeapYear) {
++daysBeforeNY;
}
if (daysBeforeNY + daysAfterNY <= validDayDiff)
return true;
return false;
}

std::string Parser::Commit::getAuthorUsername() const {
return authorUsername;
}

std::string Parser::Commit::getHash() const {
return commitHash;
}

std::tm Parser::Commit::getTime() const {
return commitTime;
}
48 changes: 48 additions & 0 deletions src/Commit.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Created by DJ Tape on 21.05.2024.
//

#ifndef SCHOOL2024_TEST_TASK7_COMMIT_HPP
#define SCHOOL2024_TEST_TASK7_COMMIT_HPP

#include <iostream>
#include <chrono>
#include <sstream>
#include <iomanip>

namespace Parser {
/**
Функционал:
Проверка свежести коммита
Пример:
Commit commit{"Ivan_Petrov", "hash256", "2024-05-05T13:56:39.492"};
if (!commit.isRecentEnough(14)) {
std::cout << commit.getHash() << "Commit was made a long time ago";
} else {
std::cout << All is OK << std::endl;
}

Код из примера выводит в консоль "All is OK", в случае если коммит сделан не раньше, чем 14 дней назад.
Текущий день при проверке не учитывается
*/
class Commit {
private:
std::string authorUsername{};
std::string commitHash{};
std::tm commitTime{};
public:
Commit(std::string _username, std::string _commitHash, const std::string& _commitTime);

[[nodiscard]] bool isRecentEnough(int validDayDiff) const;

[[nodiscard]] std::string getAuthorUsername() const;

[[nodiscard]] std::string getHash() const;

[[nodiscard]] std::tm getTime() const;
};

} //Parser


#endif //SCHOOL2024_TEST_TASK7_COMMIT_HPP
59 changes: 59 additions & 0 deletions src/CommitParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Created by DJ Tape on 21.05.2024.
//

#include "CommitParser.hpp"


const std::regex Parser::CommitParser::recordPattern{R"(^([A-Za-z_][A-Za-z0-9_]*) ([a-z0-9]{7}) (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?))"};

Parser::CommitParser::CommitParser(const std::string &_commitsFile, const int _sprintDurationInDays):
sprintDurationInDays(_sprintDurationInDays)
{
commitsFile.open(_commitsFile);
if (!commitsFile.is_open()) {
throw std::runtime_error("Error opening a file by path: " + _commitsFile);
}
}

void Parser::CommitParser::setSprintDuration(int _sprintDurationInDays) {
this->sprintDurationInDays = _sprintDurationInDays;
}

// Парсинг отдельного коммита
Parser::Commit Parser::CommitParser::parseCommit(const std::string &fileLine) {
std::smatch match;
if (std::regex_search(fileLine, match, recordPattern)) {
try {
return Commit{match[1], match[2], match[3]};
} catch (const std::string& err) {
std::cout << err << std::endl;
}
} else {
throw std::string{"Commit record doesn't match the correct format"};
}
}
Parser::CommitMap Parser::CommitParser::ParseCommits() {
std::string commitRecord;
Parser::CommitMap contributorsCommits;
while (std::getline(commitsFile, commitRecord)) {
try {
auto commit = parseCommit(commitRecord);
// Если коммит не попадает в нужные временные рамки, то пропускаем его
if (!commit.isRecentEnough(sprintDurationInDays)) {
throw std::string{"Commit "} + std::string{commit.getHash()}
+ std::string{" does not fit within the required time frame"};
}
contributorsCommits[commit.getAuthorUsername()].push_back(commit);
} catch (const std::string& err) {
std::cout << err << std::endl;
}
}
return contributorsCommits;
}

Parser::CommitParser::~CommitParser() {
if(commitsFile.is_open()) {
commitsFile.close();
}
}
41 changes: 41 additions & 0 deletions src/CommitParser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Created by DJ Tape on 21.05.2024.
//

#ifndef SCHOOL2024_TEST_TASK7_COMMITPARSER_HPP
#define SCHOOL2024_TEST_TASK7_COMMITPARSER_HPP

#include <iostream>
#include <regex>
#include <fstream>
#include "Commit.hpp"

namespace Parser {

using CommitMap = std::map<std::string, std::vector<Commit>>;

// Значение по умолчанию для продолжительности спринта(4 недели)
static const int DEFAULT_DURATION_DAYS{28};

/**
Парсинг файла с коммитами
Пример:
CommitParser parser{NameOfTheFileWithCommitRecords};
Parser::CommitMap commits = parser.ParseCommits()
*/
class CommitParser {
private:
int sprintDurationInDays{DEFAULT_DURATION_DAYS};
std::ifstream commitsFile;
static const std::regex recordPattern;
static Commit parseCommit(const std::string& fileLine);
public:
explicit CommitParser(const std::string& _commitsFile, int _sprintDurationInDays=DEFAULT_DURATION_DAYS);
~CommitParser();
void setSprintDuration(int _sprintDurationInDays=DEFAULT_DURATION_DAYS);
CommitMap ParseCommits();
};
} //Parser


#endif //SCHOOL2024_TEST_TASK7_COMMITPARSER_HPP
31 changes: 31 additions & 0 deletions src/ConfigHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Created by DJ Tape on 21.05.2024.
//

#include "ConfigHandler.hpp"


ConfigHandler::ConfigHandler::ConfigHandler(std::string _configFilePath):
configFilePath(std::move(_configFilePath))
{
std::ifstream configFile{configFilePath};
if (!configFile.is_open()) {
throw std::runtime_error("Error opening a config file by path: " + configFilePath);
}

nlohmann::json j{};
configFile >> j;

commitsFilePath = j["commitsFilePath"].get<std::string>();
outputFilePath = j["outputFilePath"].get<std::string>();

configFile.close();
}

std::string ConfigHandler::ConfigHandler::getCommitsFilePath() const {
return commitsFilePath;
}

std::string ConfigHandler::ConfigHandler::getOutputFilePath() const {
return outputFilePath;
}
Loading