util  Check-in [0d4aa1c43a]

Overview
Comment:add soda
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 0d4aa1c43a9a2a03d2241d2f04c6f793309e582c336cd8a8e199d0972ce3f49e
User & Date: lexi on 2022-10-27 20:04:19
Other Links: manifest | tags
Context
2022-10-31
21:27
add wgsync base check-in: 99704cfae0 user: lexi tags: trunk
2022-10-27
20:04
add soda check-in: 0d4aa1c43a user: lexi tags: trunk
2022-04-28
22:52
clean up rels mechanism, get parvan mostly working again check-in: bc37f02701 user: lexi tags: trunk
Changes

Modified clib/iaia.c from [1d0d9f385e] to [70661568bf].

27
28
29
30
31
32
33



34
35
36
37
38
39
40
..
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

144
145
146
147
148
149
150
151
...
158
159
160
161
162
163
164
165
166
167
168
/* this is an expression that will be evaluated to
 * determine whether to compress ascii to a 7-bit
 * representation or to store it in 8-bit space.
 *  1 = 7-bit (compressed)
 *  0 = 8-bit (uncompressed) */
#	define _IAIA_EXP_ASCFORM (0)
#endif




enum /* constants */ {
	base7bit = 128,

	/* ascii address space */
	numspace        = (0x39 - 0x30) + 1, /* 10 */
	alphaspace      = (0x5a - 0x41) + 1, /* 26 */
................................................................................
		iaia_e_overflow,
	} iaia_error_type;
	typedef unsigned long long iaia_word_type;
#endif

/* -- string to integer converters -- */


iaia_error_type _IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) {
	iaia_word_type val = 0;

	for (;*s!=null;++s) {
		uint8_t v = *s;
		if (v > base7bit) return iaia_e_domain;

		if (_IAIA_EXP_ASCFORM)
			val *= base7bit;
			else val <<= 8;

		val += v;
	}

	*ret = val;
	return ok;
}


iaia_error_type _IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) {
	/* s must be a null-terminated ASCII numeral string */
	if (base > maxbase) return iaia_e_base;

	/* override the default base if it's a basèd literal */
	if (s[0] == '@' || base == 0) return _IAIA_FN_ASCTOI(s + (s[0]=='@'),ret);
	else if (s[0] == '0' && s[1] == 'x') base = 16, s += 2;
	else if (s[0] == '0' && s[1] == 'd') base = 10, s += 2;
	else if (s[0] == '0' && s[1] == 'b') base =  2, s += 2;
	else if (s[0] == '0' && s[1] == 't') base =  3, s += 2;
	else if (s[0] == '0')                base =  8, s += 1;

	bool insens = (base <= imaxbase);
	iaia_word_type val = 0;

	for (;*s!=null;++s) {
		uint8_t v = *s;
		if(v >= 0x30 && v <= 0x39) v -= 0x30; else {
			if(v >= 0x61 && v <= 0x7a) {
				if (insens) v -= 0x20; else {
					v = numspace + alphaspace + (v - 0x61);
					goto checkval;
				}
................................................................................
}


/* -- integer to string converters -- */

/* needed for efficiency's sake, but really sucky -
 * this table needs to be kept in sync with the
 * itoa algorithm by hand. unfortunately, given C's
 * abject lack of metaprogramming, we have to do this
 * by hand. */
const char iaia_ref_table[] = /* numerals[10] */ "0123456789"
	/* bigalpha[26] */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	/* smallalpha[26] */ "abcdefghijklmnopqrstuvwxyz";
_Static_assert (sizeof iaia_ref_table - 1 == maxbase, "tables out of sync");


iaia_error_type _IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) {
	char* ptr = buf_end;

	*ptr-- = 0;
	while(val > 0) {
		if (ptr < buf_start) return iaia_e_overflow;
		iaia_word_type rem = val % 128;
		if (_IAIA_EXP_ASCFORM)
			val /= base7bit;
			else val >>= 8;
		*ptr-- = (char)rem;
	}

	if (newbuf != null) *newbuf = ptr + 1;
	return ok;
}


iaia_error_type _IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start,
		char* buf_end, char** newbuf, bool lowercase) {

	char* ptr = buf_end;

	if (base > maxbase) return iaia_e_base;
	if (base == 0) return _IAIA_FN_ITOASC(val, buf_start, buf_end, newbuf);

................................................................................
		char out = iaia_ref_table[rem];
		if (lowercase && base <= imaxbase)
			if (out >= 'A' && out <= 'Z')
				out += ('a' - 'A');
		*ptr-- = out;
	}

	if (newbuf != null) *newbuf = ptr + 1;
	return iaia_e_ok;
}








>
>
>







 







>
|


|











|


>
|
|













|







 







|







>
|












|
|


>
|







 







|



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
..
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
...
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
...
165
166
167
168
169
170
171
172
173
174
175
/* this is an expression that will be evaluated to
 * determine whether to compress ascii to a 7-bit
 * representation or to store it in 8-bit space.
 *  1 = 7-bit (compressed)
 *  0 = 8-bit (uncompressed) */
#	define _IAIA_EXP_ASCFORM (0)
#endif
#ifndef NULL
#	define NULL ((void*)0)
#endif

enum /* constants */ {
	base7bit = 128,

	/* ascii address space */
	numspace        = (0x39 - 0x30) + 1, /* 10 */
	alphaspace      = (0x5a - 0x41) + 1, /* 26 */
................................................................................
		iaia_e_overflow,
	} iaia_error_type;
	typedef unsigned long long iaia_word_type;
#endif

/* -- string to integer converters -- */

iaia_error_type
_IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) {
	iaia_word_type val = 0;

	for (;*s!=0;++s) {
		uint8_t v = *s;
		if (v > base7bit) return iaia_e_domain;

		if (_IAIA_EXP_ASCFORM)
			val *= base7bit;
			else val <<= 8;

		val += v;
	}

	*ret = val;
	return iaia_e_ok;
}

iaia_error_type
_IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) {
	/* s must be a NUL-terminated ASCII numeral string */
	if (base > maxbase) return iaia_e_base;

	/* override the default base if it's a basèd literal */
	if (s[0] == '@' || base == 0) return _IAIA_FN_ASCTOI(s + (s[0]=='@'),ret);
	else if (s[0] == '0' && s[1] == 'x') base = 16, s += 2;
	else if (s[0] == '0' && s[1] == 'd') base = 10, s += 2;
	else if (s[0] == '0' && s[1] == 'b') base =  2, s += 2;
	else if (s[0] == '0' && s[1] == 't') base =  3, s += 2;
	else if (s[0] == '0')                base =  8, s += 1;

	bool insens = (base <= imaxbase);
	iaia_word_type val = 0;

	for (;*s!=0;++s) {
		uint8_t v = *s;
		if(v >= 0x30 && v <= 0x39) v -= 0x30; else {
			if(v >= 0x61 && v <= 0x7a) {
				if (insens) v -= 0x20; else {
					v = numspace + alphaspace + (v - 0x61);
					goto checkval;
				}
................................................................................
}


/* -- integer to string converters -- */

/* needed for efficiency's sake, but really sucky -
 * this table needs to be kept in sync with the
 * itoa algorithm manually. unfortunately, given C's
 * abject lack of metaprogramming, we have to do this
 * by hand. */
const char iaia_ref_table[] = /* numerals[10] */ "0123456789"
	/* bigalpha[26] */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	/* smallalpha[26] */ "abcdefghijklmnopqrstuvwxyz";
_Static_assert (sizeof iaia_ref_table - 1 == maxbase, "tables out of sync");

iaia_error_type
_IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) {
	char* ptr = buf_end;

	*ptr-- = 0;
	while(val > 0) {
		if (ptr < buf_start) return iaia_e_overflow;
		iaia_word_type rem = val % 128;
		if (_IAIA_EXP_ASCFORM)
			val /= base7bit;
			else val >>= 8;
		*ptr-- = (char)rem;
	}

	if (newbuf != NULL) *newbuf = ptr + 1;
	return iaia_e_ok;
}

iaia_error_type
_IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start,
		char* buf_end, char** newbuf, bool lowercase) {

	char* ptr = buf_end;

	if (base > maxbase) return iaia_e_base;
	if (base == 0) return _IAIA_FN_ITOASC(val, buf_start, buf_end, newbuf);

................................................................................
		char out = iaia_ref_table[rem];
		if (lowercase && base <= imaxbase)
			if (out >= 'A' && out <= 'Z')
				out += ('a' - 'A');
		*ptr-- = out;
	}

	if (newbuf != NULL) *newbuf = ptr + 1;
	return iaia_e_ok;
}

