diff --git a/main.cpp b/main.cpp index c3fbad7..f231bd5 100644 --- a/main.cpp +++ b/main.cpp @@ -1,16 +1,14 @@ #include -#include - -#include -#include +#include #include -#include +#include +#include #include using json = nlohmann::json; -// used to have a dynamic string -typedef struct Response +// Used to store dynamically allocated HTTP response +struct Response { char *string; size_t size; @@ -23,17 +21,14 @@ struct TestCaseResponse }; size_t write_chunk(void *data, size_t size, size_t nmemb, void *userData); - void formatResponse(char *response); std::string FormatHTMLToString(const std::string &response); TestCaseResponse GetTestCases(const std::string &content); - -std::pair GetParamName(const std::string ¶m); void CreateJSON(json *response, const TestCaseResponse &testCases); int main() { - std::string questionName = ""; + std::string questionName; std::cout << "Enter Leetcode question name: " << std::endl; std::cin >> questionName; @@ -47,25 +42,22 @@ int main() std::cerr << "HTTP REQUEST FAILED: curl_easy_init() failed!" << std::endl; return -1; } - else - { - std::cout << "Curl initialized successfully!" << std::endl; - } + + std::cout << "Curl initialized successfully!" << std::endl; Response response; - response.string = (char *)malloc(1); + response.string = static_cast(malloc(1)); response.size = 0; // Set options for the HTTP request curl_easy_setopt(curl, CURLOPT_URL, "https://leetcode.com/graphql"); - // Set Post data (like JSON body) to match leetcode graph ql query + // Set POST data (JSON body) to match LeetCode GraphQL query json query = { {"query", "query questionData($titleSlug: String!) { question(titleSlug: $titleSlug) { title content difficulty topicTags { name } hints } }"}, - {"variables", { - {"titleSlug", questionName} // This can now be easily modified - }}}; + {"variables", {{"titleSlug", questionName}}} + }; const std::string postData = query.dump(); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); @@ -78,12 +70,9 @@ int main() headers = curl_slist_append(headers, referer.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - /** - * WriteFunction allows for specifying a callback function - * Curl_easy_perfrom will call this function repeatedly - * Each time it is called the pointer is passed to a new chunk of response - * string - */ + + // Set write callback function to handle response data + // curl_easy_perform will call this function repeatedly for each chunk received curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk); // Address of response string is passed in write_chunk as userData @@ -99,50 +88,49 @@ int main() } formatResponse(response.string); - free(response.string); + // Cleanup + free(response.string); curl_easy_cleanup(curl); return 0; } -// returns number of bytes in the chunk -// data is set to a ptr that points to block of data recieved in this chunk -// nmemb is the number of bytes in the block of data -// userData points to what we want (points to where the response string is stored) +// Callback function for handling received HTTP response data +// Returns number of bytes processed +// data: pointer to received data chunk +// size * nmemb: total size of data chunk in bytes +// userData: pointer to Response struct where data is stored size_t write_chunk(void *data, size_t size, size_t nmemb, void *userData) { // size is always 1 size_t real_size = size * nmemb; - Response *response = (Response *)userData; - // allocate more space for chunk that was recieved - // response->size is size of existing mem and real_size is the size recieved and +1 accounts for null - char *ptr = (char *)realloc(response->string, response->size + real_size + 1); + Response *response = static_cast(userData); + + // Allocate more space for the received chunk + // response->size is the current size, real_size is the new chunk size, +1 for null terminator + char *ptr = static_cast(realloc(response->string, response->size + real_size + 1)); if (ptr == nullptr) { - std::cerr << "Problem reallocating space for chunk recieved" << std::endl; + std::cerr << "Failed to reallocate memory for received chunk" << std::endl; return 0; } - // set response string to the new (larger) memory address + + // Update response string to the new memory address response->string = ptr; - // append new porition onto existing string + // Append new portion to existing string memcpy(&(response->string[response->size]), data, real_size); - // update strings size + // Update string size response->size += real_size; - // append null character + // Add null terminator response->string[response->size] = '\0'; return real_size; } -/** - * Returns a map containing the following tags stored as keys - * and their description as their value. - * - * title content difficulty topicTags { name } hints - * - * Assumes json response will use the tags in the given order above. - */ +// Parse and format the JSON response from LeetCode API +// Extracts: title, content, difficulty, topicTags, and hints +// Formats HTML content and extracts test cases void formatResponse(char *response) { std::vector currentTags = {"title", "content", "difficulty", "topicTags", "hints"}; @@ -156,29 +144,30 @@ void formatResponse(char *response) for (const auto &tag : currentTags) { - if (question.contains(tag) && tag == "topicTags") + if (!question.contains(tag)) + { + continue; + } + + if (tag == "topicTags") { std::vector topics; - for (auto topic : question[tag]) + for (const auto &topic : question[tag]) { topics.push_back(topic["name"]); } question[tag] = topics; - continue; } - if (question.contains(tag) && tag == "hints") + else if (tag == "hints") { - if (question[tag][0].size() == 0) + if (question[tag][0].size() > 0) { - continue; + question[tag][0] = FormatHTMLToString(question[tag][0]); } - question[tag][0] = FormatHTMLToString(question[tag][0]); - continue; } - if (question.contains(tag)) + else { question[tag] = FormatHTMLToString(question[tag]); - // Get testcases from given content if (tag == "content") { testCases = GetTestCases(question[tag]); @@ -195,15 +184,15 @@ void formatResponse(char *response) } } -// check for tag +// Convert HTML content to plain text by removing HTML tags and decoding entities std::string FormatHTMLToString(const std::string &response) { - int i = 0; - std::string result = ""; + size_t i = 0; + std::string result; while (i < response.length()) { - // check for HTML elements + // Remove HTML tags if (response[i] == '<') { while (response[i] != '>') @@ -214,7 +203,7 @@ std::string FormatHTMLToString(const std::string &response) continue; } - // check for < (<) , > (>); + // Decode HTML entities: < and > if (i < response.length() - 4 && (response.substr(i, 4) == "<" || response.substr(i, 4) == ">")) { std::string expression = response.substr(i, 4); @@ -230,7 +219,7 @@ std::string FormatHTMLToString(const std::string &response) continue; } - // check for & (&) + // Decode HTML entity: & if (i < response.length() - 5 && (response.substr(i, 5) == "&")) { result += "&"; @@ -238,22 +227,21 @@ std::string FormatHTMLToString(const std::string &response) continue; } - // check for 's + // Remove 's (apostrophe entity) if (i < response.length() - 6 && response.substr(i, 6) == "'s") { i += 6; continue; } - // check for   tags + // Remove   (non-breaking space) if (i < response.length() - 6 && response.substr(i, 6) == " ") { i += 6; continue; } - // check for multiple whitespace characters - // want to keep 1 where there are multiple + // Collapse multiple newlines into single newline if (response[i] == '\n') { result += "\n"; @@ -281,16 +269,12 @@ std::string FormatHTMLToString(const std::string &response) return result; } -/** - * Basic test cases given by leetcode are given in a string of the form. Example case & output. - * Should always be at least 2 test cases given. - * @returns array of oxpected outputs for the test cases. - */ +// Extract test cases from the problem content +// Returns test case inputs and expected outputs parsed from Example sections TestCaseResponse GetTestCases(const std::string &content) { TestCaseResponse tests; - - int i = 0; + size_t i = 0; while (i < content.length()) { if (i < content.length() - 7 && content.substr(i, 7) == "Example") @@ -301,13 +285,13 @@ TestCaseResponse GetTestCases(const std::string &content) if (i <= content.length() - 6 && content.substr(i, 6) == "Input:") { i += 6; - std::string input = ""; - std::string paramName = ""; - std::string paramRes = ""; + std::string input; + std::string paramName; + std::string paramRes; int j = -1; while (i < content.length() - 7 && content.substr(i, 7) != "\nOutput") { - // check if new param is being searched + // Check if parsing a new parameter if (i < content.length() - 1 && (content[i] == ',' && content[i + 1] == ' ')) { tests.testCaseParams.push_back({paramName, paramRes}); @@ -317,7 +301,7 @@ TestCaseResponse GetTestCases(const std::string &content) i++; continue; } - // now looking for paramResult so set j (flag for where = is) + // Set flag for parameter value parsing if (content[i] == '=') { j = i; @@ -335,17 +319,16 @@ TestCaseResponse GetTestCases(const std::string &content) } i++; } - if (paramName.length() != 0 && paramRes.length() != 0) + if (!paramName.empty() && !paramRes.empty()) { tests.testCaseParams.push_back({paramName, paramRes}); } - // std::cout << paramName << " " << paramRes << std::endl; } if (i <= content.length() - 6 && content.substr(i, 6) == "Output") { i += 6; - std::string testCase = ""; + std::string testCase; while (i < content.length() && content[i] != '\n') { if (content[i] != ' ' && content[i] != ':') @@ -371,7 +354,7 @@ TestCaseResponse GetTestCases(const std::string &content) void CreateJSON(json *response, const TestCaseResponse &tests) { - // filter out invalid characters from title + // Filter out invalid filename characters from title std::string title = (*response)["title"]; const std::string invalid_chars = "\\/:*?\"<>|"; for (char c : invalid_chars) @@ -380,9 +363,7 @@ void CreateJSON(json *response, const TestCaseResponse &tests) } std::string jsonName = "../../../Questions/" + title + ".txt"; - std::ofstream outputJSON; - outputJSON.open(jsonName); - // should have to create the file so always should open + std::ofstream outputJSON(jsonName); if (!outputJSON.is_open()) { std::cerr << "Error creating output file for JSON response" << std::endl; @@ -390,85 +371,44 @@ void CreateJSON(json *response, const TestCaseResponse &tests) } outputJSON << "{\n"; - // iterates through json response inserting key and value as pair into output file + // Write JSON key-value pairs for (auto it = (*response).begin(); it != (*response).end(); ++it) { outputJSON << "\"" << it.key() << "\"" << ": " << it.value() << ',' << "\n"; } - // handle situation where testCases might not generate - - // Insert testcases + // Insert test cases array outputJSON << "\"testCases\"" << ": [" << "\n"; int j = 0; int size = tests.testCases.size(); for (int i = 0; i < size; i++) { - // start inserting new object into array inside json file outputJSON << "{\n"; - - std::string expectedResult = tests.testCases[i]; // testcase expected outputs + std::string expectedResult = tests.testCases[i]; outputJSON << "\"expectedResult\": " << "\"" << expectedResult << "\",\n"; int numParams = tests.testCaseParams.size() / tests.testCases.size(); for (int x = 0; x < numParams; x++) { std::pair fixedParam = tests.testCaseParams[j++]; - if (x == numParams - 1) - { - outputJSON << "\"" << fixedParam.first << "\": " << "\"" << fixedParam.second << "\"\n"; - } - else + outputJSON << "\"" << fixedParam.first << "\": " << "\"" << fixedParam.second << "\""; + if (x < numParams - 1) { - outputJSON << "\"" << fixedParam.first << "\": " << "\"" << fixedParam.second << "\",\n"; + outputJSON << ","; } + outputJSON << "\n"; } - // if i is at the end then we need to close off the obj - if (i == size - 1) + outputJSON << "}"; + if (i < size - 1) { - outputJSON << "}\n"; - } - else - { - outputJSON << "},\n"; + outputJSON << ","; } + outputJSON << "\n"; } outputJSON << "]\n"; - outputJSON << "}"; outputJSON.close(); -} - -/** - * params are taken from the json as a string containing 'paramName'='param' - * This function splits the paramName and param seperately to label them in the output JSON easier. - * (the problem function calls explicility used by the users will contain the same paramNames so makes using them easier as well) - */ -std::pair GetParamName(const std::string ¶m) -{ - std::string paramName = ""; - std::string paramResult = ""; - bool nameParsed = false; - for (int i = 0; i < param.length(); i++) - { - - if (param[i] == '=') - { - nameParsed = true; - continue; - } - - if (param[i] != ' ' && !nameParsed) - { - paramName += param[i]; - } - else if (param[i] != ' ' && nameParsed) - { - paramResult += param[i]; - } - } - return {paramName, paramResult}; } \ No newline at end of file