4 Commits

Author SHA1 Message Date
92e97ab6fe Making edits and integrating ArgsParser 2025-07-08 11:14:49 -04:00
efd8ba9cd8 Integrate ArgsParser as separate package. Setup json_cli app testbed. 2025-07-03 02:03:11 -04:00
c2f95ad146 Ended up writing an ArgsParser class. 2025-07-02 03:23:54 -04:00
b938bebafe Update README.md 2025-07-01 18:14:34 -04:00
4 changed files with 225 additions and 158 deletions

View File

@@ -11,7 +11,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
#include(cmake/CPM.cmake)
include(cmake/CPM.cmake)
file(GLOB_RECURSE HEADERS "include/*.hpp")
@@ -28,6 +28,21 @@ 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)
CPMAddPackage(
NAME ArgsParser
URL https://git.redacted.cc/josh/ArgsParser/archive/Prerelease-3.zip
)
CPMAddPackage(
NAME mcolor
URL https://git.redacted.cc/maxine/mcolor/archive/Release-1.zip
)
target_include_directories(json_cli PUBLIC
${ArgsParser_SOURCE_DIR}/include
${mcolor_SOURCE_DIR}/include
)
target_link_libraries(json_cli PUBLIC json ArgsParser mcolor)

View File

@@ -12,10 +12,27 @@ A bare-minimal, yet industrial-strength C++ 20 library for reading, writing, and
* Modern C++ (20)
* Simple, well-documented API.
* Included parsing, validation, and manipulation tool for the CLI.
* Static Library
* GCC and MSVC support
* Tested on Fedora Linux and Windows 10.
## API Overview
```cpp
tuple<value, string> parse(string);
string deparse(value, string whitespace = "");
struct json::value {...};
struct json::string {...};
struct json::number {...};
struct json::boolean {...};
struct json::object {...};
struct json::array {...};
```
## Contributing

24
cmake/CPM.cmake Normal file
View File

@@ -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})

321
main.cpp
View File

@@ -1,7 +1,17 @@
/// A command-line tool for JSON parsing and file manipulation.
#include <json.hpp>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <string>
#include <Colors.hpp>
#include <ArgsParser.hpp>
#include <filesystem>
#include "AnsiEscapeCodes.hpp"
/// Open a text file and return the contents.
std::string read_file(const std::string& file_path)
@@ -10,9 +20,8 @@ std::string read_file(const std::string& file_path)
if (!file)
throw std::runtime_error("We couldn't find the file: " + file_path);
std::streamsize file_size;
file.seekg(0, std::ios::end);
file_size = file.tellg();
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::string file_content(file_size, '\0');
@@ -33,167 +42,169 @@ void parse_json_file(const std::string& file_path) {
std::cout << json::deparse(text) << std::endl;
}
struct Vector3
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<review> 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 << Colors::Oranges::Coral.ToEscapeCode(false);
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;
std::cout << mcolor::AnsiEscapeCodes::ResetAll;
}
///
void print_all_args(int argc, char* argv[]) {
}
/// Read options and their args, remove from master args list.
int json_app(std::vector<std::string> params) {
bool color_output = true;
std::string input_content;
bool read_from_stdin = true;
bool verbose = false;
ArgsParser args(params);
//std::vector<std::string> 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("--colorful")) color_output = true;
if (args.has_flag("--quiet")) verbose = false;
if (args.has_flag("--verbose")) verbose = true;
if (args.has_flag("--file")) {
if (args.has_flag_arg("--file"))
{
auto file_name = args.consume_flag_arg("--file").value();
if (!std::filesystem::exists(file_name)) {
std::cout << "Error: File " << file_name << "not found!";
}
input_content = read_file(file_name);
read_from_stdin = false;
}
}
if (read_from_stdin)
{
std::string line;
std::getline(std::cin, line);
// TODO: Add mode for merge?
input_content = 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<std::string> remaining = args.get_remaining_args();
for (auto& e : remaining) {
std::cout << e << " ";
}
std::cout << std::endl;
if (input_content.empty()) {
if (!verbose)
std::cout << "No input provided!" << std::endl;;
return 0;
}
auto query_tokens = args.get_remaining_args();
if (!query_tokens.empty())
{
}
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;
}
int main(int argc, char* argv[]) {
test_product_info();
test_tile_data();
ArgsParser pre_parser(argc, argv);
if (pre_parser.has_flag("--test-all")) {
int readme = json_app({"--readme"});
int help = json_app({"--help"});
int license = json_app({"--license"});
int version = json_app({"--version"});
int result_0 = json_app({"--file", "nonexistant.json"});
int result_1 = json_app({"--validate", "{\"test\":42069}"});
return 0;
} else
return json_app(pre_parser.get_remaining_args());
}