Modified makefile from [69ff088193] to [ca4a818574].

3
4
5
6
7
8
9







10
11
12
13
14
15
16
17
18
19
20
# pass debug=yes for debugging symbols, and to disable
# stripping and optimization of all binaries

# to build project contained in their own directory, run 
# $ make (dir).proj

all: ctl conv xutil gen








xutil: xpriv safekill
  # X11 tools
gen: mkpw kpw rosshil
  # procedural generators
conv: ord
  # converters
ctl: nkvd.so bgrd
  # manipulating disobedient software

nkvd.so: nkvd.c







>
>
>
>
>
>
>



|







3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# pass debug=yes for debugging symbols, and to disable
# stripping and optimization of all binaries

# to build project contained in their own directory, run 
# $ make (dir).proj

all: ctl conv xutil gen
clean:
	rm xpriv safekill mkpw kpw.bin rosshil ord nkvd.so bgrd
install:
	mkdir -p $(prefix)/bin $(prefix)/lib
	cp xpriv safekill mkpw rosshil ord bgrd ${out}/bin/
	cp kpw.bin ${out}/bin/kpw
	cp nkvd.so $(prefix)/lib

xutil: xpriv safekill
  # X11 tools
gen: mkpw kpw.bin rosshil
  # procedural generators
conv: ord
  # converters
ctl: nkvd.so bgrd
  # manipulating disobedient software

nkvd.so: nkvd.c

Modified makerules from [a224223f8c] to [e7dd2b0bcc].

14
15
16
17
18
19
20


21
22
23
24
25
26
27
cc-flags = $(if $(debug),-g,-O$(cc-opt))
cc = $(CC) $(cc-flags)

post = $(if $(debug),, && strip $@)
cc-post = $(post)
sc-post = $(post)
mc-post = $(post)



os = $(shell uname -o)

%: %.c
	$(cc) $< -o$@ $(cc-post)

%: %.ml







>
>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cc-flags = $(if $(debug),-g,-O$(cc-opt))
cc = $(CC) $(cc-flags)

post = $(if $(debug),, && strip $@)
cc-post = $(post)
sc-post = $(post)
mc-post = $(post)

prefix = $(if ${out},${out},/usr)

os = $(shell uname -o)

%: %.c
	$(cc) $< -o$@ $(cc-post)

%: %.ml

Modified ord.c from [9504e2c85c] to [4549cc99f3].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* [ʞ] ord.c - integer converter
 *  ~ lexi hale <lexi@hale.su>
 *  © AGPLv3
 *  * ord has no dependencies except for libc.
 *  ? ord converts integers to ascii characters
 *    and back. written because the only fucking
 *    way to do this in shell is FUCKING PRINTF.
 *  $ cc ord.c -o ord [-D_IO=(LIBC|POSIX)]
 *  	- the flag D_IO will instruct ord.c whether
 *  	  to use POSIX io primitives (write and read)
 *  	  instead of libc primitives (printf). if
 *  	  you're on a UNIX system, POSIX primitives
 *  	  will be used by default, but you can block
 *  	  them with LIBC or force them with POSIX.
 *  	  if you are on a POSIX- compliant system,
................................................................................
 *  	  you *should* use POSIX IO, for improved
 *  	  performance and safety.

 	TODO: take full advantage of write(2) by storing
	      output in single string & making single
		  write call */

#if (defined(__unix__) && _IO != LIBC) || (_IO == POSIX)
#	define _POSIX_IO
#endif

#ifdef _POSIX_IO
#	include <unistd.h>
#	define say(x) (write(2, (x), (sizeof (x))))
#	define print(sz,x) (write(1, (x), (sz)))
#	define forposix(x) x
#	define forlibc(x)
#else
#	include <stdio.h>
#	define say(x) (fprintf(stderr, (x)))
#	define print(x) (printf("%s",(x)))
#	define forposix(x)
#	define forlibc(x) x
#endif
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>







|







 







|












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* [ʞ] ord.c - integer converter
 *  ~ lexi hale <lexi@hale.su>
 *  © AGPLv3
 *  * ord has no dependencies except for libc.
 *  ? ord converts integers to ascii characters
 *    and back. written because the only fucking
 *    way to do this in shell is FUCKING PRINTF.
 *  $ cc ord.c -o ord [-D_(POSIX|LIBC)_IO]
 *  	- the flag D_IO will instruct ord.c whether
 *  	  to use POSIX io primitives (write and read)
 *  	  instead of libc primitives (printf). if
 *  	  you're on a UNIX system, POSIX primitives
 *  	  will be used by default, but you can block
 *  	  them with LIBC or force them with POSIX.
 *  	  if you are on a POSIX- compliant system,
................................................................................
 *  	  you *should* use POSIX IO, for improved
 *  	  performance and safety.

 	TODO: take full advantage of write(2) by storing
	      output in single string & making single
		  write call */

#if (defined(__unix__) && !defined(_POSIX_IO)) && !defined(_LIBC_IO)
#	define _POSIX_IO
#endif

#ifdef _POSIX_IO
#	include <unistd.h>
#	define say(x) (write(2, (x), (sizeof (x))))
#	define print(sz,x) (write(1, (x), (sz)))
#	define forposix(x) x
#	define forlibc(x)
#else
#	include <stdio.h>
#	define say(x) (fprintf(stderr, (x)))
#	define print(sz,x) (printf("%s",(x)))
#	define forposix(x)
#	define forlibc(x) x
#endif
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>

Modified safekill.c from [59e57157c0] to [283d7b5c67].

176
177
178
179
180
181
182
183
184
185
186
187
188
189

usage:
	#define info  "\x1b[34m-- "
	#define param "\x1b[32m"
	#define eol   "\x1b[m\n"
	#define bold  "\x1b[1m"
	#define nl    "       "
	fprintf(stderr, bold "usage:\x1b[0m %s    "       "     " info "kill active window if non-vital" eol
						 nl "%1$s -v " param "[id] " info "make [id] or active window vital" eol
						 nl "%1$s -c " param "[id] " info "clear vital flag on [id] or active window" eol
						 nl "%1$s -q " param "[id] " info "'emergency' kill w/o reference to vital flag" eol,
				argv[0]);
	return 1;
}







|






176
177
178
179
180
181
182
183
184
185
186
187
188
189

usage:
	#define info  "\x1b[34m-- "
	#define param "\x1b[32m"
	#define eol   "\x1b[m\n"
	#define bold  "\x1b[1m"
	#define nl    "       "
	fprintf(stderr, bold "usage:\x1b[0m %1$s    "       "     " info "kill active window if non-vital" eol
						 nl "%1$s -v " param "[id] " info "make [id] or active window vital" eol
						 nl "%1$s -c " param "[id] " info "clear vital flag on [id] or active window" eol
						 nl "%1$s -q " param "[id] " info "'emergency' kill w/o reference to vital flag" eol,
				argv[0]);
	return 1;
}

Modified smake.c from [8f8960ef17] to [1bfb924710].

17
18
19
20
21
22
23

24
25
26
27
28
29
30
..
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
...
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>


enum       e { ok, badname, nofile };
enum    kind { css, sass, scss, bad };
typedef enum { false, true } bool;

#define corebufsz 1024
char corebuf[corebufsz];
................................................................................
				size_t rsz = bufptr-extbuf;
				extbuf = realloc(extbuf, run);
				bufptr = extbuf + rsz + sz;
				return bufptr - sz;
			}
		}
	}
	// this point should never be reached
	mkptr: {
		void* ret = bufptr;
		bufptr += sz;
		return ret;
	}
}

................................................................................
		*namecur = 0;
		printf("found import to %s;\n", namebuf);
		namecur = namebuf;
	}
	++cur; goto read_start;

