summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile24
-rw-r--r--Tupfile19
-rw-r--r--config.mk32
-rw-r--r--include/book_store/book_count.hh30
-rw-r--r--include/book_store/customer.hh49
-rw-r--r--include/book_store/library.hh13
-rw-r--r--include/book_store/member.hh41
-rw-r--r--include/book_store/person.hh14
-rw-r--r--include/book_store/price.hh18
-rw-r--r--include/book_store/purchase_error.hh26
-rw-r--r--include/book_store/store.hh87
-rw-r--r--include/book_store/utility.hh12
-rw-r--r--source/book.cc4
-rw-r--r--source/book_count.cc7
-rw-r--r--source/customer.cc29
-rw-r--r--source/library.cc179
-rw-r--r--source/main.cc107
-rw-r--r--source/member.cc23
-rw-r--r--source/person.cc16
-rw-r--r--source/price.cc7
-rw-r--r--source/purchase_error.cc21
-rw-r--r--source/random.cc32
-rw-r--r--source/store.cc235
-rw-r--r--source/test.cc96
-rw-r--r--source/utility.cc23
25 files changed, 928 insertions, 216 deletions
diff --git a/Makefile b/Makefile
index a962a6f..7fb3bd0 100644
--- a/Makefile
+++ b/Makefile
@@ -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
+
diff --git a/Tupfile b/Tupfile
index 36f4be6..4bdecf2 100644
--- a/Tupfile
+++ b/Tupfile
@@ -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