diff options
| author | Fuwn <[email protected]> | 2024-05-31 00:00:12 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-05-31 00:20:41 -0700 |
| commit | 968e1c4af014b7f40bfaa4f57fcc0f38e5e0d847 (patch) | |
| tree | df80525ad0cc9e4b561bfb0772c30ccc5b64b4a2 | |
| parent | feat: initial commit (diff) | |
| download | cst_136_assignment_eight-968e1c4af014b7f40bfaa4f57fcc0f38e5e0d847.tar.xz cst_136_assignment_eight-968e1c4af014b7f40bfaa4f57fcc0f38e5e0d847.zip | |
| -rw-r--r-- | Makefile | 24 | ||||
| -rw-r--r-- | Tupfile | 19 | ||||
| -rw-r--r-- | config.mk | 32 | ||||
| -rw-r--r-- | include/book_store/book_count.hh | 30 | ||||
| -rw-r--r-- | include/book_store/customer.hh | 49 | ||||
| -rw-r--r-- | include/book_store/library.hh | 13 | ||||
| -rw-r--r-- | include/book_store/member.hh | 41 | ||||
| -rw-r--r-- | include/book_store/person.hh | 14 | ||||
| -rw-r--r-- | include/book_store/price.hh | 18 | ||||
| -rw-r--r-- | include/book_store/purchase_error.hh | 26 | ||||
| -rw-r--r-- | include/book_store/store.hh | 87 | ||||
| -rw-r--r-- | include/book_store/utility.hh | 12 | ||||
| -rw-r--r-- | source/book.cc | 4 | ||||
| -rw-r--r-- | source/book_count.cc | 7 | ||||
| -rw-r--r-- | source/customer.cc | 29 | ||||
| -rw-r--r-- | source/library.cc | 179 | ||||
| -rw-r--r-- | source/main.cc | 107 | ||||
| -rw-r--r-- | source/member.cc | 23 | ||||
| -rw-r--r-- | source/person.cc | 16 | ||||
| -rw-r--r-- | source/price.cc | 7 | ||||
| -rw-r--r-- | source/purchase_error.cc | 21 | ||||
| -rw-r--r-- | source/random.cc | 32 | ||||
| -rw-r--r-- | source/store.cc | 235 | ||||
| -rw-r--r-- | source/test.cc | 96 | ||||
| -rw-r--r-- | source/utility.cc | 23 |
25 files changed, 928 insertions, 216 deletions
@@ -1,16 +1,6 @@ -SOURCE_DIRECTORY = source -INCLUDE_DIRECTORY = include -BUILD_DIRECTORY = build -CC = clang++ -CC_EXTENSION = cc -CC_FLAGS = -std=c++23 -I $(INCLUDE_DIRECTORY) -Weverything -Wno-padded -Wno-c++98-compat -MMD -CLANG_TIDY_CHECKS = '-*,bugprone-*,clang-analyzer-*,concurrency-*,cppcoreguildelines-*,llvm-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-readability-magic-numbers,-llvm-header-guard' -CLANG_TIDY_FLAGS = -checks=$(CLANG_TIDY_CHECKS) -header-filter=.* -warnings-as-errors=* -system-headers=0 -SOURCES = $(wildcard $(SOURCE_DIRECTORY)/*.$(CC_EXTENSION)) -OBJECTS = $(SOURCES:$(SOURCE_DIRECTORY)/%.$(CC_EXTENSION)=$(BUILD_DIRECTORY)/%.o) -DEPS = $(OBJECTS:.o=.d) - -all: $(BUILD_DIRECTORY)/book_store +include *.mk + +all: $(MAIN_OUTPUT) $(TEST_OUTPUT) format: clang-format -i $(SOURCES) $(wildcard $(INCLUDE_DIRECTORY)/*.hh) @@ -21,7 +11,10 @@ tidy: $(BUILD_DIRECTORY)/%.o: $(SOURCE_DIRECTORY)/%.$(CC_EXTENSION) | $(BUILD_DIRECTORY) $(CC) $(CC_FLAGS) -MF $(@:.o=.d) -c $< -o $@ -$(BUILD_DIRECTORY)/book_store: $(OBJECTS) +$(TEST_OUTPUT): $(TEST_OBJECTS) + $(CC) $^ -o $@ + +$(MAIN_OUTPUT): $(MAIN_OBJECTS) $(CC) $^ -o $@ $(BUILD_DIRECTORY): @@ -30,6 +23,7 @@ $(BUILD_DIRECTORY): clean: rm -rf $(BUILD_DIRECTORY) --include $(DEPS) +-include $(CC_DEPENDENCIES) .PHONY: all format tidy clean + @@ -1,13 +1,22 @@ +# Input & Output Directories SOURCE_DIRECTORY = source INCLUDE_DIRECTORY = include BUILD_DIRECTORY = build + +# Compiler Configuration CC = clang++ CC_EXTENSION = cc -CC_FLAGS = -std=c++23 -I $(INCLUDE_DIRECTORY) -Weverything -Wno-padded -Wno-c++98-compat -MMD -CLANG_TIDY_CHECKS = '-*,bugprone-*,clang-analyzer-*,concurrency-*,cppcoreguildelines-*,llvm-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-readability-magic-numbers,-llvm-header-guard' -CLANG_TIDY_FLAGS = -checks=$(CLANG_TIDY_CHECKS) -header-filter=.* -warnings-as-errors=* -system-headers=0 +CC_FLAGS = -std=c++23 -I $(INCLUDE_DIRECTORY) -Weverything -Wno-padded -Wno-c++98-compat -MMD -fno-diagnostics-show-note-include-stack -Wno-c++98-compat-pedantic + +# Clang-Tidy Configuration +CLANG_TIDY_CHECKS = '-*,bugprone-*,clang-analyzer-*,concurrency-*,cppcoreguildelines-*,llvm-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-readability-magic-numbers,-llvm-header-guard,-bugprone-suspicious-include,-readability-function-cognitive-complexity,-bugprone-exception-escape' +CLANG_TIDY_FLAGS = -checks=$(CLANG_TIDY_CHECKS) -warnings-as-errors=* -quiet -: foreach $(SOURCE_DIRECTORY)/*.$(CC_EXTENSION) $(INCLUDE_DIRECTORY)/*.hh |> clang-format -i %f |> +NAME = book_store + +# : foreach $(SOURCE_DIRECTORY)/*.$(CC_EXTENSION) $(INCLUDE_DIRECTORY)/*.hh |> clang-format -i %f |> : foreach $(SOURCE_DIRECTORY)/*.$(CC_EXTENSION) |> clang-tidy $(CLANG_TIDY_FLAGS) %f -- $(CC_FLAGS) |> : foreach $(SOURCE_DIRECTORY)/*.$(CC_EXTENSION) |> ^j^ $(CC) $(CC_FLAGS) -MF $(BUILD_DIRECTORY)/%B.d -c %f -o %o |> $(BUILD_DIRECTORY)/%B.o | $(BUILD_DIRECTORY)/%B.d -: $(BUILD_DIRECTORY)/*.o |> $(CC) %f -o %o |> $(BUILD_DIRECTORY)/book_store +: $(BUILD_DIRECTORY)/*.o ^test.o |> $(CC) %f -o %o |> $(BUILD_DIRECTORY)/$(NAME) +: $(BUILD_DIRECTORY)/*.o ^main.o |> $(CC) %f -o %o |> $(BUILD_DIRECTORY)/$(NAME)_test + diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..5637879 --- /dev/null +++ b/config.mk @@ -0,0 +1,32 @@ +# Input & Output Directories +SOURCE_DIRECTORY = source +INCLUDE_DIRECTORY = include +BUILD_DIRECTORY = build + +# Clang-Tidy Configuration +CLANG_TIDY_CHECKS = '-*,bugprone-*,clang-analyzer-*,concurrency-*,cppcoreguildelines-*,llvm-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-readability-magic-numbers,-llvm-header-guard' +CLANG_TIDY_FLAGS = -checks=$(CLANG_TIDY_CHECKS) -warnings-as-errors=* -quiet + +# All Sources & Objects +SOURCES = $(wildcard $(SOURCE_DIRECTORY)/*.$(CC_EXTENSION)) +OBJECTS = $(SOURCES:$(SOURCE_DIRECTORY)/%.$(CC_EXTENSION)=$(BUILD_DIRECTORY)/%.o) + +# Main Sources & Objects +MAIN_SOURCES = $(filter-out $(SOURCE_DIRECTORY)/test.cc, $(SOURCES)) +MAIN_OBJECTS = $(MAIN_SOURCES:$(SOURCE_DIRECTORY)/%.$(CC_EXTENSION)=$(BUILD_DIRECTORY)/%.o) + +# Test Sources & Objects +TEST_SOURCES = $(filter-out $(SOURCE_DIRECTORY)/main.cc, $(SOURCES)) +TEST_OBJECTS = $(TEST_SOURCES:$(SOURCE_DIRECTORY)/%.$(CC_EXTENSION)=$(BUILD_DIRECTORY)/%.o) + +# Compiler Configuration +CC = clang++ +CC_EXTENSION = cc +CC_FLAGS = -std=c++23 -I $(INCLUDE_DIRECTORY) -Weverything -Wno-padded -Wno-c++98-compat -MMD -W-noc++98-compat-pedantic +CC_DEPENDENCIES = $(OBJECTS:.o=.d) + +# Output Configuration +NAME = book_store +MAIN_OUTPUT = $(BUILD_DIRECTORY)/$(NAME) +TEST_OUTPUT = $(BUILD_DIRECTORY)/$(NAME)_test + diff --git a/include/book_store/book_count.hh b/include/book_store/book_count.hh index a9c68c1..29e599b 100644 --- a/include/book_store/book_count.hh +++ b/include/book_store/book_count.hh @@ -2,7 +2,6 @@ #define BOOK_COUNT_HH #include <ostream> -#include <string> namespace book_store::product { class book_count { @@ -16,8 +15,10 @@ public: book_count() = default; book_count(book_count_type count) : _count(count) {} - friend auto operator<<(std::ostream &output_stream, const book_count &price) - -> std::ostream & { + [[nodiscard]] auto value() const noexcept -> book_count_type; + + friend auto operator<<(std::ostream &output_stream, + const book_count &price) -> std::ostream & { output_stream << price._count; return output_stream; @@ -27,10 +28,29 @@ public: return lhs._count == rhs._count; } - friend auto operator+(const book_count &lhs, const book_count &rhs) - -> book_count { + friend auto operator+(const book_count &lhs, + const book_count &rhs) -> book_count { return {lhs._count + rhs._count}; } + + friend auto operator<(const book_count &lhs, const book_count &rhs) -> bool { + return lhs._count < rhs._count; + } + + friend auto operator-(const book_count &lhs, + const book_count &rhs) -> book_count { + return {lhs._count - rhs._count}; + } + + friend auto operator%(const book_count &lhs, + const book_count &rhs) -> book_count { + return {lhs._count % rhs._count}; + } + + friend auto operator*(const book_count &lhs, + const book_count &rhs) -> book_count { + return {lhs._count * rhs._count}; + } }; } // namespace book_store::product diff --git a/include/book_store/customer.hh b/include/book_store/customer.hh new file mode 100644 index 0000000..86f3df6 --- /dev/null +++ b/include/book_store/customer.hh @@ -0,0 +1,49 @@ +#ifndef MEMBER_HH +#define MEMBER_HH + +#include <cstddef> + +#include "book.hh" +#include "person.hh" +#include "price.hh" + +namespace book_store::consumer { +class customer : public person { + +private: + product::book::book::size_type _books_bought; + product::price::usd _amount_spent; + bool _is_member; + +public: + customer(std::string first_name, std::string last_name, std::size_t member_id) + : person(std::move(first_name), std::move(last_name), member_id), + _books_bought(0), _amount_spent(0) {} + customer(std::string first_name, std::string last_name, std::size_t member_id, + bool is_member) + : person(std::move(first_name), std::move(last_name), member_id), + _is_member(is_member) {} + customer(std::string first_name, std::string last_name, std::size_t member_id, + bool is_member, product::book::size_type books_bought, + product::price::usd amount_spent) + : person(std::move(first_name), std::move(last_name), member_id), + _books_bought(books_bought), _amount_spent(amount_spent), + _is_member(is_member) {} + customer() = default; + customer(const customer &) = default; + customer(customer &&) = default; + + [[nodiscard]] auto books_bought() const noexcept -> product::book::size_type; + [[nodiscard]] auto amount_spent() const noexcept -> product::price::usd; + [[nodiscard]] auto is_member() const noexcept -> bool; + + auto books_bought(product::book::size_type books_bought) noexcept -> void; + auto amount_spent(product::price::usd amount_spent) noexcept -> void; + auto is_member(bool is_member) noexcept -> void; + + auto operator=(const customer &) -> customer & = default; + auto operator=(customer &&) -> customer & = default; +}; +} // namespace book_store::consumer + +#endif // MEMBER_HH diff --git a/include/book_store/library.hh b/include/book_store/library.hh new file mode 100644 index 0000000..9044bb9 --- /dev/null +++ b/include/book_store/library.hh @@ -0,0 +1,13 @@ +#ifndef PRIMARY_HH +#define PRIMARY_HH + +#include "book_store/store.hh" + +namespace book_store::library { +auto purchase_handler(book_store::store &store) -> void; +auto populate_books(book_store::store &store, std::size_t library_size) -> void; +auto populate_consumers(book_store::store &store) -> void; +auto manage(book_store::store &store) -> void; +} // namespace book_store::library + +#endif // PRIMARY_HH diff --git a/include/book_store/member.hh b/include/book_store/member.hh deleted file mode 100644 index 0796a11..0000000 --- a/include/book_store/member.hh +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef MEMBER_HH -#define MEMBER_HH - -#include <cstddef> - -#include "book.hh" -#include "person.hh" -#include "price.hh" - -namespace book_store::consumer { -class member : person { - -private: - product::book::book::size_type _books_bought; - product::price::usd _amount_spent; - -public: - member(std::string last_name, std::string first_name, std::size_t member_id) - : person(std::move(last_name), std::move(first_name), member_id), - _books_bought(0), _amount_spent(0) {} - member(std::string last_name, std::string first_name, std::size_t member_id, - product::book::size_type books_baught, - product::price::usd amount_spent) - : person(std::move(last_name), std::move(first_name), member_id), - _books_bought(books_baught), _amount_spent(amount_spent) {} - member() = default; - member(const member &) = default; - member(member &&) = default; - - [[nodiscard]] auto books_bought() const noexcept -> product::book::size_type; - [[nodiscard]] auto amount_spent() const noexcept -> product::price::usd; - - auto books_bought(product::book::size_type books_bought) noexcept -> void; - auto amount_spent(product::price::usd amount_spent) noexcept -> void; - - auto operator=(const member &) -> member & = default; - auto operator=(member &&) -> member & = default; -}; -} // namespace book_store::consumer - -#endif // MEMBER_HH diff --git a/include/book_store/person.hh b/include/book_store/person.hh index 46b9d59..729001a 100644 --- a/include/book_store/person.hh +++ b/include/book_store/person.hh @@ -6,26 +6,26 @@ namespace book_store::consumer { class person { private: - std::string _last_name; std::string _first_name; + std::string _last_name; std::size_t _id; public: - person(std::string last_name, std::string first_name, std::size_t person_id) - : _last_name(std::move(last_name)), _first_name(std::move(first_name)), + person(std::string first_name, std::string last_name, std::size_t person_id) + : _first_name(std::move(first_name)), _last_name(std::move(last_name)), _id(person_id) {} person() = default; person(const person &) = default; person(person &&) = default; - [[nodiscard]] auto last_name() const noexcept -> std::string_view; [[nodiscard]] auto first_name() const noexcept -> std::string_view; - [[nodiscard]] auto full_name(bool last_first = false) const noexcept - -> std::string; + [[nodiscard]] auto last_name() const noexcept -> std::string_view; + [[nodiscard]] auto + full_name(bool last_first = false) const noexcept -> std::string; [[nodiscard]] auto id() const noexcept -> std::size_t; - auto last_name(std::string_view last_name) noexcept -> person &; auto first_name(std::string_view first_name) noexcept -> person &; + auto last_name(std::string_view last_name) noexcept -> person &; auto id(std::size_t person_id) noexcept -> person &; auto operator=(const person &) -> person & = default; diff --git a/include/book_store/price.hh b/include/book_store/price.hh index 39f14b8..223b1e9 100644 --- a/include/book_store/price.hh +++ b/include/book_store/price.hh @@ -17,14 +17,16 @@ public: usd(price_type price) : _price(price) {} usd(const std::string &price) : _price(std::stod(price)) {} + [[nodiscard]] auto value() const noexcept -> price_type; + auto operator=(const std::string &value) -> usd & { _price = std::stod(value); return *this; } - friend auto operator<<(std::ostream &output_stream, const usd &price) - -> std::ostream & { + friend auto operator<<(std::ostream &output_stream, + const usd &price) -> std::ostream & { output_stream << price._price; return output_stream; @@ -33,6 +35,18 @@ public: friend auto operator==(const usd &lhs, const usd &rhs) -> bool { return std::abs(lhs._price - rhs._price) < 0.0001; } + + friend auto operator+(const usd &lhs, const usd &rhs) -> usd { + return {lhs._price + rhs._price}; + } + + friend auto operator*(const usd &lhs, const usd &rhs) -> bool { + return std::abs(lhs._price * rhs._price) < 0.0001; + } + + friend auto operator+=(const usd &lhs, const usd &rhs) -> bool { + return std::abs(lhs._price + rhs._price) < 0.0001; + } }; } // namespace book_store::product::price diff --git a/include/book_store/purchase_error.hh b/include/book_store/purchase_error.hh new file mode 100644 index 0000000..718ca46 --- /dev/null +++ b/include/book_store/purchase_error.hh @@ -0,0 +1,26 @@ +#ifndef PURCHASE_ERROR_HH +#define PURCHASE_ERROR_HH + +#include <string_view> + +namespace book_store { +class purchase_error { +public: + enum purchase_error_type { + person_not_found, + book_not_found, + not_enough_stock, + }; + +private: + purchase_error_type _error; + +public: + purchase_error(purchase_error_type error) : _error(error) {} + + [[nodiscard]] auto error() const noexcept -> purchase_error_type; + [[nodiscard]] auto what() const noexcept -> std::string_view; +}; +} // namespace book_store + +#endif // PURCHASE_ERROR_HH diff --git a/include/book_store/store.hh b/include/book_store/store.hh new file mode 100644 index 0000000..29c72db --- /dev/null +++ b/include/book_store/store.hh @@ -0,0 +1,87 @@ +#ifndef BOOK_STORE_HH +#define BOOK_STORE_HH + +#include <cctype> +#include <cstddef> +#include <functional> +#include <optional> +#include <string_view> +#include <utility> +#include <vector> + +#include <book_store/book.hh> +#include <book_store/book_count.hh> +#include <book_store/customer.hh> +#include <book_store/price.hh> +#include <book_store/purchase_error.hh> + +namespace book_store { +class store { +private: + using books_type = std::vector<product::book>; + using customer_type = consumer::customer; + using customer_container = std::vector<customer_type>; + // An `std::unordered_map` would be more appropriate here, but + // here we are. While we aren't using a proper database, we can + // take these liberties. + using transactions_type = std::vector<std::pair<std::size_t, product::book>>; + using discount_percent_type = double; + using size_type = std::size_t; + + size_type _books_max_size; + size_type _customers_max_size; + books_type _books; + customer_container _customers; + transactions_type _transactions; + product::price::usd _membership_fee; + discount_percent_type _membership_discount_percent; + +public: + store() = default; + store(size_type books_max_size, size_type customers_max_size) + : _books_max_size(books_max_size), + _customers_max_size(customers_max_size) {} + store(size_type books_max_size, size_type customers_max_size, + product::price::usd membership_fee, + discount_percent_type membership_discount_percent) + : _books_max_size(books_max_size), + _customers_max_size(customers_max_size), + _membership_fee(membership_fee), + _membership_discount_percent(membership_discount_percent) {} + store(const store &) = default; + store(store &&) = default; + + auto append_book(const product::book &book) -> void; + auto append_customer(const customer_type &person) -> void; + auto + purchase_book(size_type person_id, std::string_view isbn, + product::book_count count) -> std::optional<purchase_error>; + auto tick_year() -> void; + auto membership_fee(product::price::usd fee) -> void; + auto + membership_discount_percent(discount_percent_type discount_percent) -> void; + + [[nodiscard]] auto books_size() const noexcept -> size_type; + [[nodiscard]] auto people_size() const noexcept -> size_type; + [[nodiscard]] auto books_max_size() const noexcept -> size_type; + [[nodiscard]] auto people_max_size() const noexcept -> size_type; + [[nodiscard]] auto membership_fee() const noexcept -> product::price::usd; + [[nodiscard]] auto + membership_discount_percent() const noexcept -> discount_percent_type; + auto find_person_by_id(size_type person_id) -> std::optional<customer_type>; + auto find_book_by_isbn(std::string_view isbn) + -> std::optional<std::reference_wrapper<product::book>>; + auto + find_books_by_title(std::string_view title) -> std::vector<product::book>; + auto + find_books_by_author(std::string_view name) -> std::vector<product::book>; + auto transactions_by_id(size_type person_id) -> std::vector<product::book>; + auto books() -> books_type &; + auto people() -> customer_container &; + + auto operator=(const store &) -> store & = default; + auto operator=(store &&) -> store & = default; +}; +} // namespace book_store + +#endif // BOOK_STORE_HH diff --git a/include/book_store/utility.hh b/include/book_store/utility.hh new file mode 100644 index 0000000..3971abc --- /dev/null +++ b/include/book_store/utility.hh @@ -0,0 +1,12 @@ +#ifndef UTILITY_HH +#define UTILITY_HH + +#include <string> +#include <string_view> + +namespace book_store::utility { +auto prompt(std::string_view message) -> std::string; +auto clear_cerr() -> void; +} // namespace book_store::utility + +#endif // UTILITY_HH diff --git a/source/book.cc b/source/book.cc index 6b0bd6a..a2c0a13 100644 --- a/source/book.cc +++ b/source/book.cc @@ -81,8 +81,8 @@ auto book::copies(book::size_type copies) noexcept -> book & { return *this; } -auto operator<<(std::ostream &output_stream, const book &book) - -> std::ostream & { +auto operator<<(std::ostream &output_stream, + const book &book) -> std::ostream & { output_stream << "Title: " << book._title << '\n'; output_stream << "Authors: "; diff --git a/source/book_count.cc b/source/book_count.cc new file mode 100644 index 0000000..7e03acd --- /dev/null +++ b/source/book_count.cc @@ -0,0 +1,7 @@ +#include <book_store/book_count.hh> + +namespace book_store::product { +[[nodiscard]] auto book_count::value() const noexcept -> book_count_type { + return this->_count; +} +} // namespace book_store::product diff --git a/source/customer.cc b/source/customer.cc new file mode 100644 index 0000000..c4bcf97 --- /dev/null +++ b/source/customer.cc @@ -0,0 +1,29 @@ +#include <book_store/book.hh> +#include <book_store/customer.hh> +#include <book_store/price.hh> + +namespace book_store::consumer { +using namespace product; + +auto customer::books_bought() const noexcept -> book::size_type { + return this->_books_bought; +} + +auto customer::amount_spent() const noexcept -> price::usd { + return this->_amount_spent; +} + +auto customer::is_member() const noexcept -> bool { return this->_is_member; } + +auto customer::books_bought(book::size_type books_bought) noexcept -> void { + this->_books_bought = books_bought; +} + +auto customer::amount_spent(price::usd amount_spent) noexcept -> void { + this->_amount_spent = amount_spent; +} + +auto customer::is_member(bool is_member) noexcept -> void { + this->_is_member = is_member; +} +} // namespace book_store::consumer diff --git a/source/library.cc b/source/library.cc new file mode 100644 index 0000000..1ff1f7c --- /dev/null +++ b/source/library.cc @@ -0,0 +1,179 @@ +#include <cstddef> +#include <iostream> +#include <random> +#include <string> + +#include <book_store/book.hh> +#include <book_store/customer.hh> +#include <book_store/library.hh> +#include <book_store/random.hh> +#include <book_store/store.hh> +#include <book_store/utility.hh> + +namespace book_store::library { +auto purchase_handler(book_store::store &store) -> void { + using namespace book_store; + using namespace book_store::utility; + + std::cout << "You may now purchase books. Press enter to exit."; + + for (;;) { + std::string isbn; + std::size_t customer_id; + std::size_t copy_count; + + isbn = + prompt("\n\nEnter the ISBN of the book you would like to purchase: "); + + if (isbn.empty()) { + break; + } + + customer_id = std::stoul(prompt("Enter your ID: ")); + copy_count = + std::stoul(prompt("Enter the number of copies you would like to " + "purchase: ")); + + auto book = store.find_book_by_isbn(isbn); + + if (!book.has_value()) { + clear_cerr(); + + std::cerr << "\nBook not found."; + + continue; + } + + auto person = store.find_person_by_id(customer_id); + + if (!person.has_value()) { + clear_cerr(); + + std::cerr << "\nPerson not found."; + + continue; + } + + auto &book_reference = book.value(); + auto purchase_book = store.purchase_book(customer_id, isbn, copy_count); + + if (purchase_book.has_value()) { + clear_cerr(); + + std::cerr << '\n' << purchase_book.value().what(); + + continue; + } + + std::cout << "\nPurchase successful.\nRemaining copies: " + << book_reference.get().copies(); + } +} + +auto populate_books(book_store::store &store, + std::size_t library_size) -> void { + utility::random::book_random_engine random( + std::mt19937(std::random_device{}())); + + for (std::size_t i = 0; i < library_size; ++i) { + store.append_book(product::book{random.title(), random.authors(), + random.publisher(), random.isbn(), + random.price_usd(), random.copy_count()}); + } +} + +auto populate_consumers(book_store::store &store) -> void { + using namespace book_store; + using namespace book_store::utility; + + std::string first_name; + std::string last_name; + std::size_t person_id; + std::string member_type; + + std::cout << "Enter a person's information. Press enter for the first name " + "field to complete data entry.\n\n"; + + for (;;) { + if (store.people_size() == store.people_max_size()) { + clear_cerr(); + + std::cerr << "Maximum number of people reached.\n"; + + break; + } + + first_name = prompt("First Name: "); + + if (first_name.empty()) { + break; + } + + last_name = prompt("Last Name: "); + person_id = std::stoul(prompt("ID: ")); + member_type = prompt("Member Type (1 = person, 2 = member): "); + + if (member_type == "1") { + store.append_customer( + consumer::customer(first_name, last_name, person_id, false)); + } else if (member_type == "2") { + store.append_customer( + consumer::customer(first_name, last_name, person_id, true)); + } else { + clear_cerr(); + + std::cerr << "Invalid member type. Try again.\n"; + } + + std::cout << '\n'; + } +} + +auto manage(book_store::store &store) -> void { + using namespace book_store::utility; + + std::cout << "\nYou may now search for books by title, author, purchase by " + "ISBN, view a person's transactions by ID, or tick a year " + "forward. Press enter to " + "exit.\n\n"; + + for (;;) { + std::string search_type; + std::string search_term; + + search_type = + prompt("Operation (title, author, purchase, transactions, tick): "); + + if (search_type.empty()) { + break; + } + + if (search_type == "title") { + search_term = prompt("\nEnter the title: "); + + for (const auto &book : store.find_books_by_title(search_term)) { + std::cout << book << '\n'; + } + } else if (search_type == "author") { + search_term = prompt("\nEnter the author: "); + + for (const auto &book : store.find_books_by_author(search_term)) { + std::cout << book << '\n'; + } + } else if (search_type == "purchase") { + purchase_handler(store); + } else if (search_type == "tick") { + store.tick_year(); + } else if (search_type == "transactions") { + for (const auto &book : store.transactions_by_id( + std::stoul(prompt("Enter the person's ID: ")))) { + std::cout << '\n' << book << "\n"; + } + } else { + clear_cerr(); + + std::cerr << "Invalid search type. Try again.\n\n"; + } + } +} +} // namespace book_store::library diff --git a/source/main.cc b/source/main.cc index 5d262f7..39cff8e 100644 --- a/source/main.cc +++ b/source/main.cc @@ -1,99 +1,22 @@ -#include <algorithm> -#include <array> -#include <cstdlib> -#include <exception> -#include <iostream> -#include <random> -#include <ranges> -#include <string> -#include <string_view> +#include <cstddef> -#include <book_store/book.hh> -#include <book_store/member.hh> -#include <book_store/random.hh> +#include <book_store/library.hh> +#include <book_store/store.hh> auto main() -> int { using namespace book_store; - - std::array<product::book, 100> books; - std::array<std::string, 100> book_titles; - std::array<consumer::member, 100> members; - std::array<std::size_t, 100> member_ids; - std::mt19937 random_number_generator(std::random_device{}()); - utility::random::book_random_engine random(random_number_generator); - auto perform = [](std::string_view name, auto action) { - std::cout << name << " ..."; - - action(); - - std::cout << " ok.\n"; - }; - auto clear_cerr = []() { - if (!std::cerr.good()) { - std::cerr.clear(); - } - }; - - perform("populating books and book_titles", [&]() { - for (auto [index, book] : std::ranges::views::enumerate(books)) { - auto random_title = random.title(); - - book_titles[static_cast<std::size_t>(index)] = random_title; - book = - product::book{random_title, random.authors(), random.publisher(), - random.isbn(), random.price_usd(), random.copy_count()}; - } - }); - perform("verifying all books are present", [&]() { - for (const auto &title : book_titles) { - if (std::ranges::find(book_titles, title) == book_titles.end()) { - clear_cerr(); - - std::cerr << "error: title not found" << '\n'; - - std::terminate(); - } - } - }); - perform("verifying book copy count increment", [&]() { - for (int i = 0; i < 100; ++i) { - auto &book = books[std::uniform_int_distribution<std::size_t>( - 0, books.size() - 1)(random_number_generator)]; - auto copy_count = book.copies(); - auto random_copy_count = static_cast<product::book::size_type>( - std::uniform_int_distribution<std::size_t>(0, 100)( - random_number_generator)); - - book.copies(copy_count + random_copy_count); - - if (book.copies() != copy_count + random_copy_count) { - clear_cerr(); - - std::cerr << "error: invalid copy count after increment" << '\n'; - - std::terminate(); - } - } - }); - perform("populating members and member_ids", [&]() { - for (auto [index, member] : std::views::enumerate(members)) { - auto random_name = random.name(); - auto random_surname = random.name(); - auto random_id = random.id(); - - member_ids[static_cast<std::size_t>(index)] = random_id; - member = consumer::member{random_name, random_surname, random_id}; - } - }); - perform("verifying all members are present", [&]() { - for (auto member_id : member_ids) { - if (std::ranges::find(member_ids, member_id) == member_ids.end()) { - std::cerr << "error: id not found" << '\n'; - - std::terminate(); - } - } - }); + using namespace book_store::library; + + constexpr std::size_t LIBRARY_BOOK_SIZE = 1000; + constexpr std::size_t LIBRARY_CUSTOMER_SIZE = 500; + constexpr std::size_t YEARLY_MEMBERSHIP_FEE_USD = 10; + constexpr std::size_t MEMBER_DISCOUNT_PERCENT = 5; + book_store::store store(LIBRARY_BOOK_SIZE, LIBRARY_CUSTOMER_SIZE, + YEARLY_MEMBERSHIP_FEE_USD, MEMBER_DISCOUNT_PERCENT); + + populate_books(store, LIBRARY_BOOK_SIZE); + populate_consumers(store); + manage(store); return 0; } diff --git a/source/member.cc b/source/member.cc deleted file mode 100644 index 0e8a41c..0000000 --- a/source/member.cc +++ /dev/null @@ -1,23 +0,0 @@ -#include <book_store/book.hh> -#include <book_store/member.hh> -#include <book_store/price.hh> - -namespace book_store::consumer { -using namespace product; - -auto member::books_bought() const noexcept -> book::size_type { - return this->_books_bought; -} - -auto member::amount_spent() const noexcept -> price::usd { - return this->_amount_spent; -} - -auto member::books_bought(book::size_type books_bought) noexcept -> void { - this->_books_bought = books_bought; -} - -auto member::amount_spent(price::usd amount_spent) noexcept -> void { - this->_amount_spent = amount_spent; -} -} // namespace book_store::consumer diff --git a/source/person.cc b/source/person.cc index e80e6df..542bfe7 100644 --- a/source/person.cc +++ b/source/person.cc @@ -5,14 +5,14 @@ #include <book_store/person.hh> namespace book_store::consumer { -auto person::last_name() const noexcept -> std::string_view { - return this->_last_name; -} - auto person::first_name() const noexcept -> std::string_view { return this->_first_name; } +auto person::last_name() const noexcept -> std::string_view { + return this->_last_name; +} + auto person::full_name(bool last_first) const noexcept -> std::string { if (last_first) { return this->_last_name + ", " + this->_first_name; @@ -23,14 +23,14 @@ auto person::full_name(bool last_first) const noexcept -> std::string { auto person::id() const noexcept -> std::size_t { return this->_id; } -auto person::last_name(std::string_view last_name) noexcept -> person & { - this->_last_name = last_name; +auto person::first_name(std::string_view first_name) noexcept -> person & { + this->_first_name = first_name; return *this; } -auto person::first_name(std::string_view first_name) noexcept -> person & { - this->_first_name = first_name; +auto person::last_name(std::string_view last_name) noexcept -> person & { + this->_last_name = last_name; return *this; } diff --git a/source/price.cc b/source/price.cc new file mode 100644 index 0000000..2914bd3 --- /dev/null +++ b/source/price.cc @@ -0,0 +1,7 @@ +#include <book_store/price.hh> + +namespace book_store::product::price { +[[nodiscard]] auto usd::value() const noexcept -> price_type { + return this->_price; +} +} // namespace book_store::product::price diff --git a/source/purchase_error.cc b/source/purchase_error.cc new file mode 100644 index 0000000..f9ae931 --- /dev/null +++ b/source/purchase_error.cc @@ -0,0 +1,21 @@ +#include <string_view> + +#include <book_store/purchase_error.hh> + +namespace book_store { +[[nodiscard]] auto +purchase_error::error() const noexcept -> purchase_error_type { + return this->_error; +} + +[[nodiscard]] auto purchase_error::what() const noexcept -> std::string_view { + switch (this->_error) { + case person_not_found: + return "Person not found"; + case book_not_found: + return "Book not found"; + case not_enough_stock: + return "Not enough stock"; + } +} +} // namespace book_store diff --git a/source/random.cc b/source/random.cc index d237b9f..e27ffa7 100644 --- a/source/random.cc +++ b/source/random.cc @@ -9,12 +9,12 @@ namespace book_store::utility::random { auto book_random_engine::title() -> std::string { - static std::uniform_int_distribution<> distrubtion{0, 49}; + static std::uniform_int_distribution<> distribution{0, 49}; std::string book_name; for (int i = 0; i < 3; ++i) { book_name += book_title_parts[static_cast<std::size_t>( - distrubtion(this->_random_number_generator))]; + distribution(this->_random_number_generator))]; book_name += ' '; } @@ -24,10 +24,10 @@ auto book_random_engine::title() -> std::string { } auto book_random_engine::name() -> std::string { - static std::uniform_int_distribution<> distrubtion{0, 49}; + static std::uniform_int_distribution<> distribution{0, 49}; return std::string(names[static_cast<std::size_t>( - distrubtion(this->_random_number_generator))]); + distribution(this->_random_number_generator))]); } auto book_random_engine::author() -> consumer::person { @@ -36,8 +36,8 @@ auto book_random_engine::author() -> consumer::person { auto book_random_engine::authors() -> std::vector<consumer::person> { std::vector<consumer::person> authors; - static std::uniform_int_distribution<> distrubtion{1, 4}; - auto author_count = distrubtion(this->_random_number_generator); + static std::uniform_int_distribution<> distribution{1, 4}; + auto author_count = distribution(this->_random_number_generator); authors.reserve(static_cast<std::size_t>(author_count)); @@ -49,39 +49,39 @@ auto book_random_engine::authors() -> std::vector<consumer::person> { } auto book_random_engine::publisher() -> std::string { - static std::uniform_int_distribution<> distrubtion{0, 9}; + static std::uniform_int_distribution<> distribution{0, 9}; return std::string(publishers[static_cast<std::size_t>( - distrubtion(this->_random_number_generator))]); + distribution(this->_random_number_generator))]); } auto book_random_engine::isbn() -> std::string { - static std::uniform_int_distribution<> distrubtion{0, 9}; + static std::uniform_int_distribution<> distribution{0, 9}; std::string isbn; for (int i = 0; i < 13; ++i) { - isbn += std::to_string(distrubtion(this->_random_number_generator)); + isbn += std::to_string(distribution(this->_random_number_generator)); } return isbn; } auto book_random_engine::price_usd() -> double { - static std::uniform_real_distribution<> distrubtion{0.0, 100.0}; + static std::uniform_real_distribution<> distribution{0.0, 100.0}; - return distrubtion(this->_random_number_generator); + return distribution(this->_random_number_generator); } auto book_random_engine::copy_count() -> product::book::size_type { - static std::uniform_int_distribution<> distrubtion{0, 10000}; + static std::uniform_int_distribution<> distribution{0, 10000}; return static_cast<product::book::size_type::book_count_type>( - distrubtion(this->_random_number_generator)); + distribution(this->_random_number_generator)); } auto book_random_engine::id() -> std::size_t { - static std::uniform_int_distribution<> distrubtion{0, 1000000}; + static std::uniform_int_distribution<> distribution{0, 1000000}; - return static_cast<std::size_t>(distrubtion(this->_random_number_generator)); + return static_cast<std::size_t>(distribution(this->_random_number_generator)); } } // namespace book_store::utility::random diff --git a/source/store.cc b/source/store.cc new file mode 100644 index 0000000..528e358 --- /dev/null +++ b/source/store.cc @@ -0,0 +1,235 @@ +#include <algorithm> +#include <cctype> +#include <functional> +#include <optional> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +#include <book_store/book.hh> +#include <book_store/book_count.hh> +#include <book_store/customer.hh> +#include <book_store/price.hh> +#include <book_store/purchase_error.hh> +#include <book_store/store.hh> + +namespace book_store { +auto store::append_book(const product::book &book) -> void { + if (this->books_max_size() != 0 && + this->_books.size() < this->books_max_size()) { + this->_books.push_back(book); + } +} + +auto store::append_customer(const customer_type &person) -> void { + if (this->people_max_size() != 0 && + this->_customers.size() < this->people_max_size()) { + this->_customers.push_back(person); + } +} + +auto store::purchase_book(size_type person_id, std::string_view isbn, + product::book_count count) + -> std::optional<purchase_error> { + auto person = this->find_person_by_id(person_id); + + if (!person) { + return purchase_error( + purchase_error::purchase_error_type::person_not_found); + } + + auto book = this->find_book_by_isbn(isbn); + + if (!book) { + return purchase_error(purchase_error::purchase_error_type::book_not_found); + } + + if (book->get().copies() < count) { + return purchase_error( + purchase_error::purchase_error_type::not_enough_stock); + } + + auto &customer = *person; + auto book_price = book.value().get().price_usd(); + + if (customer.is_member()) { + auto discount = 1.0 - (this->_membership_discount_percent * 0.01); + + book_price = + static_cast<product::price::usd::price_type>(book_price * discount); + + if (customer.books_bought() % 11 == 0) { + auto transaction_count = 0; + auto transaction_total = 0.0; + + for (auto &transaction : this->_transactions) { + if (transaction.first == person_id) { + transaction_total += transaction.second.price_usd(); + transaction_count += 1; + } + } + + if (transaction_count >= 10) { + book_price = (transaction_total / transaction_count) * discount; + } + + customer.amount_spent(0); + } + } + + customer.books_bought(customer.books_bought() + count); + // There's some fun casting going on here. It's all safe, though ... probably. + customer.amount_spent(customer.amount_spent() + + static_cast<product::price::usd::price_type>( + (static_cast<product::book_count::book_count_type>( + book_price.value()) * + count.value()))); + book.value().get().copies(book.value().get().copies() - count); + this->_transactions.emplace_back( + person_id, product::book{book.value().get().title().data(), + book.value().get().authors(), + book.value().get().publisher().data(), + book.value().get().isbn().data(), book_price, + product::book_count(count)}); + + return std::nullopt; +} + +auto store::tick_year() -> void { + for (auto &customer : this->_customers) { + customer.amount_spent(customer.amount_spent() + this->_membership_fee); + } +} + +auto store::membership_fee(product::price::usd fee) -> void { + this->_membership_fee = fee; +} + +auto store::membership_discount_percent(discount_percent_type discount_percent) + -> void { + this->_membership_discount_percent = discount_percent; +} + +auto store::books_max_size() const noexcept -> size_type { + return this->_books_max_size; +} + +auto store::people_max_size() const noexcept -> size_type { + return this->_customers_max_size; +} + +auto store::books_size() const noexcept -> size_type { + return this->_books.size(); +} + +auto store::people_size() const noexcept -> size_type { + return this->_customers.size(); +} + +auto store::membership_fee() const noexcept -> product::price::usd { + return this->_membership_fee; +} + +auto store::membership_discount_percent() const noexcept + -> discount_percent_type { + return this->_membership_discount_percent; +} + +auto store::find_person_by_id(size_type person_id) + -> std::optional<customer_type> { + for (auto &person : this->_customers) { + if (person.id() == person_id) { + return person; + } + } + + return std::nullopt; +} + +auto store::find_book_by_isbn(std::string_view isbn) + -> std::optional<std::reference_wrapper<product::book>> { + for (auto &book : this->_books) { + std::string book_isbn = book.isbn().data(); + std::string search_isbn = isbn.data(); + + std::transform( + book_isbn.begin(), book_isbn.end(), book_isbn.begin(), + [](unsigned char character) { return std::tolower(character); }); + std::transform( + search_isbn.begin(), search_isbn.end(), search_isbn.begin(), + [](unsigned char character) { return std::tolower(character); }); + + if (book_isbn == search_isbn) { + return book; + } + } + + return std::nullopt; +} + +auto store::find_books_by_title(std::string_view title) + -> std::vector<product::book> { + std::vector<product::book> books; + + for (auto &book : this->_books) { + std::string book_title = book.title().data(); + std::string search_title = title.data(); + + std::transform( + book_title.begin(), book_title.end(), book_title.begin(), + [](unsigned char character) { return std::tolower(character); }); + std::transform( + search_title.begin(), search_title.end(), search_title.begin(), + [](unsigned char character) { return std::tolower(character); }); + + if (book_title.find(search_title) != std::string::npos) { + books.push_back(book); + } + } + + return books; +} + +auto store::find_books_by_author(std::string_view name) + -> std::vector<product::book> { + std::vector<product::book> books; + + for (auto &book : this->_books) { + for (auto &author : book.authors()) { + std::string author_name = author.full_name(); + std::string search_name = name.data(); + + std::transform( + author_name.begin(), author_name.end(), author_name.begin(), + [](unsigned char character) { return std::tolower(character); }); + std::transform( + search_name.begin(), search_name.end(), search_name.begin(), + [](unsigned char character) { return std::tolower(character); }); + + if (author_name.find(search_name) != std::string::npos) { + books.push_back(book); + } + } + } + + return books; +} + +auto store::transactions_by_id(size_type person_id) + -> std::vector<product::book> { + std::vector<product::book> books; + + for (auto &transaction : this->_transactions) { + if (transaction.first == person_id) { + books.push_back(transaction.second); + } + } + + return books; +} + +auto store::books() -> books_type & { return this->_books; } + +auto store::people() -> customer_container & { return this->_customers; } +} // namespace book_store diff --git a/source/test.cc b/source/test.cc new file mode 100644 index 0000000..09e6bc5 --- /dev/null +++ b/source/test.cc @@ -0,0 +1,96 @@ +#include <algorithm> +#include <array> +#include <cstdlib> +#include <exception> +#include <iostream> +#include <random> +#include <ranges> +#include <string> +#include <string_view> + +#include <book_store/book.hh> +#include <book_store/customer.hh> +#include <book_store/random.hh> +#include <book_store/utility.hh> + +auto main() -> int { + using namespace book_store; + using namespace book_store::utility; + + std::array<product::book, 100> books; + std::array<std::string, 100> book_titles; + std::array<consumer::customer, 100> members; + std::array<std::size_t, 100> member_ids; + std::mt19937 random_number_generator(std::random_device{}()); + utility::random::book_random_engine random(random_number_generator); + auto perform = [](std::string_view name, auto action) { + std::cout << name << " ..."; + + action(); + + std::cout << " ok.\n"; + }; + + perform("populating books and book_titles", [&]() { + for (auto [index, book] : std::ranges::views::enumerate(books)) { + auto random_title = random.title(); + + book_titles[static_cast<std::size_t>(index)] = random_title; + book = + product::book{random_title, random.authors(), random.publisher(), + random.isbn(), random.price_usd(), random.copy_count()}; + } + }); + perform("verifying all books are present", [&]() { + for (const auto &title : book_titles) { + if (std::ranges::find(book_titles, title) == book_titles.end()) { + clear_cerr(); + + std::cerr << "error: title not found" << '\n'; + + std::terminate(); + } + } + }); + perform("verifying book copy count increment", [&]() { + for (int i = 0; i < 100; ++i) { + auto &book = books[std::uniform_int_distribution<std::size_t>( + 0, books.size() - 1)(random_number_generator)]; + auto copy_count = book.copies(); + auto random_copy_count = static_cast<product::book::size_type>( + std::uniform_int_distribution<std::size_t>(0, 100)( + random_number_generator)); + + book.copies(copy_count + random_copy_count); + + if (book.copies() != copy_count + random_copy_count) { + clear_cerr(); + + std::cerr << "error: invalid copy count after increment" << '\n'; + + std::terminate(); + } + } + }); + perform("populating members and member_ids", [&]() { + for (auto [index, member] : std::views::enumerate(members)) { + auto random_name = random.name(); + auto random_surname = random.name(); + auto random_id = random.id(); + + member_ids[static_cast<std::size_t>(index)] = random_id; + member = consumer::customer{random_name, random_surname, random_id}; + } + }); + perform("verifying all members are present", [&]() { + for (auto member_id : member_ids) { + if (std::ranges::find(member_ids, member_id) == member_ids.end()) { + std::cerr << "error: id not found" << '\n'; + + std::terminate(); + } + } + }); + + return 0; +} diff --git a/source/utility.cc b/source/utility.cc new file mode 100644 index 0000000..0cfd818 --- /dev/null +++ b/source/utility.cc @@ -0,0 +1,23 @@ +#include <iostream> +#include <string> +#include <string_view> + +#include <book_store/utility.hh> + +namespace book_store::utility { +auto prompt(std::string_view message) -> std::string { + std::string input; + + std::cout << message; + + std::getline(std::cin, input); + + return input; +} + +auto clear_cerr() -> void { + if (!std::cerr.good()) { + std::cerr.clear(); + } +} +} // namespace book_store::utility |