Skip to content

Commit

Permalink
emacs support discussion (#66)
Browse files Browse the repository at this point in the history
* emacs support discussion

* fix segment fault

* add query categories
  • Loading branch information
jiacai2050 committed Jul 16, 2023
1 parent 8fa5dce commit 2169cb9
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .dir-locals.el
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
(eglot-ensure)
(add-hook 'before-save-hook 'eglot-format nil t)))))
(nil . ((omg-pull-target-repo . "jiacai2050/oh-my-github")
(omg-pull-target-branch . "master")
(omg-pull-target-branch . "main")
(omg-pull-username . "jiacai2050")
(omg-pull-draft . "false"))))
5 changes: 0 additions & 5 deletions .github/pull_request_template.md

This file was deleted.

2 changes: 2 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub fn build(b: *std.Build) !void {
const quick = b.option(bool, "quick", "Enable quick mode");
const core_lib = b.addStaticLibrary(.{
.name = "omg-core",
.root_source_file = .{ .path = "core/omg.zig" },
.target = target,
.optimize = optimize,
});
Expand All @@ -33,6 +34,7 @@ pub fn build(b: *std.Build) !void {
try cflags.append("-DOMG_TEST");
}
}
core_lib.addIncludePath("core");
core_lib.addCSourceFile("./core/omg.c", cflags.items);
core_lib.linkSystemLibrary("sqlite3");
core_lib.linkSystemLibrary("libcurl");
Expand Down
73 changes: 40 additions & 33 deletions core/omg.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,17 @@ static void free_response(response *resp) {
}
}

/* static void free_curl_slist(struct curl_slist **lst) { */
/* if (*lst) { */
/* #ifdef VERBOSE */
/* printf("free curl list, body is %s\n", (*lst)->data); */
/* #endif */
/* curl_slist_free_all(*lst); */
/* } */
/* } */

/* #define auto_curl_slist \ */
/* struct curl_slist __attribute__((cleanup(free_curl_slist))) */
// static void free_curl_slist(struct curl_slist **lst) {
// if (*lst) {
// #ifdef VERBOSE
// printf("free curl list, body is %s\n", (*lst)->data);
// #endif
// curl_slist_free_all(*lst);
// }
// }

// #define auto_curl_slist \
// struct curl_slist __attribute__((cleanup(free_curl_slist)))

