gs

painless gist creator
git clone git://edryd.org/gs
Log | Files | Refs | LICENSE

gs.c (5391B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <curl/curl.h>
      3 #include <libgen.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <unistd.h>
      8 
      9 #include "extern/arg.h"
     10 #include "extern/frozen.h"
     11 #include "util.h"
     12 
     13 /* defines */
     14 /* TODO remove BUF_SIZEs */
     15 #define LBUF_SIZE 1024
     16 #define BUF_SIZE  100000
     17 #define URL_SIZE  2048
     18 
     19 /* typedefs */
     20 typedef struct {
     21 	char *ptr;
     22 	size_t len;
     23 } Str;
     24 
     25 /* variables */
     26 char *argv0;
     27 
     28 #include "config.h"
     29 
     30 /* used by cURL to write its response to a Str */
     31 static size_t
     32 str_write(void *ptr, size_t size, size_t nmemb, Str *s)
     33 {
     34 	size_t nlen = s->len + size*nmemb;
     35 	s->ptr = erealloc(s->ptr, nlen+1);
     36 	memcpy(s->ptr+s->len, ptr, size*nmemb);
     37 	s->ptr[nlen] = '\0';
     38 	s->len = nlen;
     39 
     40 	return size*nmemb;
     41 }
     42 
     43 /* HTTP POST request with content, returning response */
     44 static Str
     45 http_post(char *content)
     46 {
     47 	char *resmsg, url[URL_SIZE];
     48 	long code;
     49 	CURL *curl;
     50 	CURLcode res;
     51 	Str resstr;
     52 
     53 	resstr.len = 0;
     54 	resstr.ptr = emalloc(resstr.len+1);
     55 	resstr.ptr[0] = '\0';
     56 
     57 	/* init cURL */
     58 	curl_global_init(CURL_GLOBAL_ALL);
     59 	if (!(curl = curl_easy_init())) {
     60 		curl_global_cleanup();
     61 		die(1, "%s: cURL: could not init", argv0);
     62 	}
     63 
     64 	/* set cURL options */
     65 	if (gist)
     66 		snprintf(url, sizeof(url), "%s/%s", ghurl, gist);
     67 	else
     68 		strcpy(url, ghurl);
     69 	curl_easy_setopt(curl, CURLOPT_URL, url);
     70 	curl_easy_setopt(curl, CURLOPT_USERAGENT, "gs/"VERSION);
     71 	if (user) {
     72 		if (strchr(user, ':')) {
     73 			curl_easy_setopt(curl, CURLOPT_USERPWD, user);
     74 		} else {
     75 			curl_easy_setopt(curl, CURLOPT_USERNAME, user);
     76 			curl_easy_setopt(curl, CURLOPT_PASSWORD, getpass("GitHub password: "));
     77 		}
     78 	}
     79 	if (gist)
     80 		curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
     81 	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, content);
     82 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, str_write);
     83 	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resstr);
     84 
     85 	/* run cURL */
     86 	if ((res = curl_easy_perform(curl)) != CURLE_OK) {
     87 		curl_global_cleanup();
     88 		die(1, "%s: cURL: %s", argv0, curl_easy_strerror(res));
     89 	}
     90 
     91 	/* response checking and cleanup */
     92 	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
     93 	curl_easy_cleanup(curl);
     94 	curl_global_cleanup();
     95 	/* 200 returned when editing gist, 201 when creating */
     96 	if (code != (gist ? 200 : 201)) {
     97 		json_scanf(resstr.ptr, resstr.len, "{message: %Q}", &resmsg);
     98 		die(-1, "%s: [%d] could not create Gist: %s",
     99 		         argv0, code, resmsg);
    100 		free(resmsg);
    101 		exit(1);
    102 	}
    103 
    104 	return resstr;
    105 }
    106 
    107 /* read file fp into returned string */
    108 static char *
    109 file_str(FILE *fp)
    110 {
    111 	char lbuf[LBUF_SIZE];                         /* buffer for each line */
    112 	char *str = ecalloc(LBUF_SIZE, sizeof(char)); /* complete file */
    113 	long flen = 1;                                /* file length */
    114 
    115 	/* loop through each line in the file, append it to str */
    116 	while (fgets(lbuf, LBUF_SIZE, fp)) {
    117 		flen += strlen(lbuf);
    118 		str = erealloc(str, flen);
    119 		strcat(str, lbuf);
    120 	}
    121 
    122 	return str;
    123 }
    124 
    125 /* turn list of file names into returned json string */
    126 static char *
    127 files_js(char *files[], int filec)
    128 {
    129 	char *fbuf; /* file contents */
    130 	char *js = emalloc(BUF_SIZE*sizeof(char)); /* json string returned */
    131 	FILE *fp = stdin; /* read from stdin by default if no file is given */
    132 	struct json_out jout = JSON_OUT_BUF(js, BUF_SIZE);
    133 
    134 	json_printf(&jout, "{ public: %B,", pub);
    135 	if (!desc && !gist) /* when creating new gist if no description given make it empty */
    136 		desc = "";
    137 	if (desc) /* only add description as blank if gist is being created */
    138 		json_printf(&jout, "description: %Q, ", desc);
    139 	json_printf(&jout, "files: {");
    140 
    141 	if (del) /* delete file if given by setting it to NULL */
    142 		json_printf(&jout, "%Q: %Q", basename(del), NULL);
    143 
    144 	/* add each file */
    145 	for (int i = 0; !i || i < filec; i++) {
    146 		if (filec && !(fp = fopen(files[i], "r")))
    147 			die(1, "%s: %s: could not load file", argv0, files[i]);
    148 		if (filec) /* set file name if given */
    149 			fname = files[i];
    150 		if (!fname && gist) /* don't need file if we are editing gist */
    151 			break;
    152 		if (!fname) /* check for file name when using stdin */
    153 			die(1, "%s: file name not given", argv0);
    154 		if (i || del) /* insert comma if this is another file */
    155 			json_printf(&jout, ",");
    156 		fbuf = file_str(fp);
    157 		json_printf(&jout, "%Q: { content: %Q }", basename(fname), fbuf);
    158 		free(fbuf);
    159 	}
    160 
    161 	json_printf(&jout, "} }");
    162 
    163 	return js;
    164 }
    165 
    166 static void
    167 usage(const int eval)
    168 {
    169 	die(eval, "usage: %s [-pPhv] [-e ID [-D FILE]] [-d DESCRIPTION] [-f FILENAME]\n"
    170 	          "          [-g URL] [-u USER[:PASSWORD] | -U] FILES ...", argv0);
    171 }
    172 
    173 int
    174 main(int argc, char *argv[])
    175 {
    176 	char *js, *url;
    177 	Str resstr;
    178 
    179 	ARGBEGIN {
    180 	case 'e':
    181 		gist = EARGF(usage(1));
    182 		break;
    183 	case 'd':
    184 		desc = EARGF(usage(1));
    185 		break;
    186 	case 'D':
    187 		del = EARGF(usage(1));
    188 		break;
    189 	case 'f':
    190 		fname = EARGF(usage(1));
    191 		break;
    192 	case 'g':
    193 		ghurl = EARGF(usage(1));
    194 		break;
    195 	case 'p':
    196 		pub = 0;
    197 		break;
    198 	case 'P':
    199 		pub = 1;
    200 		break;
    201 	case 'u':
    202 		user = EARGF(usage(1));
    203 		break;
    204 	case 'U':
    205 		user = NULL;
    206 		break;
    207 	case 'h':
    208 		usage(0);
    209 	case 'v':
    210 		printf("%s v%s (c) 2017 Ed van Bruggen\n", argv0, VERSION);
    211 		return 0;
    212 	default:
    213 		usage(1);
    214 	} ARGEND;
    215 
    216 	if (gist && !user)
    217 		die(1, "%s: cannot edit Gists without user", argv0);
    218 
    219 	js = files_js(argv, argc);
    220 	resstr = http_post(js);
    221 
    222 	json_scanf(resstr.ptr, resstr.len, "{html_url: %Q}", &url);
    223 	puts(url);
    224 
    225 	free(js);
    226 	free(url);
    227 	free(resstr.ptr);
    228 
    229 	return 0;
    230 }