diff --git a/cpp/src/helper.h b/cpp/src/helper.h index 684a72e..b49013e 100644 --- a/cpp/src/helper.h +++ b/cpp/src/helper.h @@ -1,6 +1,7 @@ #pragma once #include +#include std::string trim(std::string s); diff --git a/solutions/cpp/solution1/CMakeLists.txt b/solutions/cpp/solution1/CMakeLists.txt new file mode 100644 index 0000000..4be08d0 --- /dev/null +++ b/solutions/cpp/solution1/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.14) + +set(CMAKE_CXX_STANDARD 11) + +project(youtube) + +include(FetchContent) +FetchContent_Declare( + googletest + # Specify the commit you depend on and update it regularly. + URL https://github.com/google/googletest/archive/53495a2a7d6ba7e0691a7f3602e9a5324bba6e45.zip +) + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) +include(GoogleTest) + +if(MSVC) + add_compile_options(/Wall) +else() + add_compile_options( + -Wall + -Wextra + -pedantic + # Below is to avoid unnecessary warnings before code is implemented. + -Wno-unused-parameter) +endif() + +file(COPY src/videos.txt DESTINATION src/) + +add_library(youtube_lib + src/commandparser.cpp + src/commandparser.h + src/helper.cpp + src/helper.h + src/video.cpp + src/video.h + src/videolibrary.cpp + src/videolibrary.h + src/videoplayer.cpp + src/videoplayer.h + src/videoplaylist.h + src/videoplaylist.cpp) + +add_executable(youtube src/main.cpp) +target_link_libraries(youtube youtube_lib) + +enable_testing() + +add_executable(part1_test test/part1_test.cpp) +target_link_libraries(part1_test youtube_lib gmock gtest gtest_main) +gtest_discover_tests(part1_test) + +add_executable(part2_test test/part2_test.cpp) +target_link_libraries(part2_test youtube_lib gmock gtest gtest_main) +gtest_discover_tests(part2_test) + +add_executable(part3_test test/part3_test.cpp) +target_link_libraries(part3_test youtube_lib gmock gtest gtest_main) +gtest_discover_tests(part3_test) + +add_executable(part4_test test/part4_test.cpp) +target_link_libraries(part4_test youtube_lib gmock gtest gtest_main) +gtest_discover_tests(part4_test) + +add_executable(videolibrary_test test/videolibrary_test.cpp) +target_link_libraries(videolibrary_test youtube_lib gmock gtest gtest_main) +gtest_discover_tests(videolibrary_test) diff --git a/solutions/cpp/solution1/README.md b/solutions/cpp/solution1/README.md new file mode 100644 index 0000000..f4b24b5 --- /dev/null +++ b/solutions/cpp/solution1/README.md @@ -0,0 +1,97 @@ +# YouTube Challenge - C++ + +The C++ YouTube Challenge uses C++ 11, CMake and [GTest](https://google.github.io/googletest/). + +NOTE: **Please do not edit videos.txt as it will cause tests to break. There is no need to modify this file to complete this challenge.** + +## Installing C++ and CMake +You need to install: + +- [CMake 3.14+](https://cmake.org/install/) and a build tool like GNU make or Ninja. +- A compiler that supports C++ 11 or higher ([clang](https://clang.llvm.org/get_started.html), gcc/g++, MSVC). + +On Debian-based Linux distributions you can install the dependencies with this +command: + +```shell +sudo apt install cmake make clang +``` + +On Windows, you can install the [Microsoft Visual Studio Community +Edition](https://visualstudio.microsoft.com/downloads/) for the compiler and +build tools. Make sure to choose to install CMake as part of this. + +On a Mac, you need to go to the App Store and download Xcode for free - it is Apple's IDE - Integrated Development Environment. Without Xcode, you will have no compiler (i.e. clang or gcc or g++) and no build tools, (i.e. make). +After you've installed Xcode you need to install command line tools: + +```shell +xcode-select --install +``` + +For CMake: + +- Download the latest CMake Mac binary distribution here: https://cmake.org/download/. + +- Double click the downloaded .dmg file to install it. In the window that pops up, drag the CMake icon into the Application folder. + +- Add this line to your .bashrc file: PATH="/Applications/CMake.app/Contents/bin":"$PATH" + +- Reload your .bashrc file: source ~/.bashrc + +- Verify the latest cmake version is installed: cmake --version + +> Note: You can use a higher version of C++ (C++ 17 or C++ 20) if you want: +> Just change the `CMAKE_CXX_STANDARD` in `CMakeLists.txt`! + +## Setting up + +You can write code in the editor or IDE of your choice. However, different +editors have different ways of dealing with C++ code, so in case of doubt we +recommend you run the code and tests from the command line as shown below. + +The below commands assume you are located in the `cpp/` folder of the git +repository. + +## Running and Testing from the Command line + +To build: + +```shell script +cmake -S . -B build +``` + +Followed by: + +```shell script +cmake --build build/ +``` + +To run: + +```shell script +./build/youtube +``` + +To run all the tests: + +```shell script +ctest --test-dir build --output-on-failure +``` + +To run tests for a single Part (after building): + +```shell script +./build/part1_test +./build/part2_test +./build/part3_test +./build/part4_test +``` + +To run a subset of the tests, in this example, all tests of part 1 that contain +`showAllVideos` in their name: + +```shell script +./build/part1_test --gtest_filter='*showAllVideos*' +``` + +> NOTE: Don't forget to rebuild your code ater making changes for testing. diff --git a/solutions/cpp/solution1/src/commandparser.cpp b/solutions/cpp/solution1/src/commandparser.cpp new file mode 100644 index 0000000..4e079e1 --- /dev/null +++ b/solutions/cpp/solution1/src/commandparser.cpp @@ -0,0 +1,158 @@ +#include "commandparser.h" + +#include +#include +#include +#include + +CommandParser::CommandParser(VideoPlayer&& vp) : mVideoPlayer(std::move(vp)) {} + +void CommandParser::executeCommand(const std::vector& command) { + if (command.empty()) { + std::cout << "No commands passed in to executeCommand, that is unexpected" + << std::endl; + return; + } + + if (command[0] == "NUMBER_OF_VIDEOS") { + mVideoPlayer.numberOfVideos(); + } else if (command[0] == "SHOW_ALL_VIDEOS") { + mVideoPlayer.showAllVideos(); + } else if (command[0] == "PLAY") { + if (command.size() != 2) { + std::cout << "Please enter PLAY command followed by video_id." + << std::endl; + } else { + mVideoPlayer.playVideo(command[1]); + } + } else if (command[0] == "PLAY_RANDOM") { + mVideoPlayer.playRandomVideo(); + } else if (command[0] == "STOP") { + mVideoPlayer.stopVideo(); + } else if (command[0] == "PAUSE") { + mVideoPlayer.pauseVideo(); + } else if (command[0] == "CONTINUE") { + mVideoPlayer.continueVideo(); + } else if (command[0] == "SHOW_PLAYING") { + mVideoPlayer.showPlaying(); + } else if (command[0] == "CREATE_PLAYLIST") { + if (command.size() != 2) { + std::cout << "Please enter CREATE_PLAYLIST command followed by video_id." + << std::endl; + } else { + mVideoPlayer.createPlaylist(command[1]); + } + } else if (command[0] == "ADD_TO_PLAYLIST") { + if (command.size() != 3) { + std::cout << "Please enter ADD_TO_PLAYLIST command followed by playlist " + "name and video_id." + << std::endl; + } else { + mVideoPlayer.addVideoToPlaylist(command[1], command[2]); + } + } else if (command[0] == "REMOVE_FROM_PLAYLIST") { + if (command.size() != 3) { + std::cout << "Please enter REMOVE_FROM_PLAYLIST command followed by " + "playlist name and video_id." + << std::endl; + } else { + mVideoPlayer.removeFromPlaylist(command[1], command[2]); + } + } else if (command[0] == "CLEAR_PLAYLIST") { + if (command.size() != 2) { + std::cout + << "Please enter CLEAR_PLAYLIST command followed by a playlist name." + << std::endl; + } else { + mVideoPlayer.clearPlaylist(command[1]); + } + } else if (command[0] == "DELETE_PLAYLIST") { + if (command.size() != 2) { + std::cout + << "Please enter DELETE_PLAYLIST command followed by a playlist name." + << std::endl; + } else { + mVideoPlayer.deletePlaylist(command[1]); + } + } else if (command[0] == "SHOW_PLAYLIST") { + if (command.size() != 2) { + std::cout + << "Please enter SHOW_PLAYLIST command followed by a playlist name." + << std::endl; + } else { + mVideoPlayer.showPlaylist(command[1]); + } + } else if (command[0] == "SHOW_ALL_PLAYLISTS") { + mVideoPlayer.showAllPlaylists(); + } else if (command[0] == "SEARCH_VIDEOS") { + if (command.size() != 2) { + std::cout + << "Please enter SEARCH_VIDEOS command followed by a search term." + << std::endl; + } else { + mVideoPlayer.searchVideos(command[1]); + } + } else if (command[0] == "SEARCH_VIDEOS_WITH_TAG") { + if (command.size() != 2) { + std::cout << "Please enter SEARCH_VIDEOS_WITH_TAG command followed by a " + "video tag." + << std::endl; + } else { + mVideoPlayer.searchVideosWithTag(command[1]); + } + } else if (command[0] == "FLAG_VIDEO") { + switch (command.size()) { + case 2: + mVideoPlayer.flagVideo(command[1]); + break; + case 3: + mVideoPlayer.flagVideo(command[1], command[2]); + break; + default: + std::cout << "Please enter FLAG_VIDEO command followed by a video_id " + "and an optional flag reason." + << std::endl; + } + } else if (command[0] == "ALLOW_VIDEO") { + if (command.size() != 2) { + std::cout << "Please enter ALLOW_VIDEO command followed by a video_id." + << std::endl; + } else { + mVideoPlayer.allowVideo(command[1]); + } + } else if (command[0] == "HELP") { + getHelp(); + } else { + std::cout << "Please enter a valid command, type HELP for a list of " + "available commands." + << std::endl; + } +} + +void CommandParser::getHelp() const { + static const char* const helpText = R"( +Available commands: + NUMBER_OF_VIDEOS - Shows how many videos are in the library. + SHOW_ALL_VIDEOS - Lists all videos from the library. + PLAY - Plays specified video. + PLAY_RANDOM - Plays a random video from the library. + STOP - Stop the current video. + PAUSE - Pause the current video. + CONTINUE - Resume the current paused video. + SHOW_PLAYING - Displays the title, url and paused status of the video that is currently playing (or paused). + CREATE_PLAYLIST - Creates a new (empty) playlist with the provided name. + ADD_TO_PLAYLIST - Adds the requested video to the playlist. + REMOVE_FROM_PLAYLIST - Removes the specified video from the specified playlist + CLEAR_PLAYLIST - Removes all the videos from the playlist. + DELETE_PLAYLIST - Deletes the playlist. + SHOW_PLAYLIST - List all the videos in this playlist. + SHOW_ALL_PLAYLISTS - Display all the available playlists. + SEARCH_VIDEOS - Display all the videos whose titles contain the search_term. + SEARCH_VIDEOS_WITH_TAG -Display all videos whose tags contains the provided tag. + FLAG_VIDEO - Mark a video as flagged. + ALLOW_VIDEO - Removes a flag from a video. + HELP - Displays help. + EXIT - Terminates the program execution. +)"; + std::cout << helpText << std::endl; +} diff --git a/solutions/cpp/solution1/src/commandparser.h b/solutions/cpp/solution1/src/commandparser.h new file mode 100644 index 0000000..3016d66 --- /dev/null +++ b/solutions/cpp/solution1/src/commandparser.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "videoplayer.h" + +/** + * A class used to parse and execute a user Command. + */ +class CommandParser { + private: + VideoPlayer mVideoPlayer; + + void getHelp() const; + + public: + CommandParser(VideoPlayer&& vp); + + // This class is not copyable to avoid expensive copies. + CommandParser(const CommandParser&) = delete; + CommandParser& operator=(const CommandParser&) = delete; + + // This class is movable. + CommandParser(CommandParser&&) = default; + CommandParser& operator=(CommandParser&&) = default; + + // Executes the given user command. + void executeCommand(const std::vector& command); +}; diff --git a/solutions/cpp/solution1/src/helper.cpp b/solutions/cpp/solution1/src/helper.cpp new file mode 100644 index 0000000..cf93f9e --- /dev/null +++ b/solutions/cpp/solution1/src/helper.cpp @@ -0,0 +1,48 @@ +#include "helper.h" + +#include +#include +#include + +std::string trim(std::string toTrim) { + size_t trimPos = toTrim.find_first_not_of(" \t"); + toTrim.erase(0, trimPos); + trimPos = toTrim.find_last_not_of(" \t"); + if (std::string::npos != trimPos) { + toTrim.erase(trimPos + 1); + } + return toTrim; +} + +std::vector splitlines(std::string output) { + std::vector commandOutput; + std::stringstream ss(output); + std::string line; + while (std::getline(ss, line, '\n')) { + commandOutput.emplace_back(line); + } + return commandOutput; +} + +std::string toLower(const std::string& str) { + std::string output; + for (const auto& ch : str) { + output += (char)(tolower(ch)); + } + return output; +} + +bool contains(const std::string& s1, const std::string& s2) { + std::string s1_lower = toLower(s1); + std::string s2_lower = toLower(s2); + return s1_lower.find(s2_lower) != std::string::npos; +} + +bool isNumber(const std::string& str) { + for (const char ch : str) { + if (!isdigit(ch)) { + return false; + } + } + return true; +} diff --git a/solutions/cpp/solution1/src/helper.h b/solutions/cpp/solution1/src/helper.h new file mode 100644 index 0000000..0db382d --- /dev/null +++ b/solutions/cpp/solution1/src/helper.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +// Removes spaces and tabs from start and end string. +std::string trim(std::string s); + +// Splits string at newlines into a vector of string. +std::vector splitlines(std::string output); + +// Converts a string to lowercase. +std::string toLower(const std::string& str); + +// Checks if string s1 contains string s2. +bool contains(const std::string& s1, const std::string& s2); + +// Checks if a string can be converted into a valid number. +// This is done by checking that each character in string is a digit. +bool isNumber(const std::string& str); \ No newline at end of file diff --git a/solutions/cpp/solution1/src/main.cpp b/solutions/cpp/solution1/src/main.cpp new file mode 100644 index 0000000..2de3781 --- /dev/null +++ b/solutions/cpp/solution1/src/main.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "commandparser.h" +#include "helper.h" +#include "videolibrary.h" +#include "videoplayer.h" + +int main() { + std::cout << "Hello and welcome to YouTube, what would you like to do? " + "Enter HELP for list of available commands or EXIT to terminate." + << std::endl; + + std::string userInput; + std::string command; + std::vector commandList; + VideoPlayer vp; + CommandParser cp = CommandParser(std::move(vp)); + + for (;;) { + std::cout << "YT> "; + if (!std::getline(std::cin, userInput)) { + break; + } + if (userInput.empty()) { + std::cout << "Please enter a valid command, type HELP for a list of " + "available commands." + << std::endl; + } else { + std::stringstream test(userInput); + while (std::getline(test, command, ' ')) { + command = trim(command); + commandList.push_back(command); + } + std::transform(commandList[0].begin(), commandList[0].end(), + commandList[0].begin(), + [](char c) { return static_cast(std::toupper(c)); }); + if (commandList[0] == "EXIT") { + break; + } + cp.executeCommand(commandList); + commandList.clear(); + } + } + std::cout + << "YouTube has now terminated it's execution. Thank you and goodbye!" + << std::endl; +} diff --git a/solutions/cpp/solution1/src/video.cpp b/solutions/cpp/solution1/src/video.cpp new file mode 100644 index 0000000..c28ec58 --- /dev/null +++ b/solutions/cpp/solution1/src/video.cpp @@ -0,0 +1,27 @@ +#include "video.h" + +#include +#include +#include + +Video::Video(std::string&& title, std::string&& videoId, + std::vector&& tags) : + mTitle(std::move(title)), + mVideoId(std::move(videoId)), + mTags(std::move(tags)) { +} + +const std::string& Video::getTitle() const { return mTitle; } + +const std::string& Video::getVideoId() const { return mVideoId; } + +const std::vector& Video::getTags() const { return mTags; } + +const std::string Video::getTagsAsStr() const { + std::string tag_str = ""; + for (const auto& tag : mTags) { + tag_str = tag_str + tag + " "; + } + tag_str = tag_str.substr(0, tag_str.size() - 1); + return tag_str; +} diff --git a/solutions/cpp/solution1/src/video.h b/solutions/cpp/solution1/src/video.h new file mode 100644 index 0000000..dd1a8f7 --- /dev/null +++ b/solutions/cpp/solution1/src/video.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +/** + * A class used to represent a video. + */ +class Video { + private: + std::string mTitle; + std::string mVideoId; + std::vector mTags; + + public: + Video(std::string&& title, std::string&& videoId, + std::vector&& tags); + + // Returns the title of the video. + const std::string& getTitle() const; + + // Returns the video id of the video. + const std::string& getVideoId() const; + + // Returns a readonly collection of the tags of the video. + const std::vector& getTags() const; + + // Returns the tags of video joined as a string separated by spaces. + const std::string getTagsAsStr() const; +}; diff --git a/solutions/cpp/solution1/src/videolibrary.cpp b/solutions/cpp/solution1/src/videolibrary.cpp new file mode 100644 index 0000000..0136762 --- /dev/null +++ b/solutions/cpp/solution1/src/videolibrary.cpp @@ -0,0 +1,148 @@ +#include "videolibrary.h" + +#include +#include +#include +#include +#include +#include + +#include "helper.h" +#include "video.h" + +VideoLibrary::VideoLibrary() { + std::ifstream file("./src/videos.txt"); + if (file.is_open()) { + std::string line; + while (std::getline(file, line)) { + std::stringstream linestream(line); + std::string title; + std::string id; + std::string tag; + std::vector tags; + std::getline(linestream, title, '|'); + std::getline(linestream, id, '|'); + while (std::getline(linestream, tag, ',')) { + tags.emplace_back(trim(std::move(tag))); + } + Video video = Video(trim(std::move(title)), trim(id), std::move(tags)); + mVideos.emplace(trim(std::move(id)), std::move(video)); + } + } else { + std::cout << "Couldn't find videos.txt" << std::endl; + } +} + +std::vector