read_string:
	if (*cur == 0) goto read_done; //unterminated string!?
	if (*cur++ == strqt) goto read_start;
	goto read_string;

read_ml_comment:
	if (*cur == 0) goto read_done; //unterminated comment!
	if (*cur == '*' && cur[1] == '/') {
		cur += 2;
		goto read_start;
	}
	++cur; goto read_ml_comment;

skip_line:







>







 







|







 







|




|







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
...
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>

enum       e { ok, badname, nofile };
enum    kind { css, sass, scss, bad };
typedef enum { false, true } bool;

#define corebufsz 1024
char corebuf[corebufsz];
................................................................................
				size_t rsz = bufptr-extbuf;
				extbuf = realloc(extbuf, run);
				bufptr = extbuf + rsz + sz;
				return bufptr - sz;
			}
		}
	}
	assert(false); /* this point should never be reached */
	mkptr: {
		void* ret = bufptr;
		bufptr += sz;
		return ret;
	}
}

................................................................................
		*namecur = 0;
		printf("found import to %s;\n", namebuf);
		namecur = namebuf;
	}
	++cur; goto read_start;

read_string:
	if (*cur == 0) goto read_done; /* unterminated string!? */
	if (*cur++ == strqt) goto read_start;
	goto read_string;

read_ml_comment:
	if (*cur == 0) goto read_done; /* unterminated comment! */
	if (*cur == '*' && cur[1] == '/') {
		cur += 2;
		goto read_start;
	}
	++cur; goto read_ml_comment;

skip_line:

Added soda.c version [0502e2ccfd].































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
/* [ʞ] soda - libsodium front end
 *  ~ lexi hale <lexi@hale.su>
 *  © AGPLv3
 *  @ vim: ft=c
 */

#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdio.h>
#include <errno.h> /* boo hiss */
#include <stdint.h>
#include <limits.h>
#include <stdbool.h>
#include <string.h>
#include <sodium.h>
#include <fcntl.h>
#define sz(x) (sizeof(x)/sizeof((x)[0]))

#define _IAIA_FN_ITOA str_of_int
#define _IAIA_FN_ATOI int_of_str
#include "clib/iaia.c"

#ifdef _WIN32
#	define open  _open
#	define close _close
#	define read  _read
#	define write _write
#endif

#define _self_name "soda"

typedef unsigned long long word;

typedef enum atom {
	/* commands */
	atom_none,
	atom_key,
	atom_gen,
	atom_pub,
	atom_psk,
	atom_pair,
	atom_cert,
	atom_sign,
	atom_verify,
	atom_enc,
	atom_dec,
	atom_wrap,
	atom_unwrap,
	atom_help,
	_atom_cmd_n,

	/* flags */
	atom_verbose,
	atom_quiet,
	atom_noise,
	atom_ascii_armor,
	atom_color_on,
	atom_color_off,
	atom_out, atom_outfd,
	atom_in, atom_infd,
	atom_pw, atom_pwfd, atom_pwtty,
	atom_sig, atom_sigfd,
	atom_symmetric,
	atom_raw,
	atom_fd,
	atom_fmtv,
	_atom_flag_n,

	/* debug values */
	atom_debug,
	atom_info,
	atom_warn,
	atom_fatal,
	atom_silent,

	_atom_n,
	atom_capture = _atom_n
} atom;

enum noise {
	noise_debug,
	noise_info,
	noise_warn,
	noise_fatal,
	noise_silent,

	_noise_n
};
struct { const char* label; unsigned char color; }
noise_level_text[] = {
	[noise_debug] = {"(debug",5},
	[noise_info ] = {" (info", 4},
	[noise_warn ] = {" (warn", 3},
	[noise_fatal] = {"(fatal",1},
};

struct cmd_spec { const char* desc; const char** params; struct cmd_spec* sub; }
cmd_specs[_atom_cmd_n] = { {NULL,NULL,NULL},
	[atom_key] = { "create and manipulate keypairs for public-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){
		[atom_gen] = { "generate new keypair", NULL, NULL },
		[atom_pub] = { "extract the public key portion from a keypair", NULL, NULL},
		[atom_wrap] = { "convert a binary key to its ascii-armor representation", NULL, NULL},
	}},
	[atom_psk] = { "create and manipulate keys for secret-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){
		[atom_gen] = { "generate new PSK", NULL, NULL },
	}},
	[atom_help] = { "display usage listings for commands",
		(const char*[]){"commands…",NULL}, NULL },
	[atom_sign] = { "sign data using public-key crypto",
		(const char*[]){"keyfile",NULL}, NULL },
	[atom_verify] = { "verify signature on data",
		(const char*[]){"pubfile",NULL}, NULL },
	[atom_enc] = { "encrypt data", NULL, (struct cmd_spec[_atom_cmd_n]){
		[atom_pub] = { "encrypt against public key", (const char*[]){
			"pubfile", NULL
		}, NULL},
		[atom_psk] = { "encrypt against symmetric, pre-shared key", (const char*[]){
			"pskfile", NULL
		}, NULL},
	}},
	[atom_wrap] = {"wrap binary input with ascii armor so it can survive transmission mechanisms that are not 8-bit clean", NULL,NULL},
	[atom_unwrap] = {"translate ascii-armored input back to the original binary", NULL,NULL},
};

enum say { say_stop, say_string, say_string_list, say_char, say_uint, say_sint, say_hex };
struct sayp {
	enum say kind; union {
		const char*  str;
		const char** strlist;
		char ch;
		long long unsigned int uint;
		long long signed   int sint;
	};
};

enum feature {
	feature_off = 0,
	feature_on = 1,
	feature_auto = 2,
};

struct ctx {
	size_t argc; const char** argv;
	size_t cmdc; atom* cmdv;
	const char* bin;
	struct {
		enum noise noise;
		enum feature
			ascii_armor,
			color;
		bool fd;
		bool pw_force_tty;
		const char* infile,* outfile,* pwfile,* sigfile;
		word infd, outfd, pwfd, sigfd;
		bool symcrypt;
		word fmtv;
	} flags;
};

bool
isws(const char c) {
	/* TODO: maybe ignore all characters outside the ascii armory? */
	return (c == ' ' || c == '\t' || c == '\n');
}

enum { _buffer_max = 512 };
struct buffer {
	int fd; /* where to flush */
	size_t sz;
	char bytes[_buffer_max];
};
void buffer_flush(struct buffer* b) { write(b->fd, b->bytes, b->sz); b->sz = 0; }
#define _buffer_write(s,b) buffer_write(s, b, sizeof(b))
void buffer_write(struct buffer* b, const char* str, size_t sz) {
	if (sz == 0) {
		size_t i;
		for (i = 0; str[i] != 0; ++i) {
			const size_t ofs = (b->sz + i) % _buffer_max;
			b->bytes[ofs] = str[i];
			if (ofs == 0 && i != 0) buffer_flush(b);
		}
		b -> sz = (b->sz + i) % _buffer_max;
	} else if (sz > _buffer_max) {
		buffer_flush(b);
		write(b->fd, str, sz);
		b->sz = sz;
	} else if (_buffer_max - b->sz < sz) {
		buffer_flush(b);
		memcpy(b->bytes, str, sz);
		b->sz = sz;
	} else {
		memcpy(b->bytes + b->sz, str, sz);
		b->sz += sz;
	}
}
void buffer_push(struct buffer* b, const char c) { buffer_write(b, &c, 1); }

