diff --git a/CMakeLists.txt b/CMakeLists.txt index b2cd79f..1b3c4e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,6 @@ if (WIN32) add_library(json STATIC ${SOURCES}) endif() -add_executable(json_demo main.cpp) +add_executable(json_cli main.cpp) -target_link_libraries(json_demo PUBLIC json) \ No newline at end of file +target_link_libraries(json_cli PUBLIC json) \ No newline at end of file diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..d866ad7 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.38.7) +set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) \ No newline at end of file diff --git a/main.cpp b/main.cpp index 1b149b8..a8e9d00 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,11 @@ +/// A command-line tool for JSON parsing and file manipulation. + #include #include #include +#include +#include +#include /// Open a text file and return the contents. @@ -33,167 +38,205 @@ void parse_json_file(const std::string& file_path) { std::cout << json::deparse(text) << std::endl; } -struct Vector3 +namespace argue { + class ArgsParser { + public: + ArgsParser(int argc, char** argv) { + args = std::vector(argv, argv+argc); + } + explicit ArgsParser(const std::vector& value): args(value) {} + + bool has_flag(const std::string& option) { + return std::ranges::find(args, option) != args.end(); + } + + bool has_flag(const std::initializer_list& flags) { + for (auto& flag : flags) + if (has_flag(flag)) + return true; + + return false; + } + + + /// Searches the arguments for all given flag tokens, and removes all occurrences of it. + /// @return true if the flag was found in the arguments. + bool consume_flag(const std::string& option) { + if (has_flag(option)) { + args.erase(std::remove(args.begin(), args.end(), option), args.end()); + return true; + } + return false; + + } + + + /// Searches the arguments for all given flag tokens, and removes them. + /// @return True if the flag was found in the arguments. + bool consume_flag(const std::initializer_list& flags) { + bool had_any_flag = false; + for (auto& flag : flags) { + if (has_flag(flags)) { + had_any_flag = true; + args.erase(std::remove(args.begin(), args.end(), flag), args.end()); + + } + } + + return had_any_flag; + } + + bool has_flag_arg(const std::string& option) { + if (has_flag(option)) { + auto it = std::ranges::find(args, option); + return it!=args.end() && ++it!=args.end(); + } + } + + std::string get_flag_arg(const std::string& option) { + auto it = std::ranges::find(args, option); + ++it; + + return *it; + } + + std::tuple consume_flag_arg(const std::string& flag) { + + } + + std::tuple consume_flag_arg(const std::initializer_list& flags) { + + } + + + std::vector args; + protected: + private: + }; + +} + + + + + +void show_help() { + std::cout << "Usage:" << std::endl; + std::cout << ">json <--options> content" << std::endl; + std::cout << "Options: " << std::endl; + std::cout << "--help --about --license " << std::endl; +} + +void show_license() { + std::cout << "" << std::endl; +} + +void show_version() { - float x, y, z; -}; - - -/// Creates json array out of Vector3 type. -json::value vector3_to_json(const Vector3& v) { - //auto x = json::number(input.x); - //auto y = json::number(input.y); - //auto z = json::number(input.z); - return json::array({json::number(v.x), json::number(v.y), json::number(v.z)}); + } -/// Creates Vector3 type out of json array object. -Vector3 json_to_vector3(const json::value& val) { - Vector3 value; - auto arr = val.as_array_value(); - - value.x = arr[0].as_float_or(0); - value.y = arr.at(1).as_float_or(0); - value.z = arr.at(2).as_float_or(0); - - return {value}; -} - -struct product_info -{ - [[nodiscard]] json::value serialize() const; - product_info() = default; - /// @schema object {string category, string description, boolean is_available, string name, number price, array[@review] reviews} - /// @note This class design assumes all entries in `reviews` are serializable to @struct review, - /// - explicit product_info(const json::value& jv); - - struct rating_metrics { - double average; - int total; - }; - - /// @schema { rating : number, review_text : string, user : string } - struct review { - [[nodiscard]] json::value serialize() const; - explicit review(const json::value& jv); - - int rating; - std::string review_text; - std::string user; - }; - - std::string category; - std::string description; - bool is_available; - std::string manufacturer; - std::string name; - double price; - std::string product_id; - rating_metrics ratings; - std::string release_date; - std::vector reviews; -}; - -json::value product_info::serialize() const { - json::object root; - - root.insert("category", this->category); - root += {"category", json::string( this->category)}; - root += {"manufacturer", json::string( this->manufacturer)}; - root += {"is_available", json::boolean(this->is_available)}; - root += {"description", json::string( this->description)}; - root += {"name", json::string( this->name)}; - - auto review_list = json::array(); - for (auto& rev : this->reviews) - review_list += rev.serialize(); - - root["ratings"] = json::object({ - {"average", json::number(this->ratings.average)}, - {"total", json::number(this->ratings.total)} - }); - - root += {"reviews", review_list}; - root += {"taters", json::number(5)}; - - return root; -} - -product_info::product_info(const json::value &jv) { - if (jv.type != json::value_type::object) - throw std::runtime_error("Malformed JSON for product info!"); - - auto jvo = jv.as_object(); - - this->category = jvo["category"].as_string(); - this->description = jvo["description"].as_string(); - this->is_available = jvo["is_available"].as_bool(); - this->manufacturer = jvo["manufacturer"].as_string(); - this->name = jvo["name"].string.value(); - this->price = double(jvo["price"]); - - for (auto& review_json : jvo["reviews"].as_array()) { - this->reviews.emplace_back(review_json); - } - - this->ratings.average = jvo["ratings"]["average"].number.value(); - this->ratings.total = jvo["ratings"]["total"].number.value(); +/// Parses a string of JSON and outputs any syntax errors, if present. +void validate_json_string(const std::string& input) { } -json::value product_info::review::serialize() const { - json::object obj = json::object(); - obj["rating"] = json::number(rating); - obj["review_text"] = json::string(review_text); - obj["user"] = json::string(user); - return obj; -} - -product_info::review::review(const json::value &jv) { - if (jv.type != json::value_type::object) - throw std::runtime_error("Malformed JSON for review!"); - - rating = jv["rating"].as_int(); - review_text = jv["review_text"].as_string(); - user = jv["user"].as_string(); -} - -void test_product_info() -{ - std::cout << "Testing parsing of product_info file." << std::endl; - //parse_json_file("../samples/product_info.json"); - auto file_contents = read_file("../samples/product_info.json"); - - auto [text, err] = json::parse(file_contents); - - if (!err.empty()) { - std::cerr << err << std::endl; - return; - } - - std::cout << "Testing deserialization of product_info json." << std::endl; - // Construct from json object. - product_info test(text); - - std::cout << "Testing serialization of product_info struct." << std::endl; - - json::value result = test.serialize(); - - result.convert_descendants(); - std::cout << json::deparse(result); -} - -void test_tile_data() { - //parse_json_file("../samples/tiles.json"); -} - -void test_widgets() { +void echo_json_formatted(const std::string& input) { } -// Some progress has been made on small scale. +void show_readme() { + std::cout << "A cli tool for parsing json files." << std::endl; + std::cout << "Developed & maintained by josh@redacted.cc" << std::endl; + std::cout << "This program is included with the Redacted Software JSON Library for C++." << std::endl; + std::cout << "https://git.redacted.cc/josh/json" << std::endl; + std::cout << "Run `json --help` for help information." << std::endl; + std::cout << "Run `json --license` for license information." << std::endl; +} + +/// Read options and their args, remove from master args list. + int main(int argc, char* argv[]) { - test_product_info(); - test_tile_data(); + struct flag { + std::string name; + std::vector aliases; + + std::vector Matches() { + auto copy = aliases; + copy.push_back(name); + + } + }; + + bool color_output = true; + std::string input_content; + bool read_from_stdin = true; + + argue::ArgsParser args(argc,argv); + + //std::vector args(argv, argv+argc); + + // Terminal Options - Parsed first, close the program w/o further action. + + if (args.has_flag({"--about", "--info", "--readme"})) { + + show_readme(); return 0; + } + + if (args.has_flag({"--help", "-h", "--h"})) { + show_help(); return 0; + } + + if (args.has_flag({"--license", "-L"})) { + show_license(); return 0; + } + + if (args.has_flag("--raw")) + color_output = false; + + if (args.has_flag_arg("--file")) + { + + auto [success, file_name] = args.consume_flag_arg("--file"); + + input_content = read_file(file_name); + read_from_stdin = false; + } + + if (read_from_stdin) + { + std::string line; + while(std::getline(std::cin, line) && !line.empty()) { + input_content += line; + } + } + + if (args.has_flag("--validate")) + { + validate_json_string(input_content); return 0; + } + + std::vector remaining = args.args; + + for (auto& e : remaining) { + std::cout << e << " "; + } + std::cout << std::endl; + + auto[val, err] = json::parse(input_content); + + if (!err.empty()) + { + std::cerr << "Error reading JSON: " << err << std::endl; + return -1; + } + + + + + std::string roundtripped = json::deparse(val); + + std::cout << roundtripped << std::endl; + + return 0; }