static void free_curl_handler(CURL **curl) {
if (*curl) {
Expand Down Expand Up @@ -145,7 +145,7 @@ bool is_ok(omg_error err) { return err.code == OMG_CODE_OK; }

static const omg_error NO_ERROR = {.code = OMG_CODE_OK, .message = {}};

static omg_error new_error(int code, const char *msg) {
omg_error new_error(int code, const char *msg) {
size_t msg_size = strlen(msg);
if (msg_size == 0) {
return NO_ERROR;
Expand Down Expand Up @@ -278,6 +278,8 @@ omg_error omg_setup_context(const char *path, const char *github_token,
return NO_ERROR;
}

CURL *omg__curl_handler(omg_context ctx) { return ctx->api_curl; }

static omg_error omg_request(omg_context ctx, const char *method,
const char *url, json_t *payload, json_t **out) {
CURL *curl = ctx->api_curl;
Expand Down Expand Up @@ -345,8 +347,7 @@ omg_error omg_download(omg_context ctx, const char *url, const char *filename) {

FILE *file = fopen(filename, "wb");
if (!file) {
return (omg_error){.code = OMG_CODE_INTERNAL,
.message = "open file failed"};
return new_error(OMG_CODE_INTERNAL, "open db file failed");
}

curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
Expand All @@ -360,8 +361,7 @@ omg_error omg_download(omg_context ctx, const char *url, const char *filename) {
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
if (response_code >= 400) {
fprintf(stderr, "Download %s failed with %ld", filename, response_code);
return (omg_error){.code = OMG_CODE_CURL,
.message = "download file failed"};
return new_error(OMG_CODE_CURL, "download file failed");
}

return NO_ERROR;
Expand Down Expand Up @@ -1529,32 +1529,39 @@ omg_error omg_create_discusstion(omg_context ctx, const char *repo_id,
json_auto_t *request = json_object();
json_object_set_new(request, "query", json_string(mutation));

// {
// "data": {
// "createDiscussion": {
// "clientMutationId": null,
// "discussion": {
// "id": "D_kwDOAScDVc4AUngI",
// "title": "The title",
// "body": "The body",
// "createdAt": "2023-07-15T11:03:47Z",
// "url": "https://github.com/jiacai2050/blog/discussions/14"
// }
// }
// }
// }
json_auto_t *response = NULL;
omg_error err =
omg_request(ctx, POST_METHOD, GRAPHQL_ROOT, request, &response);
if (!is_ok(err)) {
return err;
}
json_t *discussion = json_object_get(
json_object_get(json_object_get(response, "data"), "createDiscussion"),
"discussion");
json_t *create =
json_object_get(json_object_get(response, "data"), "createDiscussion");
if (json_is_null(create)) {
json_t *errors = json_object_get(response, "errors");
if (json_is_null(errors)) {
return (omg_error){.code = OMG_CODE_CURL, .message = "Unknown error!"};
}

return new_error(OMG_CODE_CURL, json_string_value(json_object_get(
json_array_get(errors, 0), "message")));
}

json_t *discussion = json_object_get(create, "discussion");
*out = (omg_discussion){.id = dup_json_string(discussion, "id"),
.url = dup_json_string(discussion, "url")};

return NO_ERROR;
}

void omg_free_discussion(omg_discussion *discussion) {
if (discussion != NULL) {
if (discussion->url) {
free(discussion->url);
}

if (discussion->id) {
free(discussion->id);
}
}
}
29 changes: 27 additions & 2 deletions core/omg.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#ifndef OMG_H
#define OMG_H
#include <curl/curl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
Expand All @@ -23,9 +24,10 @@ typedef struct omg_error {

void print_error(omg_error err);
bool is_ok(omg_error err);
omg_error new_error(int code, const char *msg);

/* Opaque pointer representing omg core concept: context
All omg-related functions require this argument! */
// Opaque pointer representing omg core concept.
// All omg-related functions require this argument!
typedef struct omg_context *omg_context;

omg_error omg_setup_context(const char *path, const char *github_token,
Expand All @@ -34,6 +36,9 @@ omg_error omg_setup_context(const char *path, const char *github_token,
void omg_free_context(omg_context *ctx);
#define omg_auto_context omg_context __attribute__((cleanup(omg_free_context)))

// Internal usage.
CURL *omg__curl_handler(omg_context);

typedef struct {
int id;
const char *full_name;
Expand Down Expand Up @@ -253,10 +258,30 @@ typedef struct {
char *url;
} omg_discussion;

void omg_free_discussion(omg_discussion *);

#define omg_auto_discussion \
omg_discussion __attribute__((cleanup(omg_free_discussion)))

omg_error omg_create_discusstion(omg_context ctx, const char *repo_id,
const char *category_id, const char *title,
const char *body, omg_discussion *out);

typedef struct {
char *id;
char *name;
} omg_discussion_category;

typedef struct {
char *id;
omg_discussion_category *categories;
size_t len;
} omg_repo_discussion_category;

omg_error omg_query_repo_discussion_category(omg_context, const char *owner,
const char *name,
omg_repo_discussion_category *);

// Utils
omg_error omg_download(omg_context ctx, const char *url, const char *filename);

Expand Down
157 changes: 157 additions & 0 deletions core/omg.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const std = @import("std");
const c = @cImport({
@cInclude("omg.h");
});

const allocator = std.heap.c_allocator;
const ResponseBuffer = std.ArrayList(u8);
const GRAPHQL_API = "https://api.github.com/graphql";

// Copy from https://ziglang.org/learn/samples/#using-curl-from-zig
fn writeToArrayListCallback(data: *anyopaque, size: c_uint, nmemb: c_uint, user_data: *anyopaque) callconv(.C) c_uint {
var buffer: *ResponseBuffer = @alignCast(@ptrCast(user_data));
var typed_data: [*]u8 = @ptrCast(data);
buffer.appendSlice(typed_data[0 .. nmemb * size]) catch return 0;
return nmemb * size;
}

fn request(ctx: ?*c.struct_omg_context, url: [:0]const u8, method: [:0]const u8, payload: ?[:0]const u8) !ResponseBuffer {
const handle: ?*c.CURL = c.omg__curl_handler(ctx);
if (c.curl_easy_setopt(handle, c.CURLOPT_URL, url.ptr) != c.CURLE_OK)
return error.CouldNotSetURL;
if (c.curl_easy_setopt(handle, c.CURLOPT_CUSTOMREQUEST, method.ptr) != c.CURLE_OK)
return error.CouldNotSetMethod;

if (payload) |p| {
if (c.curl_easy_setopt(handle, c.CURLOPT_POSTFIELDS, p.ptr) != c.CURLE_OK) {
return error.CouldNotSetPayload;
}
}
var response_buffer = ResponseBuffer.init(allocator);

// _ = c.curl_easy_setopt(handle, c.CURLOPT_VERBOSE, @as(c_long, 10));

if (c.curl_easy_setopt(handle, c.CURLOPT_WRITEFUNCTION, writeToArrayListCallback) != c.CURLE_OK)
return error.CouldNotSetWriteCallback;
if (c.curl_easy_setopt(handle, c.CURLOPT_WRITEDATA, &response_buffer) != c.CURLE_OK)
return error.CouldNotSetWriteCallback;

// perform
if (c.curl_easy_perform(handle) != c.CURLE_OK)
return error.FailedToPerformRequest;

return response_buffer;
}

fn query_inner(ctx: ?*c.struct_omg_context, owner: []const u8, name: []const u8, diag: *c.omg_error) !c.omg_repo_discussion_category {
var q = try std.fmt.allocPrintZ(allocator,
\\ query {{
\\ repository(name: "{s}", owner: "{s}") {{
\\ id
\\ url
\\ discussionCategories(first:100) {{
\\ edges {{
\\ node {{
\\ id
\\ name
\\ slug
\\ }}
\\ }}
\\ }}
\\ }}
\\ }}
, .{ name, owner });
defer allocator.free(q);

var payload = std.ArrayList(u8).init(allocator);
defer payload.deinit();

try std.json.stringify(.{ .query = q }, .{}, payload.writer());
try payload.append(0);

const resp = try request(
ctx,
GRAPHQL_API,
"POST",
payload.items[0 .. payload.items.len - 1 :0],
);

const CategoryList = struct {
data: struct {
repository: ?struct {
id: []const u8,
discussionCategories: struct {
edges: []struct {
node: struct {
id: []const u8,
name: []const u8,
slug: []const u8,
},
},
},
} = null,
},
errors: ?[]struct {
message: []const u8,
} = null,
};

const json = try std.json.parseFromSlice(CategoryList, allocator, resp.items, .{
.ignore_unknown_fields = true,
});
defer json.deinit();

try std.json.stringify(json.value, .{
.whitespace = .{
.indent = .{ .space = 1 },
.separator = false,
},
}, std.io.getStdOut().writer());

if (json.value.errors) |e| {
const msg = try std.fmt.allocPrintZ(allocator, "{s}", .{e[0].message});
defer allocator.free(msg);

diag.* = c.new_error(c.OMG_CODE_GITHUB, msg);
return error.GitHubError;
}

if (json.value.data.repository) |repo| {
const repo_id = try std.fmt.allocPrintZ(allocator, "{s}", .{repo.id});
const len = repo.discussionCategories.edges.len;
var list = try allocator.alloc(c.omg_discussion_category, len);
for (repo.discussionCategories.edges, 0..) |edge, idx| {
list[idx] = c.omg_discussion_category{
.id = try std.fmt.allocPrintZ(allocator, "{s}", .{edge.node.id}),
.name = try std.fmt.allocPrintZ(allocator, "{s}", .{edge.node.name}),
};
}

return c.omg_repo_discussion_category{
.id = repo_id.ptr,
.categories = list.ptr,
.len = len,
};
} else {
return error.EmptyCategory;
}
}

export fn omg_query_repo_discussion_category(
ctx: c.omg_context,
owner: [*c]const u8,
name: [*c]const u8,
out: *c.omg_repo_discussion_category,
) c.omg_error {
var diag = c.omg_error{ .code = c.OMG_CODE_OK, .message = .{} };
const r = query_inner(ctx, std.mem.span(owner), std.mem.span(name), &diag) catch |e| {
if (c.is_ok(diag)) {
return c.new_error(c.OMG_CODE_INTERNAL, @errorName(e));
}

return diag;
};

out.* = r;
return c.omg_error{ .code = c.OMG_CODE_OK, .message = .{} };
}

0 comments on commit 2169cb9

Please sign in to comment.