void say(struct ctx c, enum noise kind, struct sayp* s) {
	struct buffer o = {2,0};
	if (c.flags.noise > kind) return; 
	if (noise_level_text -> label != NULL)
	if (c.flags.color) {
		buffer_write(&o,"\x1b[1;3",0);
		buffer_push(&o, 0x30 + noise_level_text[kind].color);
		_buffer_write(&o,"m");
		buffer_write(&o, noise_level_text[kind].label, 0);
		_buffer_write(&o,")\x1b[m");
	} else {
		buffer_write(&o, noise_level_text[kind].label, 0);
		buffer_push(&o,')');
	}

	bool bright = false;
	while(s -> kind != say_stop) {
		if (c.flags.color) {
			if (bright) _buffer_write(&o," \x1b[1m");
				   else _buffer_write(&o," \x1b[m");
		} else {
			buffer_push(&o,' ');
		}
		char intbuf[128];
		char* const intbuf_end = intbuf + sizeof intbuf;
		char* start;

		explain: switch(s -> kind) {
			case say_string: buffer_write(&o, s -> str, 0);  break;
			case say_char:   buffer_push(&o, s -> ch);       break;
			case say_uint:
				str_of_int(10, s -> uint, intbuf, intbuf_end - 1, &start, true);
				buffer_write(&o, start, intbuf_end - start);
				break;
			case say_sint:
				/* TODO oops! iaia doesn't have signed int support yet 🙃 */
				/* str_of_int(10, s -> uint, intbuf, intbuf + sizeof intbuf, &end, true) */
				/* buffer_write(&o, intbuf, end - intbuf); */
				break;
			case say_hex:
				str_of_int(16, s -> uint, intbuf, intbuf_end - 1, &start, true);
				_buffer_write(&o, "0x");
				buffer_write(&o, start, intbuf_end - start);
				break;
			case say_string_list:
				buffer_push(&o,'[');
				for(size_t i = 0; s -> strlist[i] != NULL; ++i) {
					if (i != 0) dprintf(2,", ");
					_buffer_write(&o,"“");
					buffer_write(&o,s -> strlist[i],0);
					_buffer_write(&o,"”");
				}
				buffer_push(&o,']');
			break;
		}

		++s; bright = !bright;
	}

	if (c.flags.color) _buffer_write(&o, "\x1b[m\n");
		else buffer_push(&o, '\n');
	
	buffer_flush(&o);
}

enum { _atom_max_str_len = 12 };
struct { atom val; const char* str;}
/* first name listed for an atom is treated as the canonical name
 * by reconstitute() and displayed in usage listings */
atom_strings[] = {
	/* commands */
	{atom_key, "key"}, {atom_key, "privkey"}, {atom_key, "priv"},
	{atom_pub, "pub"}, {atom_pub, "pubkey"},
	{atom_psk, "psk"},
	{atom_cert, "cert"}, {atom_cert, "certificate"},
	{atom_sign, "sign"},
	{atom_verify, "verify"}, {atom_verify, "vfy"}, {atom_verify, "v"},
	{atom_enc, "enc"}, {atom_enc, "encipher"}, {atom_enc, "encrypt"},
	{atom_dec, "dec"}, {atom_dec, "decipher"}, {atom_dec, "decrypt"},
	{atom_wrap, "wrap"}, {atom_wrap, "armor"},
	{atom_unwrap, "unwrap"}, {atom_unwrap, "dearmor"}, {atom_unwrap, "disarmor"},
	{atom_gen, "gen"}, {atom_gen, "generate"}, {atom_gen, "create"}, {atom_gen, "new"},
	{atom_help, "help"},

	/* flags */
	{atom_quiet, "quiet"},
	{atom_ascii_armor, "ascii-armor"},
	{atom_raw, "raw"},
	{atom_sig, "sig"}, {atom_sig, "sigfile"},
	{atom_sigfd, "sig-fd"}, {atom_sig, "sigfd"},
	{atom_verbose, "verbose"},
	{atom_noise, "noise"},
	{atom_color_on, "color"},
	{atom_color_off, "no-color"},
	{atom_out, "out"}, {atom_out, "output"}, {atom_out, "outfile"},
	{atom_outfd, "out-fd"}, {atom_outfd, "outfd"},
	{atom_in, "in"}, {atom_out, "input"}, {atom_out, "infile"},
	{atom_infd, "in-fd"}, {atom_infd, "infd"},
	{atom_pw, "pw"}, {atom_pw, "passphrase"}, {atom_pw, "password"},
	{atom_pwfd, "pw-fd"}, {atom_pwfd, "pwfd"},
	{atom_pwtty, "pw-tty"}, {atom_pwtty, "pwtty"},
	{atom_fd, "fd"},
	{atom_symmetric, "symmetric"}, {atom_symmetric, "sym"}, {atom_symmetric, "symcrypt"},
	{atom_fmtv, "format"}, {atom_fmtv, "fmt"}, {atom_fmtv, "format-version"}, 

	/* debug levels */
	{atom_silent, "silent"},
	{atom_debug, "debug"}, {atom_debug, "dbg"},
	{atom_info, "info"},
	{atom_warn, "warn"}, {atom_warn, "warnings"},
	{atom_fatal, "fatal"}, {atom_fatal, "error"},
};

atom
atomize(const char* str) {
	for(size_t i = 0; i < sz(atom_strings); ++i) {
		if (strncmp(atom_strings[i].str, str, _atom_max_str_len) == 0) {
			return atom_strings[i].val;
		}
	}
	return atom_none;
}

const char*
reconstitute(atom a) {
	for(size_t i = 0; i < sz(atom_strings); ++i) {
		if (atom_strings[i].val == a) return atom_strings[i].str;
	}
	return NULL;
}

struct flag {
	enum flag_kind {
		flag_none, 
		_flag_noparam,
			flag_on, flag_off, flag_switch,
			flag_inc, flag_dec, flag_sdec,
		_flag_param,
			flag_atom, flag_str,
			flag_uint, flag_sint,
			flag_raw_atom,
		flag_value
	} kind;
	char shortname;
	const char* desc;
	struct { atom first; atom last; } opts;
	size_t offset;
}

#define _declflag_raw(a,k,s,o,rs,re,d) [atom_##a] = { \
	.kind = flag_##k, \
	.shortname = s, \
	.desc = d, \
	.opts = { rs,re }, \
	.offset = (size_t)&(((struct ctx*)0) -> flags.o) \
},
#define _declflag(a,k,s,o,d) _declflag_raw(a,k,s,o,0,0,d)
#define _declflaga(a,rs,re,s,o,d) _declflag_raw(a,atom,s,o,rs,re,d)

flags[_atom_n] = {
	{flag_none},
	_declflag(ascii_armor, value + feature_on, 'a', ascii_armor,
			"emit ascii-encoded data instead of raw binary "
			"data (default if stdout is a TTY)")
	_declflag(raw, value + feature_off, 'r', ascii_armor,
			"emit binary data instead of raw ascii-armored "
			"data (default if stdout is not a TTY)")
	_declflag(in, str, 'i', infile,
			"read data from a file instead of stdin")
	_declflag(infd, uint, 'I', infd,
			"read data from a file instead of stdin")
	_declflag(out, str, 'o', outfile,
			"write data to a file instead of stdout")
	_declflag(outfd, uint, 'O', outfd,
			"write data to a file instead of stdout")
	_declflag(pw, str, 'p', pwfile,
			"load passphrase from file instead of stdin")
	_declflag(pwfd, uint, 'P', pwfd,
			"load passphrase from file instead of stdin")
	_declflag(pwtty, switch, 'W', pw_force_tty,
			"always read password from the input TTY unless a file is specified instead")
	_declflag(sig, str, 'g', sigfile,
			"read signature from specified file instead of extracting it from input data")
	_declflag(sigfd, uint, 'G', sigfd,
			"read signature from specified file descriptor instead of extracting it from input data")
	_declflag(symmetric, switch, 's', symcrypt,
			"secure sensitive key material with symmetric cryptography")
	_declflag(fd, switch, 'f', fd,
			"interpret filename arguments to commands as file descriptor numbers")
	_declflag(color_on, value + feature_on, 'c', color,
			"enable colorized output (default if stderr is a TTY)")
	_declflag(color_off, value + feature_off, 'C', color,
			"disable colorized output")
	_declflag(verbose, dec, 'v', noise,
			"increase noise level")
	_declflag(quiet, value + noise_silent, 'q', noise,
			"emit nothing to stderr")
	_declflaga(noise, atom_debug, atom_silent, 'n', noise,
			"set noise level")
	_declflag(fmtv, uint, 'F', fmtv,
			"use a specific data file format version instead of the native version (0)")
};

const char** flag_enum_desc[_atom_n] = {
	[atom_noise] = (const char*[_noise_n]){
		[noise_silent] = "run in complete silence",
		[noise_debug] = "emit all notices, including those useful only for debugging",
		[noise_info] = "report on the progress of the operation",
		[noise_warn] = "emit non-fatal warnings",
		[noise_fatal] = "only emit fatal errors",
	},
};

