diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cdd326..7c65640 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.12) project( weather-forecast - VERSION 1.3.5 + VERSION 1.3.6 DESCRIPTION "Console weather forecast app that uses OpenMeteo and Yandex Geocoder API" LANGUAGES CXX ) diff --git a/README.md b/README.md index 7b4d72c..e739dab 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ cd %userprofile%\weather-forecast && .\weather-forecast.exe ## Использование -Программа weather-forecast - консольное приложение для просмотра погоды. +Программа weather-forecast — консольное приложение для просмотра погоды. Предусмотрен показ погоды для локаций, перечисленных в конфигурационном файле, на текущий момент, а также на утро, день, вечер и ночь некоторого количества дней. Программа в один момент времени отображает непосредственно прогноз на три дня, для @@ -164,7 +164,7 @@ cd %userprofile%\weather-forecast && .\weather-forecast.exe ### Вызов -Программа может быть вызвана без аргументов - будут применены значения по умолчанию. +Программа может быть вызвана без аргументов — будут применены значения по умолчанию. Порядок аргументов не имеет значения. #### Аргументы командной строки: @@ -212,7 +212,7 @@ cd %userprofile%\weather-forecast && .\weather-forecast.exe * `F5` или `r` - обновление данных. * `+` - увеличение количества отображаемых дней на единицу, но не более 15. * `-` - уменьшение количества отображаемых дней на единицу, но не менее 3. - При этом в том случае, если фокус направлен на последни день, происходит + При этом в том случае, если фокус направлен на последний день, происходит смещение фокуса вверх. * `w` или `ArrowUp` - смещение фокуса отображения вверх (меньшая дата) на единицу. * `s` или `ArrowDown` - смещение фокуса отображения вверх (меньшая дата) на единицу. diff --git a/docs/README.md b/docs/README.md index f581a4e..efb10b6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,11 +1,11 @@ # Документация проекта -Данный документ - сборник документации по проекту Weather Forecast. Здесь -будут все ссылки на документацию. +Данный документ — сборник документации по проекту Weather Forecast. +Здесь будут все ссылки на документацию. ## Документация разработки * [Проблема](dev/problem.md) * [Требования](dev/requirements.md) * [Архитектура](dev/architecture.md) -* [Модуль ArgParser, v1.1.0](https://github.com/bialger/ArgParser/blob/v1.1.0/lib/argparser/docs/README.md) +* [Модуль ArgParser, v1.3.5](https://github.com/bialger/ArgParser/blob/v1.3.5/lib/argparser/docs/README.md) diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 9a2c5a8..0274f7b 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -1,16 +1,16 @@ # Архитектура продукта -В этом документе описывается архитектура продукта - парсера аргументов командной +В этом документе описывается архитектура продукта — парсера аргументов командной строки, разработанная на основании [требований](requirements.md). ## Системная архитектура -Продукт состоит из нескольких связанных подсистем. Несколько из них - внешние -библиотеки. +Продукт состоит из нескольких связанных подсистем. +Несколько из них — внешние библиотеки. * Используется модуль UI для взаимодействия с пользовательским вводом любого рода и вывода информации. -* Используется модуль [ArgParser](https://github.com/bialger/ArgParser/tree/v1.1.0) - подсистема +* Используется модуль [ArgParser](https://github.com/bialger/ArgParser/tree/v1.3.5) — подсистема для обработки аргументов командной строки. * Используется модуль Forecast для выполнения и обработки запросов прогноза. * Используется библиотека [C++ Requests](https://github.com/libcpr/cpr) для выполнения HTTP-запросов. @@ -70,8 +70,6 @@ classDiagram +string kProgramName$ +CompositeString kDefaultConfigPath$ +CompositeString kDefaultLogPath$ - -string kIntervalDescription$ - -string kDaysCountDescription$ -ostream& out_ -istream& in_ -ConditionalOutput error_output_ diff --git a/docs/dev/requirements.md b/docs/dev/requirements.md index 4c2bc36..a2bbedf 100644 --- a/docs/dev/requirements.md +++ b/docs/dev/requirements.md @@ -1,6 +1,6 @@ # Требования к продукту -В этом документе описаны требования к продукту - приложению для показа прогноза +В этом документе описаны требования к продукту — приложению для показа прогноза погоды в консоли. ## Функциональные требования @@ -15,14 +15,14 @@ ### Требования к формату выходных данных -* Выходные данные - периодически меняющееся ASCII-изображение в терминале, +* Выходные данные — периодически меняющееся ASCII-изображение в терминале, отображающее прогноз погоды. ### Требования к функциональности продукта * Приложение должно корректно отображать прогноз погоды для выбранного города. * Приложение должно иметь визуально приятный интерфейс. -* Приложение должно иметь "оффлайн-режим" - при отсутствии Интернет-соединения +* Приложение должно иметь "оффлайн-режим" — при отсутствии Интернет-соединения программа не должна завершаться с ошибкой. * Отображать прогноз погоды на несколько дней вперед (значение по умолчанию задается конфигом) * Обновлять с некоторой частотой (задается конфигом) diff --git a/lib/argparser/CMakeLists.txt b/lib/argparser/CMakeLists.txt index a56b413..304f4c1 100644 --- a/lib/argparser/CMakeLists.txt +++ b/lib/argparser/CMakeLists.txt @@ -1,3 +1,3 @@ include(FetchContent) -FetchContent_Declare(argparser GIT_REPOSITORY https://github.com/bialger/ArgParser GIT_TAG v1.1.0) +FetchContent_Declare(argparser GIT_REPOSITORY https://github.com/bialger/ArgParser GIT_TAG v1.3.5) FetchContent_MakeAvailable(argparser) diff --git a/lib/forecast/Forecaster.cpp b/lib/forecast/Forecaster.cpp index 330bb50..c31fd3d 100644 --- a/lib/forecast/Forecaster.cpp +++ b/lib/forecast/Forecaster.cpp @@ -1,4 +1,5 @@ #include +#include #include "Forecaster.hpp" @@ -7,17 +8,17 @@ Forecaster::Forecaster(int32_t days_count, int32_t location_index, const std::vector& locations, - const std::string& api_key, + std::string api_key, const std::string& config_dir, ConditionalOutput error_output, - ConditionalOutput log_output) : geocoder_cache_("geocoder", config_dir), + ConditionalOutput log_output) : locations_(locations), days_count_(days_count), location_index_(location_index), - locations_(locations), - api_key_(api_key), + api_key_(std::move(api_key)), + current_weather_("Now"), + geocoder_cache_("geocoder", config_dir), error_output_(error_output), - log_output_(log_output), - current_weather_("Now") { + log_output_(log_output) { forecast_ = std::vector(WeatherDay::kDaysInForecast); } @@ -236,7 +237,7 @@ int32_t Forecaster::ProcessPosition(const json& answer) { WriteCurrentTime(log_output_); log_output_ << "FORECASTER: Processing position\n"; - int32_t result = geocoder_.SetCoordinates(answer); + const int32_t result = geocoder_.SetCoordinates(answer); if (result != 0) { DisplayError("Unknown error occurred while geocoding.\n", error_output_); @@ -256,7 +257,7 @@ int32_t Forecaster::ProcessPosition(const json& answer) { int32_t Forecaster::ProcessForecast(const json& answer) { for (int32_t day_number = 0; day_number < WeatherDay::kDaysInForecast; ++day_number) { - bool result = forecast_[day_number].SetForecast(answer, day_number); + const bool result = forecast_[day_number].SetForecast(answer, day_number); if (!result) { geocoder_cache_.PutJsonToCache("error", answer); diff --git a/lib/forecast/Forecaster.hpp b/lib/forecast/Forecaster.hpp index 3d1356c..eb556d9 100644 --- a/lib/forecast/Forecaster.hpp +++ b/lib/forecast/Forecaster.hpp @@ -15,7 +15,7 @@ class Forecaster { Forecaster(int32_t days_count, int32_t location_index, const std::vector& locations, - const std::string& api_key, + std::string api_key, const std::string& config_dir, ConditionalOutput error_output, ConditionalOutput log_output); diff --git a/lib/forecast/Geocoder.cpp b/lib/forecast/Geocoder.cpp index 5fef9f9..13f3983 100644 --- a/lib/forecast/Geocoder.cpp +++ b/lib/forecast/Geocoder.cpp @@ -17,7 +17,7 @@ int32_t Geocoder::SetCoordinates(const json& geocode) { return 1; } - std::vector positions = Split( + const std::vector positions = Split( geocode["response"]["GeoObjectCollection"]["featureMember"][0]["GeoObject"]["Point"]["pos"].get() ); diff --git a/lib/forecast/JsonCache.cpp b/lib/forecast/JsonCache.cpp index 1e80fc8..c4e1126 100644 --- a/lib/forecast/JsonCache.cpp +++ b/lib/forecast/JsonCache.cpp @@ -1,8 +1,6 @@ #include #include "JsonCache.hpp" -#include "lib/utils/utils.hpp" -#include "lib/utils/utils.hpp" const json JsonCache::kNotFound = "{}"; @@ -15,12 +13,12 @@ JsonCache::JsonCache(const std::string& cache_group, const std::string& common_c } } -void JsonCache::PutJsonToCache(const std::string& cache_name, const json& data) { +void JsonCache::PutJsonToCache(const std::string& cache_name, const json& data) const { std::ofstream cache_file(GetCacheFilename(cache_name).c_str()); cache_file << data; } -json JsonCache::GetJsonFromCache(const std::string& cache_name) { +json JsonCache::GetJsonFromCache(const std::string& cache_name) const { if (!std::filesystem::is_regular_file(GetCacheFilename(cache_name))) { return kNotFound; } @@ -30,6 +28,6 @@ json JsonCache::GetJsonFromCache(const std::string& cache_name) { return data; } -std::string JsonCache::GetCacheFilename(const std::string& cache_name) { +std::string JsonCache::GetCacheFilename(const std::string& cache_name) const { return cache_dir_ + "/" + cache_name + ".json"; } diff --git a/lib/forecast/JsonCache.hpp b/lib/forecast/JsonCache.hpp index 3286c4c..3c464f9 100644 --- a/lib/forecast/JsonCache.hpp +++ b/lib/forecast/JsonCache.hpp @@ -12,16 +12,16 @@ class JsonCache { static const json kNotFound; JsonCache() = delete; - JsonCache(const std::string& cache_group, const std::string& cache_dir); + JsonCache(const std::string& cache_group, const std::string& common_cache_dir); - void PutJsonToCache(const std::string& cache_name, const json& data); - json GetJsonFromCache(const std::string& cache_name); + void PutJsonToCache(const std::string& cache_name, const json& data) const; + json GetJsonFromCache(const std::string& cache_name) const; private: std::string cache_group_; std::string cache_dir_; - std::string GetCacheFilename(const std::string& cache_name); + std::string GetCacheFilename(const std::string& cache_name) const; }; #endif //JSONCACHE_HPP_ diff --git a/lib/forecast/WeatherDay.cpp b/lib/forecast/WeatherDay.cpp index 044daf1..2ed3ed1 100644 --- a/lib/forecast/WeatherDay.cpp +++ b/lib/forecast/WeatherDay.cpp @@ -62,7 +62,7 @@ WeatherTimeUnit WeatherDay::GetCurrentWeather(const json& forecast) { return time_unit; } - int32_t hour = std::stoi(current[kOpenMeteoNames.time].get().substr(11, 2)); + const int32_t hour = std::stoi(current[kOpenMeteoNames.time].get().substr(11, 2)); if (hour < 0 || hour >= kHoursInDay) { return time_unit; @@ -89,11 +89,11 @@ bool WeatherDay::SetForecast(const json& forecast, int32_t day_number) { } date_ = daily_values_[kOpenMeteoNames.dates][day_number].get(); - double uv_index = daily_values_[kOpenMeteoNames.uv_index][day_number].get(); + const double uv_index = daily_values_[kOpenMeteoNames.uv_index][day_number].get(); const size_t start_index = day_number * kHoursInDay; const size_t end_index = start_index + kHoursInDay - 1; - const size_t kHoursInUnit = kHoursInDay / kUnitsInDay; + constexpr size_t kHoursInUnit = kHoursInDay / kUnitsInDay; for (size_t unit_index = 0; unit_index < kUnitsInDay; ++unit_index) { WeatherTimeUnit& unit = units_[unit_index]; @@ -110,7 +110,7 @@ bool WeatherDay::SetForecast(const json& forecast, int32_t day_number) { double humidity_summa = 0; for (size_t i = 0; i < kHoursInUnit; ++i) { - size_t element_index = start_index + unit_index * kHoursInUnit + i; + const size_t element_index = start_index + unit_index * kHoursInUnit + i; if (element_index > end_index) { return false; diff --git a/lib/forecast/WeatherTimeUnit.cpp b/lib/forecast/WeatherTimeUnit.cpp index a0e1433..98976af 100644 --- a/lib/forecast/WeatherTimeUnit.cpp +++ b/lib/forecast/WeatherTimeUnit.cpp @@ -1,5 +1,7 @@ #include "WeatherTimeUnit.hpp" +#include + const std::map WeatherTimeUnit::kWeatherCodeToString = { {0, "Clear sky"}, {1, "Mainly clear"}, @@ -55,7 +57,7 @@ const std::map WeatherTimeUnit::kChargeUnits = { {kShownNames.humidity, "%"} }; -WeatherTimeUnit::WeatherTimeUnit(const std::string& name) : name_(name) {} +WeatherTimeUnit::WeatherTimeUnit(std::string name) : name_(std::move(name)) {} std::map WeatherTimeUnit::GetAllAsMap() const { std::map result; @@ -71,13 +73,13 @@ std::map WeatherTimeUnit::GetAllAsMap() const { uv_level = "Very high"; } - std::string str_visibility = std::string(128, '\0'); + auto str_visibility = std::string(128, '\0'); std::snprintf(str_visibility.data(), str_visibility.size(), "%.2f", visibility); - std::string str_pressure = std::string(128, '\0'); + auto str_pressure = std::string(128, '\0'); std::snprintf(str_pressure.data(), str_pressure.size(), "%.1f", pressure); - std::string str_uv_index = std::string(128, '\0'); + auto str_uv_index = std::string(128, '\0'); std::snprintf(str_uv_index.data(), str_uv_index.size(), "%.2f", uv_index); - std::string str_precipitation = std::string(128, '\0'); + auto str_precipitation = std::string(128, '\0'); std::snprintf(str_precipitation.data(), str_precipitation.size(), "%.1f", precipitation); result[kShownNames.weather_code] = kWeatherCodeToString.at(weather_type); diff --git a/lib/forecast/WeatherTimeUnit.hpp b/lib/forecast/WeatherTimeUnit.hpp index 816bbf8..3f87177 100644 --- a/lib/forecast/WeatherTimeUnit.hpp +++ b/lib/forecast/WeatherTimeUnit.hpp @@ -38,7 +38,7 @@ class WeatherTimeUnit { double uv_index{}; int32_t humidity{}; - explicit WeatherTimeUnit(const std::string& name); + explicit WeatherTimeUnit(std::string name); [[nodiscard]] std::map GetAllAsMap() const; [[nodiscard]] std::string GetName() const; diff --git a/lib/ui/ConfigParser.cpp b/lib/ui/ConfigParser.cpp index 1bc1bb2..cf96a67 100644 --- a/lib/ui/ConfigParser.cpp +++ b/lib/ui/ConfigParser.cpp @@ -5,6 +5,7 @@ #include "ConfigParser.hpp" #include "lib/utils/utils.hpp" +#include "lib/forecast/Forecaster.hpp" const std::string ConfigParser::kDefaultLocation = "First location in config"; @@ -32,9 +33,9 @@ bool ConfigParser::IsValidConfig(const std::string& str_config) { return false; } - json api_key_file = config["api_key_file"]; - json locations = config["locations"]; - json defaults = config["defaults"]; + const json& api_key_file = config["api_key_file"]; + const json& locations = config["locations"]; + const json& defaults = config["defaults"]; if (!api_key_file.is_string() || !locations.is_array() || !defaults.is_object()) { return false; @@ -55,10 +56,10 @@ int32_t ConfigParser::ParseConfig() { return 1; } - json config = json::parse(str_config); - json api_key_file = config["api_key_file"]; - json locations = config["locations"]; - json defaults = config["defaults"]; + const json config = json::parse(str_config); + const json& api_key_file = config["api_key_file"]; + const json& locations = config["locations"]; + const json& defaults = config["defaults"]; std::string api_key_path = config_dir_ + "/" + api_key_file.get(); @@ -67,7 +68,7 @@ int32_t ConfigParser::ParseConfig() { return 1; } - int32_t result = SetApiKey(api_key_path); + const int32_t result = SetApiKey(api_key_path); if (result != 0) { DisplayError("Invalid API key!\n", error_output_); @@ -86,7 +87,7 @@ int32_t ConfigParser::ParseConfig() { days_count_ = defaults["days_count"].get(); } - size_t listed_locations_size = locations.size(); + const size_t listed_locations_size = locations.size(); interval_ = std::clamp(interval_, kLowerLimitIntervalSize + 1, kUpperLimitIntervalSize - 1); days_count_ = std::clamp(days_count_, Forecaster::kLowerLimitDaysCount + 1, Forecaster::kUpperLimitDaysCount - 1); location_index_ = std::clamp(location_index_, 0, static_cast(listed_locations_size + locations_.size() - 1)); @@ -100,15 +101,15 @@ int32_t ConfigParser::ParseConfig() { int32_t ConfigParser::SetApiKey(const std::string& api_key_path) { std::ifstream api_key_file_(api_key_path); - std::string api_key = std::string((std::istreambuf_iterator(api_key_file_)), - std::istreambuf_iterator()); + auto api_key = std::string((std::istreambuf_iterator(api_key_file_)), + std::istreambuf_iterator()); if (api_key.size() < 36) { return 1; } api_key = api_key.substr(0, 36); - std::regex yandex_api_regex = std::regex(R"(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)"); + const auto yandex_api_regex = std::regex(R"(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)"); if (!std::regex_match(api_key, yandex_api_regex)) { return 1; diff --git a/lib/ui/ConfigParser.hpp b/lib/ui/ConfigParser.hpp index f48da62..260e190 100644 --- a/lib/ui/ConfigParser.hpp +++ b/lib/ui/ConfigParser.hpp @@ -6,7 +6,6 @@ #include #include "lib/utils/utils.hpp" -#include "lib/forecast/Forecaster.hpp" #include diff --git a/lib/ui/TextUserInterface.cpp b/lib/ui/TextUserInterface.cpp index 0ded59c..912d00e 100644 --- a/lib/ui/TextUserInterface.cpp +++ b/lib/ui/TextUserInterface.cpp @@ -7,61 +7,56 @@ #include "ConfigParser.hpp" const std::string TextUserInterface::kProgramName = "weather-forecast"; -const std::string TextUserInterface::kVersion = "1.3.5 Ferdinando II de' Medici"; -const std::string TextUserInterface::kHelpText = "A program for displaying the weather forecast in the terminal.\nCurrent version is " + kVersion + "."; +const std::string TextUserInterface::kVersion = "1.3.6 Ferdinando II de' Medici"; const CompositeString TextUserInterface::kDefaultConfigPath = "default_config.json"; const CompositeString TextUserInterface::kDefaultLogPath = "Print to standard output"; -const std::string TextUserInterface::kIntervalDescription = - "Initial time between weather forecast updates in hours. Should be more than " - + std::to_string(ConfigParser::kLowerLimitIntervalSize) + " and less than " - + std::to_string(ConfigParser::kUpperLimitIntervalSize) - + ". The default value means using the parameter from the configuration file."; -const std::string TextUserInterface::kDaysCountDescription = - "Initial number of days for which the forecast is shown. Should be more than " - + std::to_string(Forecaster::kLowerLimitDaysCount) + " and less than " - + std::to_string(Forecaster::kUpperLimitDaysCount) - + ". The default value means using the parameter from the configuration file."; TextUserInterface::TextUserInterface(std::ostream& out, std::ostream& err, std::istream& in) - : out_(out), in_(in), error_output_{err, true}, parser_(kProgramName.c_str()) { + : out_(out), in_(in), error_output_{err, true}, parser_(kProgramName + " " + kVersion) { config_path_ = kDefaultConfigPath; - std::function IsGoodIntervalLength = [&](std::string& str_size) -> bool { - int32_t block_size = std::stoi(str_size); + const std::function IsGoodIntervalLength = [&](const std::string& str_size) -> bool { + const int32_t block_size = std::stoi(str_size); return block_size > ConfigParser::kLowerLimitIntervalSize && block_size < ConfigParser::kUpperLimitIntervalSize; }; - std::function IsGoodDaysCount = [&](std::string& str_count) -> bool { - int32_t days_count = std::stoi(str_count); + const std::function IsGoodDaysCount = [&](const std::string& str_count) -> bool { + const int32_t days_count = std::stoi(str_count); return days_count > Forecaster::kLowerLimitDaysCount && days_count < Forecaster::kUpperLimitDaysCount; }; - std::function IsNotADirectory = [&](std::string& potential_filename) -> bool { + const std::function IsNotADirectory = [&](std::string& potential_filename) -> bool { return !IsDirectory(potential_filename); }; + const std::string interval_description = "Initial time between weather forecast updates in hours. Should be more than " + + std::to_string(ConfigParser::kLowerLimitIntervalSize) + " and less than " + + std::to_string(ConfigParser::kUpperLimitIntervalSize) + + ". The default value means using the parameter from the configuration file."; + + const std::string days_count_description = "Initial number of days for which the forecast is shown. Should be more than " + + std::to_string(Forecaster::kLowerLimitDaysCount) + " and less than " + + std::to_string(Forecaster::kUpperLimitDaysCount) + + ". The default value means using the parameter from the configuration file."; + parser_.AddCompositeArgument('c', "config", "Path to the configuration file").Default(kDefaultConfigPath) .StoreValue(config_path_).AddValidate(&IsValidFilename).AddIsGood(&IsRegularFile); parser_.AddCompositeArgument('L', "log-file", "Path to the log file. If not specified, prints to standard output") .Default(kDefaultLogPath).AddValidate(&IsValidFilename).AddIsGood(IsNotADirectory); parser_.AddStringArgument('l', "location", "Name of the location for which the forecast is requested") .Default(ConfigParser::kDefaultLocation); - parser_.AddIntArgument('i', - "interval", - kIntervalDescription.c_str()) + parser_.AddIntArgument('i', "interval", interval_description) .Default(ConfigParser::kLowerLimitIntervalSize).AddIsGood(IsGoodIntervalLength); - parser_.AddIntArgument('d', - "days-count", - kDaysCountDescription.c_str()) + parser_.AddIntArgument('d', "days-count", days_count_description) .Default(Forecaster::kLowerLimitDaysCount).AddIsGood(IsGoodDaysCount); - parser_.AddFlag('v', "verbose", "Print additional information to the terminal."); - parser_.AddHelp('h', "help", kHelpText.c_str()); + parser_.AddFlag('v', "verbose", "Show additional information."); + parser_.AddHelp('h', "help", "A program for displaying the weather forecast in the terminal."); } int32_t TextUserInterface::Start(const std::vector& args) { const std::vector potential_config_dirs = GetPotentialConfigDirectories(); - if (!parser_.Parse(args, error_output_)) { + if (!parser_.Parse(args, {error_output_.out_stream, error_output_.print_messages})) { out_ << parser_.HelpDescription(); return 1; } @@ -180,7 +175,7 @@ int32_t TextUserInterface::Start(const std::vector& args) { } int32_t TextUserInterface::BeginForecast(const ConfigParser& config) { - ConditionalOutput background_errors{background_errors_, true}; + const ConditionalOutput background_errors{background_errors_, true}; ConditionalOutput background_logs{background_logs_, parser_.GetFlag("verbose")}; WriteCurrentTime(background_logs); @@ -233,12 +228,12 @@ std::vector TextUserInterface::GetPotentialConfigDirectories() { if (IsWindows()) { try { - std::string home_path_cmd = std::getenv("userprofile"); + const std::string home_path_cmd = std::getenv("userprofile"); potential_config_dirs.push_back(home_path_cmd + "/.config/weather-forecast"); } catch (const std::exception&) {} try { - std::string home_path_shell = std::getenv("HOME"); + const std::string home_path_shell = std::getenv("HOME"); potential_config_dirs.push_back(home_path_shell + "/.config/weather-forecast"); } catch (const std::exception&) {} } else { diff --git a/lib/ui/TextUserInterface.hpp b/lib/ui/TextUserInterface.hpp index ed37376..85ed16f 100644 --- a/lib/ui/TextUserInterface.hpp +++ b/lib/ui/TextUserInterface.hpp @@ -1,7 +1,6 @@ #ifndef TEXTUSERINTERFACE_HPP_ #define TEXTUSERINTERFACE_HPP_ -#include #include #include #include @@ -17,7 +16,6 @@ class TextUserInterface { public: static const std::string kProgramName; static const std::string kVersion; - static const std::string kHelpText; static const CompositeString kDefaultConfigPath; static const CompositeString kDefaultLogPath; @@ -29,9 +27,6 @@ class TextUserInterface { static std::vector GetPotentialConfigDirectories(); private: - static const std::string kIntervalDescription; - static const std::string kDaysCountDescription; - std::ostream& out_; std::istream& in_; ConditionalOutput error_output_; diff --git a/lib/ui/TuiWorker.cpp b/lib/ui/TuiWorker.cpp index 7619680..b44efb7 100644 --- a/lib/ui/TuiWorker.cpp +++ b/lib/ui/TuiWorker.cpp @@ -37,8 +37,8 @@ TuiWorker::TuiWorker(Forecaster& forecaster, int32_t interval) : forecaster_(forecaster), error_output_(forecaster.GetErrorOutput()), log_output_(forecaster.GetLogOutput()), - screen_(ftxui::ScreenInteractive::Fullscreen()), interval_(interval), + screen_(ftxui::ScreenInteractive::Fullscreen()), start_focus_(0), result_(0) { RefreshElements(); @@ -61,7 +61,7 @@ int32_t TuiWorker::Run() { return yframe(vbox(elements_)); }); - component_renderer |= ftxui::CatchEvent([this](ftxui::Event event) -> bool { + component_renderer |= ftxui::CatchEvent([this](const ftxui::Event& event) -> bool { return HandleEvent(event); }); @@ -74,7 +74,7 @@ int32_t TuiWorker::Run() { return result_; } -bool TuiWorker::HandleEvent(ftxui::Event event) { +bool TuiWorker::HandleEvent(const ftxui::Event& event) { if (event == ftxui::Event::Character('+')) { result_ = ReloadScreen(Action::kAddLine); } else if (event == ftxui::Event::Character('-')) { @@ -123,7 +123,7 @@ int32_t TuiWorker::ReloadScreen(Action action) { WriteCurrentTime(log_output_); log_output_ << "TUI: Reloading screen with action "; - int32_t result; + int32_t result = 0; switch (action) { case Action::kAddLine: { @@ -160,10 +160,6 @@ int32_t TuiWorker::ReloadScreen(Action action) { result = forecaster_.ObtainForecast(); - if (result != 0) { - return result; - } - break; } case Action::kNext: { @@ -171,10 +167,6 @@ int32_t TuiWorker::ReloadScreen(Action action) { result = forecaster_.SwapToNext(); - if (result != 0) { - return result; - } - break; } case Action::kPrev: { @@ -182,10 +174,6 @@ int32_t TuiWorker::ReloadScreen(Action action) { result = forecaster_.SwapToPrev(); - if (result != 0) { - return result; - } - break; } } @@ -209,7 +197,7 @@ void TuiWorker::RefreshElements() { elements_.clear(); elements_.push_back(GetCurrentUnit()); - std::vector forecast = forecaster_.GetForecast(); + const std::vector forecast = forecaster_.GetForecast(); WriteCurrentTime(log_output_); log_output_ << "TUI: Forecast size: " << forecast.size() << "\n"; @@ -234,7 +222,7 @@ void TuiWorker::FocusNext() { WriteCurrentTime(log_output_); log_output_ << "TUI: Focus switched to the next element from " << start_focus_; - size_t forecast_days = forecaster_.GetForecast().size(); + const size_t forecast_days = forecaster_.GetForecast().size(); if (start_focus_ + kFocusLen < forecast_days) { ++start_focus_; @@ -269,10 +257,13 @@ ftxui::Element TuiWorker::GetUnit(const WeatherTimeUnit& unit, std::string heade std::map output_unit = unit.GetAllAsMap(); std::vector lines{}; header = header.empty() ? unit.GetName() : header; - int32_t width = header.empty() ? kMaxWidth : (2 * kMaxWidth); + const int32_t width = header.empty() ? kMaxWidth : (2 * kMaxWidth); for (const auto& [name, value] : output_unit) { - lines.push_back(ftxui::text(name + ": " + value)); + std::string label_name = name; + label_name.append(": "); + label_name.append(value); + lines.push_back(ftxui::text(label_name)); } return ftxui::vbox({ftxui::hbox({ftxui::filler(), ftxui::text(header), ftxui::filler()}), ftxui::separator(), @@ -293,6 +284,6 @@ ftxui::Element TuiWorker::GetDay(const WeatherDay& day) { | size(ftxui::WIDTH, ftxui::EQUAL, kMaxWidth * 4);; } -ftxui::Element TuiWorker::GetCurrentUnit() { +ftxui::Element TuiWorker::GetCurrentUnit() const { return GetUnit(forecaster_.GetCurrentWeather(), forecaster_.GetLocation() + ", " + forecaster_.GetLastForecastTime()); } diff --git a/lib/ui/TuiWorker.hpp b/lib/ui/TuiWorker.hpp index 32b4e8f..aa4557b 100644 --- a/lib/ui/TuiWorker.hpp +++ b/lib/ui/TuiWorker.hpp @@ -24,9 +24,9 @@ class TuiWorker { int32_t Run(); private: - static const int32_t kMaxWidth = 45; - static const int32_t kMaxHeight = 12; - static const int32_t kFocusLen = 3; + static constexpr int32_t kMaxWidth = 45; + static constexpr int32_t kMaxHeight = 12; + static constexpr int32_t kFocusLen = 3; Forecaster& forecaster_; ConditionalOutput error_output_; ConditionalOutput log_output_; @@ -37,7 +37,7 @@ class TuiWorker { std::mutex mutex_; int32_t result_; - bool HandleEvent(ftxui::Event event); + bool HandleEvent(const ftxui::Event& event); void RefreshElements(); void RedrawScreen(); @@ -49,7 +49,7 @@ class TuiWorker { static ftxui::Element GetWeatherIcon(int32_t weather_code); static ftxui::Element GetUnit(const WeatherTimeUnit& unit, std::string header = ""); static ftxui::Element GetDay(const WeatherDay& day); - ftxui::Element GetCurrentUnit(); + ftxui::Element GetCurrentUnit() const; }; #endif //TUIWORKER_HPP_ diff --git a/lib/utils/utils.cpp b/lib/utils/utils.cpp index 025dd48..98808b5 100644 --- a/lib/utils/utils.cpp +++ b/lib/utils/utils.cpp @@ -38,7 +38,7 @@ void DisplayError(const std::string& message, ConditionalOutput error_output) { return; } - bool is_console_output = &error_output.out_stream == &std::cout || &error_output.out_stream == &std::cerr; + const bool is_console_output = &error_output.out_stream == &std::cout || &error_output.out_stream == &std::cerr; if (is_console_output) { SetRedColor(); @@ -78,9 +78,9 @@ bool IsValidFilename(std::string& pre_filename) { if (IsWindows() && pre_filename.size() > 2) { for (uint64_t position = 2; position < pre_filename.size(); ++position) { - char current = pre_filename[position]; + const char current = pre_filename[position]; if (!(std::isalnum(current) || current == '\\' || current == '.' || - current == '-' || current == ' ' || current == '_')) { + current == '-' || current == ' ' || current == '_')) { return false; } } @@ -88,11 +88,11 @@ bool IsValidFilename(std::string& pre_filename) { /* This Windows-specific check is important because different code pages can * corrupt non-alphanumeric filenames, but UNIX-like systems (like MacOS or - * Linux) handle unicode correctly. */ + * Linux) handle Unicode correctly. */ for (uint64_t position = 0; position < pre_filename.size() - 1; ++position) { - char current = pre_filename[position]; - char next = pre_filename[position + 1]; + const char current = pre_filename[position]; + const char next = pre_filename[position + 1]; if ((current == '\\' || current == '/') && next == current) { return false; @@ -103,20 +103,20 @@ bool IsValidFilename(std::string& pre_filename) { } bool IsRegularFile(std::string& filename) { - std::filesystem::path path(filename); + const std::filesystem::path path(filename); return std::filesystem::is_regular_file(path); } bool IsDirectory(std::string& dirname) { - std::filesystem::path path(dirname); + const std::filesystem::path path(dirname); return std::filesystem::is_directory(path); } std::streamsize GetFileSize(const std::string& filename) { std::ifstream file(filename, std::ios::in | std::ios::binary); file.ignore(std::numeric_limits::max()); - std::streamsize length = file.gcount(); - file.clear(); // Since ignore will have set eof. + const std::streamsize length = file.gcount(); + file.clear(); // Since ignore will have set eof. file.seekg(0, std::ios_base::beg); return length; } @@ -124,7 +124,7 @@ std::streamsize GetFileSize(const std::string& filename) { std::string GetStringFromFile(const std::string& filename) { std::ifstream ifs(filename.c_str(), std::ios::in | std::ios::binary); - std::streamsize file_size = GetFileSize(filename); + const std::streamsize file_size = GetFileSize(filename); std::vector bytes(file_size); ifs.read(bytes.data(), file_size); @@ -154,29 +154,16 @@ void WriteCurrentTime(ConditionalOutput& target) { return; } - auto now = std::chrono::system_clock::now(); - std::time_t now_c = std::chrono::system_clock::to_time_t(now); - std::tm* parts = std::localtime(&now_c); + const auto now = std::chrono::system_clock::now(); + const std::time_t now_c = std::chrono::system_clock::to_time_t(now); + const std::tm* parts = std::localtime(&now_c); - std::ios_base::fmtflags oldflags = target.out_stream.flags(); - std::streamsize oldprecision = target.out_stream.precision(); + const std::ios_base::fmtflags old_flags = target.out_stream.flags(); + const std::streamsize old_precision = target.out_stream.precision(); target << std::put_time(parts, "%Y-%m-%d %H:%M:%S.") << std::setfill('0') << std::setw(3) - << (std::chrono::duration_cast(now.time_since_epoch()).count() % 1000) << " "; + << (std::chrono::duration_cast(now.time_since_epoch()).count() % 1000) << " "; - target.out_stream.flags(oldflags); - target.out_stream.precision(oldprecision); + target.out_stream.flags(old_flags); + target.out_stream.precision(old_precision); } - -/* The code provides dummy function definitions for Windows console-related - * functions when the code is being compiled in a non-Windows environment. - * This ensures that the code can compile and run without errors in such - * environments. The dummy functions have minimal functionality and simply - * return their input parameters. */ - -#if not(defined _WIN32 || defined _WIN64 || defined __CYGWIN__) -int SetConsoleOutputCP(int a) { return a; } -int SetConsoleCP(int a) { return a; } -int GetStdHandle(int a) { return a; } -int SetConsoleTextAttribute(int a, int b) { return a + b; } -#endif \ No newline at end of file diff --git a/lib/utils/utils.hpp b/lib/utils/utils.hpp index a0704fc..3ddb5af 100644 --- a/lib/utils/utils.hpp +++ b/lib/utils/utils.hpp @@ -2,7 +2,6 @@ #define UTILS_HPP_ #include -#include #include #include #include @@ -19,13 +18,19 @@ #include #endif #else +/* The code provides fake function definitions for Windows console-related + * functions when the code is being compiled in a non-Windows environment. + * This ensures that the code can be compiled and run without errors in such + * environments. + * The fake functions have minimal functionality and simply return their + * input parameters. */ #define CP_UTF8 0 #define STD_OUTPUT_HANDLE 0 #define HANDLE int -inline int SetConsoleOutputCP(int a); -inline int SetConsoleCP(int a); -inline int GetStdHandle(int a); -inline int SetConsoleTextAttribute(int a, int b); +inline int SetConsoleOutputCP(int a) { return a; } +inline int SetConsoleCP(int a) { return a; } +inline int GetStdHandle(int a) { return a; } +inline int SetConsoleTextAttribute(int a, int b) { return a + b; } #endif /**\n This function sets the console text color to red. */ @@ -36,7 +41,7 @@ void SetRedColor(); void ResetColor(); -/**\n This function prints a error message. */ +/**\n This function prints an error message. */ void DisplayError(const std::string& message, ConditionalOutput error_output); @@ -79,11 +84,11 @@ void WriteStringToStream(const std::string& content, std::ostream& target); std::vector Split(const std::string& str); -/**\n This function returns average value of the std::vector. */ +/**\n This function returns the average value of the std::vector. */ template U CountAverage(const std::vector& values) { - static_assert(std::is_arithmetic>::value, + static_assert(std::is_arithmetic_v>, "Can only average arithmetic values"); return static_cast(std::accumulate(values.begin(), values.end(), static_cast(0)) / values.size()); } @@ -92,10 +97,10 @@ U CountAverage(const std::vector& values) { template::value_type> T MostCommon(InputIt begin, InputIt end) { - std::map counts; + std::map counts; for (InputIt it = begin; it != end; ++it) { - if (counts.find(*it) != counts.end()) { + if (counts.contains(*it)) { ++counts[*it]; } else { counts[*it] = 1; @@ -112,7 +117,7 @@ T MostCommon(InputIt begin, InputIt end) { std::string GetStringCurrentTime(); -/**\n This function writes the current time to conditional stream. */ +/**\n This function writes the current time to a conditional stream. */ void WriteCurrentTime(ConditionalOutput& target); diff --git a/tests/WeatherForecastIntegrationTestSuite.cpp b/tests/WeatherForecastIntegrationTestSuite.cpp index fb171f7..13c5501 100644 --- a/tests/WeatherForecastIntegrationTestSuite.cpp +++ b/tests/WeatherForecastIntegrationTestSuite.cpp @@ -1,8 +1,7 @@ #include "WeatherForecastIntegrationTestSuite.hpp" -const std::string WeatherForecastIntegrationTestSuite::kExpectedHelp = "weather-forecast\n" +const std::string WeatherForecastIntegrationTestSuite::kExpectedHelp = "weather-forecast 1.3.6 Ferdinando II de' Medici\n" "A program for displaying the weather forecast in the terminal.\n" - "Current version is 1.3.5 Ferdinando II de' Medici.\n" "\n" "OPTIONS:\n" "-l, --location=: Name of the location for which the forecast is requested [default = First location in config]\n" @@ -14,7 +13,7 @@ const std::string WeatherForecastIntegrationTestSuite::kExpectedHelp = "weather- "-i, --interval=: Initial time between weather forecast updates in hours. " "Should be more than 0 and less than 49. The default value means using the parameter " "from the configuration file. [default = 0]\n" - "-v, --verbose: Print additional information to the terminal.\n" + "-v, --verbose: Show additional information.\n" "\n" "-h, --help: Display this help and exit\n";