gs

Unnamed repository; edit this file 'description' to name the repository.
git clone git://edryd.org/gs
Log | Files | Refs | LICENSE

commit 2421c0ce4f3dc5eb51dce55bf02992b5201d7fc2
Author: Ed van Bruggen <edvb54@gmail.com>
Date:   Wed, 26 Jul 2017 20:41:35 -0700

Initial commit

Diffstat:
.gitignore | 21+++++++++++++++++++++
LICENSE | 19+++++++++++++++++++
Makefile | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
arg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 4++++
config.mk | 18++++++++++++++++++
frozen.c | 1001+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frozen.h | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
gs.1 | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
gs.c | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
util.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
util.h | 14++++++++++++++
13 files changed, 1917 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,21 @@ +# swap files +*~ +*.swp +*.swo +\#*\# + +# compiled files +gs +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.o +*.obj +*.so +*.so.* + +# misc +tags +config.h diff --git a/LICENSE b/LICENSE @@ -0,0 +1,19 @@ +zlib License + +Copyright (c) 2017 Ed van Bruggen + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/Makefile b/Makefile @@ -0,0 +1,66 @@ +# gs - gist creator +# See LICENSE file for copyright and license details. + +include config.mk + +EXE = gs +SRC = $(wildcard *.c) +OBJ = $(SRC:.c=.o) + +all: options $(EXE) + +options: + @echo $(EXE) build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + +.o: + @echo $(LD) $@ + @$(LD) -o $@ $< $(LDFLAGS) + +.c.o: + @echo $(CC) $< + @$(CC) -c -o $@ $< $(CFLAGS) + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +$(EXE): $(OBJ) + @echo $(CC) -o $@ + @$(CC) -o $@ $(OBJ) $(LDFLAGS) + +clean: + @echo -n cleaning ... + @rm -f $(OBJ) $(EXE) + @echo \ done + +install: all + @echo -n installing $(EXE) to $(DESTDIR)$(PREFIX)/bin ... + @mkdir -p $(DESTDIR)$(PREFIX)/bin + @cp -f $(EXE) $(DESTDIR)$(PREFIX)/bin + @chmod 755 $(DESTDIR)$(PREFIX)/bin/$(EXE) + @echo \ done + @echo -n installing manual page to $(DESTDIR)$(MANPREFIX)/man1 ... + @mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + @sed "s/VERSION/$(VERSION)/g" < $(EXE).1 > $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @echo \ done + +uninstall: + @echo -n removing $(EXE) from $(DESTDIR)$(PREFIX)/bin ... + @rm -f $(DESTDIR)$(PREFIX)/bin/$(EXE) + @echo \ done + @echo -n removing manual page from $(DESTDIR)$(MANPREFIX)/man1 ... + @rm -f $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @echo \ done + +man: + @echo -n updating man page $(EXE).1 ... + @cat README.md | sed "s/# $(EXE)/# $(EXE) 1\n\n##NAME\n\n$(EXE) /" | \ + md2man-roff | sed "s/\\[la\]/\</" | sed "s/\\[ra\]/\>/" > $(EXE).1 + @echo \ done + +.PHONY: all options clean install uninstall man diff --git a/README.md b/README.md @@ -0,0 +1,96 @@ +# gs - gist creator + +## SYNOPSIS + +`gs` [**-pPhv**] [**-d** *DESCRIPTION*] [**-f** *FILENAME*] [**-u** *USER*[:*PASSWORD*] | **-U**] [*FILES* ...] + +## DESCRIPTION + +Easy way to create Github gists with the command line. + +## OPTIONS + +**-d** *DESCRIPTION* + Set gist description + +**-f** *FILENAME* + Set file name when reading from `stdin` + +**-p** + Make gist private + +**-P** + Make gist public (default) + +**-u** *USER*[:*PASSWORD*] + Change the Github account the gist will be posted under. A password can + given as well with a separating colon, a prompt is provided if not. + +**-U** + Post gist anonymously (default) + +**-h** + Print help and exit + +**-v** + Print version info and exit + +## USAGE + +Create a new gist of file `file.txt`: + + $ gs file.txt + https://gist.github.com/<new-id> + +There is also support for multiple files in a single gist: + + $ gs README.md Makefile prog.c + https://gist.github.com/<new-id> + +Specify the gist's description: + + $ gs -d 'a cool shell script' script.sh + https://gist.github.com/<new-id> + +The gist's URL can be piped to other programs, for example to your clipboard to +be pasted else where: + + $ gs file.txt | xsel -bi + +You can also create a new gist from `stdin`. The file name needs to be supplied +however: + + $ cmd-which-errors | gs -f log.txt + https://gist.github.com/<new-id> + +New gists can be private as well: + + $ gs -p personal.info + https://gist.github.com/<new-id> + +The new gist can be posted under a Github user, a prompt will ask for your +password: + + $ gs -u your-name good-proj.rs + Github password: + https://gist.github.com/<new-id> + +To skip the prompt your password can be supplied after a colon with the +username: + + $ gs -u your-name:password plugin/func.vim doc/func.txt + https://gist.github.com/<new-id> + +## AUTHOR + +Ed van Bruggen <edvb54@gmail.com> + +## SEE ALSO + +See project page at: <http://edryd.org/projects/gs.html> + +View source code at: <https://github.com/edvb/gs> + +## LICENSE + +zlib License diff --git a/arg.h b/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = basename(*argv), argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, atoi(argv[0])) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.def.h b/config.def.h @@ -0,0 +1,4 @@ +char *desc = ""; /* default gist description */ +char *fname = NULL; /* default gist filename when reading from stdin */ +char *user = NULL; /* default user, password can be given after colon */ +int pub = 1; /* 0: private need link to view, 1: public anyone can find */ diff --git a/config.mk b/config.mk @@ -0,0 +1,18 @@ +# gs version number +VERSION = 0.0.0 + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +# includes and libraries +INCS = -Iinclude +LIBS = -lcurl + +# flags +CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +CFLAGS = -g -std=c99 -pedantic -Wall ${INCS} ${CPPFLAGS} +LDFLAGS = -g ${LIBS} + +# compiler and linker +CC = gcc diff --git a/frozen.c b/frozen.c @@ -0,0 +1,1001 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> + * Copyright (c) 2013 Cesanta Software Limited + * All rights reserved + * + * This library is dual-licensed: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. For the terms of this + * license, see <http: *www.gnu.org/licenses/>. + * + * You are free to use this library under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * Alternatively, you can license this library under a commercial + * license, as set out in <http://cesanta.com/products.html>. + */ + +#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */ + +#include "frozen.h" +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if !defined(WEAK) +#if (defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif +#endif + +#ifdef _WIN32 +#define snprintf cs_win_snprintf +#define vsnprintf cs_win_vsnprintf +int cs_win_snprintf(char *str, size_t size, const char *format, ...); +int cs_win_vsnprintf(char *str, size_t size, const char *format, va_list ap); +#if _MSC_VER >= 1700 +#include <stdint.h> +#else +typedef _int64 int64_t; +typedef unsigned _int64 uint64_t; +#endif +#define PRId64 "I64d" +#define PRIu64 "I64u" +#if !defined(SIZE_T_FMT) +#if _MSC_VER >= 1310 +#define SIZE_T_FMT "Iu" +#else +#define SIZE_T_FMT "u" +#endif +#endif +#else /* _WIN32 */ +/* <inttypes.h> wants this for C++ */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include <inttypes.h> +#if !defined(SIZE_T_FMT) +#define SIZE_T_FMT "zu" +#endif +#endif /* _WIN32 */ + +#define INT64_FMT PRId64 +#define UINT64_FMT PRIu64 + +#ifndef va_copy +#define va_copy(x, y) x = y +#endif + +#ifndef JSON_MAX_PATH_LEN +#define JSON_MAX_PATH_LEN 60 +#endif + +struct frozen { + const char *end; + const char *cur; + + const char *cur_name; + size_t cur_name_len; + + /* For callback API */ + char path[JSON_MAX_PATH_LEN]; + size_t path_len; + void *callback_data; + json_walk_callback_t callback; +}; + +struct fstate { + const char *ptr; + size_t path_len; +}; + +#define SET_STATE(fr, ptr, str, len) \ + struct fstate fstate = {(ptr), (fr)->path_len}; \ + append_to_path((fr), (str), (len)); + +#define CALL_BACK(fr, tok, value, len) \ + do { \ + if ((fr)->callback && \ + ((fr)->path_len == 0 || (fr)->path[(fr)->path_len - 1] != '.')) { \ + struct json_token t = {(value), (len), (tok)}; \ + \ + /* Call the callback with the given value and current name */ \ + (fr)->callback((fr)->callback_data, (fr)->cur_name, (fr)->cur_name_len, \ + (fr)->path, &t); \ + \ + /* Reset the name */ \ + (fr)->cur_name = NULL; \ + (fr)->cur_name_len = 0; \ + } \ + } while (0) + +static int append_to_path(struct frozen *f, const char *str, int size) { + int n = f->path_len; + f->path_len += + snprintf(f->path + f->path_len, sizeof(f->path) - (f->path_len), "%.*s", size, str); + if (f->path_len > sizeof(f->path) - 1) { + f->path_len = sizeof(f->path) - 1; + } + + return n; +} + +static void truncate_path(struct frozen *f, size_t len) { + f->path_len = len; + f->path[len] = '\0'; +} + +static int parse_object(struct frozen *f); +static int parse_value(struct frozen *f); + +#define EXPECT(cond, err_code) \ + do { \ + if (!(cond)) return (err_code); \ + } while (0) + +#define TRY(expr) \ + do { \ + int _n = expr; \ + if (_n < 0) return _n; \ + } while (0) + +#define END_OF_STRING (-1) + +static int left(const struct frozen *f) { + return f->end - f->cur; +} + +static int is_space(int ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; +} + +static void skip_whitespaces(struct frozen *f) { + while (f->cur < f->end && is_space(*f->cur)) f->cur++; +} + +static int cur(struct frozen *f) { + skip_whitespaces(f); + return f->cur >= f->end ? END_OF_STRING : *(unsigned char *) f->cur; +} + +static int test_and_skip(struct frozen *f, int expected) { + int ch = cur(f); + if (ch == expected) { + f->cur++; + return 0; + } + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; +} + +static int is_alpha(int ch) { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); +} + +static int is_digit(int ch) { + return ch >= '0' && ch <= '9'; +} + +static int is_hex_digit(int ch) { + return is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +} + +static int get_escape_len(const char *s, int len) { + switch (*s) { + case 'u': + return len < 6 ? JSON_STRING_INCOMPLETE + : is_hex_digit(s[1]) && is_hex_digit(s[2]) && + is_hex_digit(s[3]) && is_hex_digit(s[4]) + ? 5 + : JSON_STRING_INVALID; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + return len < 2 ? JSON_STRING_INCOMPLETE : 1; + default: + return JSON_STRING_INVALID; + } +} + +/* identifier = letter { letter | digit | '_' } */ +static int parse_identifier(struct frozen *f) { + EXPECT(is_alpha(cur(f)), JSON_STRING_INVALID); + { + SET_STATE(f, f->cur, "", 0); + while (f->cur < f->end && + (*f->cur == '_' || is_alpha(*f->cur) || is_digit(*f->cur))) { + f->cur++; + } + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); + } + return 0; +} + +static int get_utf8_char_len(unsigned char ch) { + if ((ch & 0x80) == 0) return 1; + switch (ch & 0xf0) { + case 0xf0: + return 4; + case 0xe0: + return 3; + default: + return 2; + } +} + +/* string = '"' { quoted_printable_chars } '"' */ +static int parse_string(struct frozen *f) { + int n, ch = 0, len = 0; + TRY(test_and_skip(f, '"')); + { + SET_STATE(f, f->cur, "", 0); + for (; f->cur < f->end; f->cur += len) { + ch = *(unsigned char *) f->cur; + len = get_utf8_char_len((unsigned char) ch); + EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */ + EXPECT(len < left(f), JSON_STRING_INCOMPLETE); + if (ch == '\\') { + EXPECT((n = get_escape_len(f->cur + 1, left(f))) > 0, n); + len += n; + } else if (ch == '"') { + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); + f->cur++; + break; + }; + } + } + return ch == '"' ? 0 : JSON_STRING_INCOMPLETE; +} + +/* number = [ '-' ] digit+ [ '.' digit+ ] [ ['e'|'E'] ['+'|'-'] digit+ ] */ +static int parse_number(struct frozen *f) { + int ch = cur(f); + SET_STATE(f, f->cur, "", 0); + if (ch == '-') f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID); + while (f->cur < f->end && is_digit(f->cur[0])) f->cur++; + if (f->cur < f->end && f->cur[0] == '.') { + f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID); + while (f->cur < f->end && is_digit(f->cur[0])) f->cur++; + } + if (f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) { + f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + if ((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID); + while (f->cur < f->end && is_digit(f->cur[0])) f->cur++; + } + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_NUMBER, fstate.ptr, f->cur - fstate.ptr); + return 0; +} + +/* array = '[' [ value { ',' value } ] ']' */ +static int parse_array(struct frozen *f) { + int i = 0, current_path_len; + char buf[20]; + TRY(test_and_skip(f, '[')); + { + CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); + { + SET_STATE(f, f->cur - 1, "", 0); + while (cur(f) != ']') { + snprintf(buf, sizeof(buf), "[%d]", i); + i++; + current_path_len = append_to_path(f, buf, strlen(buf)); + f->cur_name = + f->path + strlen(f->path) - strlen(buf) + 1 /*opening brace*/; + f->cur_name_len = strlen(buf) - 2 /*braces*/; + TRY(parse_value(f)); + truncate_path(f, current_path_len); + if (cur(f) == ',') f->cur++; + } + TRY(test_and_skip(f, ']')); + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_ARRAY_END, fstate.ptr, f->cur - fstate.ptr); + } + } + return 0; +} + +static int expect(struct frozen *f, const char *s, int len, + enum json_token_type tok_type) { + int i, n = left(f); + SET_STATE(f, f->cur, "", 0); + for (i = 0; i < len; i++) { + if (i >= n) return JSON_STRING_INCOMPLETE; + if (f->cur[i] != s[i]) return JSON_STRING_INVALID; + } + f->cur += len; + truncate_path(f, fstate.path_len); + + CALL_BACK(f, tok_type, fstate.ptr, f->cur - fstate.ptr); + + return 0; +} + +/* value = 'null' | 'true' | 'false' | number | string | array | object */ +static int parse_value(struct frozen *f) { + int ch = cur(f); + + switch (ch) { + case '"': + TRY(parse_string(f)); + break; + case '{': + TRY(parse_object(f)); + break; + case '[': + TRY(parse_array(f)); + break; + case 'n': + TRY(expect(f, "null", 4, JSON_TYPE_NULL)); + break; + case 't': + TRY(expect(f, "true", 4, JSON_TYPE_TRUE)); + break; + case 'f': + TRY(expect(f, "false", 5, JSON_TYPE_FALSE)); + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + TRY(parse_number(f)); + break; + default: + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + + return 0; +} + +/* key = identifier | string */ +static int parse_key(struct frozen *f) { + int ch = cur(f); +#if 0 + printf("%s [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur); +#endif + if (is_alpha(ch)) { + TRY(parse_identifier(f)); + } else if (ch == '"') { + TRY(parse_string(f)); + } else { + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + return 0; +} + +/* pair = key ':' value */ +static int parse_pair(struct frozen *f) { + int current_path_len; + const char *tok; + skip_whitespaces(f); + tok = f->cur; + TRY(parse_key(f)); + { + f->cur_name = *tok == '"' ? tok + 1 : tok; + f->cur_name_len = *tok == '"' ? f->cur - tok - 2 : f->cur - tok; + current_path_len = append_to_path(f, f->cur_name, f->cur_name_len); + } + TRY(test_and_skip(f, ':')); + TRY(parse_value(f)); + truncate_path(f, current_path_len); + return 0; +} + +/* object = '{' pair { ',' pair } '}' */ +static int parse_object(struct frozen *f) { + TRY(test_and_skip(f, '{')); + { + CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); + { + SET_STATE(f, f->cur - 1, ".", 1); + while (cur(f) != '}') { + TRY(parse_pair(f)); + if (cur(f) == ',') f->cur++; + } + TRY(test_and_skip(f, '}')); + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); + } + } + return 0; +} + +static int doit(struct frozen *f) { + if (f->cur == 0 || f->end < f->cur) return JSON_STRING_INVALID; + if (f->end == f->cur) return JSON_STRING_INCOMPLETE; + return parse_value(f); +} + +int json_escape(struct json_out *out, const char *p, size_t len) WEAK; +int json_escape(struct json_out *out, const char *p, size_t len) { + size_t i, cl, n = 0; + const char *hex_digits = "0123456789abcdef"; + const char *specials = "btnvfr"; + + for (i = 0; i < len; i++) { + unsigned char ch = ((unsigned char *) p)[i]; + if (ch == '"' || ch == '\\') { + n += out->printer(out, "\\", 1); + n += out->printer(out, p + i, 1); + } else if (ch >= '\b' && ch <= '\r') { + n += out->printer(out, "\\", 1); + n += out->printer(out, &specials[ch - '\b'], 1); + } else if (isprint(ch)) { + n += out->printer(out, p + i, 1); + } else if ((cl = get_utf8_char_len(ch)) == 1) { + n += out->printer(out, "\\u00", 4); + n += out->printer(out, &hex_digits[(ch >> 4) % 0xf], 1); + n += out->printer(out, &hex_digits[ch % 0xf], 1); + } else { + n += out->printer(out, p + i, cl); + i += cl - 1; + } + } + + return n; +} + +int json_printer_buf(struct json_out *out, const char *buf, size_t len) WEAK; +int json_printer_buf(struct json_out *out, const char *buf, size_t len) { + size_t avail = out->u.buf.size - out->u.buf.len; + size_t n = len < avail ? len : avail; + memcpy(out->u.buf.buf + out->u.buf.len, buf, n); + out->u.buf.len += n; + if (out->u.buf.size > 0) { + size_t idx = out->u.buf.len; + if (idx >= out->u.buf.size) idx = out->u.buf.size - 1; + out->u.buf.buf[idx] = '\0'; + } + return len; +} + +int json_printer_file(struct json_out *out, const char *buf, size_t len) WEAK; +int json_printer_file(struct json_out *out, const char *buf, size_t len) { + return fwrite(buf, 1, len, out->u.fp); +} + +static int b64idx(int c) { + if (c < 26) { + return c + 'A'; + } else if (c < 52) { + return c - 26 + 'a'; + } else if (c < 62) { + return c - 52 + '0'; + } else { + return c == 62 ? '+' : '/'; + } +} + +static int b64rev(int c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c + 26 - 'a'; + } else if (c >= '0' && c <= '9') { + return c + 52 - '0'; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else { + return 64; + } +} + +static uint8_t hexdec(const char *s) { +#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W') + int a = tolower(*(const unsigned char *) s); + int b = tolower(*(const unsigned char *) (s + 1)); + return (HEXTOI(a) << 4) | HEXTOI(b); +} + +static int b64enc(struct json_out *out, const unsigned char *p, int n) { + char buf[4]; + int i, len = 0; + for (i = 0; i < n; i += 3) { + int a = p[i], b = i + 1 < n ? p[i + 1] : 0, c = i + 2 < n ? p[i + 2] : 0; + buf[0] = b64idx(a >> 2); + buf[1] = b64idx((a & 3) << 4 | (b >> 4)); + buf[2] = b64idx((b & 15) << 2 | (c >> 6)); + buf[3] = b64idx(c & 63); + if (i + 1 >= n) buf[2] = '='; + if (i + 2 >= n) buf[3] = '='; + len += out->printer(out, buf, sizeof(buf)); + } + return len; +} + +static int b64dec(const char *src, int n, char *dst) { + const char *end = src + n; + int len = 0; + while (src + 3 < end) { + int a = b64rev(src[0]), b = b64rev(src[1]), c = b64rev(src[2]), + d = b64rev(src[3]); + dst[len++] = (a << 2) | (b >> 4); + if (src[2] != '=') { + dst[len++] = (b << 4) | (c >> 2); + if (src[3] != '=') { + dst[len++] = (c << 6) | d; + } + } + src += 4; + } + return len; +} + +int json_vprintf(struct json_out *out, const char *fmt, va_list xap) WEAK; +int json_vprintf(struct json_out *out, const char *fmt, va_list xap) { + int len = 0; + const char *quote = "\"", *null = "null"; + va_list ap; + va_copy(ap, xap); + + while (*fmt != '\0') { + if (strchr(":, \r\n\t[]{}\"", *fmt) != NULL) { + len += out->printer(out, fmt, 1); + fmt++; + } else if (fmt[0] == '%') { + char buf[21]; + size_t skip = 2; + + if (fmt[1] == 'l' && fmt[2] == 'l' && (fmt[3] == 'd' || fmt[3] == 'u')) { + int64_t val = va_arg(ap, int64_t); + const char *fmt2 = fmt[3] == 'u' ? "%" UINT64_FMT : "%" INT64_FMT; + snprintf(buf, sizeof(buf), fmt2, val); + len += out->printer(out, buf, strlen(buf)); + skip += 2; + } else if (fmt[1] == 'z' && fmt[2] == 'u') { + size_t val = va_arg(ap, size_t); + snprintf(buf, sizeof(buf), "%" SIZE_T_FMT, val); + len += out->printer(out, buf, strlen(buf)); + skip += 1; + } else if (fmt[1] == 'M') { + json_printf_callback_t f = va_arg(ap, json_printf_callback_t); + len += f(out, &ap); + } else if (fmt[1] == 'B') { + int val = va_arg(ap, int); + const char *str = val ? "true" : "false"; + len += out->printer(out, str, strlen(str)); + } else if (fmt[1] == 'H') { + const char *hex = "0123456789abcdef"; + int i, n = va_arg(ap, int); + const unsigned char *p = va_arg(ap, const unsigned char *); + len += out->printer(out, quote, 1); + for (i = 0; i < n; i++) { + len += out->printer(out, &hex[(p[i] >> 4) & 0xf], 1); + len += out->printer(out, &hex[p[i] & 0xf], 1); + } + len += out->printer(out, quote, 1); + } else if (fmt[1] == 'V') { + const unsigned char *p = va_arg(ap, const unsigned char *); + int n = va_arg(ap, int); + len += out->printer(out, quote, 1); + len += b64enc(out, p, n); + len += out->printer(out, quote, 1); + } else if (fmt[1] == 'Q' || + (fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 'Q')) { + size_t l = 0; + const char *p; + + if (fmt[1] == '.') { + l = (size_t) va_arg(ap, int); + skip += 2; + } + p = va_arg(ap, char *); + + if (p == NULL) { + len += out->printer(out, null, 4); + } else { + if (fmt[1] == 'Q') { + l = strlen(p); + } + len += out->printer(out, quote, 1); + len += json_escape(out, p, l); + len += out->printer(out, quote, 1); + } + } else { + /* + * we delegate printing to the system printf. + * The goal here is to delegate all modifiers parsing to the system + * printf, as you can see below we still have to parse the format + * types. + * + * Currently, %s with strings longer than 20 chars will require + * double-buffering (an auxiliary buffer will be allocated from heap). + * TODO(dfrank): reimplement %s and %.*s in order to avoid that. + */ + + const char *end_of_format_specifier = "sdfFgGlhuI.*-0123456789"; + size_t n = strspn(fmt + 1, end_of_format_specifier); + char *pbuf = buf; + size_t need_len; + char fmt2[20]; + va_list sub_ap; + strncpy(fmt2, fmt, n + 1 > sizeof(fmt2) ? sizeof(fmt2) : n + 1); + fmt2[n + 1] = '\0'; + + va_copy(sub_ap, ap); + need_len = + vsnprintf(buf, sizeof(buf), fmt2, sub_ap) + 1 /* null-term */; + /* + * TODO(lsm): Fix windows & eCos code path here. Their vsnprintf + * implementation returns -1 on overflow rather needed size. + */ + if (need_len > sizeof(buf)) { + /* + * resulting string doesn't fit into a stack-allocated buffer `buf`, + * so we need to allocate a new buffer from heap and use it + */ + pbuf = (char *) malloc(need_len); + va_copy(sub_ap, ap); + vsnprintf(pbuf, need_len, fmt2, sub_ap); + } + + /* + * however we need to parse the type ourselves in order to advance + * the va_list by the correct amount; there is no portable way to + * inherit the advancement made by vprintf. + * 32-bit (linux or windows) passes va_list by value. + */ + if ((n + 1 == strlen("%" PRId64) && strcmp(fmt2, "%" PRId64) == 0) || + (n + 1 == strlen("%" PRIu64) && strcmp(fmt2, "%" PRIu64) == 0)) { + (void) va_arg(ap, int64_t); + skip += strlen(PRIu64) - 1; + } else if (strcmp(fmt2, "%.*s") == 0) { + (void) va_arg(ap, int); + (void) va_arg(ap, char *); + } else { + switch (fmt2[n]) { + case 'u': + case 'd': + (void) va_arg(ap, int); + break; + case 'g': + case 'f': + (void) va_arg(ap, double); + break; + case 'p': + (void) va_arg(ap, void *); + break; + default: + /* many types are promoted to int */ + (void) va_arg(ap, int); + } + } + + len += out->printer(out, pbuf, strlen(pbuf)); + skip = n + 1; + + /* If buffer was allocated from heap, free it */ + if (pbuf != buf) { + free(pbuf); + pbuf = NULL; + } + } + fmt += skip; + } else if (*fmt == '_' || is_alpha(*fmt)) { + len += out->printer(out, quote, 1); + while (*fmt == '_' || is_alpha(*fmt) || is_digit(*fmt)) { + len += out->printer(out, fmt, 1); + fmt++; + } + len += out->printer(out, quote, 1); + } else { + fmt++; + } + } + va_end(ap); + + return len; +} + +int json_printf(struct json_out *out, const char *fmt, ...) WEAK; +int json_printf(struct json_out *out, const char *fmt, ...) { + int n; + va_list ap; + va_start(ap, fmt); + n = json_vprintf(out, fmt, ap); + va_end(ap); + return n; +} + +int json_printf_array(struct json_out *out, va_list *ap) WEAK; +int json_printf_array(struct json_out *out, va_list *ap) { + int len = 0; + char *arr = va_arg(*ap, char *); + size_t i, arr_size = va_arg(*ap, size_t); + size_t elem_size = va_arg(*ap, size_t); + const char *fmt = va_arg(*ap, char *); + len += json_printf(out, "[", 1); + for (i = 0; arr != NULL && i < arr_size / elem_size; i++) { + union { + int64_t i; + double d; + } val; + memcpy(&val, arr + i * elem_size, + elem_size > sizeof(val) ? sizeof(val) : elem_size); + if (i > 0) len += json_printf(out, ", "); + if (strchr(fmt, 'f') != NULL) { + len += json_printf(out, fmt, val.d); + } else { + len += json_printf(out, fmt, val.i); + } + } + len += json_printf(out, "]", 1); + return len; +} + +#ifdef _WIN32 +int cs_win_vsnprintf(char *str, size_t size, const char *format, va_list ap) WEAK; +int cs_win_vsnprintf(char *str, size_t size, const char *format, va_list ap) { + int res = _vsnprintf(str, size, format, ap); + va_end(ap); + if (res >= size) { + str[size - 1] = '\0'; + } + return res; +} + +int cs_win_snprintf(char *str, size_t size, const char *format, ...) WEAK; +int cs_win_snprintf(char *str, size_t size, const char *format, ...) { + int res; + va_list ap; + va_start(ap, format); + res = vsnprintf(str, size, format, ap); + va_end(ap); + return res; +} +#endif /* _WIN32 */ + +int json_walk(const char *json_string, int json_string_length, + json_walk_callback_t callback, void *callback_data) WEAK; +int json_walk(const char *json_string, int json_string_length, + json_walk_callback_t callback, void *callback_data) { + struct frozen frozen; + + memset(&frozen, 0, sizeof(frozen)); + frozen.end = json_string + json_string_length; + frozen.cur = json_string; + frozen.callback_data = callback_data; + frozen.callback = callback; + + TRY(doit(&frozen)); + + return frozen.cur - json_string; +} + +struct scan_array_info { + char path[JSON_MAX_PATH_LEN]; + struct json_token *token; +}; + +static void json_scanf_array_elem_cb(void *callback_data, const char *name, + size_t name_len, const char *path, + const struct json_token *token) { + struct scan_array_info *info = (struct scan_array_info *) callback_data; + + (void) name; + (void) name_len; + + if (strcmp(path, info->path) == 0) { + *info->token = *token; + } +} + +int json_scanf_array_elem(const char *s, int len, const char *path, int idx, + struct json_token *token) WEAK; +int json_scanf_array_elem(const char *s, int len, const char *path, int idx, + struct json_token *token) { + struct scan_array_info info; + info.token = token; + memset(token, 0, sizeof(*token)); + snprintf(info.path, sizeof(info.path), "%s[%d]", path, idx); + json_walk(s, len, json_scanf_array_elem_cb, &info); + return token->len; +} + +struct json_scanf_info { + int num_conversions; + char *path; + const char *fmt; + void *target; + void *user_data; + int type; +}; + +int json_unescape(const char *src, int slen, char *dst, int dlen) WEAK; +int json_unescape(const char *src, int slen, char *dst, int dlen) { + char *send = (char *) src + slen, *dend = dst + dlen, *orig_dst = dst, *p; + const char *esc1 = "\"\\/bfnrt", *esc2 = "\"\\/\b\f\n\r\t"; + + while (src < send) { + if (*src == '\\') { + if (++src >= send) return JSON_STRING_INCOMPLETE; + if (*src == 'u') { + /* TODO(lsm): \uXXXX escapes drag utf8 lib... Do it at some stage */ + return JSON_STRING_INVALID; + } else if ((p = (char *) strchr(esc1, *src)) != NULL) { + if (dst < dend) *dst = esc2[p - esc1]; + } else { + return JSON_STRING_INVALID; + } + } else { + if (dst < dend) *dst = *src; + } + dst++; + src++; + } + + return dst - orig_dst; +} + +static void json_scanf_cb(void *callback_data, const char *name, + size_t name_len, const char *path, + const struct json_token *token) { + struct json_scanf_info *info = (struct json_scanf_info *) callback_data; + + (void) name; + (void) name_len; + + if (strcmp(path, info->path) != 0) { + /* It's not the path we're looking for, so, just ignore this callback */ + return; + } + + if (token->ptr == NULL) { + /* + * We're not interested here in the events for which we have no value; + * namely, JSON_TYPE_OBJECT_START and JSON_TYPE_ARRAY_START + */ + return; + } + + switch (info->type) { + case 'B': + info->num_conversions++; + *(int *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + break; + case 'M': { + union { + void *p; + json_scanner_t f; + } u = {info->target}; + info->num_conversions++; + u.f(token->ptr, token->len, info->user_data); + break; + } + case 'Q': { + char **dst = (char **) info->target; + if (token->type == JSON_TYPE_NULL) { + *dst = NULL; + } else { + int unescaped_len = json_unescape(token->ptr, token->len, NULL, 0); + if (unescaped_len >= 0 && + (*dst = (char *) malloc(unescaped_len + 1)) != NULL) { + info->num_conversions++; + json_unescape(token->ptr, token->len, *dst, unescaped_len); + (*dst)[unescaped_len] = '\0'; + } + } + break; + } + case 'H': { + char **dst = (char **) info->user_data; + int i, len = token->len / 2; + *(int *) info->target = len; + if ((*dst = (char *) malloc(len + 1)) != NULL) { + for (i = 0; i < len; i++) { + (*dst)[i] = hexdec(token->ptr + 2 * i); + } + (*dst)[len] = '\0'; + info->num_conversions++; + } + break; + } + case 'V': { + char **dst = (char **) info->target; + int len = token->len * 4 / 3 + 2; + if ((*dst = (char *) malloc(len + 1)) != NULL) { + int n = b64dec(token->ptr, token->len, *dst); + (*dst)[n] = '\0'; + *(int *) info->user_data = n; + info->num_conversions++; + } + break; + } + case 'T': + info->num_conversions++; + *(struct json_token *) info->target = *token; + break; + default: + info->num_conversions += sscanf(token->ptr, info->fmt, info->target); + break; + } +} + +int json_vscanf(const char *s, int len, const char *fmt, va_list ap) WEAK; +int json_vscanf(const char *s, int len, const char *fmt, va_list ap) { + char path[JSON_MAX_PATH_LEN] = "", fmtbuf[20]; + int i = 0; + char *p = NULL; + struct json_scanf_info info = {0, path, fmtbuf, NULL, NULL, 0}; + + while (fmt[i] != '\0') { + if (fmt[i] == '{') { + strcat(path, "."); + i++; + } else if (fmt[i] == '}') { + if ((p = strrchr(path, '.')) != NULL) *p = '\0'; + i++; + } else if (fmt[i] == '%') { + info.target = va_arg(ap, void *); + info.type = fmt[i + 1]; + switch (fmt[i + 1]) { + case 'M': + case 'V': + case 'H': + info.user_data = va_arg(ap, void *); + /* FALLTHROUGH */ + case 'B': + case 'Q': + case 'T': + i += 2; + break; + default: { + const char *delims = ", \t\r\n]}"; + int conv_len = strcspn(fmt + i + 1, delims) + 1; + snprintf(fmtbuf, sizeof(fmtbuf), "%.*s", conv_len, fmt + i); + i += conv_len; + i += strspn(fmt + i, delims); + break; + } + } + json_walk(s, len, json_scanf_cb, &info); + } else if (is_alpha(fmt[i]) || get_utf8_char_len(fmt[i]) > 1) { + const char *delims = ": \r\n\t"; + int key_len = strcspn(&fmt[i], delims); + if ((p = strrchr(path, '.')) != NULL) p[1] = '\0'; + sprintf(path + strlen(path), "%.*s", key_len, &fmt[i]); + i += key_len + strspn(fmt + i + key_len, delims); + } else { + i++; + } + } + return info.num_conversions; +} + +int json_scanf(const char *str, int len, const char *fmt, ...) WEAK; +int json_scanf(const char *str, int len, const char *fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = json_vscanf(str, len, fmt, ap); + va_end(ap); + return result; +} diff --git a/frozen.h b/frozen.h @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> + * Copyright (c) 2013 Cesanta Software Limited + * All rights reserved + * + * This library is dual-licensed: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. For the terms of this + * license, see <http: *www.gnu.org/licenses/>. + * + * You are free to use this library under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * Alternatively, you can license this library under a commercial + * license, as set out in <http://cesanta.com/products.html>. + */ + +#ifndef CS_FROZEN_FROZEN_H_ +#define CS_FROZEN_FROZEN_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> + +/* JSON token type */ +enum json_token_type { + JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */ + JSON_TYPE_STRING, + JSON_TYPE_NUMBER, + JSON_TYPE_TRUE, + JSON_TYPE_FALSE, + JSON_TYPE_NULL, + JSON_TYPE_OBJECT_START, + JSON_TYPE_OBJECT_END, + JSON_TYPE_ARRAY_START, + JSON_TYPE_ARRAY_END, + + JSON_TYPES_CNT +}; + +/* + * Structure containing token type and value. Used in `json_walk()` and + * `json_scanf()` with the format specifier `%T`. + */ +struct json_token { + const char *ptr; /* Points to the beginning of the value */ + int len; /* Value length */ + enum json_token_type type; /* Type of the token, possible values are above */ +}; + +#define JSON_INVALID_TOKEN \ + { 0, 0, JSON_TYPE_INVALID } + +/* Error codes */ +#define JSON_STRING_INVALID -1 +#define JSON_STRING_INCOMPLETE -2 + +/* + * Callback-based SAX-like API. + * + * Property name and length is given only if it's available: i.e. if current + * event is an object's property. In other cases, `name` is `NULL`. For + * example, name is never given: + * - For the first value in the JSON string; + * - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END + * + * E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`, + * the sequence of callback invocations will be as follows: + * + * - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL + * - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123" + * - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL + * - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1" + * - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2" + * - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL + * - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\": + *true }" + * - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, { + *\"baz\": true } ]" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123, + *\"bar\": [ 1, 2, { \"baz\": true } ] }" + */ +typedef void (*json_walk_callback_t)(void *callback_data, const char *name, + size_t name_len, const char *path, + const struct json_token *token); + +/* + * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; + * see `json_walk_callback_t`. + */ +int json_walk(const char *json_string, int json_string_length, + json_walk_callback_t callback, void *callback_data); + +/* + * JSON generation API. + * struct json_out abstracts output, allowing alternative printing plugins. + */ +struct json_out { + int (*printer)(struct json_out *, const char *str, size_t len); + union { + struct { + char *buf; + size_t size; + size_t len; + } buf; + void *data; + FILE *fp; + } u; +}; + +extern int json_printer_buf(struct json_out *, const char *, size_t); +extern int json_printer_file(struct json_out *, const char *, size_t); + +#define JSON_OUT_BUF(buf, len) \ + { \ + json_printer_buf, { \ + { buf, len, 0 } \ + } \ + } +#define JSON_OUT_FILE(fp) \ + { \ + json_printer_file, { \ + { (void *) fp, 0, 0 } \ + } \ + } + +typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap); + +/* + * Generate formatted output into a given sting buffer. + * This is a superset of printf() function, with extra format specifiers: + * - `%B` print json boolean, `true` or `false`. Accepts an `int`. + * - `%Q` print quoted escaped string or `null`. Accepts a `const char *`. + * - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *` + * - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`. + * - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`. + * - `%M` invokes a json_printf_callback_t function. That callback function + * can consume more parameters. + * + * Return number of bytes printed. If the return value is bigger then the + * supplied buffer, that is an indicator of overflow. In the overflow case, + * overflown bytes are not printed. + */ +int json_printf(struct json_out *, const char *fmt, ...); +int json_vprintf(struct json_out *, const char *fmt, va_list ap); + +/* + * Helper %M callback that prints contiguous C arrays. + * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt + * Return number of bytes printed. + */ +int json_printf_array(struct json_out *, va_list *ap); + +/* + * Scan JSON string `str`, performing scanf-like conversions according to `fmt`. + * This is a `scanf()` - like function, with following differences: + * + * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}" + * 2. Order of keys in an object is irrelevant. + * 3. Several extra format specifiers are supported: + * - %B: consumes `int *`, expects boolean `true` or `false`. + * - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned + * string is malloc-ed, caller must free() the string. + * - %V: consumes `char **`, `int *`. Expects base64-encoded string. + * Result string is base64-decoded, malloced and NUL-terminated. + * The length of result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %H: consumes `int *`, `char **`. + * Expects a hex-encoded string, e.g. "fa014f". + * Result string is hex-decoded, malloced and NUL-terminated. + * The length of the result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %M: consumes custom scanning function pointer and + * `void *user_data` parameter - see json_scanner_t definition. + * - %T: consumes `struct json_token *`, fills it out with matched token. + * + * Return number of elements successfully scanned & converted. + * Negative number means scan error. + */ +int json_scanf(const char *str, int str_len, const char *fmt, ...); +int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap); + +/* json_scanf's %M handler */ +typedef void (*json_scanner_t)(const char *str, int len, void *user_data); + +/* + * Helper function to scan array item with given path and index. + * Fills `token` with the matched JSON token. + * Return 0 if no array element found, otherwise non-0. + */ +int json_scanf_array_elem(const char *s, int len, const char *path, int index, + struct json_token *token); + +/* + * Unescape JSON-encoded string src,slen into dst, dlen. + * src and dst may overlap. + * If destination buffer is too small (or zero-length), result string is not + * written but the length is counted nevertheless (similar to snprintf). + * Return the length of unescaped string in bytes. + */ +int json_unescape(const char *src, int slen, char *dst, int dlen); + +/* + * Escape a string `str`, `str_len` into the printer `out`. + * Return the number of bytes printed. + */ +int json_escape(struct json_out *out, const char *str, size_t str_len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_FROZEN_FROZEN_H_ */ diff --git a/gs.1 b/gs.1 @@ -0,0 +1,118 @@ +.TH gs 1 +.SH NAME +.PP +gs \- gist creator +.SH SYNOPSIS +.PP +\fB\fCgs\fR [\fB\-pPhv\fP] [\fB\-d\fP \fIDESCRIPTION\fP] [\fB\-f\fP \fIFILENAME\fP] [\fB\-u\fP \fIUSER\fP[:\fIPASSWORD\fP] | \fB\-U\fP] [\fIFILES\fP ...] +.SH DESCRIPTION +.PP +Easy way to create Github gists with the command line. +.SH OPTIONS +.PP +\fB\-d\fP \fIDESCRIPTION\fP + Set gist description +.PP +\fB\-f\fP \fIFILENAME\fP + Set file name when reading from \fB\fCstdin\fR +.PP +\fB\-p\fP + Make gist private +.PP +\fB\-P\fP + Make gist public (default) +.PP +\fB\-u\fP \fIUSER\fP[:\fIPASSWORD\fP] + Change the Github account the gist will be posted under. A password can + given as well with a separating colon, a prompt is provided if not. +.PP +\fB\-U\fP + Post gist anonymously (default) +.PP +\fB\-h\fP + Print help and exit +.PP +\fB\-v\fP + Print version info and exit +.SH USAGE +.PP +Create a new gist of file \fB\fCfile.txt\fR: +.PP +.RS +.nf +$ gs file.txt +https://gist.github.com/<new\-id> +.fi +.RE +.PP +There is also support for multiple files in a single gist: +.PP +.RS +.nf +$ gs file.txt list.txt readme.md +https://gist.github.com/<new\-id> +.fi +.RE +.PP +The gist's URL can be piped to other programs, for example to your clipboard to +be pasted else where: +.PP +.RS +.nf +$ gs file.txt | xsel \-bi +.fi +.RE +.PP +You can also create a new gist from \fB\fCstdin\fR\&. The file name needs to be supplied +however: +.PP +.RS +.nf +$ cmd\-which\-errors | gs \-f log.txt +https://gist.github.com/<new\-id> +.fi +.RE +.PP +New gists can be private as well: +.PP +.RS +.nf +$ gs \-p file.txt +https://gist.github.com/<new\-id> +.fi +.RE +.PP +The new gist can be posted under a Github user, a prompt will ask for your +password: +.PP +.RS +.nf +$ gs \-u your\-name file.txt +Github password: +https://gist.github.com/<new\-id> +.fi +.RE +.PP +To skip the prompt your password can be supplied after a colon with the +username: +.PP +.RS +.nf +$ gs \-u your\-name:password file.txt +https://gist.github.com/<new\-id> +.fi +.RE +.SH AUTHOR +.PP +Ed van Bruggen +\<edvb54@gmail.com\> +.SH SEE ALSO +.PP +See project page at: +\<http://edryd.org/projects/gs.html\> +.PP +View source code at: +\<https://github.com/edvb/gs\> +.SH LICENSE +.PP +zlib License diff --git a/gs.c b/gs.c @@ -0,0 +1,203 @@ +/* See LICENSE file for copyright and license details. */ +#include <curl/curl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "arg.h" +#include "frozen.h" +#include "util.h" + +/* defines */ +#define GIST_URL "https://api.github.com/gists" +#define LBUF_SIZE 1024 /* TODO remove BUF_SIZEs */ +#define BUF_SIZE 100000 + +/* typedefs */ +typedef struct { + char *ptr; + size_t len; +} Str; + +/* functions */ +static size_t str_write(void *ptr, size_t size, size_t nmemb, Str *s); +static char *file_str(FILE *fp); +static char *files_js(char *files[], int filec); +static void gs_new(char *files[], int filec); + +/* variables */ +char *argv0; + +#include "config.h" + +/* used by cURL to write its response to a Str */ +static size_t +str_write(void *ptr, size_t size, size_t nmemb, Str *s) +{ + size_t nlen = s->len + size*nmemb; + s->ptr = erealloc(s->ptr, nlen+1); + memcpy(s->ptr+s->len, ptr, size*nmemb); + s->ptr[nlen] = '\0'; + s->len = nlen; + + return size*nmemb; +} + +/* HTTP POST request from content, returning response */ +static Str +http_post(char *content, long okcode) +{ + long code; + CURL *curl; + CURLcode res; + Str resstr; + + resstr.len = 0; + resstr.ptr = emalloc(resstr.len+1); + resstr.ptr[0] = '\0'; + + if (!(curl = curl_easy_init())) + die("%s: cURL: could not init", argv0); + curl_easy_setopt(curl, CURLOPT_URL, GIST_URL); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "gs/"VERSION); + if (user) { + if (strchr(user, ':')) { + curl_easy_setopt(curl, CURLOPT_USERPWD, user); + } else { + curl_easy_setopt(curl, CURLOPT_USERNAME, user); + curl_easy_setopt(curl, CURLOPT_PASSWORD, getpass("Github password: ")); + } + } + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, content); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, str_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resstr); + if ((res = curl_easy_perform(curl)) != CURLE_OK) + die("%s: cURL: %s", argv0, curl_easy_strerror(res)); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + if (code != okcode) + die("%s: could not create gist: %s", argv0, resstr.ptr); + + return resstr; +} + +/* read file fp into returned string */ +static char * +file_str(FILE *fp) +{ + char lbuf[LBUF_SIZE]; /* buffer for each line */ + char *str = ecalloc(LBUF_SIZE, sizeof(char)); /* complete file */ + long flen = 1; /* file length */ + + /* loop through each line in the file, append it to str */ + while (fgets(lbuf, LBUF_SIZE, fp)) { + flen += strlen(lbuf); + str = erealloc(str, flen); + strcat(str, lbuf); + } + + return str; +} + +/* turn list of file into returned json string */ +static char * +files_js(char *files[], int filec) +{ + char *fbuf, *js = emalloc(BUF_SIZE*sizeof(char)); /* fbuf: file contents, js: json string returned */ + int i; + FILE *fp = stdin; + struct json_out jout = JSON_OUT_BUF(js, BUF_SIZE); + + if (!desc) + die("%s: description not given", argv0); + json_printf(&jout, "{ description: %Q, public: %B, files: {", desc, pub); + + /* add each file */ + for (i = 0; !i || i < filec; i++) { + if (i) /* insert comma if this is another file */ + json_printf(&jout, ","); + if (filec && !(fp = fopen(files[i], "r"))) + die("%s: %s: could not load file", argv0, files[i]); + if (filec) /* set file name if given */ + fname = files[i]; + if (!fname) /* check for file name when using stdin */ + die("%s: file name not given", argv0); + fbuf = file_str(fp); + json_printf(&jout, "%Q: { content: %Q }", basename(fname), fbuf); + } + + json_printf(&jout, "} }"); + + efree(fbuf); + + return js; +} + +static void +gs_new(char *files[], int filec) +{ + char *js, *url; + Str resstr; + + js = files_js(files, filec); + + resstr = http_post(js, 201); + + json_scanf(resstr.ptr, resstr.len, "{html_url: %Q}", &url); + printf("%s\n", url); + + efree(js); + efree(url); + efree(resstr.ptr); + +} + +static void +usage(const int e) +{ + fprintf(stderr, "usage: %s [-pPhv] [-d DESCRIPTION] [-f FILENAME]\n" + " [-u USER[:PASSWORD] | -U] [FILES ...]\n", argv0); + exit(e); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'd': + desc = EARGF(usage(1)); + break; + case 'f': + fname = EARGF(usage(1)); + break; + case 'p': + pub = 0; + break; + case 'P': + pub = 1; + break; + case 'u': + user = EARGF(usage(1)); + break; + case 'U': + user = NULL; + break; + case 'h': + usage(0); + case 'v': + printf("%s v%s (c) 2017 Ed van Bruggen\n", argv0, VERSION); + return 0; + default: + usage(1); + } ARGEND; + + curl_global_init(CURL_GLOBAL_ALL); + + gs_new(argv, argc); + + curl_global_cleanup(); + + return 0; +} diff --git a/util.c b/util.c @@ -0,0 +1,74 @@ +/* See LICENSE file for copyright and license details. */ +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc: out of memory"); + + return p; +} + +void * +emalloc(size_t size) +{ + void *p; + + if (!(p = malloc(size))) + die("malloc: out of memory"); + + return p; +} + +void * +erealloc(void *p, size_t size) +{ + if (!(p = realloc(p, size))) + die("realloc: out of memory"); + + return p; +} + +char * +estrdup(char *s) +{ + if (!(s = strdup(s))) + die("strdup: out of memory"); + + return s; +} + +void +efree(void *p) +{ + if (p) + free(p); +} + +void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/util.h b/util.h @@ -0,0 +1,14 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) +#define LEN(X) (sizeof(X) / sizeof((X)[0])) + +void *ecalloc(size_t nmemb, size_t size); +void *emalloc(size_t size); +void *erealloc(void *p, size_t size); +char *estrdup(char *s); +void efree(void *p); + +void die(const char *errstr, ...);