#undef _declflag
#undef _declflaga
#undef _declflag_raw

/* the casts here are a bit wacky. this is unfortunately necessary as C
 * insists on evaluating each branch of the generic expression regardless
 * of which type is picked. why anyone thought this was a good idea is
 * beyond me. */
#define _t(x) (_Generic((x), \
	      const char**: (struct sayp){say_string_list, .strlist  = (const char**)(size_t)(x)}, \
	              char: (struct sayp){say_char,   .ch   = (char)(size_t)(x)}, \
	             char*: (struct sayp){say_string, .str  = (const char*)(size_t)(x)}, \
	       const char*: (struct sayp){say_string, .str  = (const char*)(size_t)(x)}, \
				   int: (struct sayp){say_sint,   .sint = (long long)(x)}, \
	             short: (struct sayp){say_sint,   .sint = (long long)(x)}, \
	              long: (struct sayp){say_sint,   .sint = (long long)(x)}, \
	         long long: (struct sayp){say_sint,   .sint = (long long)(x)}, \
		      unsigned: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
	    unsigned short: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
	     long unsigned: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
	long long unsigned: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
	default: (struct sayp){say_hex, .uint = (size_t)(x)})),
#define _say(k,...)  say(c, noise_##k, ((struct sayp[]){__VA_ARGS__ {say_stop}}))
#define _report(k,s) _say(k, _t(s))

void
usage(struct ctx c, const struct cmd_spec* tree) {
	const char* bin = c.bin;
	const char* fmt_flag,* fmt_usage,* fmt_enum,* head_start,* head_fin;
	if (c.flags.color) {
		fmt_usage = "\x1b[1musage:\x1b[m %s \x1b[32m[flags]\x1b[m%s \x1b[33mcommand\x1b[m\n";
		fmt_flag = "  \x1b[1;36m%c%c \x1b[;95m--%s\x1b[m: %s\n";
		fmt_enum = "      \x1b[94m%s\x1b[m: %s\n";
		head_start = "\x1b[1m"; head_fin = "\x1b[m";
	} else {
		fmt_usage = "usage: %s [flags]%s <command>\n";
		fmt_flag = "  %c%c --%s: %s\n";
		fmt_enum = "      %s: %s\n";
		head_start = head_fin = "";
	}

	char ccmd[32]; char *cptr = ccmd; *cptr = 0;
	if (tree == NULL) tree = cmd_specs;
	else if(c.cmdc > 1) for (size_t i = 1; i<c.cmdc; ++i) {
		*(cptr++)=' ';
		cptr=stpcpy(cptr, reconstitute(c.cmdv[i]));
	}


	dprintf(2,fmt_usage,bin,ccmd);

	dprintf(2,"%scommands:%s\n",head_start,head_fin);
	for (size_t i = 0; i<_atom_cmd_n; ++i) {
		if (tree[i].desc == NULL) continue;
		
		dprintf(2, "  %s", reconstitute(i));
		if (tree[i].params != NULL) {
			for (size_t j = 0; tree[i].params[j] != NULL; ++j) {
				dprintf(2," <%s>", tree[i].params[j]);
			}
		}
		dprintf(2,"%s: %s\n",
				tree[i].sub != NULL ? " …" : "",
				tree[i].desc);
	}

	dprintf(2,"%sflags:%s\n",head_start,head_fin);
	for (size_t i = _atom_cmd_n + 1; i<_atom_flag_n; ++i) {
		/* if (flags[i].kind == flag_none) continue; */
		dprintf(2,fmt_flag,
				flags[i].shortname != 0 ? '-' : ' ',
				flags[i].shortname != 0 ? flags[i].shortname : ' ',
				reconstitute(i), flags[i].desc);
		if (flags[i].kind == flag_atom || flags[i].kind == flag_raw_atom) {
			for (atom a = flags[i].opts.first; a < flags[i].opts.last + 1; ++a) {
				dprintf(2,fmt_enum,reconstitute(a),flag_enum_desc[i][a - flags[i].opts.first]);
			}
		}
	}
}

bool
atom_match(struct ctx c, size_t* match) {
	/* primitive pattern-matching function */
	for (size_t i = 0; i < c.cmdc; ++i) {
		if (match[i] == 0) return false;

		if (match[i] < atom_capture) {
			if (match[i] != c.cmdv[i]) return false;
		} else if (c.cmdv[i] >= _atom_n) {
			const char** dest = (const char**)(match[i] - atom_capture);
			*dest = c.argv[c.cmdv[i] - _atom_n];
		} else {
			return false;
		}
	}
	return match[c.cmdc] == 0;
}

/* the ascii armory is intentionally limited to characters that are
 * not only printable but that are unlikely to have special meaning
 * such that they would require escaping for any form of input.
 * while this is a more limited set than base64, it's significantly
 * stronger armor -- a user can, for instance, simply paste it into
 * most shells without quoting. */
const char ascii_armory[] = "0123456789/abcdefghijklmopqrstuvwxyz."
                            "-ABCDEFGHIJKLMOPQRSTUVWXYZ_";
const char ascii_value[] = 
	"\x25\x24\x0a\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\00\00\00"
	"\00\00\00\00\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
	"\x31\x32\00\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e"
	"\00\00\00\00\x3f\00\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"
	"\x14\x15\x16\x17\00\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21"
	"\x22";
	/* generated by a script. do not touch. */

enum { armor_split_every = 48 };
char*
armor(const unsigned char* src, char* dest, size_t sz, size_t brstart, bool format) {
	char* bufptr = dest;
	unsigned char carry = 0;
	for(size_t i = 0; i<sz; ++i) {
		/* insert line breaks every so many chars */
		*(bufptr++)=ascii_armory[src[i] % 64];
		if (format) if ((brstart + (bufptr - dest)) % armor_split_every == 0)
			*(bufptr++)='\n', *(bufptr++)=' ';

		carry<<=2;
		carry |= (src[i] / 64);
		if (i && (i%3 == 0)) { /* :( */
			*(bufptr++)=ascii_armory[carry];
			carry = 0;
		}
		if (format) if ((brstart + (bufptr - dest)) % armor_split_every == 0)
			*(bufptr++)='\n', *(bufptr++)=' ';
	}
	/* if(carry != 0) */ *(bufptr++)=ascii_armory[carry];
	return bufptr;
}

char* disarmor(const unsigned char* src, char* dest, size_t sz) {
	/* transforms ascii armor into binary. can transform in place. */
	for(size_t i = 0; i<sz; i += 3) {
		while (isws(src[i])) ++i;
		const char* s = src + i;
		unsigned char carry = ascii_value[s[2] - '-'],
			          b1    = ascii_value[s[0] - '-'] + (64 * ((carry & 12) >> 2)),
		              b2    = ascii_value[s[1] - '-'] + (64 * ( carry &  3));
		*(dest++)=b1; *(dest++)=b2;
	}
	return dest;
}

struct pstr {
	size_t len;
	const char* str;
};
#define _p(x) {sizeof(x)-1,(x)}
struct encoding { struct pstr bin; struct pstr asc; const char* desc;};
enum doc {
	doc_none,
	doc_header,
	doc_footer,

	_doc_versions,
		doc_version_1,
		doc_version_2,
		doc_version_3,

	_doc_types,
		doc_type_priv /* private key */,
		doc_type_pub /* public key */,
		doc_type_psk /* shared secret key */,
		doc_type_asc /* arbitrary ascii-armored data */,
		doc_type_sig /* separate signature */,
		doc_type_signed /* document prefixed with signature */,
		doc_type_master /* master key used to derive keys deterministically */,

	_doc_n
};
const struct encoding docbytes[_doc_n] = { {0,NULL},
	[doc_header]      = {_p("\x26\xe9\x11\x1a"), _p("[" _self_name)},
	[doc_footer]      = {_p(""), _p("]")},
	[doc_version_1]   = {_p("\01"), _p("1-")},
	[doc_version_2]   = {_p("\02"), _p("2-")},
	[doc_version_3]   = {_p("\03"), _p("3-")},
	[doc_type_priv]   = {_p("\01"), _p("priv-"),"private key material"},
	[doc_type_pub]    = {_p("\02"), _p("pub-"), "public key material"},
	[doc_type_psk]    = {_p("\03"), _p("psk-"), "shared key material"},
	[doc_type_asc]    = {_p("\04"), _p("asc-"), "ascii-armored binary data"},
	[doc_type_sig]    = {_p("\05"), _p("sig-"), "separate signature"},
	[doc_type_signed] = {_p("\06"), _p("signed-"), "data with signature"},
	[doc_type_master] = {_p("\07"), _p("master-"), "master key"},
};


char*
compare_wsi(const char* s, char* d, size_t sz) {
	for (size_t i=0; i<sz; ++i) {
		while(isws(*s)) ++s;
		while(isws(*d)) ++d;
		if (*s != *d) return NULL;
		++s; ++d;
	}
	return d;
}

bool
file_cmp(enum doc fld, char** s, bool binary) {
	/* compares against a field and increments the callee's 
	 * pointer appropriately if a match is found */
	if (binary) {
		if (memcmp(*s, docbytes[fld].bin.str, docbytes[fld].bin.len) == 0) {
			*s += docbytes[fld].bin.len;
			return true;
		} else return false;
	} else {
		char* p = compare_wsi(docbytes[fld].asc.str, *s, docbytes[fld].asc.len);
		if (p != NULL) {
			*s = p;
			return true;
		} else return false;
	}
}

struct file {
	enum doc version, type;
	union {
		struct file_signed {
			char* sig, *text;
		} signtext;
		struct file_key {
			char* priv, *pub;
		} key;
		char* pub;
	} data;
	char* datastart;
	size_t rawlen, datlen;
	char raw [];
}* load_file(struct ctx c, const int fd) {
	_say(debug, _t("reading in file from fd") _t(fd));
	size_t run = 512; size_t len = 0;
	struct file* buf = malloc(run + sizeof(struct file)); /* :( */
	int e; while(e = read(fd, buf->raw + len, run - len)) {
		if (e == -1) {
			switch(errno) {
				case EBADF: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("does not reference an open, readable file")); break;
				case EFAULT: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("references a file outside your accessible address space")); break;
				case EIO: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be read due to an I/O error")); break;
				case EISDIR: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("is a directory, not a readable file")); break;
				default: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be opened due to an unknown error - consult errno table for code") _t((unsigned)errno)); break;
			}
			goto err;
		}

		len += e;
		if (len == run) {
			run *= 2; buf = realloc(buf, run + sizeof(struct file));
		} else if (len < run) continue;
	}

	buf -> rawlen = len;
	buf -> type = doc_none;
	return buf;

	err: free(buf);
		 return NULL;
}

struct file* read_file(struct ctx c, const int fd) {
	struct file* buf = load_file(c, fd);
	bool binary; size_t len = buf -> rawlen;
	char* cur = buf -> raw;

	if (file_cmp(doc_header,&cur,true)) {
		_report(debug, "parsing binary data");
		binary = true;
	} else if (file_cmp(doc_header,&cur,false)) {
		_report(debug, "parsing ascii-armored data");
		binary = false;
	} else {
		_say(fatal, _t("cannot parse data from fd") _t((unsigned)fd));
		goto err;
	}

	/* write(2, cur, run - (cur - buf->raw)); */

	for (enum doc i = _doc_versions + 1; i < _doc_types; ++ i) {
		if (file_cmp(i, &cur, binary)) {
			_say(debug, _t("file is format version") _t((unsigned) i - _doc_versions));
			buf -> version = i; goto found_version;
		}
	} /* else */ {
		_say(fatal, _t("unrecognized file format version; are you using the most recent release of " _self_name "?"));
		goto err;
	}
	found_version:;
	
	for (enum doc i = _doc_types + 1; i < _doc_n; ++ i) {
		if (file_cmp(i, &cur, binary)) {
			_say(debug, _t("file contains") _t(docbytes[i].desc));
			buf -> type = i; goto found_type;
		}
	} /* else */ {
		_say(fatal, _t("unrecognized file type; are you using the most recent release of " _self_name "?"));
		goto err;
	}
	found_type: if (!binary) {
		size_t s = (len - (cur - buf->raw)) - docbytes[doc_footer].asc.len;
		_say(debug, _t("disarmoring") _t(s) _t("bytes of encoded text"));
		char* end = disarmor(cur, buf -> raw, s);
		buf -> datlen = end - cur;
	} else {
		buf -> datlen = (len - (cur - buf->raw)) - docbytes[doc_footer].bin.len;
	}

	buf -> datastart = cur;
	return buf;

	err: free(buf);
		 return NULL;
}

void
write_armored_v1(struct ctx c, const unsigned char* data, size_t sz) {
	struct buffer o = {c.flags.outfd,0};
#	define _wra_fld(f) buffer_write(&o, docbytes[doc_##f].asc.str,\
		docbytes[doc_##f].asc.len);
	_wra_fld(header);
	_wra_fld(version_1);
	_wra_fld(type_asc);

	char buf[sz*2];
	char* end = armor(data,buf,sz,13, isatty(c.flags.outfd));
	buffer_write(&o, buf, end - buf);

	_wra_fld(footer);
#	undef _wra_fld

	if (isatty(c.flags.outfd)) buffer_push(&o, '\n');
	buffer_flush(&o);
}

void
write_privkey_v1(struct ctx c, const unsigned char* material) {
	struct buffer o = {c.flags.outfd,0};
#	define _wrp_fld(f) buffer_write(&o, \
		(c.flags.ascii_armor ? docbytes[doc_##f].asc.str   \
		                     : docbytes[doc_##f].bin.str), \
		(c.flags.ascii_armor ? docbytes[doc_##f].asc.len   \
		                     : docbytes[doc_##f].bin.len))
	
	_wrp_fld(header);
	_wrp_fld(version_1);
	_wrp_fld(type_priv);

	if (c.flags.ascii_armor) {
		char buf[crypto_sign_SECRETKEYBYTES * 2];
		char* end = armor(material,buf,crypto_sign_SECRETKEYBYTES,13,
				isatty(c.flags.outfd));
		buffer_write(&o, buf, end - buf);
	} else {
		buffer_write(&o, material, crypto_sign_SECRETKEYBYTES);
	}

	_wrp_fld(footer);
	if (isatty(c.flags.outfd)) buffer_push(&o, '\n');
	buffer_flush(&o);
}

typedef void (*write_privkey_t)(struct ctx, const unsigned char*);
typedef void (*write_armored_t)(struct ctx, const unsigned char*, size_t);

write_privkey_t write_privkey[] = {
	[0] = write_privkey_v1, /* current version */

	[1] = write_privkey_v1,
};
write_armored_t write_armored[] = {
	[0] = write_armored_v1, /* current version */

	[1] = write_armored_v1,
};

int
route(struct ctx c) {
	if (sodium_init() < 0) {
		_report(fatal, "could not initialize libsodium");
		return 2;
	}

	switch (c.cmdv[0]) {
		case atom_key: {
			switch (c.cmdv[1]) {
				case atom_gen: {
					if (c.cmdc != 2) { usage(c,NULL); return 1; }
					_report(info, "generating new private key");
					if (c.flags.symcrypt == false) {
						_say(warn, _t("not using symmetric encryption; key will be emitted in") _t("plain text"));
					} else {
						/* read passphrase from stdin/file */
					}
					
					/* libsodium is a bit weird. it uses different key types for
					 * encrypting and signing. we emit a signing (ed25519) key
					 * because it can be converted to an encryption key, but not
					 * vice-versa. libsodium also does not have any means to
					 * derive a public key from a private key, even though this
					 * is trivial, but instead stores the public key in the second
					 * half of the private key (despite emitting it AGAIN into the
					 * second parameter). so we generate a signing keypair, discard
					 * the returned public key, and save the "private key" (including
					 * its copy of the public key). this is extremely awkward and
					 * hopefully a better solution will be found in the fullness of
					 * time. */
					unsigned char skey[crypto_sign_SECRETKEYBYTES];
					{ unsigned char _[crypto_sign_PUBLICKEYBYTES];
					  crypto_sign_keypair(_,skey); }
					(*write_privkey[c.flags.fmtv])(c, skey);

				} break;
				case atom_pub: {
					if (c.cmdc != 2) { usage(c,NULL); return 1; }
					struct file* skey = read_file(c, c.flags.infd);
					if (skey == NULL) return 6;

					free(skey);
				} break;
				case atom_wrap: {
					if (c.cmdc != 2) { usage(c,NULL); return 1; }
					struct file* key = read_file(c, c.flags.infd);
					c.flags.ascii_armor = feature_on;

					if (key -> type == doc_type_priv) {
						(*write_privkey[c.flags.fmtv])(c, key -> datastart);
					} else if (key -> type == doc_type_pub) {
						/* (*write_pubkey[c.flags.fmtv])(c, key); */
					} else {
						_report(fatal, "file is not a " _self_name " key");
						free(key); return 8;
					}
					free(key); return 0;
				} break;

				default: goto no_cmd;
			}
		} break;
		case atom_wrap: {
			if (c.cmdc != 1) { usage(c,NULL); return 1; }

			struct file* data = load_file(c, c.flags.infd);
			if (data == NULL) return 6;

			(*write_armored[c.flags.fmtv])(c, data->raw, data->rawlen);

			free(data); return 0;
		} break;
		case atom_unwrap: {
			if (c.cmdc != 1) { usage(c,NULL); return 1; }
			struct file* data = read_file(c, c.flags.infd);
			if (data == NULL) return 6;
			if (isatty(c.flags.outfd)) _say(warn, _t("printing binary values to tty"));
			write(c.flags.outfd, data->datastart, data->datlen);
			free(data); return 0;
		} break;
		case atom_help: {
			if (c.cmdc == 1) usage(c,NULL);
			else {
				const struct cmd_spec* tree = cmd_specs;
				for (size_t i = 1; i < c.cmdc; ++i) {
					if (c.cmdv[i] < _atom_n) {
						if (tree[c.cmdv[i]].sub != NULL) {
							tree = tree[c.cmdv[i]].sub;
						} else {
							_say(fatal, _t("no help available for subcommand") _t(reconstitute(c.cmdv[i])));
							return 3;
						}
					} else {
						_say(fatal, _t("no help available;") _t(c.argv[c.cmdv[i] - _atom_n]) _t("is not a recognized command"));
						return 4;
					}
				}
				usage(c,tree);
			}
			return 0;
		} break;
		default: goto no_cmd;
	}
	return 0;

	no_cmd: _say(fatal, _t("no such command"));
			usage(c,NULL);
			return 1;
}


int
open_file(struct ctx c, const char* path, int flags, word* retfd) {
	int fd = open(path,flags,0600);
	if (fd == -1) {
		switch (errno) {
			case EACCES: _say(fatal, _t("file") _t(path) _t("could not be opened because you do not have permission to read it")); break;
			case EFAULT: _say(fatal, _t("file") _t(path) _t("could not be opened because the path is outside your accessible address space")); break;
			case EISDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because it is a directory, not a regular file")); break;
			case ELOOP: _say(fatal, _t("file") _t(path) _t("could not be opened because too many symbolic links were encountered")); break;
			case EMFILE: _say(fatal, _t("file") _t(path) _t("could not be opened because the per-process file limit is too low")); break;
			case ENOENT: _say(fatal, _t("file") _t(path) _t("could not be opened because it does not exist")); break;
			case ENOMEM: _say(fatal, _t("file") _t(path) _t("could not be opened, either because not enough memory remains to make use of a FIFO or because the kernel is running out of memory (!)")); break;
			case ENOTDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because a file was specified in place of a directory in the path")); break;
			case ENXIO: _say(fatal, _t("file") _t(path) _t("could not be opened because it is not a regular file or FIFO")); break;
			case EPERM: _say(fatal, _t("file") _t(path) _t("could not be opened because it is sealed against access")); break;
			case EROFS: _say(fatal, _t("file") _t(path) _t("could not be opened for writing because it is located on a read-only filesystem")); break;
			default: _say(fatal, _t("file") _t(path) _t("could not be opened for unclear reasons - consult errno entry for code") _t((unsigned)errno)); break;
		}
		return errno;
	}

	*retfd = fd; return 0;
}

int
main(unsigned int argc, const char** argv) {
	atom cmdlist[argc];
	const char* paramlist[argc];
	size_t paramcount = 0;

	struct ctx c = {
		.argv = paramlist,
		.cmdc = 0,
		.cmdv = cmdlist,
		.bin = (argc > 0 ? argv[0] : NULL),
		.flags = {
			.ascii_armor = feature_auto,
			.color = feature_auto,
			.noise = noise_fatal,
			.infile = NULL, .infd = 0,
			.outfile = NULL, .outfd = 1,
			.pwfile = NULL, .pwfd = 0,
			.pw_force_tty = false,
			.sigfile = NULL, .sigfd = 0,
				/* if sigfd == infd, extract sig from input stream */
			.fd = false,
			.symcrypt = false,
			.fmtv = 0, /* 0 = native version */
		},
	};

	if (argc == 0) {
		_report(fatal, "invoked incorrectly or your environment is not sane; the 0th value of the argument array should be the name of or path to the program you are attempting to execute");
		return -1;
	}

	struct flag* paramstack[argc];
	size_t stackptr = 0;
	enum {
		reading_params,
		reading_commands,
		reading_flags
	} reading_mode = reading_flags;
	_say(debug, _t("parsing command line") _t(argv));
	for (size_t i = 1; i < argc; ++i) {
		const char* const p = argv[i];
		
		if (reading_mode >= reading_flags && p[0] == '-') { /* flag */
			bool longflag;
			if (p[1] == '-') {
				if (p[2] == 0) {
					/* "--" has been passed; all further arguments must be
					 * interpreted as parameters or commands, not flags */
					reading_mode = reading_commands;
					continue;
				} else if (p[2] == '-' && p[3] == 0) {
					/* "---" has been passed; all further arguments must be
					 * interpreted as parameters, not flags or commands */
					reading_mode = reading_params;
					continue;
				} else longflag = true;
			} else longflag = false;
			_say(debug, _t("parsing flag") _t(p));
			for (size_t o = 1; p[o] != 0; ++o) {
				atom option = atom_none;

				if (longflag) option = atomize(p+2);
				else { /* short flag */
					_say(debug, _t("parsing short flag") _t(p[o]));
					for (size_t j = 0; j < sz(flags); ++ j)
						if (flags[j].kind != flag_none && flags[j].shortname == p[o]) {
							option = (atom)j; break;
						}
				}

				if (option == atom_none) {
					if (!longflag && p[o] == '-') {
						if (p[o+1] == '-' && p[o+2] == 0) {
							/* shorthand for --- */
							reading_mode = reading_params;
							continue;
						} else if (p[o+1] == 0) {
							/* shorthand for -- */
							reading_mode = reading_commands;
							continue;
						}
					}
					char ftext[3] = {'-',p[o],0};
					_say(fatal, _t("flag") _t(longflag ? p+2 : ftext) _t("not meaningful"));
					goto parse_fail;
				}

				handle_flag:;
					struct flag* f = flags + option;
					void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset);
					if (f -> kind > _flag_param) {
						if (f->kind>=flag_value) {
							*((unsigned*)fld) = f->kind - flag_value;
						} else paramstack[stackptr++] = f;
					} else if (f -> kind > _flag_noparam) {
						switch(f -> kind) {
							case flag_on:  *((bool*)fld) = true; break;
							case flag_off: *((bool*)fld) = false; break;
							case flag_switch: *((bool*)fld) = !*((bool*)fld); break;
							/* in order to avoid creepy behavior in pathological cases,
							 * we enforce saturation arithmetic for the increment/decrement
							 * flag types */
							case flag_inc: {
								unsigned* v = fld;
								if (*v < UINT_MAX) ++*v;
							} break;
							case flag_dec: {
								unsigned* v = fld;
								if (*v > 0) --*v;
							} break;
							case flag_sdec: {
								signed* v = fld;
								if (*v > INT_MIN) --*v;
							} break;
						}
					}

				if (p[1] == '-') break;
			}
			continue;
		}

		if (stackptr > 0) {
			struct flag* f = paramstack[--stackptr];
			_say(debug, _t("handling argument for flag") _t(reconstitute(f - flags)));
			void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset);
			switch(f -> kind) {
				case flag_str: *((const char**)fld) = p; break;
				case flag_uint: {
					iaia_error_type e = int_of_str(10, p, (unsigned long long*)fld);
					switch (e) {
						case iaia_e_ok: continue;
						case iaia_e_domain: 
							_say(fatal, _t("argument") _t(p) _t("outside domain for base"));
					}
					return 5;
				} break;
				case flag_atom: case flag_raw_atom: {
					atom a = atomize(p);
					if (a == atom_none || a < f -> opts.first || a > f -> opts.last) {
						_say(fatal, _t("invalid argument") _t(p) _t("for flag")
								_t(reconstitute(f - flags)));
						goto parse_fail;
					}
					*((atom*)fld) = f->kind == flag_raw_atom ? a : a - f -> opts.first;
				} break;

			}
			continue;
		}

		if (reading_mode >= reading_commands) {
			if (p[0] == '=') { /* parameter even if atomizable */
				paramlist[paramcount] = p + 1;
				cmdlist[c.cmdc] = _atom_n + paramcount;
				++c.cmdc; ++ paramcount;
				continue;
			} else {
				atom a = atomize(p);
				if (a != atom_none) {
					cmdlist[c.cmdc] = a;
					++ c.cmdc;
					continue;
				}
			}
		}

		/* only parameters remain to be read */
		paramlist[paramcount] = p;
		cmdlist[c.cmdc] = _atom_n + paramcount;
		++ c.cmdc; ++ paramcount;
	}
	
	parse_done: _say(debug, _t("stored") _t(c.cmdc) _t("arguments and") _t(paramcount) _t("parameters"));
	
	if (c.flags.noise <= noise_debug) for (size_t i = 0; i<c.cmdc; ++i) {
		if (cmdlist[i] < _atom_n) {
			dprintf(2, "\t%llu. atom %u (normal form “%s”)\n", i, cmdlist[i], reconstitute(cmdlist[i]));
		} else {
			dprintf(2,"\t%llu. parameter “%s”\n", i, paramlist[cmdlist[i] - _atom_n]);
		}
	}

	if (c.flags.fmtv >= sz(write_privkey)) {
		_say(fatal, _t("data format version") _t(c.flags.fmtv) _t("is not known. the highest available version is") _t(sz(write_privkey) - 1) _t("- are you sure you're using the latest release of " _self_name "?"));
		return -2;
	}
	int err;
	if (c.flags.pwfile != NULL) {
		err = open_file(c,c.flags.pwfile, O_RDONLY, &c.flags.pwfd);
		if(err) return err;
	}
	if (c.flags.infile != NULL) {
		err = open_file(c,c.flags.infile, O_RDONLY, &c.flags.infd);
		if(err) return err;
	}
	if (c.flags.outfile != NULL) {
		err = open_file(c,c.flags.outfile, O_WRONLY | O_TRUNC | O_CREAT, &c.flags.outfd);
		if(err) return err;
	}
	if (c.flags.sigfile != NULL) {
		err = open_file(c,c.flags.sigfile, O_RDONLY, &c.flags.sigfd);
		if(err) return err;
	}


	if (c.flags.color == feature_auto)
		c.flags.color = isatty(2);
	if (c.flags.ascii_armor == feature_auto)
		c.flags.ascii_armor = isatty(c.flags.outfd);

	/* this is hateful but i can't figure out a better way */
	if (c.flags.pw_force_tty && !isatty(c.flags.pwfd)) {
		_say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as it is not a tty and -W was passed;") _t("/dev/tty") _t("will be used instead"));
	} else if (c.flags.pwfd == c.flags.infd) {
		_say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as input data is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead"));
	} else if (c.flags.pwfd == c.flags.sigfd) {
		_say(warn, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as the signature is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead"));
	} else goto skip_pwfd_change;

	pwfd_change: {
		err = open_file(c,"/dev/tty", O_WRONLY, &c.flags.pwfd);
		if(err) return err;
	}

	skip_pwfd_change:

	_say(debug, _t("input fd =") _t(c.flags.infd));
	_say(debug, _t("output fd =") _t(c.flags.outfd));
	_say(debug, _t("pw fd =") _t(c.flags.pwfd));

	if (c.cmdc > 0) return route(c);

	parse_fail: if (c.flags.noise < noise_silent) usage(c,NULL);
	            return 1;
}

Modified tenki/tenki.c from [b2cd504148] to [be25cce95d].

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
...
186
187
188
189
190
191
192




193


194
195
196
197
198
199
200
...
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
#else
#	define _http "?"
#endif
#define http _http "lang=" ds_lang " HTTP/1.1\r\nHost: api.darksky.net\r\n\r\n"
#define start "GET /forecast/" ds_apikey "/"

#define min(x,y) (x>y?y:x)
#define len(x) (sizeof(x)/sizeof(*x))

typedef enum bool { false, true } bool;

SSL_CTX* sc;
bool head(size_t* len, char* txt, char** body) {
	char* hend = strstr(txt, "\r\n\r\n");
	if (hend == NULL) return false;
................................................................................
	size_t len = *key++;
	for (size_t i = 0; i<len; ++i) {
		printf("/%.*s",*key,key+1);
		key += *key + 1;
	};
	printf("\n");
} */




void pplt(const char* str, const char* const end, const char** keys, const char** const keyend, jsv* values) {


	// initialize parser state
	size_t depth = 0;
	const char* fld = NULL;
	const char* path[32];
	path[0] = NULL;
	
	while(*str++!='{'); //cheat
................................................................................
			
			pstr summary = values[0].s;
			float pcpint = values[1].f,
				temp = values[2].f,
				windspd = values[3].f;

			printf("%.1f°  %%{F#ff9bbb}%.*s\n",
					summary.sz,
					summary.a,
					temp);
		}
		// dark sky's API allows us to make 1000 free requests a day.
		// let's try not to get too near that.
		// hours in day 24 - minutes in day 24 * 60 = 1440
		// so once a minute is almost half again too many
		// 1440 / 2 = 720 leaving reasonable refresh time without
		// going too near the limit. so we aim for one update
		// every two minutes.
	//	sleep(60 * 2);
//	}

	return 0;
}







|







 







>
>
>
>
|
>
>







 







|
|
|













72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
...
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#else
#	define _http "?"
#endif
#define http _http "lang=" ds_lang " HTTP/1.1\r\nHost: api.darksky.net\r\n\r\n"
#define start "GET /forecast/" ds_apikey "/"

#define min(x,y) (x>y?y:x)
#define len(x) (sizeof(x)/sizeof(x)[0])

typedef enum bool { false, true } bool;

SSL_CTX* sc;
bool head(size_t* len, char* txt, char** body) {
	char* hend = strstr(txt, "\r\n\r\n");
	if (hend == NULL) return false;
................................................................................
	size_t len = *key++;
	for (size_t i = 0; i<len; ++i) {
		printf("/%.*s",*key,key+1);
		key += *key + 1;
	};
	printf("\n");
} */
void pplt
(	const char*        str,
	const char*  const end,
	const char**       keys,
	const char** const keyend,
	jsv* values
) {
	// initialize parser state
	size_t depth = 0;
	const char* fld = NULL;
	const char* path[32];
	path[0] = NULL;
	
	while(*str++!='{'); //cheat
................................................................................
			
			pstr summary = values[0].s;
			float pcpint = values[1].f,
				temp = values[2].f,
				windspd = values[3].f;

			printf("%.1f°  %%{F#ff9bbb}%.*s\n",
					temp,
					(int /*throwing table emoji*/)summary.sz,
					summary.a);
		}
		// dark sky's API allows us to make 1000 free requests a day.
		// let's try not to get too near that.
		// hours in day 24 - minutes in day 24 * 60 = 1440
		// so once a minute is almost half again too many
		// 1440 / 2 = 720 leaving reasonable refresh time without
		// going too near the limit. so we aim for one update
		// every two minutes.
	//	sleep(60 * 2);
//	}

	return 0;
}