From 502dbbdb5ab61d9c1034fa302d4b205af0cc894e Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Mon, 20 May 2024 22:45:11 +0300 Subject: [PATCH 1/8] Update CmakeLists --- CMakeLists.txt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cee32d..ccff009 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,21 @@ cmake_minimum_required(VERSION 3.26) -project(school2024_test_task7) + +set(PROJECT_NAME school2024_test_task7) +project(${PROJECT_NAME}) set(CMAKE_CXX_STANDARD 20) -add_executable(school2024_test_task7 main.cpp) +set(${PROJECT_NAME}_HEADERS + +) + +set(${PROJECT_NAME}_SOURCES + main.cpp +) + +set(${PROJECT_NAME}_SOURCE_LIST + ${${PROJECT_NAME}_SOURCES} + ${${PROJECT_NAME}_HEADERS} +) + +add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_LIST}) From 7f3f1cd6df1b44bcd33e3a0808a8835eb88da052 Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Tue, 21 May 2024 03:32:49 +0300 Subject: [PATCH 2/8] Implement ConfigHandler class --- CMakeLists.txt | 12 ++++++++++-- config/config.json | 4 ++++ main.cpp | 6 ------ src/ConfigHandler.cpp | 32 ++++++++++++++++++++++++++++++++ src/ConfigHandler.hpp | 33 +++++++++++++++++++++++++++++++++ src/main.cpp | 13 +++++++++++++ 6 files changed, 92 insertions(+), 8 deletions(-) delete mode 100644 main.cpp create mode 100644 src/ConfigHandler.cpp create mode 100644 src/ConfigHandler.hpp create mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ccff009..a10e499 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,14 +3,20 @@ cmake_minimum_required(VERSION 3.26) 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) set(${PROJECT_NAME}_HEADERS - + src/ConfigHandler.hpp ) set(${PROJECT_NAME}_SOURCES - main.cpp + src/ConfigHandler.cpp + src/main.cpp ) set(${PROJECT_NAME}_SOURCE_LIST @@ -19,3 +25,5 @@ set(${PROJECT_NAME}_SOURCE_LIST ) add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_LIST}) + +target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json) diff --git a/config/config.json b/config/config.json index e69de29..42381ae 100644 --- a/config/config.json +++ b/config/config.json @@ -0,0 +1,4 @@ +{ + "commitsFilePath": "../Resources/commits.txt", + "outputFilePath": "../Resources/result.txt" +} \ No newline at end of file diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 8fbfc65..0000000 --- a/main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() { - std::cout << "Hello, World!" << std::endl; - return 0; -} diff --git a/src/ConfigHandler.cpp b/src/ConfigHandler.cpp new file mode 100644 index 0000000..c4fb229 --- /dev/null +++ b/src/ConfigHandler.cpp @@ -0,0 +1,32 @@ +// +// Created by DJ Tape on 21.05.2024. +// + +#include "ConfigHandler.hpp" + +//#include + +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 file by path: " + configFilePath); + } + + nlohmann::json j{}; + configFile >> j; + + commitsFilePath = j["commitsFilePath"].get(); + outputFilePath = j["outputFilePath"].get(); + + configFile.close(); +} + +std::string ConfigHandler::ConfigHandler::getCommitsFilePath() const { + return commitsFilePath; +} + +std::string ConfigHandler::ConfigHandler::getOutputFilePath() const { + return outputFilePath; +} \ No newline at end of file diff --git a/src/ConfigHandler.hpp b/src/ConfigHandler.hpp new file mode 100644 index 0000000..26119aa --- /dev/null +++ b/src/ConfigHandler.hpp @@ -0,0 +1,33 @@ +// +// Created by DJ Tape on 21.05.2024. +// + +#ifndef SCHOOL2024_TEST_TASK7_CONFIGHANDLER_HPP +#define SCHOOL2024_TEST_TASK7_CONFIGHANDLER_HPP + +#include +#include +#include "nlohmann/json.hpp" + + +namespace ConfigHandler { + + static const std::string DEFAULT_CONFIG_PATH{"../config/config.json"}; + + class ConfigHandler { + private: + std::string configFilePath{DEFAULT_CONFIG_PATH}; + std::string commitsFilePath; + std::string outputFilePath; + public: + explicit ConfigHandler(std::string filePath=DEFAULT_CONFIG_PATH); + + [[nodiscard]] std::string getCommitsFilePath() const; + + [[nodiscard]] std::string getOutputFilePath() const; + }; + +} //ConfigHandler + + +#endif //SCHOOL2024_TEST_TASK7_CONFIGHANDLER_HPP diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..4f58f55 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,13 @@ +#include +#include "ConfigHandler.hpp" + +int main() { + std::cout << "Hello, World!" << std::endl; + try { + ConfigHandler::ConfigHandler Cfg{}; + std::cout << Cfg.getOutputFilePath() << std::endl << Cfg.getCommitsFilePath(); + } catch(const std::exception& e) { + std::cerr << "Error occurred: " << e.what() << std::endl; + } + return 0; +} From 3c1fb5faac78db346a1bbcd7bbce5c30ea8e1733 Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Tue, 21 May 2024 07:12:09 +0300 Subject: [PATCH 3/8] Add Commit class & CommitParser class for parsing commits from commits.txt --- CMakeLists.txt | 4 ++++ Resources/commits.txt | 24 ++++++++++++++++++++++++ src/Commit.cpp | 28 ++++++++++++++++++++++++++++ src/Commit.hpp | 33 +++++++++++++++++++++++++++++++++ src/CommitParser.cpp | 27 +++++++++++++++++++++++++++ src/CommitParser.hpp | 24 ++++++++++++++++++++++++ src/ConfigHandler.cpp | 24 ++++++++++++------------ src/ConfigHandler.hpp | 4 ++-- 8 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 Resources/commits.txt create mode 100644 src/Commit.cpp create mode 100644 src/Commit.hpp create mode 100644 src/CommitParser.cpp create mode 100644 src/CommitParser.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a10e499..c4fc6d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,10 +12,14 @@ set(CMAKE_CXX_STANDARD 20) set(${PROJECT_NAME}_HEADERS src/ConfigHandler.hpp + src/Commit.hpp + src/CommitParser.hpp ) set(${PROJECT_NAME}_SOURCES src/ConfigHandler.cpp + src/Commit.cpp + src/CommitParser.cpp src/main.cpp ) diff --git a/Resources/commits.txt b/Resources/commits.txt new file mode 100644 index 0000000..58f0a1d --- /dev/null +++ b/Resources/commits.txt @@ -0,0 +1,24 @@ +AIvanov 25ec001 2024-04-24T13: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 \ No newline at end of file diff --git a/src/Commit.cpp b/src/Commit.cpp new file mode 100644 index 0000000..19b805d --- /dev/null +++ b/src/Commit.cpp @@ -0,0 +1,28 @@ +// +// Created by DJ Tape on 21.05.2024. +// + +#include "Commit.hpp" +//#include + +Parser::Commit::Commit(std::string _username, std::string _commitHash, const std::string& _commitTime): + username(std::move(_username)), commitHash(std::move(_commitHash)) + { + std::istringstream iss(_commitTime); + iss >> std::get_time(&commitTime, "%Y-%m-%dT%H:%M:%S.%3N"); + if (iss.fail()) { + throw std::string{"Bad time format in commit: " + commitHash}; + } + } + +std::string Parser::Commit::getUsername() const { + return username; +} + +std::string Parser::Commit::getCommitHash() const { + return commitHash; +} + +std::tm Parser::Commit::getCommitTime() const { + return commitTime; +} diff --git a/src/Commit.hpp b/src/Commit.hpp new file mode 100644 index 0000000..ef4a01e --- /dev/null +++ b/src/Commit.hpp @@ -0,0 +1,33 @@ +// +// Created by DJ Tape on 21.05.2024. +// + +#ifndef SCHOOL2024_TEST_TASK7_COMMIT_HPP +#define SCHOOL2024_TEST_TASK7_COMMIT_HPP + +#include +#include +#include +#include +//#include + +namespace Parser { + class Commit { + private: + std::string username{}; + std::string commitHash{}; + std::tm commitTime{}; + public: + Commit(std::string _username, std::string _commitHash, const std::string& _commitTime); + + [[nodiscard]] std::string getUsername() const; + + [[nodiscard]] std::string getCommitHash() const; + + [[nodiscard]] std::tm getCommitTime() const; + }; + +} //Parser + + +#endif //SCHOOL2024_TEST_TASK7_COMMIT_HPP diff --git a/src/CommitParser.cpp b/src/CommitParser.cpp new file mode 100644 index 0000000..c8add1f --- /dev/null +++ b/src/CommitParser.cpp @@ -0,0 +1,27 @@ +// +// Created by DJ Tape on 21.05.2024. +// + +#include "CommitParser.hpp" + +#include + +auto Parser::CommitParser::recordPattern{std::regex{R"(^(\w+_?\w*\d*)\s+(\w{7})\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}))"}}; + +Parser::CommitParser::CommitParser(std::string _commitsFile): + commitsFile(std::move(_commitsFile)) + { + + } + +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; + } + } + throw std::string{"Commit record doesn't match the correct format"}; +} diff --git a/src/CommitParser.hpp b/src/CommitParser.hpp new file mode 100644 index 0000000..e5abb35 --- /dev/null +++ b/src/CommitParser.hpp @@ -0,0 +1,24 @@ +// +// Created by DJ Tape on 21.05.2024. +// + +#ifndef SCHOOL2024_TEST_TASK7_COMMITPARSER_HPP +#define SCHOOL2024_TEST_TASK7_COMMITPARSER_HPP + +#include +#include +#include "Commit.hpp" + +namespace Parser { + class CommitParser { + private: + std::string commitsFile{}; + static const auto recordPattern; + static Commit parseCommit(const std::string& fileLine); + public: + explicit CommitParser(std::string _commitsFile); + }; +} //Parser + + +#endif //SCHOOL2024_TEST_TASK7_COMMITPARSER_HPP diff --git a/src/ConfigHandler.cpp b/src/ConfigHandler.cpp index c4fb229..57feecb 100644 --- a/src/ConfigHandler.cpp +++ b/src/ConfigHandler.cpp @@ -7,21 +7,21 @@ //#include ConfigHandler::ConfigHandler::ConfigHandler(std::string _configFilePath): - configFilePath(std::move(_configFilePath)) { + configFilePath(std::move(_configFilePath)) + { + std::ifstream configFile{configFilePath}; + if (!configFile.is_open()) { + throw std::runtime_error("Error opening a file by path: " + configFilePath); + } - std::ifstream configFile{configFilePath}; - if (!configFile.is_open()) { - throw std::runtime_error("Error opening a file by path: " + configFilePath); - } - - nlohmann::json j{}; - configFile >> j; + nlohmann::json j{}; + configFile >> j; - commitsFilePath = j["commitsFilePath"].get(); - outputFilePath = j["outputFilePath"].get(); + commitsFilePath = j["commitsFilePath"].get(); + outputFilePath = j["outputFilePath"].get(); - configFile.close(); -} + configFile.close(); + } std::string ConfigHandler::ConfigHandler::getCommitsFilePath() const { return commitsFilePath; diff --git a/src/ConfigHandler.hpp b/src/ConfigHandler.hpp index 26119aa..8e3bbde 100644 --- a/src/ConfigHandler.hpp +++ b/src/ConfigHandler.hpp @@ -17,8 +17,8 @@ namespace ConfigHandler { class ConfigHandler { private: std::string configFilePath{DEFAULT_CONFIG_PATH}; - std::string commitsFilePath; - std::string outputFilePath; + std::string commitsFilePath{}; + std::string outputFilePath{}; public: explicit ConfigHandler(std::string filePath=DEFAULT_CONFIG_PATH); From 90637fb0afebb03f31ee4697db6375be9425ae40 Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Thu, 23 May 2024 00:02:24 +0300 Subject: [PATCH 4/8] MVP --- CMakeLists.txt | 2 ++ src/Commit.cpp | 49 ++++++++++++++++++++++++++++++++++++----- src/Commit.hpp | 16 ++++++++++---- src/CommitParser.cpp | 38 +++++++++++++++++++++++++++----- src/CommitParser.hpp | 20 ++++++++++++++--- src/ConfigHandler.cpp | 2 +- src/TopContributors.cpp | 41 ++++++++++++++++++++++++++++++++++ src/TopContributors.hpp | 33 +++++++++++++++++++++++++++ src/main.cpp | 13 +++++++---- 9 files changed, 190 insertions(+), 24 deletions(-) create mode 100644 src/TopContributors.cpp create mode 100644 src/TopContributors.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c4fc6d0..8bbfa10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,12 +14,14 @@ 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 ) diff --git a/src/Commit.cpp b/src/Commit.cpp index 19b805d..26acd5c 100644 --- a/src/Commit.cpp +++ b/src/Commit.cpp @@ -6,23 +6,60 @@ //#include Parser::Commit::Commit(std::string _username, std::string _commitHash, const std::string& _commitTime): - username(std::move(_username)), commitHash(std::move(_commitHash)) + authorUsername(std::move(_username)), commitHash(std::move(_commitHash)) { std::istringstream iss(_commitTime); - iss >> std::get_time(&commitTime, "%Y-%m-%dT%H:%M:%S.%3N"); + iss >> std::get_time(&commitTime, "%Y-%m-%dT%H:%M:%S"); if (iss.fail()) { throw std::string{"Bad time format in commit: " + commitHash}; } } -std::string Parser::Commit::getUsername() const { - return username; +//Parser::Commit::Commit(): authorUsername(), commitHash(), commitTime() { +// +//} + +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::getCommitHash() const { +std::string Parser::Commit::getHash() const { return commitHash; } -std::tm Parser::Commit::getCommitTime() const { +std::tm Parser::Commit::getTime() const { return commitTime; } diff --git a/src/Commit.hpp b/src/Commit.hpp index ef4a01e..76ab8d4 100644 --- a/src/Commit.hpp +++ b/src/Commit.hpp @@ -14,17 +14,25 @@ namespace Parser { class Commit { private: - std::string username{}; + std::string authorUsername{}; std::string commitHash{}; std::tm commitTime{}; public: Commit(std::string _username, std::string _commitHash, const std::string& _commitTime); - [[nodiscard]] std::string getUsername() const; +// Commit(); - [[nodiscard]] std::string getCommitHash() const; +// Commit(const Commit& commit); - [[nodiscard]] std::tm getCommitTime() const; +// Commit(Commit&& moved) noexcept; + + [[nodiscard]] bool isRecentEnough(int validDayDiff) const; + + [[nodiscard]] std::string getAuthorUsername() const; + + [[nodiscard]] std::string getHash() const; + + [[nodiscard]] std::tm getTime() const; }; } //Parser diff --git a/src/CommitParser.cpp b/src/CommitParser.cpp index c8add1f..a01d173 100644 --- a/src/CommitParser.cpp +++ b/src/CommitParser.cpp @@ -4,14 +4,16 @@ #include "CommitParser.hpp" -#include -auto Parser::CommitParser::recordPattern{std::regex{R"(^(\w+_?\w*\d*)\s+(\w{7})\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}))"}}; +const std::regex Parser::CommitParser::recordPattern{R"(^(\w+_?\w*\d*)\s+(\w{7})\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}))"}; -Parser::CommitParser::CommitParser(std::string _commitsFile): - commitsFile(std::move(_commitsFile)) +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); + } } Parser::Commit Parser::CommitParser::parseCommit(const std::string &fileLine) { @@ -22,6 +24,30 @@ Parser::Commit Parser::CommitParser::parseCommit(const std::string &fileLine) { } 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(); } - throw std::string{"Commit record doesn't match the correct format"}; } diff --git a/src/CommitParser.hpp b/src/CommitParser.hpp index e5abb35..2736192 100644 --- a/src/CommitParser.hpp +++ b/src/CommitParser.hpp @@ -7,16 +7,30 @@ #include #include +#include #include "Commit.hpp" namespace Parser { + + using CommitMap = std::map>; + + static const int DEFAULT_DURATION_DAYS{28}; + + /// Парсинг файла с коммитами\n + /// Пример:\n + /// CommitParser parser{NameOfTheFileWithCommitRecords}; + /// std::vector &commits = parser.ParseCommits() class CommitParser { private: - std::string commitsFile{}; - static const auto recordPattern; + const int sprintDurationInDays{}; + std::ifstream commitsFile; + static const std::regex recordPattern; static Commit parseCommit(const std::string& fileLine); public: - explicit CommitParser(std::string _commitsFile); + explicit CommitParser(const std::string& _commitsFile, int _sprintDurationInDays=DEFAULT_DURATION_DAYS); + ~CommitParser(); +// std::vector ParseCommits(); + std::map> ParseCommits(); }; } //Parser diff --git a/src/ConfigHandler.cpp b/src/ConfigHandler.cpp index 57feecb..9983406 100644 --- a/src/ConfigHandler.cpp +++ b/src/ConfigHandler.cpp @@ -11,7 +11,7 @@ ConfigHandler::ConfigHandler::ConfigHandler(std::string _configFilePath): { std::ifstream configFile{configFilePath}; if (!configFile.is_open()) { - throw std::runtime_error("Error opening a file by path: " + configFilePath); + throw std::runtime_error("Error opening a config file by path: " + configFilePath); } nlohmann::json j{}; diff --git a/src/TopContributors.cpp b/src/TopContributors.cpp new file mode 100644 index 0000000..8e2e7c9 --- /dev/null +++ b/src/TopContributors.cpp @@ -0,0 +1,41 @@ +// +// Created by DJ Tape on 22.05.2024. +// + +#include "TopContributors.hpp" + +Contributors::TopContributors::TopContributors(unsigned int _topCount): topCount(_topCount) +{ + if (topCount == 0) + topCount = 1; +} + +void Contributors::TopContributors::setTopCount(unsigned int _topCount) { + this->topCount = _topCount; +} + +void Contributors::TopContributors::FindTop(Parser::CommitMap commitMap) { + for (const auto& pair : commitMap) { + topContributorsList.emplace_back(pair.first, pair.second.size()); + } + + std::sort(topContributorsList.begin(), topContributorsList.end(), + [](const std::pair& a, const std::pair& b) -> bool { + return a.second > b.second; + }); +} + +void Contributors::TopContributors::WriteTopToFile(const std::string &_outputFile) { + std::ofstream outputFile{_outputFile}; + if(!outputFile.is_open()) { + throw std::runtime_error("Error opening output file by path: " + _outputFile); + } + for(size_t i{}; i < topContributorsList.size(); ++i) { + outputFile << topContributorsList[i].first << std::endl; + if (i >= topCount - 1 and i != topContributorsList.size() - 1 + and topContributorsList[i+i].second < topContributorsList[i].second) { + break; + } + } + outputFile.close(); +} diff --git a/src/TopContributors.hpp b/src/TopContributors.hpp new file mode 100644 index 0000000..84c1eb1 --- /dev/null +++ b/src/TopContributors.hpp @@ -0,0 +1,33 @@ +// +// Created by DJ Tape on 22.05.2024. +// + +#ifndef SCHOOL2024_TEST_TASK7_TOPCONTRIBUTORS_HPP +#define SCHOOL2024_TEST_TASK7_TOPCONTRIBUTORS_HPP + +#include +#include +#include "CommitParser.hpp" + +namespace Contributors { + + static const unsigned DEFAULT_TOP_COUNT{3}; + + class TopContributors { + private: + unsigned topCount{}; + std::vector> topContributorsList{}; + public: + explicit TopContributors(unsigned _topCount=DEFAULT_TOP_COUNT); + + void FindTop(Parser::CommitMap commitMap); + + void WriteTopToFile(const std::string& _outputFile); + + void setTopCount(unsigned _topCount=DEFAULT_TOP_COUNT); + }; + +} + + +#endif //SCHOOL2024_TEST_TASK7_TOPCONTRIBUTORS_HPP diff --git a/src/main.cpp b/src/main.cpp index 4f58f55..e548960 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,13 +1,18 @@ #include #include "ConfigHandler.hpp" +#include "TopContributors.hpp" int main() { - std::cout << "Hello, World!" << std::endl; try { - ConfigHandler::ConfigHandler Cfg{}; - std::cout << Cfg.getOutputFilePath() << std::endl << Cfg.getCommitsFilePath(); + ConfigHandler::ConfigHandler Config{}; +// std::cout << Config.getOutputFilePath() << std::endl << Config.getCommitsFilePath(); + Parser::CommitParser parser{Config.getCommitsFilePath()}; + Contributors::TopContributors topContributors{}; + auto commitRecords = parser.ParseCommits(); + topContributors.FindTop(commitRecords); + topContributors.WriteTopToFile(Config.getOutputFilePath()); } catch(const std::exception& e) { - std::cerr << "Error occurred: " << e.what() << std::endl; + std::cout << "Error occurred: " << e.what() << std::endl; } return 0; } From ff70c755539e3894a2444d1850f7eed19a19634a Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Thu, 23 May 2024 15:45:16 +0300 Subject: [PATCH 5/8] Add comments & do bug fixes --- Resources/commits.txt | 9 +++++++-- src/Commit.cpp | 4 ++-- src/Commit.hpp | 15 ++++++++++++++- src/CommitParser.cpp | 8 +++++++- src/CommitParser.hpp | 17 ++++++++++------- src/ConfigHandler.cpp | 1 - src/ConfigHandler.hpp | 1 + src/TopContributors.cpp | 15 +++++++++------ src/TopContributors.hpp | 10 ++++++++-- 9 files changed, 58 insertions(+), 22 deletions(-) diff --git a/Resources/commits.txt b/Resources/commits.txt index 58f0a1d..b5d8c1f 100644 --- a/Resources/commits.txt +++ b/Resources/commits.txt @@ -1,4 +1,4 @@ -AIvanov 25ec001 2024-04-24T13:56:39.492 +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 @@ -21,4 +21,9 @@ 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 \ No newline at end of file +AIvanov x7gsx2q 2024-05-17T13:56:39.492 +gopher2009 dnv34id 2024-05-18T13:56:39.492 +gopher2009 984rf3w 2024-05-19T13:56:39.492 +ivan_1petrov wh25hfi 2024-05-13T13:56:39.492 +ivan_1petrov hfw8832 2024-05-13T13:56:39.492 +ttest_case bu3r8hr 2024-05-24T13:56:39.492 \ No newline at end of file diff --git a/src/Commit.cpp b/src/Commit.cpp index 26acd5c..aa21d30 100644 --- a/src/Commit.cpp +++ b/src/Commit.cpp @@ -3,12 +3,11 @@ // #include "Commit.hpp" -//#include - 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}; @@ -43,6 +42,7 @@ bool Parser::Commit::isRecentEnough(int validDayDiff) const { return true; return false; } + // если спринт начался ещё в прошлом году int daysBeforeNY{365 - commitTime.tm_yday}, daysAfterNY{currentTime->tm_yday}; if (isLeapYear) { ++daysBeforeNY; diff --git a/src/Commit.hpp b/src/Commit.hpp index 76ab8d4..1341d24 100644 --- a/src/Commit.hpp +++ b/src/Commit.hpp @@ -9,9 +9,22 @@ #include #include #include -//#include 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{}; diff --git a/src/CommitParser.cpp b/src/CommitParser.cpp index a01d173..e62711c 100644 --- a/src/CommitParser.cpp +++ b/src/CommitParser.cpp @@ -16,6 +16,11 @@ Parser::CommitParser::CommitParser(const std::string &_commitsFile, const int _s } } +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)) { @@ -34,9 +39,10 @@ Parser::CommitMap Parser::CommitParser::ParseCommits() { 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"}; + + std::string{" does not fit within the required time frame"}; } contributorsCommits[commit.getAuthorUsername()].push_back(commit); } catch (const std::string& err) { diff --git a/src/CommitParser.hpp b/src/CommitParser.hpp index 2736192..9d605a8 100644 --- a/src/CommitParser.hpp +++ b/src/CommitParser.hpp @@ -14,23 +14,26 @@ namespace Parser { using CommitMap = std::map>; + // Значение по умолчанию для продолжительности спринта(4 недели) static const int DEFAULT_DURATION_DAYS{28}; - /// Парсинг файла с коммитами\n - /// Пример:\n - /// CommitParser parser{NameOfTheFileWithCommitRecords}; - /// std::vector &commits = parser.ParseCommits() +/** + Парсинг файла с коммитами + Пример: + CommitParser parser{NameOfTheFileWithCommitRecords}; + Parser::CommitMap commits = parser.ParseCommits() +*/ class CommitParser { private: - const int sprintDurationInDays{}; + 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(); -// std::vector ParseCommits(); - std::map> ParseCommits(); + void setSprintDuration(int _sprintDurationInDays=DEFAULT_DURATION_DAYS); + CommitMap ParseCommits(); }; } //Parser diff --git a/src/ConfigHandler.cpp b/src/ConfigHandler.cpp index 9983406..69769b1 100644 --- a/src/ConfigHandler.cpp +++ b/src/ConfigHandler.cpp @@ -4,7 +4,6 @@ #include "ConfigHandler.hpp" -//#include ConfigHandler::ConfigHandler::ConfigHandler(std::string _configFilePath): configFilePath(std::move(_configFilePath)) diff --git a/src/ConfigHandler.hpp b/src/ConfigHandler.hpp index 8e3bbde..c6a94eb 100644 --- a/src/ConfigHandler.hpp +++ b/src/ConfigHandler.hpp @@ -12,6 +12,7 @@ namespace ConfigHandler { + // Путь по умолчанию, по которому должен лежать файл с конфигами(в формате json) static const std::string DEFAULT_CONFIG_PATH{"../config/config.json"}; class ConfigHandler { diff --git a/src/TopContributors.cpp b/src/TopContributors.cpp index 8e2e7c9..26c33cd 100644 --- a/src/TopContributors.cpp +++ b/src/TopContributors.cpp @@ -14,11 +14,12 @@ void Contributors::TopContributors::setTopCount(unsigned int _topCount) { this->topCount = _topCount; } -void Contributors::TopContributors::FindTop(Parser::CommitMap commitMap) { +void Contributors::TopContributors::FindTop(const Parser::CommitMap& commitMap) { for (const auto& pair : commitMap) { topContributorsList.emplace_back(pair.first, pair.second.size()); } + // сортировка контрибьютеров по количеству коммитов std::sort(topContributorsList.begin(), topContributorsList.end(), [](const std::pair& a, const std::pair& b) -> bool { return a.second > b.second; @@ -30,12 +31,14 @@ void Contributors::TopContributors::WriteTopToFile(const std::string &_outputFil if(!outputFile.is_open()) { throw std::runtime_error("Error opening output file by path: " + _outputFile); } - for(size_t i{}; i < topContributorsList.size(); ++i) { + + size_t i{}; + for(; i < topCount; ++i) { outputFile << topContributorsList[i].first << std::endl; - if (i >= topCount - 1 and i != topContributorsList.size() - 1 - and topContributorsList[i+i].second < topContributorsList[i].second) { - break; - } + } + // Если на какое-то место претендуют сразу несколько контрибьютеров, то выводим их всех + while (topContributorsList[i].second == topContributorsList[i-1].second and i < topContributorsList.size()) { + outputFile << topContributorsList[i++].first << std::endl; } outputFile.close(); } diff --git a/src/TopContributors.hpp b/src/TopContributors.hpp index 84c1eb1..8e80c5d 100644 --- a/src/TopContributors.hpp +++ b/src/TopContributors.hpp @@ -11,16 +11,22 @@ namespace Contributors { + // Значение по умолчанию для количества лучших контрибьюторов, которых нужно записать в файл static const unsigned DEFAULT_TOP_COUNT{3}; + /** + Функционал: + Сортировка контрибьютеров по количеству коммитов + Запись DEFAULT_TOP_COUNT лучших контрибьютеров в файл + */ class TopContributors { private: - unsigned topCount{}; + unsigned topCount{DEFAULT_TOP_COUNT}; std::vector> topContributorsList{}; public: explicit TopContributors(unsigned _topCount=DEFAULT_TOP_COUNT); - void FindTop(Parser::CommitMap commitMap); + void FindTop(const Parser::CommitMap& commitMap); void WriteTopToFile(const std::string& _outputFile); From b3cb7b8184507f78f84091a3bcc9cc8366e1ff9b Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Thu, 23 May 2024 19:42:41 +0300 Subject: [PATCH 6/8] Update regex expression --- Resources/commits.txt | 5 +---- src/CommitParser.cpp | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Resources/commits.txt b/Resources/commits.txt index b5d8c1f..92e7b12 100644 --- a/Resources/commits.txt +++ b/Resources/commits.txt @@ -23,7 +23,4 @@ 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 -gopher2009 984rf3w 2024-05-19T13:56:39.492 -ivan_1petrov wh25hfi 2024-05-13T13:56:39.492 -ivan_1petrov hfw8832 2024-05-13T13:56:39.492 -ttest_case bu3r8hr 2024-05-24T13:56:39.492 \ No newline at end of file +ttest_case bu3r8hr 2024-05-24T13:56:39 \ No newline at end of file diff --git a/src/CommitParser.cpp b/src/CommitParser.cpp index e62711c..8d89940 100644 --- a/src/CommitParser.cpp +++ b/src/CommitParser.cpp @@ -5,7 +5,7 @@ #include "CommitParser.hpp" -const std::regex Parser::CommitParser::recordPattern{R"(^(\w+_?\w*\d*)\s+(\w{7})\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}))"}; +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) From ce73f18d32d18a192baac3b519740acb7be61c1f Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Thu, 23 May 2024 20:02:35 +0300 Subject: [PATCH 7/8] Version 1.0.0 --- src/Commit.cpp | 4 ---- src/Commit.hpp | 6 ------ src/ConfigHandler.cpp | 2 +- src/main.cpp | 1 - 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Commit.cpp b/src/Commit.cpp index aa21d30..01ec8ef 100644 --- a/src/Commit.cpp +++ b/src/Commit.cpp @@ -14,10 +14,6 @@ Parser::Commit::Commit(std::string _username, std::string _commitHash, const std } } -//Parser::Commit::Commit(): authorUsername(), commitHash(), commitTime() { -// -//} - 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"}; diff --git a/src/Commit.hpp b/src/Commit.hpp index 1341d24..d98e4d8 100644 --- a/src/Commit.hpp +++ b/src/Commit.hpp @@ -33,12 +33,6 @@ namespace Parser { public: Commit(std::string _username, std::string _commitHash, const std::string& _commitTime); -// Commit(); - -// Commit(const Commit& commit); - -// Commit(Commit&& moved) noexcept; - [[nodiscard]] bool isRecentEnough(int validDayDiff) const; [[nodiscard]] std::string getAuthorUsername() const; diff --git a/src/ConfigHandler.cpp b/src/ConfigHandler.cpp index 69769b1..078d86a 100644 --- a/src/ConfigHandler.cpp +++ b/src/ConfigHandler.cpp @@ -28,4 +28,4 @@ std::string ConfigHandler::ConfigHandler::getCommitsFilePath() const { std::string ConfigHandler::ConfigHandler::getOutputFilePath() const { return outputFilePath; -} \ No newline at end of file +} diff --git a/src/main.cpp b/src/main.cpp index e548960..922a053 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,6 @@ int main() { try { ConfigHandler::ConfigHandler Config{}; -// std::cout << Config.getOutputFilePath() << std::endl << Config.getCommitsFilePath(); Parser::CommitParser parser{Config.getCommitsFilePath()}; Contributors::TopContributors topContributors{}; auto commitRecords = parser.ParseCommits(); From aaa5e35cafb1bee4f255c1fb65ddaf82fc9703c4 Mon Sep 17 00:00:00 2001 From: DJ Tape Date: Thu, 23 May 2024 22:52:34 +0300 Subject: [PATCH 8/8] Added project description --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index a2bc0aa..7ae9d8d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,48 @@ CodeKiller777 ## Автор решения +### Поляков Леонид +#### Контакты
+    Telegram: [@Impactor3](https://t.me/Impactor3 "Impactor3")
+    Почта: alexsourcesence@gmail.com
+--- + ## Описание реализации +##### Проект имеет следующую структуру: + +. +├── **CMakeLists.txt** +├── **README.md** +├── **Resources** - файлы для I/O +├── **cmake-build-debug** +├── **config** - конфиги(в данном случае 1 файл
 |      хранящий пути для файлов 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] - с милесекундами и без +* Проверяется "свежесть" коммита - действительно ли он был сделан во время последнего спринта. +* Все ошибки, возникающие в процессе работы программы, а также сообщения о невалидности того или иного коммита или записи о нём в файле
логируются и выводятся в консоль + +### Дополнительные возможности + +Чтобы сделать утилиту более гибкой было принято решение реализовать возможность задавать некоторые параметры через конструкторы классов, что позволяет не затрагивая исходную реализацию классов менять настройки утилиты под нужое поведение +Возможности: + +* Задание кастомного числа "призовых мест" в конструкторе TopContributors, то есть числа лучших контрибьютеров, чьи юзернеймы будут записаны в файл, как лучших за прошедший спринт +* Задание кастомной продолжительности спринта в днях(0 - 365) вторым параметром в конструкторе CommitParser. По умолчанию 28 +* Задание своего пути до файла с конфигами в конструкторе ConfigHandler +* Задание пути(и имени) до файлов commits.txt и result.txt - откуда будут считываться данные и куда будет записан результат работы программы + ## Инструкция по сборке и запуску решения