diff --git a/AUTHORS b/AUTHORS index 5d97b7d..ab4af8a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,5 @@ # This file lists all individuals having contributed content to the repository. # For how it is generated, see `hack/generate-authors.sh`. -Lordwill1 -ejilistic +sadiq Abdulrasheed Bello +Umar Ibrahim <@gmail.com> diff --git a/README.md b/README.md index e409241..07fbb95 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ + +# GitHub Activity Fetcher + +This is a simple C command-line tool to fetch and display a user's public GitHub activity. + +## Prerequisites + +- `libcurl`: For making HTTP requests. +- `jansson`: For parsing JSON. + +On Debian/Ubuntu, you can install these using: +```bash +sudo apt-get update +sudo apt-get install -y libcurl4-openssl-dev libjansson-dev +``` + +## Building + +A build script is provided: +```bash +chmod +x build.sh +./build.sh +``` +This will compile `github_activity.c` and create an executable named `github_activity`. + +## Usage + +```bash +./github_activity +======= # 0x16. C - Simple Shell **By Spencer Cheng, featuring Julien Barbier** Project to be done in teams of 2 people (your team: Godswill Kalu, Vatalis Ibeh) @@ -106,11 +136,48 @@ write (man 2 write) ## How to add Author file `Bash script for generating the list of authors in git repo` + +``` +Example: +```bash +./github_activity octocat ``` -#!/bin/sh -git shortlog -se \ - | perl -spe 's/^\s+\d+\s+//' \ - | sed -e '/^CommitSyncScript.*$/d' \ - > AUTHORS - ``` +## Testing + +First, ensure the program is compiled by running `./build.sh`. + +### Automated Basic Test + +A simple automated test script is provided to check basic functionality: +```bash +chmod +x test.sh +./test.sh +``` +This script currently verifies the "No arguments" test case. + +### Manual Test Cases + +**Test Case 1: No arguments.** +* Command: `./github_activity` +* Expected Output: Error message "Usage: github_activity " printed to stderr, and the program should exit with a non-zero status code (e.g., 1). + +**Test Case 2: Valid username.** +* Command: `./github_activity octocat` +* Expected Output: A list of recent public events for 'octocat'. Each event should be displayed in the format: "Event Type: [Event Type], Repository: [Repository Name], Time: [Timestamp]". + * Example: `Fetching activity for user: octocat` + * `Event Type: PushEvent, Repository: octocat/Spoon-Knife, Time: 2023-10-27T12:34:56Z` (The exact events and timestamps will vary). + +**Test Case 3: Invalid/Non-existent username.** +* Command: `./github_activity NonExistentUser12345xyzAbc` +* Expected Output: The program should indicate that no events were found or the user is invalid. + * Example: `Fetching activity for user: NonExistentUser12345xyzAbc` + * Followed by either no event output, or an API error message like "JSON error on line 1: '{' expected near end of file" if the API returns an error document (e.g. `{"message":"Not Found", ...}`). + +**Test Case 4: Network Error Simulation.** +* Instruction: Disconnect your machine from the internet (e.g., turn off Wi-Fi or unplug the Ethernet cable). +* Command: `./github_activity octocat` +* Expected Output: A cURL error message should be printed to stderr. + * Example: `Fetching activity for user: octocat` + * `curl_easy_perform() failed: Couldn't resolve host name` (or similar network-related cURL error). + (Remember to reconnect to the internet after this test.) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c0d6a96 --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# This script compiles github_activity.c and links it with libcurl and jansson. +# -o github_activity: Specifies the output executable name. +# github_activity.c: The source file to compile. +# -lcurl: Links with the libcurl library. +# -ljansson: Links with the libjansson library. +gcc -o github_activity github_activity.c -lcurl -ljansson diff --git a/github_activity.c b/github_activity.c new file mode 100644 index 0000000..0129267 --- /dev/null +++ b/github_activity.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include + +struct MemoryStruct { + char *memory; + size_t size; +}; + +// Callback function to handle writing received data +static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) { + size_t realsize = size * nmemb; + struct MemoryStruct *mem = (struct MemoryStruct *)userp; + + char *ptr = realloc(mem->memory, mem->size + realsize + 1); + if(ptr == NULL) { + /* out of memory! */ + fprintf(stderr, "not enough memory (realloc returned NULL)\n"); + return 0; + } + + mem->memory = ptr; + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +void fetch_user_activity(const char *username) { + CURL *curl; + CURLcode res; + char *url = NULL; + const char *base_url = "https://api.github.com/users/"; + const char *events_suffix = "/events/public"; + + struct MemoryStruct chunk; + chunk.memory = malloc(1); /* will be grown as needed by the realloc above */ + if(chunk.memory == NULL) { + fprintf(stderr, "Failed to allocate memory for chunk\n"); + return; + } + chunk.size = 0; /* no data at this point */ + chunk.memory[0] = '\0'; + + // Calculate the length of the URL + size_t url_len = strlen(base_url) + strlen(username) + strlen(events_suffix) + 1; + url = malloc(url_len); + if (url == NULL) { + fprintf(stderr, "Failed to allocate memory for URL\n"); + free(chunk.memory); + return; + } + // Construct the URL + snprintf(url, url_len, "%s%s%s", base_url, username, events_suffix); + + curl = curl_easy_init(); + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "github-activity-c-tool/1.0"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } else { + json_t *root; + json_error_t error; + root = json_loads(chunk.memory, 0, &error); + + if (!root) { + fprintf(stderr, "JSON error on line %d: %s\n", error.line, error.text); + } else { + if (!json_is_array(root)) { + fprintf(stderr, "Error: root is not a JSON array.\n"); + } else { + for (size_t i = 0; i < json_array_size(root); i++) { + json_t *event = json_array_get(root, i); + if (!event || !json_is_object(event)) { + fprintf(stderr, "Error: event is not a JSON object.\n"); + continue; + } + + const char *type_str = "N/A"; + json_t *type_json = json_object_get(event, "type"); + if (type_json && json_is_string(type_json)) { + type_str = json_string_value(type_json); + } + + const char *repo_name_str = "N/A"; + json_t *repo_obj = json_object_get(event, "repo"); + if (repo_obj && json_is_object(repo_obj)) { + json_t *repo_name_json = json_object_get(repo_obj, "name"); + if (repo_name_json && json_is_string(repo_name_json)) { + repo_name_str = json_string_value(repo_name_json); + } + } + + const char *created_at_str = "N/A"; + json_t *created_at_json = json_object_get(event, "created_at"); + if (created_at_json && json_is_string(created_at_json)) { + created_at_str = json_string_value(created_at_json); + } + printf("Event Type: %s, Repository: %s, Time: %s\n", type_str, repo_name_str, created_at_str); + } + } + json_decref(root); + } + } + curl_easy_cleanup(curl); + } else { + fprintf(stderr, "Failed to initialize CURL\n"); + } + free(url); + free(chunk.memory); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: github_activity \n"); + return 1; + } + char *username = argv[1]; + printf("Fetching activity for user: %s\n", username); + fetch_user_activity(username); + return 0; +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..18fc4ab --- /dev/null +++ b/test.sh @@ -0,0 +1,23 @@ +#!/bin/bash +echo "Running tests..." + +# Ensure the program is built +./build.sh +if [ ! -f ./github_activity ]; then + echo "Build failed, cannot run tests." + exit 1 +fi + +echo "Test Case 1: No arguments" +OUTPUT_NO_ARGS=$(./github_activity 2>&1) # Capture both stdout and stderr +EXIT_CODE_NO_ARGS=$? + +# Check exit code (should be non-zero, e.g. 1 as implemented) +# Check stderr for usage message (as implemented) +if [ $EXIT_CODE_NO_ARGS -ne 0 ] && echo "$OUTPUT_NO_ARGS" | grep -q "Usage: github_activity "; then + echo " PASSED: Correct exit code and usage message." +else + echo " FAILED: Expected non-zero exit code and usage message. Got exit code $EXIT_CODE_NO_ARGS and output: $OUTPUT_NO_ARGS" +fi + +echo "Basic tests complete. Please perform manual tests as described in README.md."