From 1e1f1d32fd972ba910e23e08370392264a75ff04 Mon Sep 17 00:00:00 2001 From: djmil Date: Sun, 26 May 2024 17:59:17 +0200 Subject: [PATCH] first prototype --- .gitignore | 5 ++ .vscode/launch.json | 58 ++++++++++++++++++++++ CMakeLists.txt | 33 +++++++++++++ LICENSE | 26 ++++++++++ README.md | 23 ++++++++- addressbook/CMakeLists.txt | 37 ++++++++++++++ addressbook/addressbook.proto | 27 ++++++++++ addressbook/test.cpp | 31 ++++++++++++ denv/Dockerfile | 22 +++++++++ denv/build.sh | 4 ++ denv/run-vsc.sh | 17 +++++++ reader.cpp | 66 +++++++++++++++++++++++++ writer.cpp | 93 +++++++++++++++++++++++++++++++++++ 13 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 addressbook/CMakeLists.txt create mode 100644 addressbook/addressbook.proto create mode 100644 addressbook/test.cpp create mode 100644 denv/Dockerfile create mode 100755 denv/build.sh create mode 100755 denv/run-vsc.sh create mode 100644 reader.cpp create mode 100644 writer.cpp diff --git a/.gitignore b/.gitignore index cf584b2..369d80c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,9 @@ install_manifest.txt compile_commands.json CTestTestfile.cmake _deps +build/ + +# ---> Obsidian +.obsidian/ + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bc3f327 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,58 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "writer", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/writer", + "args": ["pb1"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, + + { + "name": "reader", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/reader", + "args": ["pb1"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..59f8d1a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_TOOLCHAIN_FILE "/opt/vcpkg/scripts/buildsystems/vcpkg.cmake" + CACHE STRING "Vcpkg toolchain file") + +project(protobook LANGUAGES CXX VERSION 0.0.1) + + +find_package(Catch2 REQUIRED) +# Disable automatic CMake build targets like: ContinuousBuild, Experimental, etc.. +# https://stackoverflow.com/questions/56089330/cmake-creates-lots-of-targets-i-didnt-specify +set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) +include(CTest) +include(Catch) + +#add_subdirectory(testing) +add_subdirectory(addressbook) + +add_executable(writer + writer.cpp + ) +target_link_libraries(writer + PRIVATE + addressbook + ) + +add_executable(reader + reader.cpp + ) +target_link_libraries(reader + PRIVATE + addressbook + ) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ea91ed3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +MIT NON-AI License + +Copyright (c) 2024, Andriy Djmil + +Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions. + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +In addition, the following restrictions apply: + +1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms, +including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives, +modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License. + +2. The Software may not be included in any dataset used for training or improving machine learning algorithms, +including but not limited to artificial intelligence, natural language processing, or data mining. + +3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable +for any damages resulting from such use. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index ce904be..e1b61f8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ -# denv-cpp +Template for C++ based projects +- environment: **Docker** container +- IDE: **VsCode** +- build system: **CMake** +- 3rd party libraries: **vcpkg** +- testing: **Catch2** -C++ project template \ No newline at end of file +# Development Environment + +```bash +./denv/build.sh # create denv image +./denv/run-vsc.sh # run container & attach VsCode instance to it +``` +# CMake and CTests + +CTest integration within VsCode is limited - it does not provide link between test case source and test shown in *TestExplorer* view. + +**Solution** +1. Disable (CMake > CTest: `Test Explorer Integration` Enabled) +2. Install *TestMate* extension + It scans the build tree for xxx_test binaries and has neat integration with Catch2. +3. You need to enable `rebuild on save` in order for tests in *TestExplorer* to automatically reflect changes in the code. If none - use F7 to rebuild project. \ No newline at end of file diff --git a/addressbook/CMakeLists.txt b/addressbook/CMakeLists.txt new file mode 100644 index 0000000..74ffd5e --- /dev/null +++ b/addressbook/CMakeLists.txt @@ -0,0 +1,37 @@ +project(addressbook LANGUAGES CXX VERSION 0.0.1) + +# https://cmake.org/cmake/help/latest/module/FindProtobuf.html +find_package(Protobuf REQUIRED) +include_directories(${Protobuf_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS addressbook.proto) + +add_library(${PROJECT_NAME} + ${PROTO_SRCS} + #${PROTO_HDRS} + ) +target_link_libraries(${PROJECT_NAME} + PRIVATE # private implementation, others can not link with this library + protobuf::libprotobuf + ) +target_include_directories(${PROJECT_NAME} + PUBLIC # Will make these headers availvable to the library itself + # and to any target trying to link agains it + ${CMAKE_CURRENT_BINARY_DIR} + ) + +#if(testing_enabled) + # https://cmake.org/cmake/help/book/mastering-cmake/chapter/Testing%20With%20CMake%20and%20CTest.html + add_executable(${PROJECT_NAME}-test + test.cpp + ) + target_link_libraries(${PROJECT_NAME}-test PRIVATE Catch2::Catch2WithMain) + catch_discover_tests(${PROJECT_NAME}-test) + + add_test( + NAME "Custom-test-script" + #CONFIGURATIONS "systemt" + #COMMAND $:ctest --success + COMMAND echo "testing [OK]" + ) +#endif() diff --git a/addressbook/addressbook.proto b/addressbook/addressbook.proto new file mode 100644 index 0000000..3408747 --- /dev/null +++ b/addressbook/addressbook.proto @@ -0,0 +1,27 @@ +syntax = "proto2"; + +package tutorial; + +message Person { + optional string name = 1; + optional int32 id = 2; + optional string email = 3; + + enum PhoneType { + PHONE_TYPE_UNSPECIFIED = 0; + PHONE_TYPE_MOBILE = 1; + PHONE_TYPE_HOME = 2; + PHONE_TYPE_WORK = 3; + } + + message PhoneNumber { + optional string number = 1; + optional PhoneType type = 2 [default = PHONE_TYPE_HOME]; + } + + repeated PhoneNumber phones = 4; +} + +message AddressBook { + repeated Person people = 1; +} \ No newline at end of file diff --git a/addressbook/test.cpp b/addressbook/test.cpp new file mode 100644 index 0000000..6f647ce --- /dev/null +++ b/addressbook/test.cpp @@ -0,0 +1,31 @@ +#include + +static int Factorial( int number ) { + return number <= 1 ? number : Factorial( number - 1 ) * number; // fail + // return number <= 1 ? 1 : Factorial( number - 1 ) * number; // pass +} + +TEST_CASE( "Factorial of 0 is 1 (fail)", "[single-file]" ) { + REQUIRE( Factorial(0) == 1 ); +} + +TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[single-file]" ) { + REQUIRE( Factorial(1) == 1 ); + REQUIRE( Factorial(2) == 2 ); + REQUIRE( Factorial(3) == 6 ); + REQUIRE( Factorial(10) == 3628800 ); +} + +// Compile & run: +// - g++ -std=c++14 -Wall -I$(CATCH_SINGLE_INCLUDE) -o 010-TestCase 010-TestCase.cpp && 010-TestCase --success +// - cl -EHsc -I%CATCH_SINGLE_INCLUDE% 010-TestCase.cpp && 010-TestCase --success + +// Expected compact output (all assertions): +// +// prompt> 010-TestCase --reporter compact --success +// 010-TestCase.cpp:14: failed: Factorial(0) == 1 for: 0 == 1 +// 010-TestCase.cpp:18: passed: Factorial(1) == 1 for: 1 == 1 +// 010-TestCase.cpp:19: passed: Factorial(2) == 2 for: 2 == 2 +// 010-TestCase.cpp:20: passed: Factorial(3) == 6 for: 6 == 6 +// 010-TestCase.cpp:21: passed: Factorial(10) == 3628800 for: 3628800 (0x375f00) == 3628800 (0x375f00) +// Failed 1 test case, failed 1 assertion. \ No newline at end of file diff --git a/denv/Dockerfile b/denv/Dockerfile new file mode 100644 index 0000000..21588dd --- /dev/null +++ b/denv/Dockerfile @@ -0,0 +1,22 @@ +FROM debian:bullseye-slim + +RUN apt update \ + && apt install -y --no-install-recommends \ + curl ca-certificates \ + tar zip unzip pkg-config \ + git build-essential \ + gdb cmake + +RUN git clone --depth 1 --branch 2024.04.26 https://github.com/Microsoft/vcpkg.git /opt/vcpkg \ + && cd /opt/vcpkg \ + && ./bootstrap-vcpkg.sh \ + && ./vcpkg integrate install \ + && ./vcpkg install catch2 + +RUN apt install -y --no-install-recommends \ + protobuf-compiler libprotobuf-dev \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir /denv-template + +WORKDIR /denv-template + diff --git a/denv/build.sh b/denv/build.sh new file mode 100755 index 0000000..0cdce92 --- /dev/null +++ b/denv/build.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euxo pipefail + +docker build --tag denv-template . \ No newline at end of file diff --git a/denv/run-vsc.sh b/denv/run-vsc.sh new file mode 100755 index 0000000..2ce8c4c --- /dev/null +++ b/denv/run-vsc.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_NAME=denv-template + +docker run \ + --volume $(pwd)/..:/$PROJECT_NAME \ + --tty \ + --detach \ + --name vsc-$PROJECT_NAME \ + $PROJECT_NAME + +YLW='\033[1;33m' +BLU='\033[1;34m' +NC='\033[0m' # No Color + +echo -e "${YLW}Now launch ${BLU}VsCode${YLW}, attach it to the ${BLU}vsc-${PROJECT_NAME}${YLW} Docker container and open ${BLU}/$PROJECT_NAME${YLW} folder as a worksapce${NC}" \ No newline at end of file diff --git a/reader.cpp b/reader.cpp new file mode 100644 index 0000000..457588c --- /dev/null +++ b/reader.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include "addressbook.pb.h" +using namespace std; + +// Iterates though all people in the AddressBook and prints info about them. +void ListPeople(const tutorial::AddressBook& address_book) { + for (int i = 0; i < address_book.people_size(); i++) { + const tutorial::Person& person = address_book.people(i); + + cout << "Person ID: " << person.id() << endl; + cout << " Name: " << person.name() << endl; + if (person.has_email()) { + cout << " E-mail address: " << person.email() << endl; + } + + for (int j = 0; j < person.phones_size(); j++) { + const tutorial::Person::PhoneNumber& phone_number = person.phones(j); + + switch (phone_number.type()) { + case tutorial::Person::PHONE_TYPE_MOBILE: + cout << " Mobile phone #: "; + break; + case tutorial::Person::PHONE_TYPE_HOME: + cout << " Home phone #: "; + break; + case tutorial::Person::PHONE_TYPE_WORK: + cout << " Work phone #: "; + break; + } + cout << phone_number.number() << endl; + } + } +} + +// Main function: Reads the entire address book from a file and prints all +// the information inside. +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (argc != 2) { + cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; + return -1; + } + + tutorial::AddressBook address_book; + + { + // Read the existing address book. + fstream input(argv[1], ios::in | ios::binary); + if (!address_book.ParseFromIstream(&input)) { + cerr << "Failed to parse address book." << endl; + return -1; + } + } + + ListPeople(address_book); + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +} \ No newline at end of file diff --git a/writer.cpp b/writer.cpp new file mode 100644 index 0000000..c4f1e6e --- /dev/null +++ b/writer.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include "addressbook.pb.h" +using namespace std; + +// This function fills in a Person message based on user input. +void PromptForAddress(tutorial::Person* person) { + cout << "Enter person ID number: "; + int id; + cin >> id; + person->set_id(id); + cin.ignore(256, '\n'); + + cout << "Enter name: "; + getline(cin, *person->mutable_name()); + + cout << "Enter email address (blank for none): "; + string email; + getline(cin, email); + if (!email.empty()) { + person->set_email(email); + } + + while (true) { + cout << "Enter a phone number (or leave blank to finish): "; + string number; + getline(cin, number); + if (number.empty()) { + break; + } + + tutorial::Person::PhoneNumber* phone_number = person->add_phones(); + phone_number->set_number(number); + + cout << "Is this a mobile, home, or work phone? "; + string type; + getline(cin, type); + if (type == "mobile") { + phone_number->set_type(tutorial::Person::PHONE_TYPE_MOBILE); + } else if (type == "home") { + phone_number->set_type(tutorial::Person::PHONE_TYPE_HOME); + } else if (type == "work") { + phone_number->set_type(tutorial::Person::PHONE_TYPE_WORK); + } else { + cout << "Unknown phone type. Using default." << endl; + } + } +} + +// Main function: Reads the entire address book from a file, +// adds one person based on user input, then writes it back out to the same +// file. +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (argc != 2) { + cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; + return -1; + } + + tutorial::AddressBook address_book; + + { + // Read the existing address book. + fstream input(argv[1], ios::in | ios::binary); + if (!input) { + cout << argv[1] << ": File not found. Creating a new file." << endl; + } else if (!address_book.ParseFromIstream(&input)) { + cerr << "Failed to parse address book." << endl; + return -1; + } + } + + // Add an address. + PromptForAddress(address_book.add_people()); + + { + // Write the new address book back to disk. + fstream output(argv[1], ios::out | ios::trunc | ios::binary); + if (!address_book.SerializeToOstream(&output)) { + cerr << "Failed to write address book." << endl; + return -1; + } + } + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +}