fnc

Check-in Differences
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From 96e142ed8e To 6eaea2465f

2021-08-28 15:43
Bump version number: version 0.2 alpha. (check-in: 39f43fcefb user: mark tags: trunk, version-0.2)
2021-08-28 15:31
Merge [9d412bdfacee14e3|libf-amalgamation] into trunk. (check-in: 96e142ed8e user: mark tags: trunk, version-0.2)
2021-08-27 14:42
Bring branch up to parity with [trunk]. (Closed-Leaf check-in: 9d412bdfac user: mark tags: libf-amalgamation)
2021-08-27 13:28
Implement simpler, more robust fix for line wrap. (check-in: b413600889 user: mark tags: trunk)
2021-08-05 11:32
Add commit branch/tags to search results. (check-in: 7dfce7ca8d user: mark tags: trunk)
2021-08-05 08:12
Initial commit of fnc. (check-in: 6eaea2465f user: mark tags: trunk)

Changes to Makefile.in.

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
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








-
+


-
-
+
+



+
+
+









+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
















-
+


+
+
+
+
+
+
+
+
+







#!/usr/bin/make # help out emacs
#
# Top-level autosetup-filtered Makefile for libfossil. This particular
# build is for Unix platforms with GNU Make 3.81+.
all:
.NOTPARALLEL: # stop subdir makes and reconfigure from launching
              # multiple times concurrently.
include config.make
ShakeNMake.CISH_SOURCES := $(wildcard *.c)
ShakeNMake.CISH_SOURCES := $(wildcard *.c) $(wildcard $(SRC.DIR)/*.c)
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
clean-.: clean-fnc clean-src
distclean-.: distclean-fnc distclean-src
clean-.: clean-doc clean-fnc clean-bindings
distclean-.: distclean-doc distclean-fnc distclean-bindings
include shakenmake.make
MAIN_MAKEFILES := $(PACKAGE.MAKEFILE) $(ShakeNMake.MAKEFILE) @AUTODEPS@

AUTOCONFIG_H := @srcdir@/include/fossil-scm/autoconfig.h
#SRCDIR := @top_srcdir@/src
SRCDIR := @srcdir@/src

define CALL.SUBDIR
endef
SUBDIRS := src fnc
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
#$(eval $(call ShakeNMake.CALL.SUBDIR,src))
subdir-fnc: subdir-src
all: subdir-src subdir-fnc


ifeq (1,@LIBFOSSIL_STATIC@)
  THELIB.LIB := libfossil$(ShakeNMake.EXTENSIONS.LIB)
else
  THELIB.LIB :=
endif
ifeq (1,@LIBFOSSIL_SHARED@)
  THELIB.DLL := libfossil$(ShakeNMake.EXTENSIONS.DLL)
else
  THELIB.DLL :=
endif

ifneq (,$(THELIB.DLL)$(THELIB.LIB))
src/$(THELIB.DLL):
	$(MAKE) -C src
src/$(THELIB.LIB):
	$(MAKE) -C src
$(THELIB.DLL): src/$(THELIB.DLL)
	ln -sf src/$(THELIB.DLL) .
$(THELIB.LIB): src/$(THELIB.LIB)
	ln -sf src/$(THELIB.LIB) .
all: $(THELIB.DLL) $(THELIB.LIB)
CLEAN_FILES += $(wildcard libfossil.so libfossil.dll libfossil.a)
endif
DISTCLEAN_FILES += config.make

########################################################################
# Other stuff...

ifeq ($(MAKE_COMPILATION_DB),yes)
all: compile_commands.json
compile_commands.json:
	@$(RM) $@
	sed -e '1s/^/[\'$$'\n''/' -e '$$s/,$$/\'$$'\n'']/' $(compdb_dir)/*.o.json > $@+
	@if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi
endif

install: all
	@echo 'No installation rules yet.'

DISTCLEAN_FILES += Makefile config.log autosetup/jimsh0 \
DISTCLEAN_FILES += $(AUTOCONFIG_H) Makefile config.log autosetup/jimsh0 \
	$(wildcard compile_commands/*) compile_commands.json+

# automake compatibility. do nothing for all these targets
#EMPTY_AUTOMAKE_TARGETS := dvi pdf ps info html tags ctags mostlyclean maintainer-clean check installcheck installdirs \
# install-pdf install-ps install-info install-html -install-dvi uninstall install-exec install-data distdir
#.PHONY: $(EMPTY_AUTOMAKE_TARGETS)
#$(EMPTY_AUTOMAKE_TARGETS):

## @top_srcdir@/Makefile.in: # b/c AUTODEPS contains this name (it probably shouldn't)
#$(FSL.OBJ): @AUTODEPS@ @top_srcdir@/Makefile @top_srcdir@/config.make

# Reconfigure if needed
ifeq ($(findstring clean,$(MAKECMDGOALS)),)
@top_srcdir@/config.make: @AUTODEPS@ @top_srcdir@/config.make.in
	@@AUTOREMAKE@
@top_srcdir@/Makefile: @AUTODEPS@ @top_srcdir@/Makefile.in
	chmod +w $@
	@@AUTOREMAKE@

Changes to auto.def.

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
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





+

+
+







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+








+
+
+
+







# vim:se syn=tcl:
#
use cc cc-shared cc-lib

options {
    shared=1 => "Disable build of a shared library."
    no-debug=0 => "Disable debug build options."
    amal => "Generates a conservative config file for the amalgamation build."
    static=1 => "Disable build of a static library."
    loud=0 => "Enables 'loud' build mode."
}


# autosetup interceps 'debug' and 'enable-debug' flags :/
#    prefix:=[get-env HOME /usr/local] -> "Installation prefix."

define FSL_PACKAGE_NAME "libfossil"
define FSL_LIBRARY_VERSION 0.0.1-alphabeta

########################################################################
# See if we can get the fossil schema version from the current
# checkout. If so, use that one, otherwise fall back to some hard-coded
# default.
set auxSchema {}
set contentSchema {}

set fossilBin [find-an-executable fossil]
if {[string length $fossilBin] > 0} {
    puts "Found fossil binary: $fossilBin"
    catch {
        set auxSchema [string trimright \
                       [exec echo \
                            {SELECT value FROM config WHERE name='aux-schema';} \
                            "|" $fossilBin sqlite3] ]
        set contentSchema [string trimright \
                       [exec echo \
                            {SELECT value FROM config WHERE name='content-schema';} \
                            "|" $fossilBin sqlite3] ]
    } ex
#    puts "exception=$ex"
}

#set uname [exec $fossilBin version -v "|" grep "Schema version"]
#puts "uname=$uname"
#return

if {[string length $auxSchema] == 16} {
    puts "Got aux-schema value from current repo: $auxSchema"
    puts "Got content-schema value from current repo: $contentSchema"
} else {
    set auxSchema "2015-01-24"
    # "2011-04-25 19:50"
    set contentSchema 2
    puts "Using hard-coded aux-schema: $auxSchema"
    puts "Using hard-coded content-schema: $contentSchema"
}
define FSL_AUX_SCHEMA $auxSchema
define FSL_CONTENT_SCHEMA $contentSchema

########################################################################
# Grab the code version and timestamp from manifest.uuid and manifest
# files...
#
# This requires that the repo is NOT generating delta manifests. As
# of 2020-03, the libfossil server uses the forbid-delta-manifest
# setting.
set timestamp [clock format [clock seconds] \
                   -gmt 1 -format "%Y-%m-%d %H:%M"]
set fp [open "manifest.uuid" r]
set libVersionHash [string trimright [read $fp]]
close $fp
set fp [open "manifest"]
# Get C-card:
gets $fp
# Get D-card:
gets $fp line
close $fp
unset fp
set manifestTimestamp [string map {"D " "" T " "} $line]
unset line
puts "libVersionHash = $libVersionHash"
puts "manifestTimestamp = $manifestTimestamp"

set FSL_PLATFORM_CONFIG_H "
\#define FSL_LIB_VERSION_HASH \"$libVersionHash\"
\#define FSL_LIB_VERSION_TIMESTAMP \"$manifestTimestamp UTC\"
\#define FSL_LIB_CONFIG_TIME \"$timestamp GMT\"
\#if defined(_MSC_VER)
\#define FSL_PLATFORM_OS \"windows\"
\#define FSL_PLATFORM_IS_WINDOWS 1
\#define FSL_PLATFORM_IS_UNIX 0
\#define FSL_PLATFORM_PLATFORM \"windows\"
\#define FSL_PLATFORM_PATH_SEPARATOR \";\"
\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\"
\/* define a __func__ compatibility macro *\/
\#if _MSC_VER < 1500    /* (vc9.0; dev studio 2008) */
/* sorry; cant do much better than nothing at all on those earlier ones */
\#define __func__ \"(func)\"
\#else
\#define __func__ __FUNCTION__
\#endif
/* for the time being at least, don't complain about there being secure crt alternatives: */
\#ifndef _CRT_SECURE_NO_WARNINGS
\#define _CRT_SECURE_NO_WARNINGS
\#endif
/* for the time being at least, don't complain about using POSIX names instead of ISO C++: */
\#pragma warning ( disable : 4996 )
/* for the time being at least, suppresss some int conversion warnings */
\#pragma warning ( disable : 4244 )     /*'fsl_size_t' to 'int'; this masks other problems that should be fixed*/
\#pragma warning ( disable : 4761 )     /*'integral size mismatch in argument'; more size_t problems*/
\#pragma warning ( disable : 4267 )     /*'size_t' to 'int'; crops up especially in 64-bit builds*/
/* these were extracted from fossil's unistd.h */
\#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
\#include <io.h>
\#elif defined(__MINGW32__)
\#define FSL_PLATFORM_OS \"mingw\"
\#define FSL_PLATFORM_IS_WINDOWS 1
\#define FSL_PLATFORM_IS_UNIX 0
\#define FSL_PLATFORM_PLATFORM \"windows\"
\#define FSL_PLATFORM_PATH_SEPARATOR \";\"
\#define FSL_CHECKOUTDB_NAME \"./.fslckout\"
\#elif defined(__CYGWIN__)
\#define FSL_PLATFORM_OS \"cygwin\"
\#define FSL_PLATFORM_IS_WINDOWS 0
\#define FSL_PLATFORM_IS_UNIX 1
\#define FSL_PLATFORM_PLATFORM \"unix\"
\#define FSL_PLATFORM_PATH_SEPARATOR \":\"
\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\"
\#else
\#define FSL_PLATFORM_OS \"unknown\"
\#define FSL_PLATFORM_IS_WINDOWS 0
\#define FSL_PLATFORM_IS_UNIX 1
\#define FSL_PLATFORM_PLATFORM \"unix\"
\#define FSL_PLATFORM_PATH_SEPARATOR \":\"
\#define FSL_CHECKOUTDB_NAME \"./.fslckout\"
\#endif
"

if {[opt-bool amal]} {
    puts "Generating conservative config for the amalgamation build..."
    set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_
    set ofile libfossil-config.h
    set out [open $ofile w]
    puts $out "\#if !defined($incGuard)
\#define $incGuard 1
\#define FSL_AUX_SCHEMA \"$auxSchema\"
\#define FSL_CONTENT_SCHEMA \"$contentSchema\"
\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\"
\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\"
/* Tweak the following for your system... */
\#if !defined(HAVE_COMPRESS)
\#  define HAVE_COMPRESS 1
\#endif
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H 0
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN 0
\#endif
\#if !defined(HAVE_GETADDRINFO)
\#  define HAVE_GETADDRINFO 0
\#endif
\#if !defined(HAVE_INET_NTOP)
\#  define HAVE_INET_NTOP 0
\#endif
\#if !defined(HAVE_INTTYPES_H)
\#  define HAVE_INTTYPES_H 0
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL 0
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL 0
\#endif
\#if !defined(_WIN32)
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 1
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 1
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 1
\#endif
\#if !defined(HAVE_STDINT_H)
\#  define HAVE_STDINT_H 0
\#endif
\#if !defined(_DEFAULT_SOURCE)
\#  define _DEFAULT_SOURCE 1
\#endif
\#if !defined(_XOPEN_SOURCE)
\#  define _XOPEN_SOURCE 500
\#endif
\#else
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 0
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 0
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 0
\#endif
\#if !defined(HAVE_STDINT_H)
\#  define HAVE_STDINT_H 0
\#endif
\#endif
/* _WIN32 */

$FSL_PLATFORM_CONFIG_H

\#endif
/* $incGuard */
"
    close $out
    puts "Generated $ofile."
    return
}
# end of --amal bootstrap config generation

#cc-check-c11
cc-check-c11

cc-check-sizeof "void *"

if {![cc-check-includes zlib.h] ||
    ![cc-check-function-in-lib compress z]} {
    user-error "Missing functional zlib"
}
cc-check-function-in-lib iconv iconv
#if {![cc-check-functions iconv] &&
#    ![cc-check-function-in-lib iconv iconv]} {
#    user-error "Cannot find iconv(3) in libc or libiconv"
#}

########################################################################
# Checks for C99 via (__STDC_VERSION__ >= 199901L). Returns 1 if so, 0
# 0 if not.
proc cc-check-c99 {} {
    msg-checking "Checking for C99 via __STDC_VERSION__... "
    if {[cctest -code {
46
47
48
49
50
51
52


































53
54
55
56
57
58
59
60
61
62
















63
64
65
66
67
68
69
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    set CC_FLAG_C99 {}
}
if {![cc-check-c99]} {
    user-error "As of 2021-02-21, libfossil requires C99."
}

define CC_FLAG_C99 $CC_FLAG_C99
########################################################################
# Module loader is currently used only by the s2 binding.
define HAVE_LIBLTDL 0
define HAVE_LIBDL 0
define LDFLAGS_MODULE_LOADER ""
define FSL_ENABLE_MODULE_LOADER 0
if {[cc-check-includes ltdl.h] && [cc-check-function-in-lib lt_dlopen ltdl]} {
    define HAVE_LIBLTDL 1
    define LDFLAGS_MODULE_LOADER "-lltdl"
    define FSL_ENABLE_MODULE_LOADER 1
} elseif {[cc-with {-includes dlfcn.h} {
	  cctest -link 1 -declare "extern char* dlerror(void);" -code "dlerror();"}]} {
    msg-result "This system can use dlopen() w/o -ldl"
    define HAVE_LIBDL 1
    define LDFLAGS_MODULE_LOADER ""
    define FSL_ENABLE_MODULE_LOADER 1
} elseif {[cc-check-includes dlfcn.h]} {
    msg-result "Found dlfcn.h."
    define HAVE_LIBDL 1
    if {[cc-check-function-in-lib dlopen dl]} {
      msg-result "Found libdl."
      define LDFLAGS_MODULE_LOADER "-ldl"
    } else {
      msg-result "No libdl found. Assuming dlopen is built-in."
      define LDFLAGS_MODULE_LOADER ""
    }
    define FSL_ENABLE_MODULE_LOADER 1
}

if {![get-define FSL_ENABLE_MODULE_LOADER]} {
    msg-result {No usable module loading library found. No worries, because we won't have a module system yet. :-D}
} else {
    msg-result {Found a module loader. Now we just need something to do with it.}
}

########################################################################
# A proxy for cc-check-function-in-lib which "undoes" any changes that
# routine makes to the LIBS define.
proc my-check-function-in-lib {function libs {otherlibs {}}} {
    set _LIBS [get-define LIBS]
    set found [cc-check-function-in-lib $function $libs $otherlibs]
    define LIBS $_LIBS
    return $found
}

########################################################################
# readline is only used (if at all) by s2sh. If it's not available, we
# fall back to a tree-local copy of the BSD-licensed linenoise editing
# library.
if {[cc-check-includes readline/readline.h] &&
    [my-check-function-in-lib readline readline]} {
    define FSL_ENABLE_READLINE 1
    define FSL_ENABLE_LINENOISE 0
    msg-result "Enabling libreadline for f-s2sh line editing."
} else {
    msg-result "libreadline not found. f-s2sh will use the linenoise line editor."
    define FSL_ENABLE_READLINE 0
    define FSL_ENABLE_LINENOISE 1
    define lib_readline ""
}

# cc-check-functions getcwd fopen
cc-check-functions opendir stat pipe inet_ntop getaddrinfo
#msg-result [cc-check-functions lstat]

if {[cc-check-functions lstat]} {
    # for lstat() on Linux and FreeBSD:
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











































































































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







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-










+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








+
+
+
+
+
+
+
+
+












+


+
+













+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    msg-result "Non-debug build."
    set cFlags "$cFlags -O2"
} else {
    msg-result "Debug build enabled. Use --no-debug to build in non-debug mode."
    set cFlags "$cFlags -g -DDEBUG -O0"
}


set pcBin [find-an-executable pkg-config]
if {"" eq $pcBin} {
    puts {pkg-config not found, so making some guesses about
available packages.}
}
########################################################################
# Curses!
set LIB_CURSES ""
set CFLAGS_CURSES ""
puts "Looking for \[n]curses..."
if {"" ne $pcBin && $::tcl_platform(os)!="Darwin"} {
# Some macOS pkg-config configurations alter library search paths, which make
# the compiler unable to find lib iconv, so don't use pkg-config on macOS.
    set np ""
    foreach p {ncursesw ncurses} {
        if {[catch {exec $pcBin --exists $p}]} {
            continue
        }
        set np $p
        puts "Using pkg-config curses package \[$p]"
        break
    }
    if {"" ne $np} {
        set ppanel ""
        if {"ncursesw" eq $np} {
            if {![catch {exec $pcBin --exists panelw}]} {
                set ppanel panelw
            }
        }
        if {"" eq $ppanel && ![catch {exec $pcBin --exists panel}]} {
            set ppanel panel
        }
        set CFLAGS_CURSES [exec $pcBin --cflags $np]
        set LIB_CURSES [exec $pcBin --libs $np]
        if {"" eq $ppanel} {
            # Apparently Mac brew has pkg-config for ncursesw but not
            # panel/panelw, but hard-coding -lpanel seems to work on
            # that platform.
            append LIB_CURSES " -lpanel"
        } else {
            append LIB_CURSES " " [exec $pcBin --libs $ppanel]
            # append CFLAGS_CURSES " " [exec $pcBin --cflags $ppanel]
            # ^^^^ appending the panel cflags will end up duplicating
            # at least one -D flag from $np's cflags, leading to
            # "already defined" errors at compile-time. Sigh. Note, however,
            # that $ppanel's cflags have flags which $np's do not, so we
            # may need to include those flags anyway and manually perform
            # surgery on the list to remove dupes. Sigh.
        }
    }
}

if {"" eq $LIB_CURSES} {
    puts "Guessing curses location (will fail for exotic locations)..."
    define HAVE_CURSES_H [cc-check-includes curses.h]
    if {[get-define HAVE_CURSES_H]} {
        # Linux has -lncurses, BSD -lcurses. Both have <curses.h>
        msg-result "Found curses.h"
        if {[my-check-function-in-lib waddnwstr ncursesw]} {
            msg-result "Found -lncursesw"
            set LIB_CURSES "-lncursesw -lpanelw"
        } elseif {[my-check-function-in-lib initscr ncurses]} {
            msg-result "Found -lncurses"
            set LIB_CURSES "-lncurses -lpanel"
        } elseif {[my-check-function-in-lib initscr curses]} {
            msg-result "Found -lcurses"
            set LIB_CURSES "-lcurses -lpanel"
        }
    }
}
if {"" eq $LIB_CURSES} {
    user-error "!Curses! Foiled again!"
} else {
    puts {
        ************************************************************
        If your build fails due to missing functions such as
        waddwstr(), make sure you have the ncursesW development
        package installed. Some platforms combine the "w" and non-w
        curses builds and some don't.

        The package may have a name such as libncursesw5-dev or
        some such.
        ************************************************************
    }
}
define LIB_CURSES $LIB_CURSES
define CFLAGS_CURSES $CFLAGS_CURSES
unset LIB_CURSES
unset CFLAGS_CURSES


puts "Checking for compile_commands.json support..."
if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} {
    msg-result "Compiler supports compile_commands.json."
    define MAKE_COMPILATION_DB yes
} else {
    msg-result "Compiler does not support compile_commands.json."
    define MAKE_COMPILATION_DB no
}

define CFLAGS $cFlags

if {[opt-bool shared]} {
    msg-result "Enabling build of shared library."
    define LIBFOSSIL_SHARED 1
} else {
    define LIBFOSSIL_SHARED 0
    msg-result "Disabling build of shared library."
}

if {[opt-bool static]} {
    msg-result "Enabling build of static library."
    define LIBFOSSIL_STATIC 1
} else {
    define LIBFOSSIL_STATIC 0
    msg-result "Disabling build of static library."
}


if {[opt-bool loud]} {
    define BUILD_QUIETLY 0
    puts "Using 'loud' build mode."
} else {
    define BUILD_QUIETLY 1
    puts "Enabling quiet build mode. Use --loud to enable loud mode."
}

set dotBin [find-an-executable dot]
if {[string length $dotBin]} {
    define DOXYGEN_HAVE_DOT YES
    msg-result "Adding HAVE_DOT=YES to doxyfile."
} else {
    define DOXYGEN_HAVE_DOT NO
}


# Creates mkefile(-like) file $name from $name.in but explicitly makes
# the output read-only, to avoid inadvertent editing (who, me?).
proc makeFromDotIn {name} {
    catch { exec chmod u+w $name }
    make-template $name.in $name
    catch { exec chmod u-w $name }
}
# Each generated Makefile requires an input file with a .in extension:
set makefiles {
    config.make
    Makefile
    doc/Doxyfile
    src/Makefile
    fnc/Makefile
    bindings/Makefile
    bindings/cpp/Makefile
}
foreach {f} $makefiles {
    makeFromDotIn $f
}

if {0} {
    # Achtung: ordering of the -bare/-str options here is important
    # because of the mixed use of strings and integers for #defines...
    make-config-header include/fossil-scm/autoconfig.h \
        -none {DOXYGEN_*} \
        -bare {HAVE_*  FSL_ENABLE_* _DEFAULT_SOURCE _XOPEN_SOURCE} \
        -str {FSL_* PACKAGE_*}
}

########################################################################
# Generate our autconf header by hand to allow finer control
# over the structure....
set confH include/fossil-scm/autoconfig.h
set incGuard _NET_FOSSIL_SCM_FSL_AUTO_CONFIG_H_INCLUDED_
if {[opt-bool amal]} {
    set confH libfossil-config.h
    set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_
}
puts "Generating config header $confH"
    set out [open $confH w]
    puts $out "\#if !defined($incGuard)
\#define $incGuard 1
\#define FSL_AUX_SCHEMA \"$auxSchema\"
\#define FSL_CONTENT_SCHEMA \"$contentSchema\"
\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\"
\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\"
\#define FSL_SHA1_HARDENED 1
/* Tweak the following for your system... */
\#if !defined(HAVE_GETADDRINFO)
\#  define HAVE_GETADDRINFO [get-define HAVE_GETADDRINFO 0]
\#endif
\#if !defined(HAVE_INET_NTOP)
\#  define HAVE_INET_NTOP [get-define HAVE_INET_NTOP 0]
\#endif
\#if !defined(_WIN32)
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H [get-define HAVE_DLFCN_H 0]
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN [get-define HAVE_DLOPEN 0]
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL [get-define HAVE_LIBDL 0]
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL [get-define HAVE_LIBLTDL 0]
\#endif
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT [get-define HAVE_LSTAT 1]
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H [get-define HAVE_LTDL_H 0]
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN [get-define HAVE_LT_DLOPEN 0]
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR [get-define HAVE_OPENDIR 1]
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE [get-define HAVE_PIPE 1]
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT [get-define HAVE_STAT 1]
\#endif
\#if !defined(_DEFAULT_SOURCE)
\#  define _DEFAULT_SOURCE [get-define _DEFAULT_SOURCE 1]
\#endif
\#if !defined(_XOPEN_SOURCE)
\#  define _XOPEN_SOURCE [get-define _XOPEN_SOURCE 500]
\#endif
\#else
/* _WIN32: */
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H 0
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN 0
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL 0
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL 0
\#endif
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 0
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 0
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 0
\#endif
\#endif
/*_WIN32*/

$FSL_PLATFORM_CONFIG_H

\#endif
/* $incGuard */
"
    close $out
    puts "Generated $confH."
    return
}

Added bindings/Makefile.



















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/usr/bin/make # help out emacs
all:
SUBDIRS :=
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
ifneq (,$(CXX))
clean-.: clean-cpp
distclean-.: distclean-cpp
SUBDIRS += cpp
all: subdir-cpp
endif

clean-.: clean-s2
distclean-.: distclean-s2
include ../subdir-inc.make
SUBDIRS += s2
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
all: subdir-s2

Added bindings/Makefile.in.



















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/usr/bin/make # help out emacs
all:
SUBDIRS :=
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
ifneq (,$(CXX))
clean-.: clean-cpp
distclean-.: distclean-cpp
SUBDIRS += cpp
all: subdir-cpp
endif

clean-.: clean-s2
distclean-.: distclean-s2
include ../subdir-inc.make
SUBDIRS += s2
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
all: subdir-s2

Added bindings/cpp/Context.cpp.




































































































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Context::~Context(){
    if(this->ownsCx){
      fsl_cx_finalize(this->f);
    }
  }

  void Context::setup(fsl_cx_init_opt const * opt){
    //this->f = fsl_cx_malloc();
    //if(!this->f) throw OOMException();
    assert(!this->f);
    int const rc = fsl_cx_init(&this->f, opt);
    if(rc){
      fsl_error err = fsl_error_empty;
      if(this->f){
        fsl_error_move( &this->f->error, &err );
        fsl_cx_finalize(this->f);
        this->f = NULL;
      }else{
        fsl_error_set( &err, rc,
                       "fsl_cx_init() failed with code %s",
                       fsl_rc_cstr(rc) );
      }
      throw Exception(err);
    }
  }

  Context::Context()
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(NULL);
  }
  
  Context::Context(fsl_cx_init_opt const & opt)
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(&opt);
  }

  Context::Context(fsl_cx * f, bool ownsHandle)
    : f(f),
      ownsCx(ownsHandle),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    
  }

  Context::operator fsl_cx * () throw(){
    return this->f;
  }

  Context::operator fsl_cx const * () const throw() {
    return this->f;
  }

  void Context::propagateError() const{
    if(this->f){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      if(err->code) throw Exception(err);
    }
  }

  void Context::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  void Context::assertHasRepo(){
    assert(this->f);
    if(!fsl_needs_repo(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  void Context::assertHasCheckout(){
    assert(this->f);
    if(!fsl_needs_ckout(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  fsl_cx * Context::handle() throw(){
    return this->f;
  }

  fsl_cx const * Context::handle() const throw(){
    return this->f;
  }

  Db & Context::db() throw() {
    if(!this->dbMain.handle()){
      fsl_db * db = fsl_cx_db(*this);
      if(db) this->dbMain.handle(db, false);
    }
    return this->dbMain;
  }


  Db & Context::dbRepo() throw() {
    if(!this->dbRe.handle()){
      fsl_db * db = fsl_cx_db_repo(*this);
      if(db) this->dbRe.handle(db, false);
    }
    return this->dbRe;
  }

  Db & Context::dbCheckout() throw() {
    if(!this->dbCkout.handle()){
      fsl_db * db = fsl_cx_db_ckout(*this);
      if(db) this->dbCkout.handle(db, false);
    }
    return this->dbCkout;
  }

  Context & Context::openCheckout( char const * dirName ){
    this->assertRC( "openCheckout()",
                    fsl_ckout_open_dir(this->f, dirName, true) );
    return *this;
  }

  Context & Context::openRepo( char const * dbName ){
    this->assertRC( "openRepo()",
                    fsl_repo_open(this->f, dbName) );
    return *this;
  }

  Context & Context::closeDbs() throw(){
    fsl_cx_close_dbs(this->f)
      /*
        Reminder to self: the 3 fsl_cx db handles are stored as
        complete fsl_db instances (not pointers) in fsl_cx, with the
        exception of the "main" db, which is just a pointer to one of
        the other 3. What does that mean? It means that when we use
        fsl_cx_close_dbs(), this->dbRe and friends will (if
        initialized) still be pointing to those pointers...  which are
        (due to internal details) actually still valid, they just
        refer to closed fsl_db handles.

        That's actually good for us here, except that certain
        combinations of C-level ops "might" get our checkout/repo db
        pointers cross a bit.
      */
      ;
    assert(!this->dbRe.ownsHandle());
    assert(!this->dbMain.ownsHandle());
    assert(!this->dbCkout.ownsHandle());
    this->dbRe.close();
    this->dbCkout.close();
    this->dbMain.close();
    if(this->dbRe.handle()){
      assert(!this->dbRe.handle()->dbh);
    }
    return *this;
  }

  bool Context::ownsHandle() const throw(){
    return this->ownsCx;
  }

  std::string Context::ridToArtifactUuid(fsl_id_t rid,
                                         fsl_satype_e type){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_artifact_uuid(*this, rid, type);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND,
                      "Could not resolve RID %" FSL_ID_T_PFMT
                      " as artifact type %s.",
                      (fsl_id_t)rid, fsl_satype_cstr(type));
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::ridToUuid(fsl_id_t rid){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_uuid(*this, rid);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND, "Could not resolve RID %" FSL_ID_T_PFMT ".",
                      (fsl_id_t)rid);
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::symToUuid(char const * symbolicName,
                                 fsl_id_t * rid,
                                 fsl_satype_e type){
    this->assertHasRepo();
    fsl_uuid_str uuid = NULL;
    int const rc = fsl_sym_to_uuid(*this, symbolicName, type, &uuid, rid);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    std::string const & rv = uuid;
    fsl_free(uuid);
    return rv;
  }


  fsl_id_t Context::symToRid(char const * symbolicName, fsl_satype_e type){
    this->assertHasRepo();
    fsl_id_t rv = 0;
    int const rc = fsl_sym_to_rid(*this, symbolicName, type, &rv);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    assert(rv>0);
    return rv;
  }

  fsl_id_t Context::symToRid(std::string const & symbolicName,
                             fsl_satype_e type){
    return this->symToRid( symbolicName.c_str(), type );
  }

  Context::Transaction::Transaction(Context &cx)
    : tr( cx.db() ),
      level(tr.level()){
  }

  Context::Transaction::~Transaction() throw(){
    if(this->level) this->tr.rollback();
  }

  void Context::Transaction::commit(){
    if(this->level){
      this->level = 0;
      this->tr.commit();
    }else{
      throw Exception(FSL_RC_MISUSE,
                      "commit() called multiple times.");
    }
  }

  Context & Context::getContent( fsl_id_t rid, Buffer & dest ){
    int const rc = fsl_content_get( *this, rid, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( char const * sym, Buffer & dest ){
    int const rc = fsl_content_get_sym( *this, sym, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( std::string const & sym, Buffer & dest ){
    return this->getContent( sym.c_str(), dest );
  }

} // namespace fsl

#undef CERR

Added bindings/cpp/Db.cpp.





















































































































































































































































































































































































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {
  Stmt::~Stmt() throw(){
    fsl_stmt_finalize(*this);
  }

  Stmt::Stmt(Db & db) throw() :
    db(db),
    stmt(fsl_stmt_empty){
  }

  Stmt::operator fsl_stmt * () throw(){
    return &this->stmt;
  }

  Stmt::operator fsl_stmt const * () const throw() {
    return &this->stmt;
  }


  void Stmt::assertPrepared() const{
    if(!this->stmt.stmt){
      throw Exception(FSL_RC_MISUSE,
                      "Statement is not prepared.");
    }
    assert(this->stmt.db);
  }

  void Stmt::assertRange(short col, short base) const{
    this->assertPrepared();
    char const * errMsg = NULL;
    if(col<0){
      errMsg = "column/parameter index %d is invalid";
    }else if(base){
      assert(1==base);
      if(!col || col > fsl_stmt_param_count(*this)){
        errMsg = "parameter index %d is out of range";
      }
    }else{
      assert(0==base);
      if(col >= fsl_stmt_col_count(*this)){
        errMsg = "column index %d is out of range";
      }
    }
    if(errMsg) throw Exception(FSL_RC_RANGE, errMsg, col+base);
  }

  void Stmt::propagateError() const{
    this->db.propagateError();
  }

  void Stmt::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  Stmt & Stmt::prepare(std::string const & sql){
    return this->prepare("%s", sql.c_str());
  }

  Stmt & Stmt::prepare(Buffer const & sql){
    return this->prepare("%s", sql.c_str());
  }

  Stmt & Stmt::prepare(char const * sql, ... ){
    va_list vargs;
    if(this->stmt.stmt) throw Exception(FSL_RC_MISUSE,
                                        "Statement is prepared and not "
                                        "yet finalized. Cowardly refusing "
                                        "to re-prepare() non-finalized statement: %s",
                                        this->sql());
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_preparev( this->db.handle(), *this,
                          sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL preparation failed for: %s", sql);
    }else{
      assert(this->stmt.db == this->db.handle());
      return *this;
    }
  }

  int Stmt::stepCount() const throw(){
    return this->stmt.rowCount;
  }

  int Stmt::paramCount() const throw(){
    return this->stmt.paramCount;
  }

  int Stmt::columnCount() const throw(){
    return this->stmt.colCount;
  }

  char const * Stmt::sql() const throw() {
    return stmt.stmt
      ? fsl_buffer_cstr(&stmt.sql)
      : NULL;
  }

  fsl_stmt * Stmt::handle() throw(){
    return &this->stmt;
  }
  
  fsl_stmt const * Stmt::handle() const throw() {
    return &this->stmt;
  }

  Stmt & Stmt::reset(bool resetStepCounterToo){
    this->assertRC("reset()",
                   fsl_stmt_reset2(*this,
                                   resetStepCounterToo));
    return *this;
  }

  Stmt & Stmt::finalize() throw(){
    fsl_stmt_finalize(*this);
    return *this;
  }

  bool Stmt::step(){
    this->assertPrepared();
    int const rc = fsl_stmt_step(*this);
    switch(rc){
      case FSL_RC_STEP_ROW: return true;
      case FSL_RC_STEP_DONE: return false;
      default:
        this->propagateError();
        throw Exception(rc,"No idea what went wrong.");
    }
  }

  Stmt & Stmt::stepExpectDone(){
    if(this->step()){
      throw Exception(FSL_RC_ERROR,
                      "Expecting statement to return no rows: %s",
                      this->sql());
    }
    return *this;
  }

  int32_t Stmt::getInt32(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_int32( *this, col );
  }

  int64_t Stmt::getInt64(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_int64( *this, col );
  }

  double Stmt::getDouble(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_double( *this, col );
  }

  fsl_id_t Stmt::getId(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_id( *this, col );
  }

  char const * Stmt::columnName(short col){
    this->assertRange(col, 0);
    return fsl_stmt_col_name(*this, col);
  }

  char const * Stmt::getText(short col, fsl_size_t * length){
    this->assertRange(col, 0);
    return fsl_stmt_g_text(*this, col, length);
  }

  void const * Stmt::getBlob(short col, fsl_size_t * length){
    void const * v = NULL;
    this->assertRange(col, 0);
    fsl_stmt_get_blob(*this, col, &v, length);
    return v;
  }

  Stmt & Stmt::bind(short col){
    this->assertRange(col, 1);
    this->assertRC("bind NULL",
                   fsl_stmt_bind_null(*this, col));
    return *this;
  }
  
  Stmt & Stmt::bind(short col, int32_t v){
    this->assertRange(col, 1);
    this->assertRC("bind int32",
                   fsl_stmt_bind_int32(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, int64_t v){
    this->assertRange(col, 1);
    this->assertRC("bind int64",
                   fsl_stmt_bind_int64(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, double v){
    this->assertRange(col, 1);
    this->assertRC("bind double",
                   fsl_stmt_bind_double(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, char const * str,
                    fsl_int_t len, bool copyBytes){
    this->assertRange(col, 1);
    this->assertRC("bind text",
                   fsl_stmt_bind_text(*this, col, str, len, copyBytes));
    return *this;
  }

  Stmt & Stmt::bind(short col, std::string const & str){
    return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1);
  }
  
  Stmt & Stmt::bind(short col, void const * v,
                    fsl_size_t len, bool copyBytes){
    this->assertRange(col, 1);
    this->assertRC("bind blob",
                   fsl_stmt_bind_blob(*this, col, v, len, copyBytes));
    return *this;
  }

  int Stmt::paramIndex(char const * name){
    this->assertPrepared();
    return fsl_stmt_param_index(*this, name);
  }

  Stmt & Stmt::bind(char const * col){
    this->assertRC("bind NULL",
                   fsl_stmt_bind_null_name(*this, col));
    return *this;
  }
  
  Stmt & Stmt::bind(char const * col, int32_t v){
    this->assertRC("bind int32",
                   fsl_stmt_bind_int32_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, int64_t v){
    this->assertRC("bind int64",
                   fsl_stmt_bind_int64_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, double v){
    this->assertRC("bind double",
                   fsl_stmt_bind_double_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, char const * str,
                    fsl_int_t len, bool copyBytes){
    this->assertRC("bind text",
                   fsl_stmt_bind_text_name(*this, col, str, len, copyBytes));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, std::string const & str){
    return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1);
  }
  
  Stmt & Stmt::bind(char const * col, void const * v,
                    fsl_size_t len, bool copyBytes){
    this->assertRC("bind blob",
                   fsl_stmt_bind_blob_name(*this, col, v, len, copyBytes));
    return *this;
  }

  StmtBinder::~StmtBinder(){}

  StmtBinder::StmtBinder(Stmt &s) : st(s), col(0)
  {}

  Stmt & StmtBinder::stmt(){
    return this->st;
  }

  StmtBinder & StmtBinder::operator()(){
    st.bind(++this->col);
    return *this;
  }

  StmtBinder & StmtBinder::operator()(char const * v, fsl_int_t len,
                                      bool copyBytes){
    st.bind(++this->col, v, len, copyBytes);
    return *this;
  }

  StmtBinder & StmtBinder::operator()(void const * v, fsl_size_t len,
                                      bool copyBytes){
    st.bind(++this->col, v, len, copyBytes);
    return *this;
  }

  StmtBinder & StmtBinder::reset(bool alsoStatement) {
    this->col = 0;
    if(alsoStatement) this->st.reset();
    return *this;
  }

  bool StmtBinder::step(){
    return this->st.step();
  }

  StmtBinder & StmtBinder::once(){
    this->st.stepExpectDone();
    return this->reset();
  }

  Db::~Db() throw(){
    this->close();
  }

  void Db::setup(){
    assert(!this->db);
    this->db = fsl_db_malloc();
    if(!this->db) throw OOMException();
    this->ownsDb = true;
  }

  Db::Db(): db(NULL),
            ownsDb(true){
  }

  Db::Db(char const * filename, int openFlags)
    : db(NULL),
      ownsDb(true){
    try{
      this->open(filename, openFlags);
    }catch(...){
      if(this->db) fsl_db_close(this->db);
      throw;
    }
  }

  void Db::propagateError() const{
    if(this->db && this->db->error.code){
      throw Exception(this->db->error);
    }
  }

  void Db::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  void Db::assertOpened() const{
    if(!this->db || !this->db->dbh){
      throw Exception(FSL_RC_MISUSE,
                      "Db is not opened.");
    }
  }

  Db::operator fsl_db * () throw(){
    return this->db;
  }

  Db::operator fsl_db const * () const throw() {
    return this->db;
  }
  
  Db & Db::handle( fsl_db * db, bool ownsHandle )throw(){
    if(this->db != db){
      this->close();
      this->db = db;
      this->ownsDb = ownsHandle;
    }
    return *this;
  }

  Db & Db::close() throw(){
    if(this->db){
      if(this->ownsDb){
        fsl_db_close(*this);
      }
      this->db = NULL;
    }
    return *this;
  }

  char const * Db::filename() throw(){
    return this->db
      ? fsl_db_filename(*this, NULL)
      : NULL;
  }

  Db & Db::open(char const * filename, int openFlags){
    if(!this->db) this->setup();
    else if(this->db->dbh){
      throw Exception(FSL_RC_MISUSE,
                      "Db is already opened: %s",
                      this->filename());
    }
    int const rc = fsl_db_open(*this, filename, openFlags);
    if(rc){
      /*
         The problem here is that open() can be called from the ctor,
         and if the ctor throws then the dtor is not called, so we
         have to free up this->db. Oh, wait...  the open() ctor does
         that for us, so this gets easier.

         But... if it throws from outside the ctor then we DO have to
         clean up.
       */
      Exception const & ex = this->db->error.code
        ? Exception(this->db->error)
        : Exception(rc,"fsl_db_open(%s) failed: %s",
                    filename, fsl_rc_cstr(rc));
      this->close();
      throw ex;
    }
    return *this;
  }

  bool Db::isOpened() const throw(){
    return (this->db && this->db->dbh) ? true : false;
  }

  bool Db::ownsHandle() const throw(){
    return this->ownsDb;
  }

  fsl_db * Db::handle() throw(){
    return this->db;
  }
  
  fsl_db const * Db::handle() const throw() {
    return this->db;
  }

  Db & Db::begin(){
    this->assertOpened();
    this->assertRC( 
                   "begin()",
                    fsl_db_transaction_begin(*this) );
    return *this;
  }

  Db & Db::commit(){
    this->assertOpened();
    this->assertRC( "commit()",
                    fsl_db_transaction_end(*this, 0) );
    return *this;
  }

  Db & Db::rollback() throw() {
    this->assertOpened();
    fsl_db_transaction_end(*this, 1);
    return *this;
  }

  Db & Db::exec(std::string const & sql){
    return this->exec("%s", sql.c_str());
  }

  Db & Db::exec(char const * sql, ...){
    this->assertOpened();
    va_list vargs;
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_execv( *this, sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL execution failed for: %s", sql);
    }
    else return *this;
  }

  Db & Db::execMulti(char const * sql, ...){
    this->assertOpened();
    va_list vargs;
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_exec_multiv( *this, sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL multi-exec failed for: %s", sql);
    }
    else return *this;
  }

  Db & Db::execMulti(std::string const & sql){
    return this->execMulti("%s", sql.c_str());
  }


  Db & Db::attach(char const * filename, char const * label){
    this->assertRC( "attach()", fsl_db_attach(*this, filename, label) );
    return *this;
  }

  Db & Db::detach(char const * label){
    this->assertRC( "detach()", fsl_db_detach(*this, label) );
    return *this;
  }

  int Db::transactionLevel() const throw(){
    return this->db
      ? this->db->beginCount
      : 0;
  }

  Db::Transaction::Transaction(Db & db)
  : db(db), inTrans(false){
    db.begin();
    inTrans = db.transactionLevel();
  }

  Db::Transaction::~Transaction() throw(){
    if(inTrans) this->rollback();
  }

  void Db::Transaction::commit(){
    assert(inTrans);
    if(inTrans>0){
      inTrans = 0;
      db.commit();
    }
  }

  void Db::Transaction::rollback() throw(){
    assert(inTrans);
    if(inTrans){
#if 1
      inTrans = 0;
      db.rollback();
#else
      /* sane? */
      while(inTrans < db.transactionLevel()){
        db.rollback();
      }
#endif
    }
  }

  int Db::Transaction::level() const throw(){
    return db.transactionLevel();
  }

}// namespace fsl

#undef CERR

Added bindings/cpp/Deck.cpp.































































































































































































































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Deck::~Deck() throw(){
    delete this->deltaBase;
    if(this->ownsDeck){
      assert(this->d->allocStamp);
      fsl_deck_finalize(this->d);
    }
  }

  void Deck::setup(fsl_deck * mf, fsl_satype_e type){
    if(!mf){
      assert(!this->d);
      assert(this->ownsDeck);
      this->d = fsl_deck_malloc();
      if(!this->d) throw OOMException();
    }else{
      assert(this->d == mf);
      if(d && d->f && (d->f != this->cx.handle())){
        throw Exception(FSL_RC_MISUSE,
                        "Mis-matched fsl_cx contexts for deck.");
      }
    }
    if(ownsDeck){
      fsl_deck_init( this->cx, d, type );
    }
  }

  Deck::Deck(Context & cx, fsl_satype_e type)
    : cx(cx),
      d(NULL),
      deltaBase(NULL),
      ownsDeck(true){
    this->setup( NULL, type );
  }

  Deck::Deck(Context & cx, fsl_deck * d, bool ownsDeck)
    : cx(cx),
      d(d),
      deltaBase(NULL),
      ownsDeck(ownsDeck){
    if(!d){
      throw Exception(FSL_RC_MISUSE,
                      "A proxied Deck may not be NULL.");
    }
    this->setup( d, d->type );
  }

  void Deck::propagateError() const{
    fsl_error const * err = fsl_cx_err_get_e(this->cx);
    if(err->code){
      throw Exception(err);
    }
  }

  void Deck::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  Deck & Deck::cleanup() throw(){
    fsl_deck_clean(*this);
    return *this;
  }

  fsl_satype_e Deck::type() const throw(){
    return this->d->type;
  }

  fsl_id_t Deck::rid() const throw(){
    return this->d->rid;
  }

  fsl_uuid_cstr Deck::uuid() const throw(){
    return this->d->uuid;
  }

  Deck::operator fsl_deck *() throw(){
    return this->d;
  }

  Deck::operator fsl_deck const *() const throw(){
    return this->d;
  }

  Context & Deck::context() throw() { return this->cx; }
  Context const & Deck::context() const throw() { return this->cx; }

  fsl_deck * Deck::handle() throw(){
    return this->d;
  }

  fsl_deck const * Deck::handle() const throw(){
    return this->d;
  }

  bool Deck::hasAllRequiredCards() const throw(){
    return fsl_deck_has_required_cards(*this);
  }

  Deck const & Deck::assertHasRequiredCards() const{
    if(!fsl_deck_has_required_cards(*this)){
      throw Exception(fsl_cx_err_get_e(this->cx));
    }
    else return *this;
  }

  bool Deck::cardIsLegal(char cardLetter) const throw(){
    return fsl_card_is_legal( this->d->type, cardLetter );
  }

  Deck & Deck::unshuffle(bool calcRCard){
    int const rc = fsl_deck_unshuffle(*this, calcRCard);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }else return *this;
  }

  Deck const & Deck::output( fsl_output_f f, void * outState ) const{
    int const rc = fsl_deck_output( this->d, f, outState );
    if(rc) throw Exception(this->d->f->error);
    else return *this;
  }

  Deck const & Deck::output( std::ostream & os ) const{
    return this->output( fsl_output_f_std_ostream, &os );
  }

  Deck & Deck::save(bool isPrivate){
    int const rc = fsl_deck_save( *this, isPrivate );
    if(rc){
      this->propagateError();
      throw Exception(rc,"fsl_deck_save() failed: %s",
                      fsl_rc_cstr(rc));
    }
    else return *this;
  }

  Deck & Deck::load( fsl_id_t rid, fsl_satype_e type ){
    this->cleanup();
    int const rc = fsl_deck_load_rid( this->cx, *this,
                                      rid, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_rid() failed "
                      "with %s for symbol: %" FSL_ID_T_PFMT,
                      fsl_rc_cstr(rc), (fsl_id_t)rid);
    }else return *this;
  }

  Deck & Deck::load( char const * symbolicName, fsl_satype_e type){
    this->cleanup();
    int const rc = fsl_deck_load_sym( this->cx, *this,
                                      symbolicName, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_sym() failed "
                      "with %s for symbol: %s",
                      fsl_rc_cstr(rc), symbolicName);
    }else return *this;
  }

  Deck & Deck::load( std::string const & symbolicName, fsl_satype_e type){
    return this->load( symbolicName.c_str(), type );
  }

  Deck & Deck::setCardA( char const * name,
                         char const * tgt,
                         fsl_uuid_cstr uuid ){
    int const rc = fsl_deck_A_set(*this, name, tgt, uuid);
    this->assertRC("fsl_deck_A_set()", rc);
    return *this;
  }

  Deck & Deck::setCardB(fsl_uuid_cstr uuid){
    if(this->deltaBase){
      delete this->deltaBase;
      this->deltaBase = NULL;
    }
    int const rc = fsl_deck_B_set(*this, uuid);
    this->assertRC("fsl_deck_B_add()", rc);
    return *this;
  }


  Deck & Deck::setCardC( char const * comment ){
    int const rc = fsl_deck_C_set(*this, comment, -1);
    this->assertRC("fsl_deck_C_set()", rc);
    return *this;
  }

  Deck & Deck::setCardD(double julianDay){
    int const rc = fsl_deck_D_set(*this,
                                  julianDay<0
                                  ? fsl_julian_now()
                                  : julianDay);
    this->assertRC("fsl_deck_D_set()", rc);
    return *this;
  }

  Deck & Deck::setCardE( fsl_uuid_cstr uuid, double julian ){
    int const rc = fsl_deck_E_set(*this,
                                  julian<0
                                  ? fsl_julian_now()
                                  : julian,
                                  uuid );
    this->assertRC("fsl_deck_E_set()", rc);
    return *this;
  }


  Deck & Deck::addCardF(char const * name,
                        fsl_uuid_cstr uuid,
                        fsl_fileperm_e perm, 
                        char const * oldName ){
    int const rc = fsl_deck_F_add(*this, name, uuid, perm, oldName);
    this->assertRC("fsl_deck_F_add()", rc);
    return *this;
  }

  Deck & Deck::addCardJ( char isAppend, char const * key, char const * value ){
    int const rc = fsl_deck_J_add(*this, isAppend, key, value);
    this->assertRC("fsl_deck_J_add()", rc);
    return *this;
  }


  Deck & Deck::setCardK(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_K_set(*this, uuid);
    this->assertRC("fsl_deck_K_set()", rc);
    return *this;
  }

  Deck & Deck::setCardL( char const * title ){
    int const rc = fsl_deck_L_set(*this, title, -1);
    this->assertRC("fsl_deck_L_set()", rc);
    return *this;
  }

  Deck & Deck::addCardM(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_M_add(*this, uuid);
    this->assertRC("fsl_deck_M_add()", rc);
    return *this;
  }


  Deck & Deck::setCardN(char const * name){
    int const rc = fsl_deck_N_set(*this, name, -1);
    this->assertRC("fsl_deck_N_set()", rc);
    return *this;
  }

  Deck & Deck::addCardP(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_P_add(*this, uuid);
    this->assertRC("fsl_deck_P_add()", rc);
    return *this;
  }

  Deck & Deck::addCardQ(char type, fsl_uuid_cstr target,
                        fsl_uuid_cstr baseline){
    int const rc = fsl_deck_Q_add(*this, type, target, baseline);
    this->assertRC("fsl_deck_Q_add()", rc);
    return *this;
  }

  Deck & Deck::addCardT(fsl_tagtype_e tagType,
                        char const * name,
                        fsl_uuid_cstr uuid,
                        char const * value){
    int const rc = fsl_deck_T_add( *this, tagType, uuid, name, value );
    this->assertRC("fsl_deck_T_add()", rc);
    return *this;
  }


  Deck & Deck::setCardU(char const * user){
    if(!user || !*user){
      user = fsl_cx_user_get(this->cx);
    }
    if(!user || !*user){
      throw Exception(FSL_RC_MISUSE,
                      "setCardU(): NULL/empty user name is not legal.");
    }
    int const rc = fsl_deck_U_set(*this, user);
    this->assertRC("fsl_deck_U_set()", rc);
    return *this;
  }

  Deck & Deck::setCardW(char const * content, fsl_int_t len){
    int const rc = fsl_deck_W_set(*this, content, len);
    this->assertRC("fsl_deck_W_set()", rc);
    return *this;
  }

  Deck::FCardIterator::~FCardIterator() throw()
  {}

  Deck::FCardIterator::FCardIterator(Deck & d, bool skipDeletedFiles)
    : d(&d),
      fc(NULL),
      skipDeleted(skipDeletedFiles)
  {
    int const rc = fsl_deck_F_rewind(d);
    if(rc) throw Exception(rc, "fsl_deck_F_rewind() failed: %s",
                           fsl_rc_cstr(rc));
    fsl_deck_F_next(d, &this->fc);
  }

  Deck::FCardIterator::FCardIterator() throw()
    : d(NULL),
      fc(NULL),
      skipDeleted(false)
  {}

  void Deck::FCardIterator::assertHasDeck(){
    if(!this->d) throw Exception(FSL_RC_MISUSE,
                                 "Iterator requires a deck object.");
  }

  Deck::FCardIterator & Deck::FCardIterator::operator++(){
    if(this->fc){
      do{
        int const rc = fsl_deck_F_next(*this->d, &this->fc);
        if(rc) throw Exception(rc);
      }while(skipDeleted && (this->fc && !this->fc->uuid));
    }
    return *this;
  }

  fsl_card_F const * Deck::FCardIterator::operator*(){
    return this->fc;
  }

  fsl_card_F const * Deck::FCardIterator::operator->(){
    if(!this->fc) throw Exception(FSL_RC_MISUSE,
                                  "Throwing to avoid "
                                  "dereferencing a NULL fsl_card_F.");
    return this->fc;
  }


  bool Deck::FCardIterator::operator==(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return true;
    else if(!this->fc || !rhs.fc) return false;
    return 0==fsl_strcmp(this->fc->name, rhs.fc->name);
  }
    
  bool Deck::FCardIterator::operator!=(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return false;
    else if(!this->fc || !rhs.fc) return true;
    return 0!=fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  bool Deck::FCardIterator::operator<(FCardIterator const &rhs) const throw(){
    if(!this->fc) return rhs.fc ? true : false;
    else if(!rhs.fc) return false;
    return 0 > fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  Deck * Deck::baseline(){
    if(FSL_SATYPE_CHECKIN==this->d->type){
      if(this->deltaBase) return this->deltaBase;
      else if(!this->d->B.uuid) return NULL;
      else if(!this->d->B.baseline){
        int const rc = fsl_deck_F_rewind(*this);
        this->assertRC("fsl_deck_rewind()", rc);
        assert(this->d->B.baseline);
      }
      return this->deltaBase = new Deck(this->cx,
                                        this->d->B.baseline,
                                        false);
    }else{
      return NULL;
    }
  }

  Deck::TCardIterator::TCardIterator(Deck & d)
  : ParentType(d.handle()->T)
  {}

  Deck::TCardIterator::TCardIterator()
  : ParentType()
  {}

  Deck::TCardIterator::~TCardIterator() throw()
  {}

  fsl_card_T const * Deck::TCardIterator::operator->() const{
    fsl_card_T const * rv = this->currentValue();
    if( !rv ) throw Exception(FSL_RC_MISUSE,
                              "Throwing to avoid dereferencing a NULL "
                              "fsl_card_T.");
    else return rv;
  }
  std::ostream & operator<<( std::ostream & os, Deck const & d ){
    d.output(os);
    return os;
  }

} // namespace fsl

#undef CERR

Added bindings/cpp/Exception.cpp.


























































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Exception::~Exception() throw() {
    fsl_error_clear(*this);
  }

  Exception::Exception(Exception const &other) throw()
    : err(fsl_error_empty){
    fsl_error_copy(&other.err, *this);
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
  }

  Exception & Exception::operator=(Exception const &other) throw(){
    if(&other != this){
      fsl_error_clear(*this);
      fsl_error_copy(&other.err, *this);
    }
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
    return *this;
  }

  Exception::Exception(int code, std::string const & msg) throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, code, "%s",
                  msg.empty()
                  ? fsl_rc_cstr(code)
                  : msg.c_str());
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
    /* Reminder: we have to ignore any error here :/ */
  }

  Exception::Exception(int code) throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, code, "%s", fsl_rc_cstr(code));
  }

  Exception::Exception() throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, FSL_RC_ERROR, NULL);
  }

  Exception::Exception( fsl_error & err ) throw()
    : err(fsl_error_empty)
  {
    assert(err.code);
    fsl_error_move( &err, *this );
  }

  Exception::Exception( fsl_error const * err ) throw()
    : err(fsl_error_empty)
  {
    if(err && err->code){
      fsl_error_copy( err, *this );
    }else{
      fsl_error_set( *this, FSL_RC_MISUSE,
                     "Exception(fsl_error const *) ctor passed a %s!",
                     err ? "fsl_error with code==0" : "NULL");
    }
  }

  void Exception::error(int code, char const * fmt, va_list vargs) throw(){
    fsl_error_setv(*this, code, fmt, vargs);
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
  }

  Exception::Exception(int code, char const * fmt, ...) throw()
    : err(fsl_error_empty){
    va_list vargs;
    va_start(vargs,fmt);
    this->error(code, fmt, vargs);
    va_end(vargs);
  }

  Exception::Exception(int code, char const * fmt, va_list vargs) throw()
    : err(fsl_error_empty){
    this->error(code, fmt, vargs);
  }

  Exception::operator fsl_error * () throw(){
    return &this->err;
  }

  Exception::operator fsl_error const * () const throw(){
    return &this->err;
  }

  char const * Exception::messsage() const throw(){
    return this->what();
  }

  char const * Exception::what() const throw() {
    return (FSL_RC_OOM==this->err.code)
      ? fsl_rc_cstr(this->err.code)
      : fsl_buffer_cstr(&this->err.msg);
  }

  char const * Exception::codeString() const throw(){
    return fsl_rc_cstr(this->err.code);
  }

  int Exception::code() const throw(){
    return this->err.code;
  }

  OOMException::OOMException() throw()
    : Exception(FSL_RC_OOM)
  {
  }

} // namespace fsl

#undef CERR

Added bindings/cpp/Fossil.cpp.


































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"

/**

 */
namespace fsl {


  Buffer::~Buffer() throw(){
    this->clear();
  }

  Buffer::Buffer(fsl_size_t startingSize)
    : buf(fsl_buffer_empty)
  {
    if(startingSize){
      if(fsl_buffer_reserve(&this->buf, startingSize)){
        throw OOMException();
      }
    }
  }

  Buffer::Buffer()
    : buf(fsl_buffer_empty)
  {}

  Buffer & Buffer::operator=(Buffer const & other){
    fsl_size_t const oldSize = this->buf.capacity;
    this->reset();
    if((&other != this)
       && !other.empty()
       && fsl_buffer_append(*this, other.mem(), other.used())
       ){
      throw OOMException();
    }
    /* A rather arbitrary heuristic to determine whether or not
       we should free up some spce by to resizing buf()
       after re-using it... */
    if((this->buf.capacity == oldSize)
       && (this->buf.capacity > 20)
       && (this->buf.used < (this->buf.capacity/2))){
      fsl_buffer_resize( *this, this->buf.used );
    }
    return *this;
  }

  Buffer::Buffer(Buffer const & other)
  : buf() {
    if(!other.empty()
       && fsl_buffer_append(*this, other.mem(), other.used())
       ){
      throw OOMException();
    }
  }

  Buffer::operator fsl_buffer *() throw(){
    return &this->buf;
  }

  Buffer::operator fsl_buffer const *() const throw(){
    return &this->buf;
  }

  fsl_buffer * Buffer::handle() throw(){
    return &this->buf;
  }
  
  fsl_buffer const * Buffer::handle() const throw(){
    return &this->buf;
  }

  bool Buffer::empty() const throw() { return 0==this->buf.used; }

  fsl_size_t Buffer::used() const throw() { return this->buf.used; }

  fsl_size_t Buffer::capacity() const throw() { return this->buf.capacity; }

  unsigned char const * Buffer::mem() const throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  unsigned char * Buffer::mem() throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  Buffer & Buffer::clear() throw() {
    fsl_buffer_clear(*this);
    return *this;
  }

  Buffer & Buffer::reset() throw() {
    fsl_buffer_reuse(*this);
    return *this;
  }

  Buffer & Buffer::reserve(fsl_size_t n){
    int const rc = fsl_buffer_reserve(*this, n);
    if(rc) throw Exception(rc, "Buffer::reserve() failed");
    return *this;
  }

  Buffer & Buffer::resize(fsl_size_t n){
    int const rc = fsl_buffer_resize(*this, n);
    if(rc) throw Exception(rc, "Buffer::resize() failed");
    return *this;
  }

  Buffer::iterator Buffer::begin() throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  Buffer::iterator Buffer::end() throw() {
    return this->buf.used
      ? this->buf.mem + this->buf.used
      : NULL;
  }

  Buffer::const_iterator Buffer::begin() const throw() {
    return this->buf.mem;
  }

  Buffer::const_iterator Buffer::end() const throw() {
    return this->buf.used
      ? this->buf.mem + this->buf.used
      : NULL;
  }

  std::ostream & operator<<( std::ostream & os, Buffer const & b ){
    fsl_size_t n = 0;
    char const * s = fsl_buffer_cstr2(b, &n);
    if(n) os.write(s, n);
    return os;
  }

  Buffer & Buffer::appendf(char const * fmt, ...){
    va_list vargs;
    int rc = 0;
    va_start(vargs,fmt);
    rc = fsl_buffer_appendfv(*this, fmt, vargs);
    va_end(vargs);
    if(rc){
      throw Exception(rc, "fsl_appendfv() failed: %s",
                      fsl_rc_cstr(rc));
    }
    return *this;
  }

  char const * Buffer::c_str() const throw(){
    return fsl_buffer_cstr(*this);
  }

  void Buffer::toss(int errorCode) const{
    throw Exception(errorCode, "%s", fsl_buffer_cstr(*this));
  }

  int fsl_input_f_std_istream( void * state, void * dest, fsl_size_t * n ){
    std::istream * is = static_cast<std::istream *>(state);
    try{
      /**
         We have to read byte-by-byte to fullfil the requirement that
         we write the number of read bytes to *n. std::istream::read()
         does not give us a (direct) way to know exactly how many
         bytes were read before EOF.
      */
      int rc;
      fsl_size_t i;
      unsigned char * out = (unsigned char *) dest;
      for( i = 0; i < *n; ++i ){
        rc = is->get();
        if(is->eof()){
          *n = i;
          return 0;
        }else if(!is->good()){
          return FSL_RC_IO;
        }else{
          *out++ = rc & 0xFF;
        }
      }
      return 0;
    }catch(...){
      return FSL_RC_IO;
    }
  }

} // namespace fsl

Added bindings/cpp/Makefile.in.


































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
all:
include ../../subdir-inc.make
#$(error $(TOP_SRCDIR))
#CPPFLAGS += -I$(TOP_INCDIR)
#CPPFLAGS += -I$(TOP_SRCDIR)/include
CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h
CXXFLAGS += -fPIC -Wall -Werror

#INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include

LIBFOSSIL.LDFLAGS := -L$(TOP_SRCDIR) -lfossil -lz

#ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS))))
#$(LIBFOSSIL.LDFLAGS):
#	$(MAKE) -C ..
#endif

CPP.SRC := 	\
	Context.cpp \
	Db.cpp \
	Deck.cpp \
	Exception.cpp \
	Fossil.cpp \
	OStream.cpp

OBJECTS := $(patsubst %.cpp,%.o,$(CPP.SRC))

libfossil++.DLL.OBJECTS := $(OBJECTS)
libfossil++.BIN.LDFLAGS := -lstdc++ -fPIC $(LIBFOSSIL.LDFLAGS)# -lsqlite3 -lz
libfossil++.LIB.OBJECTS := $(libfossil++.DLL.OBJECTS)
Makefile: Makefile.in
$(libfossil++.DLL.OBJECTS): Makefile
########################################################################
# Shared lib
$(eval $(call ShakeNMake.CALL.RULES.DLLS,libfossil++))
all: $(libfossil++.DLL)
$(libfossil++.DLL): $(libfossil++.DLL.OBJECTS)
#Static lib (don't work well with _some_ C++ template uses)
#$(eval $(call ShakeNMake.CALL.RULES.LIBS,libfossil++))
#all: $(libfossil++.LIB)
#$(libfossil++.LIB): $(libfossil++.LIB.OBJECTS)

test.BIN.OBJECTS := test.o
ifeq (0,1)
test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ -L. -lfossil++ @SH_LINKFLAGS@
else
test.BIN.OBJECTS += $(libfossil++.DLL.OBJECTS)
test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ @SH_LINKFLAGS@
endif
$(eval $(call ShakeNMake.CALL.RULES.BINS,test))
all: $(test.BIN)


########################################################################
# A quick-n-dirty amalgamation build...
INCD := $(TOP_INCDIR)/fossil-scm
AMAL_D := $(TOP_SRCDIR_REL)
AMAL_C := $(AMAL_D)/libfossil.c
AMAL_H := $(AMAL_D)/libfossil.h
AMAL_CPP := $(AMAL_D)/libfossil++.cpp
AMAL_HPP := $(AMAL_D)/libfossil++.hpp
AMAL_CONF.H := $(AMAL_D)/libfossil-config.h
CLEAN_FILES += $(AMAL_HPP) $(AMAL_CPP)

$(AMAL_C) $(AMAL_H):
	$(MAKE) -C $(TOP_SRCDIR_REL)/src amal

AMAL_CPP.SRC := \
	$(CPP.SRC)

# ACHTUNG: in AMAL_H.SRC, $(AMAL_CONF.H) MUST be included first.
AMAL_HPP.SRC := \
	$(INCD)/fossil.hpp

$(AMAL_HPP.SRC):
$(AMAL_CPP.SRC):
$(AMAL_HPP): Makefile $(AMAL_HPP.SRC)
$(AMAL_CPP): Makefile $(AMAL_HPP) $(AMAL_CPP.SRC)
$(AMAL_CONF.H): $(MAIN_MAKEFILES)
	@echo "Generating $@ ..."
	cd $(TOP_SRCDIR_REL) && sh configure --amal

$(AMAL_HPP):
	@echo "Creating $@..."
	@{ \
		echo '#if !defined(FSLPP_AMALGAMATION_BUILD)'; \
		echo '#define FSLPP_AMALGAMATION_BUILD 1'; \
		echo '#endif'; \
		echo '#include "$(notdir $(AMAL_H))"'; \
	} > $@
	@{ \
		for i in $(AMAL_HPP.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed  \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h[>"]/d' \
		 -e '/[ ]*#[ ]*include[ ]*.*".*config\.h[>"]/d' \
		>> $@

$(AMAL_CPP): $(AMAL_HPP)
	@echo "Creating $@..."
	@echo '#include "$(notdir $(AMAL_HPP))"' > $@
	@{ \
		for i in $(AMAL_CPP.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h.*[>"]/d' \
		>> $@

AMAL_CPP_FLAGS := -I$(TOP_INCDIR) -I$(TOP_SRCDIR_REL) -I. -I$(TOP_SRCDIR_REL)/src# for sqlite3.h :/
.PHONY: amal
amal: $(AMAL_CPP)
	@ls -ls $(AMAL_CPP) $(AMAL_HPP)
	@if which g++ >/dev/null; then \
		echo "Trying GCC..."; \
		g++ -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \
	fi; true
	@if which clang >/dev/null; then \
		echo "Trying clang..."; \
		clang -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \
	fi; true

# /amalgamation
########################################################################

Added bindings/cpp/OStream.cpp.















































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"

/**

 */
namespace fsl {


  fsl_int_t fsl_appendf_f_std_ostream( void * state,
                                       char const * data,
                                       fsl_int_t n ){
    std::ostream * os = static_cast<std::ostream *>(state);
    try{
      os->write( data, (std::streamsize)n );
      return (*os) ? 0 : FSL_RC_IO;
    }catch(...){
      return FSL_RC_IO;
    }
  }

  int fsl_output_f_std_ostream( void * state,
                                void const * data,
                                fsl_size_t n ){
    std::ostream * os = static_cast<std::ostream *>(state);
    try{
      os->write( (char const *)data, (std::streamsize)n );
      return (*os) ? 0 : FSL_RC_IO;
    }catch(...){
      return FSL_RC_IO;
    }
  }


  ContextOStreamBuf::~ContextOStreamBuf() throw(){
    this->sync();
    if( this->m_os ){
      this->m_os->rdbuf( this->m_old );
    }
  }

  void ContextOStreamBuf::setup( fsl_cx * fx ){
    if(!fx) throw Exception(FSL_RC_MISUSE,
                            "ContextOStreamBuf requires "
                            "a non-NULL fsl_cx.");
    this->f = fx;
    this->setp( 0, 0 );
    this->setg( 0, 0, 0 );
    if(m_os) m_os->rdbuf( this );
  }

  ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f, std::ostream & os )
    : f(NULL),
      m_os(&os),
      m_old(os.rdbuf()){
    this->setup(f);
  }

  ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f )
    : f(NULL),
      m_os(NULL),
      m_old(NULL){
    this->setup(f);
  }

  ContextOStreamBuf::ContextOStreamBuf( Context & cx, std::ostream & os )
    : f(NULL),
      m_os(&os),
      m_old(os.rdbuf()){
    this->setup(cx.handle());
  }

  ContextOStreamBuf::ContextOStreamBuf( Context & cx )
    : f(NULL),
      m_os(NULL),
      m_old(NULL){
    this->setup(cx.handle());
  }


  int ContextOStreamBuf::overflow( int c ){
    char const ch = c & 0xFF;
    int const rc = fsl_output(this->f, &ch, 1);
    if(rc) throw Exception(FSL_RC_IO,
                           "fsl_output() failed with code %d (%s)",
                           rc, fsl_rc_cstr(rc));
    else return 0;
  }

  int ContextOStreamBuf::sync(){
    return this->f
      ? fsl_flush(this->f)
      : FSL_RC_IO;
  }

  ContextOStream::~ContextOStream() throw(){
    delete this->sb;
  }

  ContextOStream::ContextOStream( fsl_cx * f )
    :f(f),
     sb(new ContextOStreamBuf(f, *this)){
  }

  ContextOStream::ContextOStream( Context & cx )
    :f(cx.handle()),
     sb(new ContextOStreamBuf(f, *this)){
  }

  ContextOStream & ContextOStream::appendf(char const * fmt, ...){
    va_list vargs;
    int rc = 0;
    va_start(vargs,fmt);
    rc = fsl_outputfv(this->f, fmt, vargs);
    va_end(vargs);
    if(rc){
      throw Exception(rc, "fsl_outputfv() failed: %s",
                      fsl_rc_cstr(rc));
    }
    return *this;
  }

  FslOutputFStreamBuf::~FslOutputFStreamBuf() throw(){
    if( this->m_os ){
      this->m_os->rdbuf( this->m_old );
    }
  }

  void FslOutputFStreamBuf::setup( fsl_output_f f, void * state ){
    if(!f) throw Exception(FSL_RC_MISUSE,
                           "FslOutputFStream output function may "
                           "not be NULL.");
    this->out = f;
    this->outState = state;
    this->setp( 0, 0 );
    this->setg( 0, 0, 0 );
    if(m_os) m_os->rdbuf( this );
  }

  FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state )
    : out(NULL), outState(NULL),
      m_os(NULL), m_old(NULL)
  {
    this->setup(f, state);
  }

  FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state,
                                            std::ostream & os)
    : out(NULL), outState(NULL),
      m_os(&os), m_old(os.rdbuf())
  {
    this->setup(f, state);
  }

  int FslOutputFStreamBuf::overflow( int c ){
    char const ch = c & 0xFF;
    int const rc = this->out( this->outState, &ch, 1);
    if(rc) throw Exception(FSL_RC_IO,
                           "fsl_output_f() proxy failed with code "
                           "%d (%s)", rc, fsl_rc_cstr(rc));
    else return 0;
  }

  int FslOutputFStreamBuf::sync(){
    return 0;
  }

  FslOutputFStream::~FslOutputFStream() throw(){
    delete this->sb;
  }

  FslOutputFStream::FslOutputFStream( fsl_output_f out, void * outState )
    : sb(new FslOutputFStreamBuf(out, outState, *this)){
  }

  fsl_int_t FslOutputFStream::fslAppendfF( void * state,
                                           char const * s, fsl_int_t n ){
    FslOutputFStream * out = static_cast<FslOutputFStream*>(state);
    if(out->good()){
      out->write( s, (std::streamsize)n );
      return out->good() ? 0 : -1;
    }else return -1;
  }

  FslOutputFStream & FslOutputFStream::appendf(char const * fmt, ...){
    va_list vargs;
    fsl_int_t rc = 0;
    va_start(vargs,fmt);
    rc = fsl_appendfv(FslOutputFStream::fslAppendfF, this, fmt, vargs);
    va_end(vargs);
    if(rc<0) throw Exception(FSL_RC_IO,
                             "fsl_appendfv() failed mysteriously.");
    return *this;
  }

  BufferOStream::BufferOStream(fsl_buffer * b) :
    FslOutputFStream(fsl_output_f_buffer, b){
    if(!b) throw Exception(FSL_RC_MISUSE,
                           "fsl_buffer argument may not be NULL.");
  }

  BufferOStream::~BufferOStream() throw(){
  }

} // namespace fsl

Added bindings/cpp/test.cpp.
















































































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp" /* MUST come first b/c of config bits. */
#include <cassert>
#include <iostream>
#include <fstream>
#include <list>
#include <cstdlib> /* EXIT_SUCCESS, EXIT_FAILURE */

#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "
#define COUT std::cout << __FILE__ << ":" << std::dec << __LINE__ << " : "

#define RCStr(C) fsl_rc_cstr(C)

/**
   A functor for use with fsl::Stmt::eachRow().
*/
struct RowDumper {
  std::ostream & os;
  char const * separator;
  bool showHeader;
  explicit RowDumper(std::ostream & os = std::cout)
    : os(os),
      separator("\t"),
      showHeader(true)
  {}
  void operator()(fsl::Stmt &s) const{
    int i;
    int const n = s.columnCount();
    if(this->showHeader && (1==s.stepCount())){
      for(i = 0; i < n; ++i ){
        this->os << (i ? this->separator : "") << s.columnName(i);
      }
      this->os << '\n';
    }
    for(i = 0; i < n; ++i ){
      this->os << (i ? this->separator : "") << s.getText(i);
    }
    this->os << '\n';
  }
};

static void test_db_1(){
  namespace f = fsl;
  char const * dbName = 0
    ? "/fail"
    : ":memory:"
    ;
  //using fsl::Exception;
  //throw Exception(FSL_RC_NYI, "Just %s", "testing");
  f::Db db;
  f::Stmt st(db);
  db.open(dbName, FSL_OPEN_F_RWC);
  COUT << "Opened db: "<<db.filename()<<'\n';
  db.begin();
  st.prepare("CREATE TABLE t(a INTEGER,b TEXT)").stepExpectDone();
  
  st.finalize().prepare("INSERT INTO t(a,b) VALUES(?1,?2)");
  // Try out the StmtBinder for insertions...
  f::StmtBinder b(st);
  b
    (1)("once").insert()
    (2)("twice").insert()
    (3.3)("Quite possibly thrice-point-thrice")
    .insert();

  int threw = 0;
  try{
    b(7)(8)
      ("must throw - too many bind() values");
  }catch(f::Exception const & ex){
    threw = ex.code();
  }
  assert(FSL_RC_RANGE==threw);

  // Bind a std::list of parameters... (not terribly useful, probably)
  typedef std::list<std::string> LI;
  LI li;
  li.push_back("4");
  li.push_back("fource. Or fice.");
  b.bindList(li).insert();

  st.finalize()
    .prepare("SELECT rowid, a, b FROM t ORDER BY a")
    .eachRow( RowDumper() )
    .finalize();

  f::Buffer sb;
  (sb << "SELECT ").appendf("%Q", "percent 'Q'");
  st.prepare(sb)
    .eachRow( RowDumper() );

}

static void test_stream_1(fsl::Context & cx){
  namespace f = fsl;

  f::ContextOStream fout(cx);
  {
    char const * sym = "rid:1";
    fout << "UUID of ["<<sym<<"]: " << cx.symToUuid(sym)<<'\n';
  }

  {
    fsl_uuid_cstr uuid = NULL;
    fsl_id_t rid = 0;
    fsl_ckout_version_info(cx, &rid, &uuid);
    fout << "Checkout version: "<<rid << " ==> "<<uuid<<'\n';
  }

  char const * filename = 1
    ? __FILE__
    : "/fail"
    ;
  std::ifstream is(filename);
  if(!is.good()){
    throw f::Exception(FSL_RC_IO,"Cannot open input file: %s",
                       filename);
  }
  f::Buffer buf;
  int rc = fsl_buffer_fill_from( buf,
                                 f::fsl_input_f_std_istream,
                                 &is );
  if(rc){
    throw f::Exception(rc,"Error (%d) %s reading from file: %s",
                       rc, fsl_rc_cstr(rc), filename);
  }
  assert(buf.used());
  COUT << "Read "<< buf.used()
       <<" bytes from "<<filename
       <<" via std::istream proxy.\n";
  buf.reset();
  assert(0==buf.used());
  assert(0<buf.capacity());
  buf << "Hi, world! ";
  buf.appendf("%Q", "sql 'escaped'") << '\n';
  COUT << "Buffer contents: " << buf;

  buf.reset();
  assert(0==buf.used());
  assert(0<buf.capacity());
  f::FslOutputFStream ops(fsl_output_f_buffer, buf.handle());
  ops << "Hi, world!";
  assert(10 == buf.used());

  buf.clear();
  assert(0==buf.used());
  assert(0==buf.capacity());
  assert(NULL==buf.mem());
}

static void test_deck_1(fsl::Context &cx){
  namespace f = fsl;
  int threw = 0;
  f::Deck d(cx, FSL_SATYPE_CONTROL);
  f::ContextOStream fout(cx);

  try {
    d.assertHasRequiredCards();
  }catch(f::Exception const &ex){
    threw = ex.code();
    fout << "Got expected exception: " << ex.what() << '\n';
  }
  assert(FSL_RC_SYNTAX==threw);

  {
    f::Context::Transaction tr(cx)
      /* We'll roll back everything done here... */
      ;
    const std::string tgtArty(cx.symToUuid("rid:1"));
      /* self-referencing tag in a FSL_SATYPE_CONTROL is not legal, so
         we arbitrarily choose the initial empty checking. */;
    fout << "Custom "<< fsl_satype_cstr(d.type()) << " deck:\n";
    d.setCardD()
      .setCardU()
      .addCardT(FSL_TAGTYPE_ADD, "myTag", tgtArty.c_str(), "its value")
      .addCardT(FSL_TAGTYPE_PROPAGATING, "myOtherTag", tgtArty.c_str())
      .addCardT(FSL_TAGTYPE_CANCEL, "sumdumtag", tgtArty.c_str())
      //.unshuffle().output(fout)
      ;
    d.save();
    fout << "RID/UUID after save (will be rolled back): "<<d.rid()
         << ' ' << d.uuid()<<'\n';

    {

      typedef f::Deck::TCardIterator TagIter;
      TagIter it(d);
      TagIter end;
      fout << "Iterating over tags:";
      for( ; it != end; ++it ){
        fsl_card_T const * tag = *it;
        fout << ' ' << fsl_tag_prefix_char(tag->type)
             << tag->name;
      }
      fout << '\n';
    }

    fout << "Re-read artifact via Deck::load():\n";
    fout << f::Deck(cx).load( d.rid() );

    f::Buffer content;
    cx.getContent( d.rid(), content );
    fout << "And again via Context::getContent():\n" << content;
  }

  if(0){
    fsl_id_t rid = 1;
    fout << "Loading checkin #"<<rid<<"...\n";
    d.load( rid ).output( fout );
  }

  { /* FCardIterator... */
    f::Deck withF(cx);
    withF.load("current");
    f::Deck::FCardIterator fit(withF);
    f::Deck::FCardIterator end;
    fout << "Iterating over F-cards...\n";
    assert(*fit);
    assert(!*end);
    assert(fit!=end);
    int counter = 0;
    for( ; fit != end; ++fit, ++counter ){
      fsl_card_F const * fc = *fit;
      assert(fc);
      assert(fc->name);
      assert(fit->name == fc->name);
    }
    fout << "Traversed "
         <<counter<<" F-card(s) in deck #"
         <<withF.rid()<<".\n";

  }

}

int main(int argc, char const * const * argv ){
  int rc = EXIT_SUCCESS;
  try {
    namespace f = fsl;
    f::Context cx;
    f::ContextOStream fout(cx);
    fout << "ContextOStream...\n";
    f::ContextOStreamBuf cout2(cx, std::cerr);
    f::ContextOStreamBuf cerr2(cx, std::cout);
    CERR << "cerr through fsl_output()\n";
    COUT << "cout through fsl_output()\n";
    cx.openCheckout();

    test_db_1();
    test_stream_1(cx);
    test_deck_1(cx);


    /* Manually closing the Context is not generally required,
       we're just testing an assertion or three... */
    assert(cx.dbRepo().isOpened());
    assert(cx.dbCheckout().isOpened());
    cx.closeDbs();
    assert(!cx.dbRepo().isOpened());
    assert(!cx.dbCheckout().isOpened());
  }catch(fsl::Exception const &fex){
    CERR << "EXCEPTION: " << RCStr(fex.code()) << ": " << fex.what() << '\n';
    rc = EXIT_FAILURE;
  }catch(std::exception const &ex){
    CERR << "EXCEPTION: " << ex.what() << '\n';
    rc = EXIT_FAILURE;
  }
  COUT << "Exiting. Result = " << (rc ? ":`(" : ":-D") << '\n';
  return rc;
}

Added bindings/s2/Makefile.

















































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
all:
include ../../subdir-inc.make

CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h
CPPFLAGS += -fPIC
CPPFLAGS+=-DCWAL_ENABLE_TRACE=0

LIBFOSSIL.LDFLAGS := -L$(TOP_SRCDIR) -lfossil
ifeq (1,1)
  # In static build, we need some extra flags...
  LIBFOSSIL.LDFLAGS += $(LDFLAGS_MODULE_LOADER) -lpthread -lz
endif

ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS))))
$(LIBFOSSIL.LDFLAGS):
	$(MAKE) -C ..
endif

S2_CLIENT_LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(LDFLAGS_MODULE_LOADER)

########################################################################
# INC_SEARCH: $(call)able function. $(1) should be the the name of
# a C header file to search for under $(INCLUDES_PATH).
INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include
define INC_SEARCH
$(call ShakeNMake.CALL.FIND_FILE,$(1),$(INCLUDES_PATH))
endef

#$(error ltdl=$(HAVE_LT_DLOPEN) dl=$(HAVE_DLOPEN) mods=$(ENABLE_MODULES))
########################################################################
# Bins and libs...

########################################################################
# Check for readline
ENABLE_READLINE := $(FSL_ENABLE_READLINE)
ifeq (1,$(ENABLE_READLINE))
  READLINE_H := $(call INC_SEARCH,readline/readline.h)
  ifneq (,$(READLINE_H))
#    READLINE_IMPL := readline
    $(info Enabling GNU Readline)
    LDFLAGS_READLINE := -lreadline
  else
    $(info Not enabling GNU Readline)
    ENABLE_READLINE := 0
  endif
endif
# /readline
########################################################################

########################################
# Linenoise (terminal input lib)...
LN_OBJ :=
ENABLE_LINENOISE := 1
ifeq (1,$(ENABLE_READLINE))
  $(info Not enabling linenoise)
  ENABLE_LINENOISE := 0
else
  LN_DIR := linenoise
  ifneq (,$(wildcard $(LN_DIR)/*.c))
    ENABLE_LINENOISE := 1
    $(info Enabling linenoise)
  else
    $(info Not enabling linenoise)
    ENABLE_LINENOISE := 0
  endif
  ifeq (1,$(ENABLE_LINENOISE))
    LN_OBJ := $(LN_DIR)/linenoise.o $(LN_DIR)/utf8.o
    CLEAN_FILES += $(LN_OBJ)
    $(LN_DIR)/utf8.o: CPPFLAGS+=-std=c99 -DUSE_UTF8=1
    $(LN_DIR)/linenoise.o: CPPFLAGS+=-std=c99 -DUSE_UTF8=1 -D_BSD_SOURCE
  else
    LN_OBJ :=
  endif
endif
# /linenoise
########################################

########################################
# s2sh a.k.a. f-s2sh
shell2.o: CPPFLAGS+=-DS2_SHELL_EXTEND
shell2.o: CPPFLAGS+=-DS2SH_FOR_UNIT_TESTS=1
cliapp.o: CPPFLAGS+=-DCLIAPP_ENABLE_LINENOISE=$(ENABLE_LINENOISE)
cliapp.o: CPPFLAGS+=-DCLIAPP_ENABLE_READLINE=$(ENABLE_READLINE)
shell2.o shell_extend.o: CPPFLAGS+=-DS2_AMALGAMATION_BUILD


f-s2sh.BIN.OBJECTS := shell2.o shell_extend.o cliapp.o $(LN_OBJ) s2_amalgamation.o
f-s2sh.BIN.LDFLAGS := $(S2_CLIENT_LDFLAGS) $(LDFLAGS_READLINE)
$(eval $(call ShakeNMake.CALL.RULES.BINS,f-s2sh))
all: $(f-s2sh.BIN)
# s2's inclusion of miniz breaks its policy of building with
# the (-pedantic -std=c89) flags, and requires that we be less
# stringent with compilation flags :(...
s2_amalgamation.o: CFLAGS=-Wall -Werror -Wsign-compare -g -UNDEBUG -DDEBUG=1
s2_amalgamation.o shell2.o shell_extend.o: CPPFLAGS+=-DS2_ENABLE_ZLIB=1 -DHAVE_CONFIG_H
s2_amalgamation.o: config.h

# end bins and libs
########################################################################

########################################
# Set up file-specific CPPFLAGS/LDFLAGS.
# basic module setup
ifeq (1,$(ENABLE_MODULES))
  s2_amalgamation.o: CPPFLAGS+=-DS2_ENABLE_MODULES=1
  f-s2sh.BIN.LDFLAGS += $(LDFLAGS_MODULE_LOADER)
  ifeq (1,$(HAVE_LIBDL)) # use libdl
    s2_amalgamation.o: CPPFLAGS+=-DS2_HAVE_DLOPEN=1
  else # use libltdl
    s2_amalgamation.o: CPPFLAGS+=-DS2_HAVE_LTDLOPEN=1
  endif
endif
# end modules
########################################################################

########################################################################
# Unit test stuff...
UNIT_SCRIPT_LIST := $(sort $(subst ./,,$(wildcard unit/???-???-*.s2 unit2/???-*.s2)))
$(UNIT_SCRIPT_LIST):
S2SH.SHELL.FLAGS ?= --no-init-script
# -w
UNIT_RUN_CMD = ./$(f-s2sh.BIN) $(S2SH.SHELL.FLAGS)
UNIT_MEGA.S2 := UNIT.s2
UNIT_MEGA2.S2 := UNIT-import.s2
UNIT_GENERATED := $(UNIT_MEGA.S2) $(UNIT_MEGA2.S2)
CLEAN_FILES += $(UNIT_GENERATED)
$(UNIT_MEGA.S2): $(UNIT_SCRIPT_LIST) Makefile
	@echo "Generating $@..."
	@{ \
		false && echo "const INTERN_THESE=['object','array','integer','double','string','function','bool'];"; \
		for i in $(UNIT_SCRIPT_LIST); do \
			echo "scope {/* begin file: $$i */"; \
			cat $$i; \
			echo "/* end file: $$i */;;}"; \
			echo ""; \
		done; \
	} > $@
$(UNIT_MEGA2.S2): $(UNIT_SCRIPT_LIST) Makefile
	@echo "Generating $@..."
	@{ \
		false && echo "const INTERN_THESE=['object','array','integer','double','string','function','bool'];"; \
		for i in $(UNIT_SCRIPT_LIST); do \
			echo "import(false,'$$i');"; \
		done; \
	} > $@

.PHONY: unit unit-proxy unit2
unit-proxy: $(f-s2sh.BIN) $(UNIT_GENERATED)
	@for i in $(UNIT_SCRIPTS_ALL); do \
		cmd="$(UNIT_RUN_CMD) -f $$i"; \
		echo "****************************** Script [$$i]"; \
		echo $$cmd; $$cmd || exit $$?; \
		echo "****************************** Done [$$i]"; \
	done
	@echo "Done running through unit test scripts."

unit: UNIT_SCRIPTS_ALL:=$(UNIT_SCRIPT_LIST) $(UNIT_GENERATED)
unit: unit-proxy
unit2: UNIT_SCRIPTS_ALL:=$(filter unit2/%,$(UNIT_SCRIPT_LIST))
unit2: unit-proxy
.PHONY: unit-r
.PHONY: unit-rc
unit-r: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc
unit-r: unit
unit-s: S2SH.SHELL.FLAGS:=--no-init-script -rc -rc -nosi
unit-s: unit
unit-rc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc
unit-rc: unit
unit-rsc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc -nosi
unit-rsc: unit
units:
	@for i in unit unit-r unit-rc unit-s unit-rsc; do \
		echo "Making $$i ..."; \
		$(MAKE) $$i || exit $$?; \
	done
include vg.make

Added bindings/s2/cliapp.c.





































































































































































































































































































































































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "cliapp.h"
#include <string.h>
#include <assert.h>
#include <stdio.h> /* vsprintf() */
#include <stdlib.h> /* abort() */

#ifndef CLIAPP_ENABLE_READLINE
#  define CLIAPP_ENABLE_READLINE 0
#endif

#ifndef CLIAPP_ENABLE_LINENOISE
#  define CLIAPP_ENABLE_LINENOISE 0
#endif

#if CLIAPP_ENABLE_READLINE && CLIAPP_ENABLE_LINENOISE
#  error "Use *one* of CLIAPP_ENABLE_READLINE or CLIAPP_ENABLE_LINENOISE, not both."
#endif

#if CLIAPP_ENABLE_LINENOISE
#  include "linenoise/linenoise.h"
#elif CLIAPP_ENABLE_READLINE
#  include "readline/readline.h"
#  include "readline/history.h"
#endif


#if 1
#define MARKER(pfexp) if(1) printf("%s:%d:\t",__FILE__,__LINE__); \
  if(1) printf pfexp
#else
static void noop_printf(char const * fmt, ...) {}
#define MARKER(pfexp) if(0) noop_printf pfexp
#endif
#define MESSAGE(pfexp) printf pfexp

struct CliApp cliApp = {
0/*argc*/, 0/*argv*/,
0/*cursorNonflag*/,
0/*errMsg*/,
0/*flags*/,
vprintf/*print*/,
0/*argCallback*/,
{/*doubleDash*/
  0/*argc*/,
  0/*argv*/
},
{/*lineread*/
  CLIAPP_ENABLE_LINENOISE ? 1 : (CLIAPP_ENABLE_READLINE ? 2 : 0) /*enabled*/,
  0 /* historyFile */, 0 /*needsSave*/
}
};

enum {
/** Buffer size for cliapp_errmsg() */
CLIAPP_MSGBUF_SIZE = 1024 * 4,
/** Buffer size for cliapp_process_argv() argument keys. */
CLIAPP_ARGV_KBUF_SIZE = 1024 * 2,
/** Maximum cliapp_process_argv() arg count. */
CLIAPP_ARGV_COUNT = (int)(1024 * 2 / sizeof(CliAppArg))
};

static char cliAppErrBuf[CLIAPP_MSGBUF_SIZE] = {0};

static char const * cliapp_verrmsg(char const * fmt, va_list args){
  int rc;
  char * buf = cliAppErrBuf;
  rc = vsprintf(buf, fmt, args)
    /* Noting that vsnprintf() requires C99 and s2 aims to compile in
       strict C89 mode. */;
  if(rc >= CLIAPP_MSGBUF_SIZE){
    fprintf(stderr,"%s:%d: Internal misuse of error message buffer. "
            "Dangerous buffer overrun!\n",
            __FILE__, __LINE__);
    abort();
  }
  return cliApp.errMsg = buf;
}
static char const * cliapp_errmsg(char const * fmt, ...){
  /** Stores error messages, at least until it's overwritten. */
  char const * rc;
  va_list args;
  va_start(args,fmt);
  rc = cliapp_verrmsg(fmt, args);
  va_end(args);
  return rc;
}

void cliapp_err_clear(){
  cliApp.errMsg = 0;
  cliAppErrBuf[0] = 0;
}

char const * cliapp_err_get(){
  return cliApp.errMsg;
}

static const CliAppSwitch CliAppSwitch_end = CliAppSwitch_sentinel;
#define cliapp__switch_is_end(S) \
  (0==memcmp(S, &CliAppSwitch_end, sizeof(CliAppSwitch)))
int cliapp_switch_is_end(CliAppSwitch const *s){
  return cliapp__switch_is_end(s);
}

CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg,
                                           int alsoFlag){
  CliAppSwitch const * a = cliApp.switches;
  for(; !cliapp__switch_is_end(a); ++a){
    if((alsoFlag==0 || arg->dash==a->dash)
       && 0==strcmp(a->key, arg->key)){
       return a;
    }
  }
  return 0;
}

CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg){
  CliAppArg const * a = arg;
  CliAppArg const * tail = cliApp.argv + cliApp.argc;
  if(arg < cliApp.argv || arg >= tail){
    return 0;
  }
  for( ++a; a < tail; ++a ){
    if(a->key && 0==strcmp(a->key, arg->key)) return a;
  }
  return 0;
}

CliAppArg const * cliapp_arg_nonflag(){
  CliAppArg const * p = 0;
  for(; cliApp.cursorNonflag < cliApp.argc;){
    p = cliApp.argv + cliApp.cursorNonflag;
    ++cliApp.cursorNonflag;
    if(p->key && 0==p->dash){
      return p;
    }
  }
  return 0;
}

void cliapp_arg_nonflag_rewind(){
  cliApp.cursorNonflag = 1;
}

CliAppArg const * cliapp_arg_flag(char const * key1,
                                  char const * key2,
                                  int *atPos){
  int i = atPos ? *atPos : 1;
  assert(key1 || key2);
  if(!key1 & !key2) return 0;
  for( ; i < cliApp.argc; ++i ){
    CliAppArg const * p = cliApp.argv + i;
    if(!p->key) continue;
    else if((key1 && 0==strcmp(key1,p->key))
             ||
             (key2 && 0==strcmp(key2,p->key))){
      if(atPos) *atPos = i;
      return p;
    }
  }
  return 0;
}

char const * cliapp_flag_prefix( int flag ){
  char const * flags[4] = {"+",  "", "-", "--"};
  assert(flag>=-1 && flag<=2);
  return flag>=-1 && flag<=2 ? flags[flag+1] : "";
}

void cliapp_switches_visit( CliAppSwitch_visitor_f visitor,
                            void * state ){
  CliAppSwitch const * s = cliApp.switches;
  assert(s);
  for( ; !cliapp__switch_is_end(s); ++s){
    if(visitor(s, state)) break;
  }  
}

void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state,
                        unsigned short skipArgs ){
  int i = skipArgs;
  CliAppArg const * a = i <  cliApp.argc
    ? &cliApp.argv[i] : NULL;
  for( ; i < cliApp.argc; ++i, ++a ){
    if(a->key && visitor(a, i, state)) break;
  }
}

void cliapp_printv(char const *fmt, va_list vargs){
  if(cliApp.print){
    cliApp.print(fmt, vargs);
  }
}

void cliapp_print(char const *fmt, ...){
  if(cliApp.print){
    va_list vargs;
    va_start(vargs,fmt);
    cliApp.print(fmt, vargs);
    va_end(vargs);
  }    
}

void cliapp_warn(char const *fmt, ...){
    va_list vargs;
    va_start(vargs,fmt);
    vfprintf(stderr, fmt, vargs);
    va_end(vargs);
}

#if 0
static void cliapp_perr(char const *fmt, ...){
    va_list vargs;
    va_start(vargs,fmt);
    cliapp_verrmsg(fmt,vargs);
    va_end(vargs);
    va_start(vargs,fmt);
    vfprintf(stderr, fmt, vargs);
    va_end(vargs);
}
#endif

/**
   The list of arguments pointed to by cliApp.argv.
*/
static CliAppArg cliAppArgv[CLIAPP_ARGV_COUNT];
/**
   An internal buffer used to store --flag keys for those keys which
   require transformation. This is: for --flag=value, we copy the
   "flag" part to this buffer so we can NUL-terminate it. For flags
   with no '=' we simply refer to the original argv string pointers,
   as those are NUL terminated. (We "could" modify the original
   globals instead, but the thought of doing so makes me a ill.)
*/
static char cliAppKeyBuf[CLIAPP_ARGV_KBUF_SIZE]
/* Store all NUL-terminated flag keys here, stripped of their
   leading dashes and terminated at their '=' (if they had
   one). */;

/**
   Internal helper for cliapp_process_argv() which checks list to see
   if s is contained in it. list must be terminated by a NULL pointer.
*/
static char cliapp_check_seen( CliAppSwitch const * const * list,
                               CliAppSwitch const * s ){
  int i = 0;
  CliAppSwitch const * p;
  assert(s);
  while(1){
    p = list[i++];
    if(p == s) return 1;
    else if(!p) break;
  }
  return 0;
}

int cliapp_process_argv(int argc, char const * const * argv,
                        unsigned int reserved ){
  /*static CliAppSwitch const * sPending = 0;
    static CliAppArg * aPending = 0;*/
  int i, rc = 0, doubleDashPos = 0;
  char * k = &cliAppKeyBuf[0];
  char const *errMsg = 0;
  CliAppSwitch const * switchPendingVal = 0
    /* Switch with the CLIAPP_F_SPACE_VALUE which is expecting
       a value in the next argument. */;
  CliAppArg * argPendingVal = 0
    /* arg counterpart of switchPendingVal */;
  CliAppSwitch const * appSwitch = 0;
  CliAppSwitch const * seenList[CLIAPP_ARGV_COUNT+1]
    /* switches we've seen so far, so we can check for/enforce
       the CLIAPP_F_ONCE flag */;
  int seenCount = 0 /* number of entries in seenList */;
  if(reserved){/*unused*/}
  memset(seenList, 0, sizeof(seenList));
  assert(cliApp.switches
         && "cliApp.switches must be set by the client "
         "before calling this.");
  cliApp.argv = &cliAppArgv[0];
  memset(&cliAppArgv[0], 0, sizeof(cliAppArgv));
#define KCHECK if(k>=&cliAppKeyBuf[0]+CLIAPP_ARGV_KBUF_SIZE) goto err_overflow
#define BADFLAG(MSG) errMsg=MSG; goto misuse
  for(i = 0; i < argc && 0==doubleDashPos; ++i){
    CliAppArg * p;
    char const * arg;
    int dashes = 0 /* number of dashes on the current flag */;
    int callbackIndex = i
      /* index to pass to callbacks. Gets modified in one case */;
    if(i == CLIAPP_ARGV_COUNT){
      cliapp_errmsg("Too many (%d) arguments: internal buffer "
                    "limit (%d) would be reached.", argc,
                    CLIAPP_ARGV_COUNT);
      return CLIAPP_RC_RANGE;
    }
    appSwitch = 0;
    p = cliApp.argv + i;
    arg = argv[i];
    if(*arg=='+'){
      dashes = -1;
      ++arg;
    }
    while(*arg && '-'==*arg && dashes<4){
      if(-1==dashes){
        dashes = 3 /* trigger error below */;
        break;
      }
      ++dashes;
      ++arg;
    }
    if(dashes>2){
      BADFLAG("Too many dashes on flag.");
    }
    p->dash = dashes;
    p->key = dashes ? k : arg
      /* dashed args get stored, without dashes, in cliAppKeyBuf so
         that we can terminate them with a NUL at their '='.  Args
         with no dashes are referenced to as-is - there's no need to
         copy them for termination purposes.  We don't know, at this
         point, whether a '=' is pending, but we pessimistically
         (optimistically?) assume there is.
      */;
    while(*arg && '='!=*arg){
      if(dashes){
        *k++ = *arg++;
        KCHECK;
      }      
      else ++arg;
    }
    if(*arg && !*p->key){
      BADFLAG("Empty flag name.");
    }
    KCHECK;
    if(*arg){
      assert('='==*arg);
      if(-1==dashes && '='==*arg){
        BADFLAG("+flags may not have a value.");
      }
      p->value = ++arg;
    }else if(!*p->key && 2==dashes && !doubleDashPos){
      doubleDashPos = i;
      p->key = p->value = 0;
    }
    if(dashes){
      *k++ = 0;
    }
    if(doubleDashPos && !switchPendingVal){
      /* Once we've encountered --, we must not continue to process
         flags because a flag after -- is typically intended for some
         downstream process, not the app on whose behalf we're
         running, and might semantically collide with flags from our
         own app. Also, such flags might have syntaxes we cannot
         handle, so even processing them as flags is potentially a
         bug. Rather than process them, we point cliApp.doubleDash.*
         to that state so the client can deal with it (possibly be
         passing it back into this function!).

         If switchPendingVal is not NULL we need to fall through to
         catch the error case of a missing value at the end of the
         arguments.
      */
      break;
    }else if(!doubleDashPos){
      ++cliApp.argc;
    }
    if(0){
      MARKER(("Arg #%d %d %s %s %s\n",
              i, p->dash, p->key,
              p->value ? "=" : "",
              p->value ? p->value : ""));
    }
    if(switchPendingVal){
      assert(argPendingVal);
      if(p->dash){
        appSwitch = switchPendingVal /* for error string */;
        BADFLAG("Got a flag while the previous flag was expecting "
                "a value");
      }else{
        argPendingVal->value = p->key;
        p->key = p->value = 0;
        p->dash = 0;
        p = argPendingVal /* for the upcoming callback(s) */;
        argPendingVal = 0;
        switchPendingVal = 0;
        --callbackIndex;
      }
    }
    assert(!doubleDashPos);
    if(p->dash && p->key){ /* -flag/--flag/+flag */
#if defined(DEBUG)
      static int check = 0;
      check = callbackIndex;
      assert(cliapp_arg_flag(p->key, 0, &check));
      assert(check == callbackIndex);
#endif
      appSwitch = cliapp_switch_for_arg(p, 1);
      if(!appSwitch){
        BADFLAG("Unknown flag");
      }
      else if((appSwitch->pflags & CLIAPP_F_ONCE)
              && cliapp_check_seen(seenList, appSwitch)){
        BADFLAG("Flag may only be provided once.");
      }
      else if(appSwitch->value && !p->value){
        if(i==argc-1){
          BADFLAG("Flag expecting a value at the end of the arguments");
        }
        switchPendingVal = appSwitch;
        argPendingVal = p;
      }else if(appSwitch->callback){
        rc = appSwitch->callback(callbackIndex, appSwitch, p);
        if(rc) return rc;
      }
      if(!switchPendingVal){
        seenList[seenCount++] = appSwitch;
      }
    }else{
      assert(cliapp_arg_nonflag()==p);
    }
    if(!switchPendingVal && cliApp.argCallack){
      rc = cliApp.argCallack(callbackIndex, appSwitch, p);
      if(rc) return rc;
    }
  }/*for(each arg)*/
  if(cliApp.argCallack){
    rc = cliApp.argCallack(i, 0, 0);
    if(rc) return rc;
  }

#undef BADFLAG
#undef KCHECK
  cliApp.cursorNonflag = 1 /* skip argv[0] */;
  if(doubleDashPos && doubleDashPos+1<argc){
    cliApp.doubleDash.argc = argc - doubleDashPos - 1;
    cliApp.doubleDash.argv = &argv[doubleDashPos+1];
  }
  return 0;
  misuse:
  cliapp_errmsg("Argument #%d: %s%s%s" /* (1) */
                "%s" /*(2)*/
                "%s%.*s" /*(3)*/
                "%s%.*s" /*(4)*/,
                /*(1)*/
                i+1, errMsg, (i<argc) ? ": " : "",
                (i<argc) ? argv[i] : "",
                /*(2)*/ 
                appSwitch ? ", flag: " : "",
                /*(3)*/ 
                appSwitch ? cliapp_flag_prefix(appSwitch->dash) : "",
                (CLIAPP_MSGBUF_SIZE/4),
                appSwitch ? appSwitch->key : "",
                /*(4)*/ 
                appSwitch && appSwitch->value ? "=" : "",
                CLIAPP_MSGBUF_SIZE/4,
                appSwitch ? appSwitch->value : "");
  return CLIAPP_RC_FLAG;
  err_overflow:
  cliapp_errmsg("Too many CLI flag keys: their accumulated names "
                "would overrun our %d-byte buffer.",
                CLIAPP_ARGV_KBUF_SIZE);
  return CLIAPP_RC_RANGE;
}

char * cliapp_lineedit_read(char const * prompt){
#if CLIAPP_ENABLE_LINENOISE
  return linenoise(prompt);
#elif CLIAPP_ENABLE_READLINE
  return readline(prompt);
#else
  if(prompt){/*avoid unused param warning*/}
  return 0;
#endif
}

void cliapp_lineedit_free(char * line){
#if CLIAPP_ENABLE_LINENOISE || CLIAPP_ENABLE_READLINE
  free(line);
#else
  if(line){
    assert(!"Where did this memory come from?");
  }
#endif
}

int cliapp_lineedit_load(char const * fname){
  if(!fname) fname = cliApp.lineread.historyFile;
  if(!fname || !*fname) return 0;
#if CLIAPP_ENABLE_LINENOISE
  return linenoiseHistoryLoad(fname);
#elif CLIAPP_ENABLE_READLINE
  return read_history(fname);
#else
  if(fname){/*avoid unused param warning*/}
  return CLIAPP_RC_UNSUPPORTED;
#endif
}

int cliapp_lineedit_save(char const * fname){
  int rc = 0;
  if(!fname) fname = cliApp.lineread.historyFile;
  if(!cliApp.lineread.needsSave
     || !fname || !*fname) return 0;
#if CLIAPP_ENABLE_LINENOISE
  rc = linenoiseHistorySave(fname) ? CLIAPP_RC_IO : 0;
#elif CLIAPP_ENABLE_READLINE
  rc = write_history(fname) ? CLIAPP_RC_IO : 0;
#else
  if(fname){/*avoid unused param warning*/}
  rc = CLIAPP_RC_UNSUPPORTED;
#endif
  if(!rc) cliApp.lineread.needsSave = 0;
  return rc;
}

int cliapp_lineedit_add(char const * line){
  assert(line);
#if CLIAPP_ENABLE_LINENOISE
  cliApp.lineread.needsSave = 1;
  linenoiseHistoryAdd(line);
  return 0;
#elif CLIAPP_ENABLE_READLINE
  cliApp.lineread.needsSave = 1;
  add_history(line);
  return 0;
#else
  if(line){/*avoid unused param warning*/}
  return CLIAPP_RC_UNSUPPORTED;
#endif
}

int cliapp_repl(CliApp_repl_f f, char const * const * prompt,
                int addHistoryPolicy, void * state){
  char * line;
  int rc = 0;
  while( !rc && (line = cliapp_lineedit_read(*prompt)) ){
    if(addHistoryPolicy<0) cliapp_lineedit_add(line);
    rc = f(line, state);
    if(!rc && addHistoryPolicy>0) cliapp_lineedit_add(line);
    cliapp_lineedit_free(line);
  }
  return rc;
}

#undef cliapp__switch_is_end

Added bindings/s2/cliapp.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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#ifndef NET_WH_CLIAPP_H_INCLUDED
#define NET_WH_CLIAPP_H_INCLUDED
/**
   A mini-framework for handling some of the grunt work required by
   CLI apps. It's main intent is to provide a halfway sane system for
   handling CLI flags. It also provides an abstraction for CLI
   editing, supporting either libreadline or liblinenoise, but only
   supporting the most basic of editing facilities, not
   library-specific customizations (e.g. custom key bindings).

   This API has no required dependencies beyond the C89 standard
   libraries and requires no dynamic memory unless it's configured to
   use an interactive line-reading backend.

   License: Public Domain

   Author: Stephan Beal <stephan@wanderinghorse.net>
*/

#include <stdarg.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
   Flags for use with the CliAppSwitch::pflags field to modify
   how cliapp_process_argv() handles the switch.
*/
enum cliapp_switch_flags {
/**
   Indicates that a CliAppSwitch is only allowed to be provided once.
   If encountered more than once by cliapp_process_argv(), an error
   is triggered.
*/
CLIAPP_F_ONCE = 1,
/**
   Indicates that a given CliAppSwitch requires a value and accepts
   its value either in the form (-switch=value) or (-switch value).
   When such a flag is encountered, cliapp_process_argv() will report
   an error if the switch has no value (even if the flag appears at
   the end of the arguments list or immediately before the
   special-case "--" flag).

   Without this flag, switch values are only recognized in the form
   (-switch=flag).
*/
CLIAPP_F_SPACE_VALUE = 2
/*TODO?: CLIAPP_F_VALUE_REQUIRED = 4 (implied by CLIAPP_F_SPACE_VALUE) */
};

/**
   Result codes used by various library routines.
*/
enum cliapp_rc {
/** The non-error code */
CLIAPP_RC_OK = 0,
/** Indicates an error in flag processing. */
CLIAPP_RC_FLAG = -1,
/** Indicates a range-related error, e.g. buffer overrun or too many
    CLI arguments. */
CLIAPP_RC_RANGE = -2,
/** Indicates that an unsupported operation was requested. */
CLIAPP_RC_UNSUPPORTED = -3,
/** Indicates some sort of I/O error. */
CLIAPP_RC_IO = -4
};


/**
   Holds state for a single CLI argument, be it a flag or non-flag.
*/
struct CliAppArg {
  /**
     Must be 1 for single-dash flags, 2 for double-dash flags, and -1
     for '+' flags, and 0 for non-flags.
  */
  int dash;
  /**
     Fow switches, this holds the switch's key, without dashes. For
     non-switches, it holds the argument's value.
  **/
  char const * key;
  /**
     For switches, the description of their value (if any) is stored here.
     For non-switches, this is 0.
  */
  char const * value;
  /**
     Arbitrary value which may be set/used by the client. It's not
     used/modifed by this API.
  */
  int opaque;
};
typedef struct CliAppArg CliAppArg;

struct CliAppSwitch;

/**
   A callback type used by cliapp_process_argv() to notify the app
   when a CLI argument is processed which matches one of the app's
   defined flags. It is passed the argument index, the switch (if any)
   and the argument.

   Note that when processing switches flagged with
   CLIAPP_F_SPACE_VALUE, the indexes passed to this function might
   have gaps, as they skip over the VAL part of (-f VAL), instead
   effectively transforming that to (-f=VAL) before calling the
   callback for the -f switch.

   If the switch argument is NULL, the argument will be a non-flag
   value. Note that arguments starting at the special-case "--" flag
   are not passed on to the callback. Instead, such arguments get
   reported via the cliApp.doubleDash member.

   It gets passed two NULL values one time at the end of processing in
   order to allow the client code to do any final validation.

   If it returns non-0, cliapp_process_argv() will fail and return the
   callback's result.
*/
typedef int (*CliAppSwitch_callback_f)(int ndx, struct CliAppSwitch const * appSwitch,
                                       CliAppArg * arg);
/**
   Models a single flag/switch for a CLI app. It's not called
   CliAppFlag because that proved confusing together with CliAppArg.
*/
struct CliAppSwitch {
  /**
     Can be used by clients to, e.g. group help items by type or
     set various levels of help verbosity.
  */
  int opaque;
  /**
     As documented for CliAppArg::dash.
  */
  int dash;
  /**
     The flag name, without leading dashes.
  */
  char const * key;
  /**
     A human-readable description of its expected value, or 0 if the
     flag does not require a value.

     BUG? It may still be assigned a value by the caller. We
     currently require that behaviour for a special-case arg handler
     in s2sh2.
  */
  char const * value;
  /**
     Brief help text.
  */
  char const * brief;
  /**
     Optional detailed help text.
  */
  char const * details;

  /**
     Optional callback to be passed a CliAppArg instance after it's been
     initialized and confirmed as being a valid arg (defined in
     cliApp.switches). If cliApp.argCallack is also used, both
     callbacks are called, but this one is called first.
  */
  CliAppSwitch_callback_f callback;

  /**
     Reserved for future use by the cliapp interface, e.g. marking
     "has seen this flag before" in order to implement only-once
     behaviour.
  */
  int pflags;
};
typedef struct CliAppSwitch CliAppSwitch;

/**
   Client-defined CliApp.argv arrays MUST end with an entry identical
   to this one. The iteration-related APIs treat any entry which
   memcmp()'s as equivalent to this entry as being the end of th list.

   @see cliapp_switch_is_end()
*/
#define CliAppSwitch_sentinel {0,0,0,0,0,0,0,0}

/**
   Returns true (non-0) if the given object memcmp()'s as equivalent
   to CliAppSwitch_sentinel.
 */
int cliapp_switch_is_end(CliAppSwitch const *s);

/**
   vprintf()-compatible logging/printing interface for use with
   CliApp.
*/
typedef int (*CliApp_print_f)(char const *, va_list);

/**
   A callback for use with cliapp_switches_visit(). It is passed the
   switch object and an arbitrary state pointer provided by the
   caller of that function.   
*/
typedef int (*CliAppSwitch_visitor_f)(CliAppSwitch const *, void *);

/**
   Global app state. This class is intended to represent a singleton,
   the cliApp object.
*/
struct CliApp {
  /**
     Number of arguments in this->argv. It is modified as
     cliapp_process_argv() executes and only counts arguments
     up to, but not including the special-case "--" flag.
  */
  int argc;

  /**
     Arguments processed by cliapp_process_argv(). Contains
     this->argc entries. This memory is not valid until
     cliapp_process_argv() has succeeded.
  */
  CliAppArg * argv;

  /**
     Internal cursor for traversing non-flag arguments using
     cliapp_arg_nonflag(). Holds the *next* index to be used by that
     function.
  */
  int cursorNonflag;

  /**
     May be set to an error description by certain APIs and it may
     point to memory which can mutate.
  */
  char const * errMsg;

  /**
    Must be set up by the client *before* calling
    cliapp_process_argv() and its final entry MUST be an object for
    which cliapp_switch_is_end() returns true (that's how we know when
    to stop processing).
  */
  CliAppSwitch const * switches;

  /**
     If this is non-NULL, cliapp_print() and friends will use it for
     output, otherwise they will elide all output. This defaults to
     vprintf().
  */
  CliApp_print_f print;

  /**
     If set, it gets called each time cliapp_process_argv() processes
     an argument. If it returns non-0, processing fails.

     After processing successfully completes, the callback is called
     one final time with NULL arguments so that the callback can
     perform any end-of-list validation or whatnot.

     Using this callback effectively turns cliapp_process_argv() into
     a push parser, which turns out to be a pretty convenient way to
     handle CLI flags.
  */
  CliAppSwitch_callback_f argCallack;

  /**
     If cliapp_process_argv() encounters the "--" flag, and additional
     arguments follow it, this object gets filled out with information
     about them.

     Note that encountering "--" with no following arugments is not
     considered an error.
  */
  struct {
    /**
       If cliapp_process_argv() encounters "--", this value gets set
       to the number of arguments available in the original argv array
       immediately following (but not including) the "--" flag
    */
    int argc;
    /**
       If cliapp_process_argv() encounters "--", and there are
       arguments after it, this value is set to the list of arguments
       (from the original argv array) immediately following the "--"
       flag. If "--" is not encountered, or there are no arguments
       after it, this member's value is 0.
    */
    char const * const * argv;
  } doubleDash;
  
  /**
     State related to interactive line-editing/reading.
  */
  struct {
    /**
       If enabled at compile-time, this has a value of 1 (for
       linenoise) or 2 (for readline), else it has a value of 0.

       To use libreadline, compile this code's C file with
       CLIAPP_ENABLE_READLINE set to a true value. To use linenoise,
       build with CLIAPP_ENABLE_LINENOISE set to a true value.
    */
    int const enabled;
    /**
       If non-NULL, cliapp_lineedit_save(NULL) will use this name for
       saving.
    */
    char const * historyFile;
    /**
       Specifies whether or not the line editing history has been
       modified since the last save.

       This initially has a value of 0 and it gets set to non-0 if
       cliapp_lineedit_add() is called.
    */
    int needsSave;
  } lineread;
};

/**
   Behold! The One True Instance of CliApp!
*/
extern struct CliApp cliApp;

/**
   Visits all switches in cliApp.switches, calling
   visitor(theSwitch,state) for each one. If the visitor returns
   non-0, visitation halts without an error.

   It stops iterating when it encounters an entry for which
   cliapp_switch_is_end() returns true.
*/
void cliapp_switches_visit( CliAppSwitch_visitor_f visitor,
                            void * state );

/**
   Callback signature for use with cliapp_args_visit().

   It gets passed the CLI argument, the index of that argument in
   cliApp.argv, and an optional client-specified state pointer.
*/
typedef int (*CliAppArg_visitor_f)(CliAppArg const *, int ndx, void *);

/**
   Visits all args in cliApp.argv, calling visitor(theSwitch,itsIndex,state)
   for each one. If skipArgs is greater than 0, that many are skipped
   over before visiting. Behaviour is undefined if a visitor modifies
   cliApp.argv or cliApp.argc. If the visitor returns non-0,
   visitation halts without an error.

   CliAppArg entries with a NULL key are skipped over, under the assumption
   that the client app has marked them as "removed".
*/
void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state,
                        unsigned short skipArgs );

/**
   Initializes the argument-processing parts of the cliApp global
   object with. It is intended to be passed the conventional argc/argv
   arguments which are passed to the application's main().

   The final parameter is reserved for future use in providing flags
   to change this function's behaviour. A value of 0 is reserved as
   meaning "the default behaviour."

   cliApp.switches must have been assigned to non-NULL before calling
   this, or behaviour is undefined. If any given switch has a callback
   assigned to it, it will be called when that switch is processed,
   and processing fails if it returns non-0. (Potential TODO: allow a
   NULL switches value to simply treat all flags a known switches.)

   If cliApp.argCallack is not-NULL, it is called for every
   argument. It will be passed the CLI argument and, if it's a flag,
   its corresponding CliAppSwitch instance (extracted from
   cliApp.switches). For non-flag arguments, a NULL CliAppSwitch is
   passed to it. If it returns non-0, processing fails. If processes
   completes successfully, the callback is called one additional time
   with NULL pointer values to indicate that the end has been
   reached. This can be used to handle post-argument cleanup, perform
   app-specific argument validation, or similar.

   If callbacks are set both on the switch and cliApp, both are called
   in that order, but only the cliApp callback is called one final
   time after processing is done.

   If this function returns 0, the client may manipulate the contents
   of cliApp.argv, within reason, but must be certain to keep
   cliApp.argc in sync with that list's entries.

   On error a non-0 code is returned, either propagated from a
   callback or (if the error originates from this function) an entry
   from the cliapp_rc enum. In the latter case, cliapp_err_get() will
   contain information about why it failed.

   Encountering an argument which is neither a non-flag nor a flag
   defined in cliApp.switches results in an error.

   Quirks:

   - Arguments after "--" are NOT processed by this
   function. Processing them would be a bug-in-waiting because those
   flags might collide with app-level flags and/or require syntaxes
   which this code treats as an error, e.g. using three dashes instead
   of 1 or 2. Instead, if "--" is encounter, cliApp.doubleDash is
   populated with information about the flags so the client may deal
   with them (which might mean passing them back into this routine!).

   - All argv-related cliApp state is reset on each call, so if this
   function is called multiple times, any client-side pointers
   referring to cliApp's state may then point to different information
   than they expect and/or may become stale pointers. (cliApp-held
   data, e.g. cliApp.argv, keeps the same pointers but re-populates
   the state, but the lifetime of external pointers,
   e.g. cliApp.doubleDash.argv, is client-dependent.)
*/
int cliapp_process_argv(int argc, char const * const * argv,
                        unsigned int reserved);

/**
   If cliApp.print is not NULL, this passes on its arguments to that
   function, else this is a no-op.
*/
void cliapp_printv(char const *fmt, va_list);

/**
   Elipses-args form of cliapp_printv().
*/
void cliapp_print(char const *fmt, ...);

/**
   Outputs a printf-formatted message to stderr.
*/
void cliapp_warn(char const *fmt, ...);

/**
   Returns the next entry in cliApp.argv which is a non-flag argument,
   skipping over argv[0]. Returns 0 when the end of the list is
   reached.
*/
CliAppArg const * cliapp_arg_nonflag();

/**
   Resets the traversal of cliapp_arg_nonflag() to start from
   the beginning.
*/
void cliapp_arg_nonflag_rewind();

/**
   If the given argument matches an app-configured flag, that flag is
   returned, else 0 is returned.

   If alsoFlag is true, the first argument and the corresponding
   switch must also have matching flag values to be considered a
   match.
*/
CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg,
                                           int alsoFlag);

/**
   Searches for a flag matching one of the given keys. Each entry
   in cliApp.argv is checked, in order, against both of the given
   keys, in the order they are provided.

   The conventional way to call it is to pass the short-form flag,
   then the long-form flag, but that's just a convention.

   Either of the first two arguments may be NULL but both may not be
   NULL.

   If the 3rd parameter is not NULL then:

   1) *atPos indicates an index position to start the search at. (Note
   that it should initially be 1, not 0, in order to skip over the
   app's name, stored in argv[0].)

   2) If non-NULL is returned, *atPos is set to the index at which the
   argument was found. If NULL is returned, *argPos is not modified.

   Thus atPos can be used to iterate through multiple copies of a
   flag, noting that its value points to the index at which the
   previous entry was found, so needs to be incremented by 1 before
   each subsequent iteration

   On a match, the corresponding CliAppArg is returned, else 0 is
   returned.
*/
CliAppArg const * cliapp_arg_flag(char const * key1, char const * key2,
                                  int * atPos);

/**
   Given a flag value for a CliAppArg or CliAppSwitch, this
   returns a prefix string depending on that value:

   1 = "-", 2 = "--", 3 = "+"

   Anything else = "". The returned bytes are static.
*/
char const * cliapp_flag_prefix( int flag );

/**
   Given a CliAppArg, presumably one from cliapp_arg_flag() or
   cliapp_arg_nonflag(), this searches for the next argument with the
   same key.

   If the given argument is from outside cliApp.argv's memory range,
   or is the last element in that list, 0 is returned.

   Bug? For non-flag arguments this does not update the internal
   non-flag traversal cursor.
*/
CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg);

/**
   Clears any error state in the cliApp object.
*/
void cliapp_err_clear();

/**
   If cliApp has a current error message set, it is returned, else 0
   is returned. The memory is static and its contents may be modified
   by any calls into this API.
*/
char const * cliapp_err_get();


/**
   Tries to save the line-editing history to the given filename, or to
   cliApp.lineedit.historyFile if fname is NULL. If both are NULL or
   empty, or if cliApp.lineedit.needsSave is 0, this is a no-op and
   returns 0. Returns CLIAPP_RC_UNSUPPORTED if line-editing is not
   enabled.
*/
int cliapp_lineedit_save(char const * fname);

/**
   Adds the given line to the line-edit history. If this function
   returns 0, it also sets cliApp.lineedit.needsSave to a non-0 value.

   Returns 0 on success or CLIAPP_RC_UNSUPPORTED if line-editing is
   not enabled.
*/
int cliapp_lineedit_add(char const * line);

/**
   Tries to load the line-editing history from the given filename, or
   to cliApp.lineedit.historyFile if fname is NULL. If both are NULL
   or empty, this is a no-op. Returns CLIAPP_RC_UNSUPPORTED if
   line-editing is not enabled. If the underlying line-editing backend
   returns an error, CLIAPP_RC_IO is returned, under the assumption
   that there was a problem with reading the file (e.g. unreadable),
   as opposed to an allocation error or similar.
*/
int cliapp_lineedit_load(char const * fname);

/**
   If cliApp.lineedit.enabled is true, this function passes its
   argument to free(3), else it will (in debug builds) trigger an
   assert if passed non-NULL. This must be called once for each line
   fetched via cliapp_lineedit_read().
*/
void cliapp_lineedit_free(char * line);

/**
   If line-editing is enabled, this reads a single line using that
   back-end and returns the new string, which must be passed to
   cliapp_lineedit_free() after the caller is done with it.

   Returns 0 if line-editing is not enabled or if the caller taps the
   platform's EOF sequence (Ctrl-D on Unix) at the start of the
   line. Returns an empty string if the user simply taps ENTER.

   TODO: if no line-editing backend is built in, fall back to fgets()
   on stdin. It ain't pretty, but it'll do in a pinch.
*/
char * cliapp_lineedit_read(char const * prompt);

/**
   Callback type for use with cliapp_repl().
*/
typedef int (*CliApp_repl_f)(char const * line, void * state);

/**
   Enters a REPL (Read, Eval, Print Loop). Each iteration does
   the following:

   1) Fetch an input line using cliapp_lineedit_read(), passing it
   *prompt. If that returns NULL, this function returns 0.

   2) If addHistoryPolicy is <0 then the read line is added to the
   history.

   3) Calls callback(theReadLine, state).

   4) If (3) returns 0 and addHistoryPolicy is >0, the read line
   is added to the history.

   5) Passes the read line to cliapp_lineedit_free().

   6) If (3) returns non-0, this function returns that value.

   Notes:

   - The prompt is a pointer to a pointer so that the caller may
   modify it between loop iterations. This function derefences *prompt
   on each iteration.

   - An addHistoryPolicy of 0 means that this function will not
   automatically add input lines to the history. The callback is free
   to do so.

   - This function never passes a NULL line value to the callback but
   it may pass an empty line.
*/
int cliapp_repl(CliApp_repl_f callback, char const * const * prompt,
                int addHistoryPolicy, void * state);

#ifdef __cplusplus
}
#endif
#endif /* NET_WH_CLIAPP_H_INCLUDED */

Added bindings/s2/config.h.






1
2
3
4
5
+
+
+
+
+
#define S2_OS_UNIX 1
#define S2_HAVE_REALPATH 1
#define S2_HAVE_STAT 1
#define S2_HAVE_CHDIR 1
#define CWAL_OBASE_ISA_HASH 1

Added bindings/s2/f-s2sh.s2.

























































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
  Initialization script for s2sh. If it is named the same as the binary
  (minus any ".exe" extension), with a ".s2" extension (and in the same directory),
  s2sh will autoload this file at startup and fail if processing it fails.
*/

assert Fossil;
assert Fossil.Context;

assert s2 && 'function' === typename s2.loadModule;
/**
  An s2.loadModule() proxy which uses a PathFinder
  instance to search for DLLs. The name argument must be the
  base name part, optionally with a partial leading (sub-)path.

  The dest argument is passed as the last argument to s2.loadModule(),
  and is returned to the caller on success. Well-behaved modules will
  install their features in that object.

  If the S2_MODULE_PATH and/or S2_MODULE_EXTENSION environment
  variables are set, they are treated as a semicolon- or
  colon-separated list of directories resp. file extensions. If not
  specified, some default set is used.

*/
s2.loadModule2 = function(name, dest = {}){
    affirm 'string' === typename name;
    const fn = pf.search( name );
    fn || throw "Cannot find '".
        concat(name, "' in search path ", pf.prefix.toJSONString());
    //print("Importing",name, '==>', fn);
    return loadModule.call(this, realpath ? realpath(fn) : fn, dest);
}.importSymbols({
    loadModule: s2.loadModule,
    realpath: s2.io ? s2.io.realpath : undefined,
    pf: s2.PathFinder.new(
        // Directories...
        ('string' === typename (var s = s2.getenv('S2_MODULE_PATH')))
            ? s.split(s.indexOf(';') >= 0 ? ';' : ':')
        : ['.'],
        // Extensions...
        ('string' === typename (s = s2.getenv('S2_MODULE_EXTENSIONS')))
       ? s.split(s.indexOf(';') >= 0 ? ';' : ':')
        : ['.so','.dll']
    )
});


/**
   For the given container, v, this displays (via s2.io.output() a
   listing of its properties. Intended for getting an overview of an
   object's API.
*/
s2.vls = proc(v,label){
    label && print(label);
    typeinfo(iscontainer v) && this.eachProperty.call(v,eachProp);
}.importSymbols({
    eachProp:proc(k,v){
        print('\t'+typeinfo(name k), k, '=', typeinfo(name v), v);
    }
});

/**
   For the given container, v, this displays (via s2.io.output() a
   tree listing, recursively, of its properties. Intended for getting
   an overview of an object's API.

   If includeProto is truthy, v.prototype is also dumped. If it is
   less than 0, that is done recursively for all entries and their
   prototypes.
*/
s2.vtree = proc vtree(v,label,includeProto){
    typeinfo(iscontainer v) || return;
    if(!typeinfo(isstring label) && undefined===includeProto){
        includeProto= label;
        label = undefined;
    }
    const doMyProto = includeProto;
    includeProto < 0 || (includeProto=0);
    //print(__FLC,'argv =',argv);
    label ?: (label = "%1$p".applyFormat(v));
    label && out(buf.toString(),label," [type: ",typeinfo(name v),'] ==>\n');
    ++buf.level;
    buf.length(buf.level*4);
    buf.fill(' ');
    var ex = catch{
        // ^^^^ s2 bug: braces around this catch should not be necessary.
        // Symptom: if the first if() passes then the 'else' is getting seen
        // after catch resolves.
        if(typeinfo(isarray v)){
            //out(' array ',v.toJSONString(0,true/*output cycles as strings*/),'\n');
            v.eachIndex(eachIndex);
        }else{
            buf.prototype.eachProperty.call(v,eachProp);
        }
    };
    ex = catch if(!ex && v.prototype){
        doMyProto
        ? vtree(v.prototype,label?label+'.prototype':0, includeProto)
        : out(buf.toString(),label ? label+'.' : '',
              'prototype ==> [type: ',
              typeinfo(name v.prototype),']\n');
    };
    --buf.level;
    buf.length(buf.level * 4);
    ex && throw ex;
}.importSymbols({
    buf: eval {
        var b = new s2.Buffer(20);
        b.level = 0;
        b;
    },
    out: s2.io.output,
    eachProp:proc(k,v){
        if(typeinfo(isfunction v)){
            out(buf.toString(), (label ? label+'.'+k :k),'()\n');
        }
        else if(typeinfo(iscontainer v)
                && typeinfo(isfunction v.mayIterate)){
            v.mayIterate()
                ? vtree(v,(label ? label+'.'+k : k), includeProto)
                : out(buf.toString(),(label ? label+'.'+k : k),
                      " = <cyclic: %1$p>\n".applyFormat(v));
        }else{
            out(buf.toString(), (label ? label+'.'+k :k),
                ' = ', typeinfo(name v), ' ', v, '\n');
        }
    },
    eachIndex:proc(v,k){
        if(typeinfo(isfunction v)){
            out(buf.toString(), (label ? label+'['+k+']','()\n'));
        }
        else if(typeinfo(iscontainer v)
                && typeinfo(isfunction v.mayIterate)){
            v.mayIterate()
                ? vtree(v,(label ? label+'['+k+']' : k), includeProto)
                : out(buf.toString(),(label ? label+'['+k+']' : k),
                      " = <cyclic: %1$p>\n".applyFormat(v));
        }else{
            out(buf.toString(), (label ? label+'['+k+']' :k),
                ' = ', typeinfo(name v), ' ', v, '\n');
        }
    }
});

/**
   Add Fossil.require(), used in loading Fossil-aware modules.
*/
Fossil.require = import( false, 'require.d/require.s2' );

// The rest of the initialization happens via here:
Fossil.require(['nocache!fsl/extendFossil'],proc(){});

Added bindings/s2/fslcgi.
































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/bin/sh
########################################################################
# Intended to be run as a CGI script. It is currently hard-coded to
# live in same dir as s2sh and the ./fslcgi.d directory and f-s2sh
# binary.
#
# A sample Apache vhost for this script looks like:
#
#<VirtualHost *:80>
#    ServerName s2.local
#    ScriptAlias /cgi-bin/ /path/to/libfossil/s2
#    DocumentRoot /path/to/libfossil/s2
#    Options +FollowSymLinks
#</VirtualHost>
#<Directory "/path/to/libfossil/s2">
#        AllowOverride All
#        Options +ExecCGI +Indexes
#        Order allow,deny
#        Allow from all
#        Require all granted
#        DirectoryIndex fslcgi
#        SetHandler cgi-script
#</Directory>
########################################################################

S2_HOME=`dirname "$0"`
# PROXY_SCRIPT is the script-side entry point for the CGI app
PROXY_SCRIPT=$0.s2
# Interpreter-level flags for s2sh:
S2_SHELL_FLAGS="-S -a -R"
S2_SHELL=$S2_HOME/f-s2sh
#S2_RC_DIR = "resource dir" for cgi bits. Passed via the --resource-dir CLI flag.
S2_RC_DIR=${S2_HOME}/fslcgi.d

S2_MODULE_PATH=".:$S2_HOME:${S2_RC_DIR}/lib"
S2_INCLUDES_PATH=".:$S2_HOME:${S2_RC_DIR}/include"
#NYI: S2_AUTOLOAD_MODULES="module1,module2,module3"

export S2_MODULE_PATH S2_INCLUDES_PATH S2_AUTOLOAD_MODULES

#export S2_HOME

# For libfossil.so:
LD_LIBRARY_PATH="${S2_HOME}/..:${LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH

# Tell the script world which repository file to use...
if [ -d ~/fossil ]; then
    S2_CGI_REPO="~/fossil/libfossil.fsl"
    true
else
    S2_CGI_REPO="$S2_HOME/../../libfossil.fsl"
    #S2_CGI_REPO="-R=$S2_HOME/../../fossil.fsl"
fi

#S2_CGI_REP0=''
# Misc flags intended for consumption by scripts...
S2_REPO_URL=http://fossil.wanderinghorse.net/repos/libfossil/
#S2_CGI_REPO=-C
S2_SCRIPT_FLAGS="--repo-db=${S2_CGI_REPO} --repo-url=${S2_REPO_URL} --resource-dir=${S2_RC_DIR}"
#S2_SCRIPT_FLAGS="${S2_SCRIPT_FLAGS} --repo-url=http://url-to-your/repo[.cgi]"

exec $S2_SHELL ${S2_SHELL_FLAGS} -f $PROXY_SCRIPT -- $S2_SCRIPT_FLAGS

Added bindings/s2/fslcgi.d/init.s2.






































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   Post-bootstrap initialization code for s2's fslcgi. Must live in the
   root of "resource directory", but that (with sufficient hacking) may
   live outside of the web-root.
*/
affirm 'undefined' !== typename $CGI /* expecting CGI module to be loaded under this name */;
affirm Fossil.require;


$CGI.config = {
    resourceDir: Fossil.file.canonicalName(__FILEDIR,true),
    cgiRoot: '<TODO:cgiRoot>'
};
scope {
    const C = $CGI;
    var uri = s2.getenv('SCRIPT_NAME');
    // Set up cgiRoot (the "standard" way of getting the root dir
    // for link-building purposes). This kludgery is not generic...
    if(uri) { C.config.cgiRoot = uri + '/' }
    else{
        uri = s2.getenv('REQUEST_URI');
        C.request.ENV || (C.request.REQUEST_URI = uri);
        if(uri){
            var head = uri.split('fslcgi',2).0;//, head = adj.0;
            C.config.cgiRoot = head ||| '/empty';
            //('/' === head) ? head : head + '/'; //.concat('fslcgi', '/');
        }
    }
    
    C.config.localServerMode =
        (var serverName = s2.getenv('SERVER_NAME'))
        && (0<serverName.indexOf(".local"));
    ;;
}

/**
   A convenience alias for s2.io.output (or functionally
   equivalent).
*/
$CGI.out = s2.io.output;

/**
   Sends its RHS argument output to the (buffered) response body.
   Returns this object (so it can be chained).
*/
$CGI.'operator<<' = proc(self,arg){
    return s2.io.output(arg), this;
};

/**
    Removes "potentially security-relevant" properties from the
    exception ex. Returns ex.
*/
$CGI.scrubException = proc(ex){
    if(this.config.scrubExceptions){
        unset ex.script, ex.stackTrace, ex.line, ex.column // security-relevant
    }
    return ex;
};

$CGI.util = {
    /**
       Converts all key/value pairs from obj to a string of urlencoded
       key value pairs, separated by '&'. If addQMark is true then the
       result is prefixed by a '?', otherwise it is not.

       If obj has no properties then an empty string is returned,
       regardless of addQMark.

       Property key order in the result string is unspecified.
    */
    objToUrlOpt: proc(obj, addQMark = false){
        affirm obj && obj.eachProperty /* expecting an Object */;
        buf.reset();
        var counter = 0;
        obj.eachProperty(each);
        return buf.isEmpty()
            ? ''
            : (addQMark ? '?' : '')+buf.toString();
    }.importSymbols({
        buf: s2.Buffer.new(),
        each: proc(k,v){
            counter++ && buf.append('&');
            buf.append($CGI.urlencode(''+k),'=',$CGI.urlencode(''+v));
            /* Reminder to self: $CGI.urlencode() and friends require
               that $CGI be their 'this' because they reuse an internal
               buffer on each call to save on allocations.
            */
        }
    }),

    absoluteLink: proc(path,label=path, urlOpt){
        affirm 'string' === typename path;
        const hasQ = path.indexOf('?')>=0,
          optStr = this.objToUrlOpt(urlOpt, !hasQ );
        return fmt.applyFormat(
            $CGI.config.cgiRoot,
            $CGI.urlencode(path)+(optStr ? hasQ ? '&' + optStr : optStr : ''),
            label);
    }.importSymbols({
        fmt: "<a href='%1$s%2$s'>%3$s</a>"
    });
};


$CGI.resourcePath = proc(name){
    return this.config.resourceDir + name;
};

$CGI.resolveResource = proc(name){
    const path = this.config.resourceDir + name;
    return Fossil.file.isFile(path) ? path : undefined;
};

// incomplete... not yet sure what i want.
$CGI.request.pathInfoList = scope {
    var ps, p = s2.getenv('PATH_INFO');
    if(p && '/'!==p){
        ps = p.split('/');
        // Trim leading/trailing null entries caused
        // by leading/trailing slashes:
        ps.0 || ps.shift();
        ps[ps.length()-1] || ps.pop();
    }
    ps;
};

if(0){ // only for testing
    $CGI.setContentType('application/json');
    print({request: $CGI.request,
          config: $CGI.config}.toJSONString(2));
}else if(1){
    const c = $CGI;
    c.setContentType('text/plain');
    print("$CGI sanity checks...");
    c << 'Fossil.time.cpuTime() says: '
        << Fossil.time.cpuTime()
        << '\n'
    ;

    const f = c.fossilInstance;
    f.db || f.openCheckout();
    c << 'Fossil context: ' << f << ' repo db: ' << f.db.repo.filename << '\n';
    c << "pathInfoList = "<<c.request.pathInfoList<<'\n';
    c << "resourcePath(foo/bar.baz)==>"
        << c.resourcePath('foo/bar.baz')
        << '\n';
    c << "resolveResource('init.s2')==>"
        << c.resolveResource('init.s2')
        << '\n';
    c << "resolveResource('x')==>"
        << c.resolveResource('x')
        << '\n';

    if(1) {
        c << '$CGI.config = ' << $CGI.config.toJSONString(2) << '\n';
    }

    c << "Most recent timeline event: "
        << f.db.selectRow('SELECT * FROM event ORDER BY mtime DESC')
        .toJSONString(2)
        << '\n';

    if(0){
        // Workaround: c is a Native, and those are rejected
        // by the C-level toJSON bits:
        var obj = {};
        c.eachProperty(proc(k,v){obj[k]=v});
        c << '$CGI properties:\n' << obj.toJSONString(4) << '\n';
    }
    if(1){
        c << 'objToUrlOpt: '
            << c.util.objToUrlOpt({a:3,c:'a b'})
            << '\n';
        c << 'objToUrlOpt again (should be empty): '
            << c.util.objToUrlOpt({},true)
            << '\n';
        c << 'absoluteLink: '
            << c.util.absoluteLink('foo/bar/baz?x=y', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'absoluteLink again: '
            << c.util.absoluteLink('foo/bar/', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'Fossil.artifactTypes = '<<Fossil.artifactTypes<<'\n';
        
    }

    0 && Fossil.require(['tmpl-compiled!layout/default.tmpl.s2'],proc(t){
        t.evalContents('default layout');
    });

    c << '\nThe end. Fossil.time.cpuTime() says: '
        << Fossil.time.cpuTime()
        << '\n';

    //throw exception(-666,"testing discarding of buffered output");
}

Added bindings/s2/fslcgi.s2.
































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   Bootstrap script for the s2/libfossil CGI bits.
*/
const $CGI = scope {
    /* Load the CGI module and bail out with an HTTP-correct response if it fails. */
    var c;
    var err = catch{
        c = s2.loadModule2('cgimod').cgi;
        affirm c;
        affirm 'function' === typename c.send;
    };
    if(err){
        const out = s2.io.output;
        out("Status: 500 Could not load CGI module.\r\n");
        out("Content-type: text/plain\r\n");
        out("\r\n");
        if(1){
            // potentially security-relevant (absolute paths, etc)
            out(err.toJSONString(2));
        }else{
            out("Could not load CGI module.");
        }
        exit;
    }
    c;
};
//print('cgi =',typename $CGI);
/* Start output buffering before the page gets a chance to output anything. */
const $ob = s2.ob;
$ob.push();
var err = catch Fossil.require(
['cliargs','fsl/context', 'fsl/db/repoOrCheckout'/*to get it opened*/],
proc(cliargs, F){
    // TODO: get our routing/dispatching in place, and import/call it here...
    const resDir = cliargs.takeFlag('resource-dir');
    typeinfo(isstring resDir) || throw "Set the --resource-dir=/path SCRIPT flag (after --) in the main CGI script.";
    $CGI.fossilInstance = F;
    requireS2.getPlugin('default').prefix[] = resDir;
    requireS2.getPlugin('tmpl').suffix[] = '.tmpl.s2';
    requireS2.getPlugin('tmpl-compiled').suffix[] = '.tmpl.s2';
    s2.import(Fossil.file.canonicalName(resDir,false)+'/init.s2');
    //$CGI.dispatchRoute();
    //$CGI.setContentType('text/plain');
    //$CGI.request.PATH_INFO = s2.getenv('PATH_INFO') ||| 'try adding a path';
    //print('(ammended) $CGI.request:');
    //print($CGI.request.toJSONString(2));
    //throw "This causes generated (buffered) output to be discarded.";
});

if(err){ // transform exception to a JSON object.
    // discard accumulated output, but keep one buffering level for our own use...
    var obLevel = $ob.level();
    for( ; obLevel > 1; $ob.pop(), --obLevel) {}
    if(obLevel){ $ob.clear() }
    else { $ob.push() /* so our headers get sent properly */ }
    if($CGI.httpStatus() < 500){
        $CGI.httpStatus(500, "Caught Exception");
    }
    $CGI.scrubException && (err = $CGI.scrubException(err));
    $CGI.setContentType('application/json');
    print({exception:err}.toJSONString(-1));
}
$CGI.send() /* will flush all $ob buffers after emiting headers */;

Added bindings/s2/linenoise/linenoise.c.


























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































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
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* linenoise.c -- guerrilla line editing library against the idea that a
 * line editing lib needs to be 20,000 lines of C code.
 *
 * You can find the latest source code at:
 *
 *   http://github.com/msteveb/linenoise
 *   (forked from http://github.com/antirez/linenoise)
 *
 * Does a number of crazy assumptions that happen to be true in 99.9999% of
 * the 2010 UNIX computers around.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 * Copyright (c) 2011, Steve Bennett <steveb at workware dot net dot au>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ------------------------------------------------------------------------
 *
 * References:
 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
 *
 * Bloat:
 * - Completion?
 *
 * Unix/termios
 * ------------
 * List of escape sequences used by this program, we do everything just
 * a few sequences. In order to be so cheap we may have some
 * flickering effect with some slow terminal, but the lesser sequences
 * the more compatible.
 *
 * EL (Erase Line)
 *    Sequence: ESC [ n K
 *    Effect: if n is 0 or missing, clear from cursor to end of line
 *    Effect: if n is 1, clear from beginning of line to cursor
 *    Effect: if n is 2, clear entire line
 *
 * CUF (CUrsor Forward)
 *    Sequence: ESC [ n C
 *    Effect: moves cursor forward of n chars
 *
 * CR (Carriage Return)
 *    Sequence: \r
 *    Effect: moves cursor to column 1
 *
 * The following are used to clear the screen: ESC [ H ESC [ 2 J
 * This is actually composed of two sequences:
 *
 * cursorhome
 *    Sequence: ESC [ H
 *    Effect: moves the cursor to upper left corner
 *
 * ED2 (Clear entire screen)
 *    Sequence: ESC [ 2 J
 *    Effect: clear the whole screen
 *
 * == For highlighting control characters, we also use the following two ==
 * SO (enter StandOut)
 *    Sequence: ESC [ 7 m
 *    Effect: Uses some standout mode such as reverse video
 *
 * SE (Standout End)
 *    Sequence: ESC [ 0 m
 *    Effect: Exit standout mode
 *
 * == Only used if TIOCGWINSZ fails ==
 * DSR/CPR (Report cursor position)
 *    Sequence: ESC [ 6 n
 *    Effect: reports current cursor position as ESC [ NNN ; MMM R
 *
 * win32/console
 * -------------
 * If __MINGW32__ is defined, the win32 console API is used.
 * This could probably be made to work for the msvc compiler too.
 * This support based in part on work by Jon Griffiths.
 */

#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */
#include <windows.h>
#include <fcntl.h>
#define USE_WINCONSOLE
#ifdef __MINGW32__
#define HAVE_UNISTD_H
#else
/* Microsoft headers don't like old POSIX names */
#define strdup _strdup
#define snprintf _snprintf
#endif
#else
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#define USE_TERMIOS
#define HAVE_UNISTD_H
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>

#include "linenoise.h"
#include "utf8.h"

#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096

#define ctrl(C) ((C) - '@')

/* Use -ve numbers here to co-exist with normal unicode chars */
enum {
    SPECIAL_NONE,
    SPECIAL_UP = -20,
    SPECIAL_DOWN = -21,
    SPECIAL_LEFT = -22,
    SPECIAL_RIGHT = -23,
    SPECIAL_DELETE = -24,
    SPECIAL_HOME = -25,
    SPECIAL_END = -26,
    SPECIAL_INSERT = -27,
    SPECIAL_PAGE_UP = -28,
    SPECIAL_PAGE_DOWN = -29
};

static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
static int history_len = 0;
static char **history = NULL;

/* Structure to contain the status of the current (being edited) line */
struct current {
    char *buf;  /* Current buffer. Always null terminated */
    int bufmax; /* Size of the buffer, including space for the null termination */
    int len;    /* Number of bytes in 'buf' */
    int chars;  /* Number of chars in 'buf' (utf-8 chars) */
    int pos;    /* Cursor position, measured in chars */
    int cols;   /* Size of the window, in chars */
    const char *prompt;
    char *capture; /* Allocated capture buffer, or NULL for none. Always null terminated */
#if defined(USE_TERMIOS)
    int fd;     /* Terminal fd */
#elif defined(USE_WINCONSOLE)
    HANDLE outh; /* Console output handle */
    HANDLE inh; /* Console input handle */
    int rows;   /* Screen rows */
    int x;      /* Current column during output */
    int y;      /* Current row */
#endif
};

static int fd_read(struct current *current);
static int getWindowSize(struct current *current);

void linenoiseHistoryFree(void) {
    if (history) {
        int j;

        for (j = 0; j < history_len; j++)
            free(history[j]);
        free(history);
        history = NULL;
        history_len = 0;
    }
}

#if defined(USE_TERMIOS)
static void linenoiseAtExit(void);
static struct termios orig_termios; /* in order to restore at exit */
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
static int atexit_registered = 0; /* register atexit just 1 time */

static const char *unsupported_term[] = {"dumb","cons25",NULL};

static int isUnsupportedTerm(void) {
    char *term = getenv("TERM");

    if (term) {
        int j;
        for (j = 0; unsupported_term[j]; j++) {
            if (strcmp(term, unsupported_term[j]) == 0) {
                return 1;
            }
        }
    }
    return 0;
}

static int enableRawMode(struct current *current) {
    struct termios raw;

    current->fd = STDIN_FILENO;
    current->cols = 0;

    if (!isatty(current->fd) || isUnsupportedTerm() ||
        tcgetattr(current->fd, &orig_termios) == -1) {
fatal:
        errno = ENOTTY;
        return -1;
    }

    if (!atexit_registered) {
        atexit(linenoiseAtExit);
        atexit_registered = 1;
    }

    raw = orig_termios;  /* modify the original mode */
    /* input modes: no break, no CR to NL, no parity check, no strip char,
     * no start/stop output control. */
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* output modes - disable post processing */
    raw.c_oflag &= ~(OPOST);
    /* control modes - set 8 bit chars */
    raw.c_cflag |= (CS8);
    /* local modes - choing off, canonical off, no extended functions,
     * no signal chars (^Z,^C) */
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    /* control chars - set return condition: min number of bytes and timer.
     * We want read to return every single byte, without timeout. */
    raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */

    /* put terminal in raw mode after flushing */
    if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) {
        goto fatal;
    }
    rawmode = 1;
    return 0;
}

static void disableRawMode(struct current *current) {
    /* Don't even check the return value as it's too late. */
    if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1)
        rawmode = 0;
}

/* At exit we'll try to fix the terminal to the initial conditions. */
static void linenoiseAtExit(void) {
    if (rawmode) {
        tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios);
    }
    linenoiseHistoryFree();
}

/* gcc/glibc insists that we care about the return code of write!
 * Clarification: This means that a void-cast like "(void) (EXPR)"
 * does not work.
 */
#define IGNORE_RC(EXPR) if (EXPR) {}

/* This is fdprintf() on some systems, but use a different
 * name to avoid conflicts
 */
static void fd_printf(int fd, const char *format, ...)
{
    va_list args;
    char buf[64];
    int n;

    va_start(args, format);
    n = vsnprintf(buf, sizeof(buf), format, args);
    va_end(args);
    IGNORE_RC(write(fd, buf, n));
}

static void clearScreen(struct current *current)
{
    fd_printf(current->fd, "\x1b[H\x1b[2J");
}

static void cursorToLeft(struct current *current)
{
    fd_printf(current->fd, "\r");
}

static int outputChars(struct current *current, const char *buf, int len)
{
    return write(current->fd, buf, len);
}

static void outputControlChar(struct current *current, char ch)
{
    fd_printf(current->fd, "\x1b[7m^%c\x1b[0m", ch);
}

static void eraseEol(struct current *current)
{
    fd_printf(current->fd, "\x1b[0K");
}

static void setCursorPos(struct current *current, int x)
{
    fd_printf(current->fd, "\r\x1b[%dC", x);
}

/**
 * Reads a char from 'fd', waiting at most 'timeout' milliseconds.
 *
 * A timeout of -1 means to wait forever.
 *
 * Returns -1 if no char is received within the time or an error occurs.
 */
static int fd_read_char(int fd, int timeout)
{
    struct pollfd p;
    unsigned char c;

    p.fd = fd;
    p.events = POLLIN;

    if (poll(&p, 1, timeout) == 0) {
        /* timeout */
        return -1;
    }
    if (read(fd, &c, 1) != 1) {
        return -1;
    }
    return c;
}

/**
 * Reads a complete utf-8 character
 * and returns the unicode value, or -1 on error.
 */
static int fd_read(struct current *current)
{
#ifdef USE_UTF8
    char buf[4];
    int n;
    int i;
    int c;

    if (read(current->fd, &buf[0], 1) != 1) {
        return -1;
    }
    n = utf8_charlen(buf[0]);
    if (n < 1 || n > 3) {
        return -1;
    }
    for (i = 1; i < n; i++) {
        if (read(current->fd, &buf[i], 1) != 1) {
            return -1;
        }
    }
    buf[n] = 0;
    /* decode and return the character */
    utf8_tounicode(buf, &c);
    return c;
#else
    return fd_read_char(current->fd, -1);
#endif
}

static int countColorControlChars(const char* prompt)
{
    /* ANSI color control sequences have the form:
     * "\x1b" "[" [0-9;]* "m"
     * We parse them with a simple state machine.
     */

    enum {
        search_esc,
        expect_bracket,
        expect_trail
    } state = search_esc;
    int len = 0, found = 0;
    char ch;

    /* XXX: Strictly we should be checking utf8 chars rather than
     *      bytes in case of the extremely unlikely scenario where
     *      an ANSI sequence is part of a utf8 sequence.
     */
    while ((ch = *prompt++) != 0) {
        switch (state) {
        case search_esc:
            if (ch == '\x1b') {
                state = expect_bracket;
            }
            break;
        case expect_bracket:
            if (ch == '[') {
                state = expect_trail;
                /* 3 for "\e[ ... m" */
                len = 3;
                break;
            }
            state = search_esc;
            break;
        case expect_trail:
            if ((ch == ';') || ((ch >= '0' && ch <= '9'))) {
                /* 0-9, or semicolon */
                len++;
                break;
            }
            if (ch == 'm') {
                found += len;
            }
            state = search_esc;
            break;
        }
    }

    return found;
}

/**
 * Stores the current cursor column in '*cols'.
 * Returns 1 if OK, or 0 if failed to determine cursor pos.
 */
static int queryCursor(int fd, int* cols)
{
    /* control sequence - report cursor location */
    fd_printf(fd, "\x1b[6n");

    /* Parse the response: ESC [ rows ; cols R */
    if (fd_read_char(fd, 100) == 0x1b &&
        fd_read_char(fd, 100) == '[') {

        int n = 0;
        while (1) {
            int ch = fd_read_char(fd, 100);
            if (ch == ';') {
                /* Ignore rows */
                n = 0;
            }
            else if (ch == 'R') {
                /* Got cols */
                if (n != 0 && n < 1000) {
                    *cols = n;
                }
                break;
            }
            else if (ch >= 0 && ch <= '9') {
                n = n * 10 + ch - '0';
            }
            else {
                break;
            }
        }
        return 1;
    }

    return 0;
}

/**
 * Updates current->cols with the current window size (width)
 */
static int getWindowSize(struct current *current)
{
    struct winsize ws;

    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) {
        current->cols = ws.ws_col;
        return 0;
    }

    /* Failed to query the window size. Perhaps we are on a serial terminal.
     * Try to query the width by sending the cursor as far to the right
     * and reading back the cursor position.
     * Note that this is only done once per call to linenoise rather than
     * every time the line is refreshed for efficiency reasons.
     *
     * In more detail, we:
     * (a) request current cursor position,
     * (b) move cursor far right,
     * (c) request cursor position again,
     * (d) at last move back to the old position.
     * This gives us the width without messing with the externally
     * visible cursor position.
     */

    if (current->cols == 0) {
        int here;

        current->cols = 80;

        /* (a) */
        if (queryCursor (current->fd, &here)) {
            /* (b) */
            fd_printf(current->fd, "\x1b[999C");

            /* (c). Note: If (a) succeeded, then (c) should as well.
             * For paranoia we still check and have a fallback action
             * for (d) in case of failure..
             */
            if (!queryCursor (current->fd, &current->cols)) {
                /* (d') Unable to get accurate position data, reset
                 * the cursor to the far left. While this may not
                 * restore the exact original position it should not
                 * be too bad.
                 */
                fd_printf(current->fd, "\r");
            } else {
                /* (d) Reset the cursor back to the original location. */
                if (current->cols > here) {
                    fd_printf(current->fd, "\x1b[%dD", current->cols - here);
                }
            }
        } /* 1st query failed, doing nothing => default 80 */
    }

    return 0;
}

/**
 * If escape (27) was received, reads subsequent
 * chars to determine if this is a known special key.
 *
 * Returns SPECIAL_NONE if unrecognised, or -1 if EOF.
 *
 * If no additional char is received within a short time,
 * 27 is returned.
 */
static int check_special(int fd)
{
    int c = fd_read_char(fd, 50);
    int c2;

    if (c < 0) {
        return 27;
    }

    c2 = fd_read_char(fd, 50);
    if (c2 < 0) {
        return c2;
    }
    if (c == '[' || c == 'O') {
        /* Potential arrow key */
        switch (c2) {
            case 'A':
                return SPECIAL_UP;
            case 'B':
                return SPECIAL_DOWN;
            case 'C':
                return SPECIAL_RIGHT;
            case 'D':
                return SPECIAL_LEFT;
            case 'F':
                return SPECIAL_END;
            case 'H':
                return SPECIAL_HOME;
        }
    }
    if (c == '[' && c2 >= '1' && c2 <= '8') {
        /* extended escape */
        c = fd_read_char(fd, 50);
        if (c == '~') {
            switch (c2) {
                case '2':
                    return SPECIAL_INSERT;
                case '3':
                    return SPECIAL_DELETE;
                case '5':
                    return SPECIAL_PAGE_UP;
                case '6':
                    return SPECIAL_PAGE_DOWN;
                case '7':
                    return SPECIAL_HOME;
                case '8':
                    return SPECIAL_END;
            }
        }
        while (c != -1 && c != '~') {
            /* .e.g \e[12~ or '\e[11;2~   discard the complete sequence */
            c = fd_read_char(fd, 50);
        }
    }

    return SPECIAL_NONE;
}
#elif defined(USE_WINCONSOLE)

static DWORD orig_consolemode = 0;

static int enableRawMode(struct current *current) {
    DWORD n;
    INPUT_RECORD irec;

    current->outh = GetStdHandle(STD_OUTPUT_HANDLE);
    current->inh = GetStdHandle(STD_INPUT_HANDLE);

    if (!PeekConsoleInput(current->inh, &irec, 1, &n)) {
        return -1;
    }
    if (getWindowSize(current) != 0) {
        return -1;
    }
    if (GetConsoleMode(current->inh, &orig_consolemode)) {
        SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT);
    }
    return 0;
}

static void disableRawMode(struct current *current)
{
    SetConsoleMode(current->inh, orig_consolemode);
}

static void clearScreen(struct current *current)
{
    COORD topleft = { 0, 0 };
    DWORD n;

    FillConsoleOutputCharacter(current->outh, ' ',
        current->cols * current->rows, topleft, &n);
    FillConsoleOutputAttribute(current->outh,
        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN,
        current->cols * current->rows, topleft, &n);
    SetConsoleCursorPosition(current->outh, topleft);
}

static void cursorToLeft(struct current *current)
{
    COORD pos = { 0, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputAttribute(current->outh,
        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n);
    current->x = 0;
}

static int outputChars(struct current *current, const char *buf, int len)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    WriteConsoleOutputCharacter(current->outh, buf, len, pos, &n);
    current->x += len;
    return 0;
}

static void outputControlChar(struct current *current, char ch)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputAttribute(current->outh, BACKGROUND_INTENSITY, 2, pos, &n);
    outputChars(current, "^", 1);
    outputChars(current, &ch, 1);
}

static void eraseEol(struct current *current)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n);
}

static void setCursorPos(struct current *current, int x)
{
    COORD pos = { (SHORT)x, (SHORT)current->y };

    SetConsoleCursorPosition(current->outh, pos);
    current->x = x;
}

static int fd_read(struct current *current)
{
    while (1) {
        INPUT_RECORD irec;
        DWORD n;
        if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) {
            break;
        }
        if (!ReadConsoleInput (current->inh, &irec, 1, &n)) {
            break;
        }
        if (irec.EventType == KEY_EVENT && irec.Event.KeyEvent.bKeyDown) {
            KEY_EVENT_RECORD *k = &irec.Event.KeyEvent;
            if (k->dwControlKeyState & ENHANCED_KEY) {
                switch (k->wVirtualKeyCode) {
                 case VK_LEFT:
                    return SPECIAL_LEFT;
                 case VK_RIGHT:
                    return SPECIAL_RIGHT;
                 case VK_UP:
                    return SPECIAL_UP;
                 case VK_DOWN:
                    return SPECIAL_DOWN;
                 case VK_INSERT:
                    return SPECIAL_INSERT;
                 case VK_DELETE:
                    return SPECIAL_DELETE;
                 case VK_HOME:
                    return SPECIAL_HOME;
                 case VK_END:
                    return SPECIAL_END;
                 case VK_PRIOR:
                    return SPECIAL_PAGE_UP;
                 case VK_NEXT:
                    return SPECIAL_PAGE_DOWN;
                }
            }
            /* Note that control characters are already translated in AsciiChar */
            else if (k->wVirtualKeyCode == VK_CONTROL)
	        continue;
            else {
#ifdef USE_UTF8
                return k->uChar.UnicodeChar;
#else
                return k->uChar.AsciiChar;
#endif
            }
        }
    }
    return -1;
}

static int countColorControlChars(const char* prompt)
{
    /* For windows we assume that there are no embedded ansi color
     * control sequences.
     */
    return 0;
}

static int getWindowSize(struct current *current)
{
    CONSOLE_SCREEN_BUFFER_INFO info;
    if (!GetConsoleScreenBufferInfo(current->outh, &info)) {
        return -1;
    }
    current->cols = info.dwSize.X;
    current->rows = info.dwSize.Y;
    if (current->cols <= 0 || current->rows <= 0) {
        current->cols = 80;
        return -1;
    }
    current->y = info.dwCursorPosition.Y;
    current->x = info.dwCursorPosition.X;
    return 0;
}
#endif

static int utf8_getchars(char *buf, int c)
{
#ifdef USE_UTF8
    return utf8_fromunicode(buf, c);
#else
    *buf = c;
    return 1;
#endif
}

/**
 * Returns the unicode character at the given offset,
 * or -1 if none.
 */
static int get_char(struct current *current, int pos)
{
    if (pos >= 0 && pos < current->chars) {
        int c;
        int i = utf8_index(current->buf, pos);
        (void)utf8_tounicode(current->buf + i, &c);
        return c;
    }
    return -1;
}

static void refreshLine(const char *prompt, struct current *current)
{
    int plen;
    int pchars;
    int backup = 0;
    int i;
    const char *buf = current->buf;
    int chars = current->chars;
    int pos = current->pos;
    int b;
    int ch;
    int n;

    /* Should intercept SIGWINCH. For now, just get the size every time */
    getWindowSize(current);

    plen = strlen(prompt);
    pchars = utf8_strlen(prompt, plen);

    /* Scan the prompt for embedded ansi color control sequences and
     * discount them as characters/columns.
     */
    pchars -= countColorControlChars(prompt);

    /* Account for a line which is too long to fit in the window.
     * Note that control chars require an extra column
     */

    /* How many cols are required to the left of 'pos'?
     * The prompt, plus one extra for each control char
     */
    n = pchars + utf8_strlen(buf, current->len);
    b = 0;
    for (i = 0; i < pos; i++) {
        b += utf8_tounicode(buf + b, &ch);
        if (ch < ' ') {
            n++;
        }
    }

    /* If too many are needed, strip chars off the front of 'buf'
     * until it fits. Note that if the current char is a control character,
     * we need one extra col.
     */
    if (current->pos < current->chars && get_char(current, current->pos) < ' ') {
        n++;
    }

    while (n >= current->cols && pos > 0) {
        b = utf8_tounicode(buf, &ch);
        if (ch < ' ') {
            n--;
        }
        n--;
        buf += b;
        pos--;
        chars--;
    }

    /* Cursor to left edge, then the prompt */
    cursorToLeft(current);
    outputChars(current, prompt, plen);

    /* Now the current buffer content */

    /* Need special handling for control characters.
     * If we hit 'cols', stop.
     */
    b = 0; /* unwritted bytes */
    n = 0; /* How many control chars were written */
    for (i = 0; i < chars; i++) {
        int ch;
        int w = utf8_tounicode(buf + b, &ch);
        if (ch < ' ') {
            n++;
        }
        if (pchars + i + n >= current->cols) {
            break;
        }
        if (ch < ' ') {
            /* A control character, so write the buffer so far */
            outputChars(current, buf, b);
            buf += b + w;
            b = 0;
            outputControlChar(current, ch + '@');
            if (i < pos) {
                backup++;
            }
        }
        else {
            b += w;
        }
    }
    outputChars(current, buf, b);

    /* Erase to right, move cursor to original position */
    eraseEol(current);
    setCursorPos(current, pos + pchars + backup);
}

static void set_current(struct current *current, const char *str)
{
    strncpy(current->buf, str, current->bufmax);
    current->buf[current->bufmax - 1] = 0;
    current->len = strlen(current->buf);
    current->pos = current->chars = utf8_strlen(current->buf, current->len);
}

static int has_room(struct current *current, int bytes)
{
    return current->len + bytes < current->bufmax - 1;
}

/**
 * Removes the char at 'pos'.
 *
 * Returns 1 if the line needs to be refreshed, 2 if not
 * and 0 if nothing was removed
 */
static int remove_char(struct current *current, int pos)
{
    if (pos >= 0 && pos < current->chars) {
        int p1, p2;
        int ret = 1;
        p1 = utf8_index(current->buf, pos);
        p2 = p1 + utf8_index(current->buf + p1, 1);

#ifdef USE_TERMIOS
        /* optimise remove char in the case of removing the last char */
        if (current->pos == pos + 1 && current->pos == current->chars) {
            if (current->buf[pos] >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) {
                ret = 2;
                fd_printf(current->fd, "\b \b");
            }
        }
#endif

        /* Move the null char too */
        memmove(current->buf + p1, current->buf + p2, current->len - p2 + 1);
        current->len -= (p2 - p1);
        current->chars--;

        if (current->pos > pos) {
            current->pos--;
        }
        return ret;
    }
    return 0;
}

/**
 * Insert 'ch' at position 'pos'
 *
 * Returns 1 if the line needs to be refreshed, 2 if not
 * and 0 if nothing was inserted (no room)
 */
static int insert_char(struct current *current, int pos, int ch)
{
    char buf[3];
    int n = utf8_getchars(buf, ch);

    if (has_room(current, n) && pos >= 0 && pos <= current->chars) {
        int p1, p2;
        int ret = 1;
        p1 = utf8_index(current->buf, pos);
        p2 = p1 + n;

#ifdef USE_TERMIOS
        /* optimise the case where adding a single char to the end and no scrolling is needed */
        if (current->pos == pos && current->chars == pos) {
            if (ch >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) {
                IGNORE_RC(write(current->fd, buf, n));
                ret = 2;
            }
        }
#endif

        memmove(current->buf + p2, current->buf + p1, current->len - p1);
        memcpy(current->buf + p1, buf, n);
        current->len += n;

        current->chars++;
        if (current->pos >= pos) {
            current->pos++;
        }
        return ret;
    }
    return 0;
}

/**
 * Captures up to 'n' characters starting at 'pos' for the cut buffer.
 *
 * This replaces any existing characters in the cut buffer.
 */
static void capture_chars(struct current *current, int pos, int n)
{
    if (pos >= 0 && (pos + n - 1) < current->chars) {
        int p1 = utf8_index(current->buf, pos);
        int nbytes = utf8_index(current->buf + p1, n);

        if (nbytes) {
            free(current->capture);
            /* Include space for the null terminator */
            current->capture = (char *)malloc(nbytes + 1);
            memcpy(current->capture, current->buf + p1, nbytes);
            current->capture[nbytes] = '\0';
        }
    }
}

/**
 * Removes up to 'n' characters at cursor position 'pos'.
 *
 * Returns 0 if no chars were removed or non-zero otherwise.
 */
static int remove_chars(struct current *current, int pos, int n)
{
    int removed = 0;

    /* First save any chars which will be removed */
    capture_chars(current, pos, n);

    while (n-- && remove_char(current, pos)) {
        removed++;
    }
    return removed;
}
/**
 * Inserts the characters (string) 'chars' at the cursor position 'pos'.
 *
 * Returns 0 if no chars were inserted or non-zero otherwise.
 */
static int insert_chars(struct current *current, int pos, const char *chars)
{
    int inserted = 0;

    while (*chars) {
        int ch;
        int n = utf8_tounicode(chars, &ch);
        if (insert_char(current, pos, ch) == 0) {
            break;
        }
        inserted++;
        pos++;
        chars += n;
    }
    return inserted;
}

#ifndef NO_COMPLETION
static linenoiseCompletionCallback *completionCallback = NULL;

static void beep() {
#ifdef USE_TERMIOS
    fprintf(stderr, "\x7");
    fflush(stderr);
#endif
}

static void freeCompletions(linenoiseCompletions *lc) {
    size_t i;
    for (i = 0; i < lc->len; i++)
        free(lc->cvec[i]);
    free(lc->cvec);
}

static int completeLine(struct current *current) {
    linenoiseCompletions lc = { 0, NULL };
    int c = 0;

    completionCallback(current->buf,&lc);
    if (lc.len == 0) {
        beep();
    } else {
        size_t stop = 0, i = 0;

        while(!stop) {
            /* Show completion or original buffer */
            if (i < lc.len) {
                struct current tmp = *current;
                tmp.buf = lc.cvec[i];
                tmp.pos = tmp.len = strlen(tmp.buf);
                tmp.chars = utf8_strlen(tmp.buf, tmp.len);
                refreshLine(current->prompt, &tmp);
            } else {
                refreshLine(current->prompt, current);
            }

            c = fd_read(current);
            if (c == -1) {
                break;
            }

            switch(c) {
                case '\t': /* tab */
                    i = (i+1) % (lc.len+1);
                    if (i == lc.len) beep();
                    break;
                case 27: /* escape */
                    /* Re-show original buffer */
                    if (i < lc.len) {
                        refreshLine(current->prompt, current);
                    }
                    stop = 1;
                    break;
                default:
                    /* Update buffer and return */
                    if (i < lc.len) {
                        set_current(current,lc.cvec[i]);
                    }
                    stop = 1;
                    break;
            }
        }
    }

    freeCompletions(&lc);
    return c; /* Return last read character */
}

/* Register a callback function to be called for tab-completion.
   Returns the prior callback so that the caller may (if needed)
   restore it when done. */
linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
    linenoiseCompletionCallback * old = completionCallback;
    completionCallback = fn;
    return old;
}

void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
    lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1));
    lc->cvec[lc->len++] = strdup(str);
}

#endif

static int linenoiseEdit(struct current *current) {
    int history_index = 0;

    /* The latest history entry is always our current buffer, that
     * initially is just an empty string. */
    linenoiseHistoryAdd("");

    set_current(current, "");
    refreshLine(current->prompt, current);

    while(1) {
        int dir = -1;
        int c = fd_read(current);

#ifndef NO_COMPLETION
        /* Only autocomplete when the callback is set. It returns < 0 when
         * there was an error reading from fd. Otherwise it will return the
         * character that should be handled next. */
        if (c == '\t' && current->pos == current->chars && completionCallback != NULL) {
            c = completeLine(current);
            /* Return on errors */
            if (c < 0) return current->len;
            /* Read next character when 0 */
            if (c == 0) continue;
        }
#endif

process_char:
        if (c == -1) return current->len;
#ifdef USE_TERMIOS
        if (c == 27) {   /* escape sequence */
            c = check_special(current->fd);
        }
#endif
        switch(c) {
        case '\r':    /* enter */
            history_len--;
            free(history[history_len]);
            return current->len;
        case ctrl('C'):     /* ctrl-c */
            errno = EAGAIN;
            return -1;
        case 127:   /* backspace */
        case ctrl('H'):
            if (remove_char(current, current->pos - 1) == 1) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('D'):     /* ctrl-d */
            if (current->len == 0) {
                /* Empty line, so EOF */
                history_len--;
                free(history[history_len]);
                return -1;
            }
            /* Otherwise fall through to delete char to right of cursor */
        case SPECIAL_DELETE:
            if (remove_char(current, current->pos) == 1) {
                refreshLine(current->prompt, current);
            }
            break;
        case SPECIAL_INSERT:
            /* Ignore. Expansion Hook.
             * Future possibility: Toggle Insert/Overwrite Modes
             */
            break;
        case ctrl('W'):    /* ctrl-w, delete word at left. save deleted chars */
            /* eat any spaces on the left */
            {
                int pos = current->pos;
                while (pos > 0 && get_char(current, pos - 1) == ' ') {
                    pos--;
                }

                /* now eat any non-spaces on the left */
                while (pos > 0 && get_char(current, pos - 1) != ' ') {
                    pos--;
                }

                if (remove_chars(current, pos, current->pos - pos)) {
                    refreshLine(current->prompt, current);
                }
            }
            break;
        case ctrl('R'):    /* ctrl-r */
            {
                /* Display the reverse-i-search prompt and process chars */
                char rbuf[50];
                char rprompt[80];
                int rchars = 0;
                int rlen = 0;
                int searchpos = history_len - 1;

                rbuf[0] = 0;
                while (1) {
                    int n = 0;
                    const char *p = NULL;
                    int skipsame = 0;
                    int searchdir = -1;

                    snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf);
                    refreshLine(rprompt, current);
                    c = fd_read(current);
                    if (c == ctrl('H') || c == 127) {
                        if (rchars) {
                            int p = utf8_index(rbuf, --rchars);
                            rbuf[p] = 0;
                            rlen = strlen(rbuf);
                        }
                        continue;
                    }
#ifdef USE_TERMIOS
                    if (c == 27) {
                        c = check_special(current->fd);
                    }
#endif
                    if (c == ctrl('P') || c == SPECIAL_UP) {
                        /* Search for the previous (earlier) match */
                        if (searchpos > 0) {
                            searchpos--;
                        }
                        skipsame = 1;
                    }
                    else if (c == ctrl('N') || c == SPECIAL_DOWN) {
                        /* Search for the next (later) match */
                        if (searchpos < history_len) {
                            searchpos++;
                        }
                        searchdir = 1;
                        skipsame = 1;
                    }
                    else if (c >= ' ') {
                        if (rlen >= (int)sizeof(rbuf) + 3) {
                            continue;
                        }

                        n = utf8_getchars(rbuf + rlen, c);
                        rlen += n;
                        rchars++;
                        rbuf[rlen] = 0;

                        /* Adding a new char resets the search location */
                        searchpos = history_len - 1;
                    }
                    else {
                        /* Exit from incremental search mode */
                        break;
                    }

                    /* Now search through the history for a match */
                    for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) {
                        p = strstr(history[searchpos], rbuf);
                        if (p) {
                            /* Found a match */
                            if (skipsame && strcmp(history[searchpos], current->buf) == 0) {
                                /* But it is identical, so skip it */
                                continue;
                            }
                            /* Copy the matching line and set the cursor position */
                            set_current(current,history[searchpos]);
                            current->pos = utf8_strlen(history[searchpos], p - history[searchpos]);
                            break;
                        }
                    }
                    if (!p && n) {
                        /* No match, so don't add it */
                        rchars--;
                        rlen -= n;
                        rbuf[rlen] = 0;
                    }
                }
                if (c == ctrl('G') || c == ctrl('C')) {
                    /* ctrl-g terminates the search with no effect */
                    set_current(current, "");
                    c = 0;
                }
                else if (c == ctrl('J')) {
                    /* ctrl-j terminates the search leaving the buffer in place */
                    c = 0;
                }
                /* Go process the char normally */
                refreshLine(current->prompt, current);
                goto process_char;
            }
            break;
        case ctrl('T'):    /* ctrl-t */
            if (current->pos > 0 && current->pos <= current->chars) {
                /* If cursor is at end, transpose the previous two chars */
                int fixer = (current->pos == current->chars);
                c = get_char(current, current->pos - fixer);
                remove_char(current, current->pos - fixer);
                insert_char(current, current->pos - 1, c);
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('V'):    /* ctrl-v */
            if (has_room(current, 3)) {
                /* Insert the ^V first */
                if (insert_char(current, current->pos, c)) {
                    refreshLine(current->prompt, current);
                    /* Now wait for the next char. Can insert anything except \0 */
                    c = fd_read(current);

                    /* Remove the ^V first */
                    remove_char(current, current->pos - 1);
                    if (c != -1) {
                        /* Insert the actual char */
                        insert_char(current, current->pos, c);
                    }
                    refreshLine(current->prompt, current);
                }
            }
            break;
        case ctrl('B'):
        case SPECIAL_LEFT:
            if (current->pos > 0) {
                current->pos--;
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('F'):
        case SPECIAL_RIGHT:
            if (current->pos < current->chars) {
                current->pos++;
                refreshLine(current->prompt, current);
            }
            break;
        case SPECIAL_PAGE_UP:
          dir = history_len - history_index - 1; /* move to start of history */
          goto history_navigation;
        case SPECIAL_PAGE_DOWN:
          dir = -history_index; /* move to 0 == end of history, i.e. current */
          goto history_navigation;
        case ctrl('P'):
        case SPECIAL_UP:
            dir = 1;
          goto history_navigation;
        case ctrl('N'):
        case SPECIAL_DOWN:
history_navigation:
            if (history_len > 1) {
                /* Update the current history entry before to
                 * overwrite it with tne next one. */
                free(history[history_len - 1 - history_index]);
                history[history_len - 1 - history_index] = strdup(current->buf);
                /* Show the new entry */
                history_index += dir;
                if (history_index < 0) {
                    history_index = 0;
                    break;
                } else if (history_index >= history_len) {
                    history_index = history_len - 1;
                    break;
                }
                set_current(current, history[history_len - 1 - history_index]);
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('A'): /* Ctrl+a, go to the start of the line */
        case SPECIAL_HOME:
            current->pos = 0;
            refreshLine(current->prompt, current);
            break;
        case ctrl('E'): /* ctrl+e, go to the end of the line */
        case SPECIAL_END:
            current->pos = current->chars;
            refreshLine(current->prompt, current);
            break;
        case ctrl('U'): /* Ctrl+u, delete to beginning of line, save deleted chars. */
            if (remove_chars(current, 0, current->pos)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('K'): /* Ctrl+k, delete from current to end of line, save deleted chars. */
            if (remove_chars(current, current->pos, current->chars - current->pos)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('Y'): /* Ctrl+y, insert saved chars at current position */
            if (current->capture && insert_chars(current, current->pos, current->capture)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('L'): /* Ctrl+L, clear screen */
            clearScreen(current);
            /* Force recalc of window size for serial terminals */
            current->cols = 0;
            refreshLine(current->prompt, current);
            break;
        default:
            /* Only tab is allowed without ^V */
            if (c == '\t' || c >= ' ') {
                if (insert_char(current, current->pos, c) == 1) {
                    refreshLine(current->prompt, current);
                }
            }
            break;
        }
    }
    return current->len;
}

int linenoiseColumns(void)
{
    struct current current;
    enableRawMode (&current);
    getWindowSize (&current);
    disableRawMode (&current);
    return current.cols;
}

char *linenoise(const char *prompt)
{
    int count;
    struct current current;
    char buf[LINENOISE_MAX_LINE];

    if (enableRawMode(&current) == -1) {
        printf("%s", prompt);
        fflush(stdout);
        if (fgets(buf, sizeof(buf), stdin) == NULL) {
            return NULL;
        }
        count = strlen(buf);
        if (count && buf[count-1] == '\n') {
            count--;
            buf[count] = '\0';
        }
    }
    else
    {
        current.buf = buf;
        current.bufmax = sizeof(buf);
        current.len = 0;
        current.chars = 0;
        current.pos = 0;
        current.prompt = prompt;
        current.capture = NULL;

        count = linenoiseEdit(&current);

        disableRawMode(&current);
        printf("\n");

        free(current.capture);
        if (count == -1) {
            return NULL;
        }
    }
    return strdup(buf);
}

/* Using a circular buffer is smarter, but a bit more complex to handle. */
int linenoiseHistoryAdd(const char *line) {
    char *linecopy;

    if (history_max_len == 0) return 0;
    if (history == NULL) {
        history = (char **)malloc(sizeof(char*)*history_max_len);
        if (history == NULL) return 0;
        memset(history,0,(sizeof(char*)*history_max_len));
    }

    /* do not insert duplicate lines into history */
    if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) {
        return 0;
    }

    linecopy = strdup(line);
    if (!linecopy) return 0;
    if (history_len == history_max_len) {
        free(history[0]);
        memmove(history,history+1,sizeof(char*)*(history_max_len-1));
        history_len--;
    }
    history[history_len] = linecopy;
    history_len++;
    return 1;
}

int linenoiseHistoryGetMaxLen(void) {
    return history_max_len;
}

int linenoiseHistorySetMaxLen(int len) {
    char **newHistory;

    if (len < 1) return 0;
    if (history) {
        int tocopy = history_len;

        newHistory = (char **)malloc(sizeof(char*)*len);
        if (newHistory == NULL) return 0;

        /* If we can't copy everything, free the elements we'll not use. */
        if (len < tocopy) {
            int j;

            for (j = 0; j < tocopy-len; j++) free(history[j]);
            tocopy = len;
        }
        memset(newHistory,0,sizeof(char*)*len);
        memcpy(newHistory,history+(history_len-tocopy), sizeof(char*)*tocopy);
        free(history);
        history = newHistory;
    }
    history_max_len = len;
    if (history_len > history_max_len)
        history_len = history_max_len;
    return 1;
}

/* Save the history in the specified file. On success 0 is returned
 * otherwise -1 is returned. */
int linenoiseHistorySave(const char *filename) {
    FILE *fp = fopen(filename,"w");
    int j;

    if (fp == NULL) return -1;
    for (j = 0; j < history_len; j++) {
        const char *str = history[j];
        /* Need to encode backslash, nl and cr */
        while (*str) {
            if (*str == '\\') {
                fputs("\\\\", fp);
            }
            else if (*str == '\n') {
                fputs("\\n", fp);
            }
            else if (*str == '\r') {
                fputs("\\r", fp);
            }
            else {
                fputc(*str, fp);
            }
            str++;
        }
        fputc('\n', fp);
    }

    fclose(fp);
    return 0;
}

/* Load the history from the specified file. If the file does not exist
 * zero is returned and no operation is performed.
 *
 * If the file exists and the operation succeeded 0 is returned, otherwise
 * on error -1 is returned. */
int linenoiseHistoryLoad(const char *filename) {
    FILE *fp = fopen(filename,"r");
    char buf[LINENOISE_MAX_LINE];

    if (fp == NULL) return -1;

    while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
        char *src, *dest;

        /* Decode backslash escaped values */
        for (src = dest = buf; *src; src++) {
            char ch = *src;

            if (ch == '\\') {
                src++;
                if (*src == 'n') {
                    ch = '\n';
                }
                else if (*src == 'r') {
                    ch = '\r';
                } else {
                    ch = *src;
                }
            }
            *dest++ = ch;
        }
        /* Remove trailing newline */
        if (dest != buf && (dest[-1] == '\n' || dest[-1] == '\r')) {
            dest--;
        }
        *dest = 0;

        linenoiseHistoryAdd(buf);
    }
    fclose(fp);
    return 0;
}

/* Provide access to the history buffer.
 *
 * If 'len' is not NULL, the length is stored in *len.
 */
char **linenoiseHistory(int *len) {
    if (len) {
        *len = history_len;
    }
    return history;
}

Added bindings/s2/linenoise/linenoise.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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* linenoise.h -- guerrilla line editing library against the idea that a
 * line editing lib needs to be 20,000 lines of C code.
 *
 * See linenoise.c for more information.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __LINENOISE_H
#define __LINENOISE_H

#ifndef NO_COMPLETION
typedef struct linenoiseCompletions {
  size_t len;
  char **cvec;
} linenoiseCompletions;

/*
 * The callback type for tab completion handlers.
 */
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);

/*
 * Sets the current tab completion handler and returns the previous one, or NULL
 * if no prior one has been set.
 */
linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *);

/*
 * Adds a copy of the given string to the given completion list. The copy is owned
 * by the linenoiseCompletions object.
 */
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
#endif

/*
 * Prompts for input using the given string as the input
 * prompt. Returns when the user has tapped ENTER or (on an empty
 * line) EOF (Ctrl-D on Unix, Ctrl-Z on Windows). Returns either
 * a copy of the entered string (for ENTER) or NULL (on EOF).  The
 * caller owns the returned string and must eventually free() it.
 */
char *linenoise(const char *prompt);

/*
 * Adds a copy of the given line of the command history.
 */
int linenoiseHistoryAdd(const char *line);

/*
 * Sets the maximum length of the command history, in lines.
 * If the history is currently longer, it will be trimmed,
 * retaining only the most recent entries. If len is 0 or less
 * then this function does nothing.
 */
int linenoiseHistorySetMaxLen(int len);

/*
 * Returns the current maximum length of the history, in lines.
 */
int linenoiseHistoryGetMaxLen(void);

/*
 * Saves the current contents of the history to the given file.
 * Returns 0 on success.
 */
int linenoiseHistorySave(const char *filename);

/*
 * Replaces the current history with the contents
 * of the given file.  Returns 0 on success.
 */
int linenoiseHistoryLoad(const char *filename);

/*
 * Frees all history entries, clearing the history.
 */
void linenoiseHistoryFree(void);

/*
 * Returns a pointer to the list of history entries, writing its
 * length to *len if len is not NULL. The memory is owned by linenoise
 * and must not be freed.
 */
char **linenoiseHistory(int *len);

/*
 * Returns the number of display columns in the current terminal.
 */
int linenoiseColumns(void);

#endif /* __LINENOISE_H */

Added bindings/s2/linenoise/utf8.c.




















































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
 * UTF-8 utility functions
 *
 * (c) 2010 Steve Bennett <steveb@workware.net.au>
 *
 * See LICENCE for licence details.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "utf8.h"

#ifdef USE_UTF8
int utf8_fromunicode(char *p, unsigned short uc)
{
    if (uc <= 0x7f) {
        *p = uc;
        return 1;
    }
    else if (uc <= 0x7ff) {
        *p++ = 0xc0 | ((uc & 0x7c0) >> 6);
        *p = 0x80 | (uc & 0x3f);
        return 2;
    }
    else {
        *p++ = 0xe0 | ((uc & 0xf000) >> 12);
        *p++ = 0x80 | ((uc & 0xfc0) >> 6);
        *p = 0x80 | (uc & 0x3f);
        return 3;
    }
}

int utf8_charlen(int c)
{
    if ((c & 0x80) == 0) {
        return 1;
    }
    if ((c & 0xe0) == 0xc0) {
        return 2;
    }
    if ((c & 0xf0) == 0xe0) {
        return 3;
    }
    if ((c & 0xf8) == 0xf0) {
        return 4;
    }
    /* Invalid sequence */
    return -1;
}

int utf8_strlen(const char *str, int bytelen)
{
    int charlen = 0;
    if (bytelen < 0) {
        bytelen = strlen(str);
    }
    while (bytelen) {
        int c;
        int l = utf8_tounicode(str, &c);
        charlen++;
        str += l;
        bytelen -= l;
    }
    return charlen;
}

int utf8_index(const char *str, int index)
{
    const char *s = str;
    while (index--) {
        int c;
        s += utf8_tounicode(s, &c);
    }
    return s - str;
}

int utf8_charequal(const char *s1, const char *s2)
{
    int c1, c2;

    utf8_tounicode(s1, &c1);
    utf8_tounicode(s2, &c2);

    return c1 == c2;
}

int utf8_tounicode(const char *str, int *uc)
{
    unsigned const char *s = (unsigned const char *)str;

    if (s[0] < 0xc0) {
        *uc = s[0];
        return 1;
    }
    if (s[0] < 0xe0) {
        if ((s[1] & 0xc0) == 0x80) {
            *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80);
            return 2;
        }
    }
    else if (s[0] < 0xf0) {
        if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) {
            *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80);
            return 3;
        }
    }

    /* Invalid sequence, so just return the byte */
    *uc = *s;
    return 1;
}

#endif

Added bindings/s2/linenoise/utf8.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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#ifndef UTF8_UTIL_H
#define UTF8_UTIL_H
/**
 * UTF-8 utility functions
 *
 * (c) 2010 Steve Bennett <steveb@workware.net.au>
 *
 * See LICENCE for licence details.
 */

#ifndef USE_UTF8
#include <ctype.h>

/* No utf-8 support. 1 byte = 1 char */
#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B))
#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1)
#define utf8_index(C, I) (I)
#define utf8_charlen(C) 1

#else
/**
 * Converts the given unicode codepoint (0 - 0xffff) to utf-8
 * and stores the result at 'p'.
 * 
 * Returns the number of utf-8 characters (1-3).
 */
int utf8_fromunicode(char *p, unsigned short uc);

/**
 * Returns the length of the utf-8 sequence starting with 'c'.
 * 
 * Returns 1-4, or -1 if this is not a valid start byte.
 *
 * Note that charlen=4 is not supported by the rest of the API.
 */
int utf8_charlen(int c);

/**
 * Returns the number of characters in the utf-8 
 * string of the given byte length.
 *
 * Any bytes which are not part of an valid utf-8
 * sequence are treated as individual characters.
 *
 * The string *must* be null terminated.
 *
 * Does not support unicode code points > \uffff
 */
int utf8_strlen(const char *str, int bytelen);

/**
 * Returns the byte index of the given character in the utf-8 string.
 * 
 * The string *must* be null terminated.
 *
 * This will return the byte length of a utf-8 string
 * if given the char length.
 */
int utf8_index(const char *str, int charindex);

/**
 * Returns the unicode codepoint corresponding to the
 * utf-8 sequence 'str'.
 * 
 * Stores the result in *uc and returns the number of bytes
 * consumed.
 *
 * If 'str' is null terminated, then an invalid utf-8 sequence
 * at the end of the string will be returned as individual bytes.
 *
 * If it is not null terminated, the length *must* be checked first.
 *
 * Does not support unicode code points > \uffff
 */
int utf8_tounicode(const char *str, int *uc);

#endif

#endif

Added bindings/s2/r-tester.sh.





















































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/bin/bash
#HELP>#####################################################################
# Runs require.s2 unit test scripts.
#
# Usages:
#
# 1) $0 [flags]
# searches for *.test.s2 in $(dirname $0)/require.d and runs them.
#
# 2) $0 [flags] moduleName ...moduleNameN
# Expects that each argument is a require.s2 module name and
# expects moduleName.test.s2 to exist in the require.s2 module
# search path.
#
# Runs each test in its own s2sh instance and exits with non-0 if
# any test fails.
#
# Flags:
#
# -V enables VERBOSE mode, which simply outputs some extra output.
#
# -vg enables valgrind/massif tests IF valgrind is found in the path.
# These generate some files which one may peruse to collect allocation
# metrics.
#
# Any flags not handled by this script are passed on to the underlying
# s2sh call (quoting might be a problem - avoid complex flags).
#
#HELP<###################################################################

dir=$(dirname $0)
#[[ '.' = "${dir}" ]] && dir=$PWD
s2sh=$dir/f-s2sh

[[ -x "$s2sh" ]] || s2sh=$(which s2sh 2>/dev/null)
[[ -x "$s2sh" ]] || {
    echo "s2sh not found :(. Looked in [${dir}] and \$PATH." 1>&2
    exit 1
}
echo "s2sh = ${s2sh}"
rdir=${REQUIRES2_HOME}
[[ -d "$rdir" ]] || rdir=$dir/require.d
[[ -d "$rdir" ]] || {
    echo "Missing 'required' dir: $rdir" 1>&2
    exit 2
}
rdir=$(cd $rdir>/dev/null; echo $PWD)
export S2_HOME=$PWD # used by require.s2

requireCall="(s2.require ||| import('${rdir}/require.s2'))"

DO_VG=0
VERBOSE=0
S2SH_ADD_FLAGS="${S2SH_XFLAGS}"
DO_DEBUG=0

list=''
for i in "$@"; do
    case $i in
        -V) VERBOSE=1
            ;;
        -vg) DO_VG=1
            ;;
        -debug)
            DO_DEBUG=1
            ;;
        -\?|--help)
            echo "$0:"
            sed -n '/^#HELP/,/^#HELP/p' < $0 | grep -v -e '^#HELP' \
                | sed -e 's/^#/    /g'
            exit 0;
            ;;
        -*)
            S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS} $i"
            ;;
        *) list="$list $i.test"
        ;;        
    esac
done

[[ x = "x${list}" ]] && {
    cd $rdir || exit $? # oh, come on, steve, this isn't C!
    list=$(find . -name '*.test.s2' | cut -c3- | sed -e 's/\.s2$//g' | sort)
    cd - >/dev/null
}

[[ "x" = "x${list}" ]] && {
    echo "Didn't find any *.test.s2 scripts :(" 1>&2
    exit 3
}
list=$(echo $list) # remove newlines
#echo "Unit test list: $list"

function verbose(){
    [[ x1 = "x${VERBOSE}" ]] && echo $@
}

function vgTest(){
    local test=$1
    shift
    local flags="$@"
    local tmp=tmp.$test
    local vgout=vg.$test
    cat <<EOF > $tmp
${requireCall}(['nocache!${test}']);
EOF
    local cmd="$vg --leak-check=full -v --show-reachable=yes --track-origins=yes $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}"
    echo "Valgrind: $test"
    verbose -e "\t$cmd"
    $cmd &> $vgout || {
        rc=$?
        rm -f $tmp
        echo "Valgrind failed. Output is in ${vgout}"
        exit $rc
    }
    #rm -f $vgout
    echo "Valground: $test [==>$vgout]"
    vgout=massif.$test
    local msp=ms_print.$test
    cmd="$massif --massif-out-file=$msp $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}"
    echo "Massifying: $test"
    verbose -e "\t$cmd"
    $cmd &> $vgout || {
        rc=$?
        rm -f $tmp
        echo "Massif failed. Output is in ${vgout}"
        exit $rc
    }
    echo "Massified $test: try: ms_print ${msp} | less"
}

#if [ -e f-s2sh ]; then
#    # kludge for the libfossil source tree
#    S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS}"
#fi
if [[ x1 = x${DO_DEBUG} ]]; then
    s2sh="gdb --args $s2sh"
fi
S2SHFLAGS="-rv -si ${S2SH_ADD_FLAGS}"
# Reminder: some fsl modules rely on code set up by s2sh init script,
# so we cannot use the --a option in libfossil.
echo S2SHFLAGS=$S2SHFLAGS
for test in $list; do
    echo "Running require.s2 test: ${test}"
    outfile=${rdir}/${test}.test_out
    verbose "Output going to: $outfile"
    rm -f "$outfile"
    tmpfile=${rdir}/${test}.tmp
    echo "${requireCall}(['nocache!${test}']);" > $tmpfile
    cmd="$s2sh ${S2SHFLAGS} -o ${outfile} -f ${tmpfile}"
    echo "Running test [${test}]: $cmd"
    $cmd
    rc=$?
    [[ 0 -eq $rc ]] || {
        echo "Test '${test}' failed. Script output (if any) saved to [${outfile}]" 1>&2
        exit $rc
    }
    #echo "Test did not fail: ${test}"
    rm -f $tmpfile
    if [[ -s "$outfile" ]]; then
        verbose -e "\tOutput is in: ${outfile}"
    else
        rm -f "${outfile}"
    fi
done

if [[ x1 = "x$DO_VG" ]]; then
   vg=$(which valgrind)
    if [[ -x "$vg" ]]; then
        echo "Runing test(s) through valgrind..."
        massif="${vg} --tool=massif --time-unit=ms --heap-admin=0"
        for test in $list; do
            outfile=$rdir/${test}.test_out
            rm -f "$outfile"
            vgTest $test -o "${outfile}"
        done
    fi
fi

echo "Done! Tests run: ${list}"

Added bindings/s2/require-demo.s2.











































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
// Alias our requirejs workalike for convenience...
assert Fossil.require /* gets set via f-s2sh.s2 */;
const R = Fossil.require;
if(!R.fsl){
    /* kludge to avoid requiring a CLI flag to provide a repo */
    const f = R(['fsl/context']).0;
    affirm f === R.fsl;
    affirm R.fsl.db;
    R.fsl.openCheckout();
}/* else a downstream module will open the repo as needed */


print('require() plugins:', R.plugins.propertyKeys());

// Use it like requirejs:
R([// Resources to load: those with a '!' are "plugins", not script files
    'fsl/wikiByName!download',
    'text!require-demo.s2',
    'fsl/blob!rid:1',
    'fsl/manifest!trunk'
  ], // Callback to pass the resources to:
  function(page, thisScript, rid1, trunk){
      print(__FLC, typeinfo(name page), typeinfo(name thisScript), typeinfo(name rid1));
      print("Wiki page [", page.L,"] is", page.W.lengthBytes(),"bytes long.");
      print("This script is",thisScript.lengthBytes(),"bytes long.");
      print("RID 1's manifest is", rid1.length(), "bytes long.");
      print("trunk has", trunk.F.length(), "F-cards.");
  });


R(['fsl/context', // shared/cached Fossil.Context instance
   'fsl/wiki/util' // various Wiki utilities
  ],
  function(fsl, wikiUtil){
      var pages = [];
      wikiUtil.getPageNames().
          eachIndex(proc(name){
              pages[] = fsl.loadManifest( wikiUtil.getLatestRid(name) );
          });
      print(pages.length(),"wiki pages found:");
      const j2h = Fossil.time.julianToHuman;
      0 && pages.sort(function(l,r){
          // It seems we have a bug in/around cwal_array_sort_stateful(), as
          // the sorted result here is very unexpected. But i'm too tired
          // to fight it.
          // print('',j2h(r.D), '\n', j2h(l.D), j2h(r.D).compare(j2h(l.D)));
          //return j2h(r.D).compare(j2h(l.D));
          //print(r.D, l.D, r.D.compare(l.D));
          //return (r.D * 10000000).toInt() - (l.D * 10000000).toInt();
          return r.D.compare(l.D);
      });
      pages.eachIndex(proc(page){
          print( "    %1$-25s @ %2$s by %3$s %4$d bytes".
                 applyFormat(page.L,
                             j2h(page.D),
                             page.U,
                             page.W.lengthBytes()));
      });
      print("");
  });

R(['fsl/timeline/basic', // list of most recent rows from the event table
   'ostream' // output utility for fans of C++
  ],
  proc(tl, os){
      os << "Most recent timeline entries:\n";
      tl.eachIndex(proc(v){
          os << v.type << ' ' << v.uuid.substr(0,10)
              << ' @ ' << Fossil.time.julianToHuman(v.mtime)
              << ' by '
              << (v.euser ||| v.user)
              << '\n\t'
              << (v.ecomment ||| v.comment)
              << '\n';
      });
      os << '\n';
  });

// An auto-loaded (not pre-registered) plugin:
R(['demo!foo',
   'demo!bar?a=1&b&c=hi there'
  ],
  proc(demo, d2){
      print("Demo auto-loaded plugin:",demo);
      assert d2 === demo /* because of how this particular plugin works */;
      assert 2 === demo.counter;
      assert 'bar' === demo.lastArgs.0;
      assert 'hi there' === demo.lastArgs.1.c;
  });

if(Fossil.file.isFile('cgimod.so')){
    R(['dll!cgimod', // the CGI API (loadable module)
       'dll!cgimod', // same instance (cached)
       'dll!cgimod?checkingCache' // params bypass caching
       /* reminder: 2 instances of this module is semantically
          invalid in a real CGI context. We're just testing that
          R()'s docs regarding caching matches its behaviour. */
      ],
      proc(cgi, cgi2, cgi3){
          assert cgi === cgi2;
          assert cgi !== cgi3;
             print("Loaded cgi module: "+cgi);
      });
}else{
    print("cgimod DLL not found.");
}

Added bindings/s2/require.d/BufferFactory.s2.



















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   A require.s2 module which returns an object containing "factory
   methods" for creating Buffers.
*/
return {
    /**
       Usage:

       var buffer = thisObj.new([reservedMemorySize=0]);
    */
    new: s2.Buffer.new,
    /**
       Usage:

       var buffer = thisObj.readFile(filename);
    */
    readFile: s2.Buffer.readFile
}

Added bindings/s2/require.d/BufferFactory.test.s2.















1
2
3
4
5
6
7
8
9
10
11
12
13
14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   A require.s2 module which returns an object containing "factory
   methods" for creating Buffers.
*/
requireS2(
['BufferFactory'],
proc(bfac){
    var b = bfac.new(100);
    affirm b.capacity()>=100;
    
    b = bfac.readFile(__FILE);
    affirm 'buffer' === typename b;
    affirm b.length()>50;
});

Added bindings/s2/require.d/DataModels/TestModel.s2.












































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   Test/demo require.s2 DataModel module the modeler module
   and/or the DataModel plugin.
*/
return {
    //__typename: 'Testing that this will not get overwritten',
    /**
       Gets called by the modeler framework when this module's
       (inherited) new() method is called. In the context of this
       function, "this" will be the new Model instance, which inherits
       this object.
       
       The return value is ignored by the modeler framework.
    */
    initialize: proc(a,b,c){
        print(__FLC, 'initialize()ing', typename this, this);
        print(__FLC, 'argv =', argv);
        print(__FLC, 'super =',super);
        assert __FILE.indexOf(typename this)>=0
        /* __typename gets automatically set from the file name
           when the model is loaded if it does not set one itself. */;
        this.attr('a',a)
            .attr('b',b)
            .attr('c',c);
    },
    /**
       Attributes defined in the prototype are visible via the
       inherited attr() method, but do not get serialized via the
       inherited toJSON() method unless their values have been
       explicitly set in the most-derived model instance.
    */
    attributes:{
        foo: -1
    }
};
/*.withThis(proc(){
    return ('undefined' === typename requireS2)
        ? this
        : ((const that=this),
           requireS2(['modeler'],proc(M){
               return M.extendModel(that)
           }));
});*/

Added bindings/s2/require.d/Ticker.s2.




























































































































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   A require.s2 module implementing an abstract timer class. See the
   inlined docs (down below) for details.

   Example usage:


   const Ticker = require(['Ticker']).0;
   const t = new Ticker();
   t.addEvent(1, true, proc(){print("repeating event")});
   t.addEvent(3, proc(){print("one-time event")});

   t.tick();
   t.tick();
   t.tick();
   // or: t.tick(3)

   Will output the following, though the order of events at firing at
   the same logical time (the last two lines here) is not defined and
   may change during the lifetime of repeating events or differ
   depending on whether the clock is incremented one tick at a time or
   more than one:

   repeating event
   repeating event
   one-time event
   repeating event
*/
/**
   Ticker is a utility class implementing an abstract
   timer. It doesn't know anything about time - it keeps
   track of time in abstract ticks. Events can be added
   to it which are fired after a certain number of ticks,
   optionally repeating every N ticks.
    
   To create a new instance, call this function. Its return
   value inherits this function.

   It's functionally similar to JavaScript's setTimeout()
   and setInterval() except for:

   a) It is strictly synchronous.

   b) It has no clock. The client has to tell it that X amount of
   (abstract) time has passed.

   c) It currently has no way to properly remove events (and doing so
   from an event handler may mess up its iteration for the time being).
*/


return {
    __typename: 'Ticker',
    /** Internal list of events */
    //tlist: [],

    /** Current timestamp (tick counter) */
    //ts: 0,

    /** Internal helper to sort event entries by their timestamp.
        Empty/null entries are sorted as less than any others
        because this make them easy to remove.
    */
    sortEvents:function f(){
        f.sorter || (f.sorter=function(l,r){
            // sort nulls to the LEFT (easier (but more memmov'ing) to lop them off that way)
            l || return r ? -1 : 0;
            r || return 1;
            return compare(l.when, r.when) |||
                compare(l.priority, r.priority) |||
                compare(l.ts,r.ts) |||
                compare(l.id,r.id);
        });
        this.tlist.sort(f.sorter);
    }.importSymbols({compare: function(l,r){
        return (l<r) ? -1 : ((l>r) ? 1 : 0);
	}}),

    /**
       Returns an incremental integer value on each call.
    */
    nextEventId: function(){
        this.idCounter || (this.idCounter = 0);
        return ++this.idCounter;
    },
    
    /**
       Adds a timer event to (potentially) be triggered
       via advancement of the timer (see tick()).

       'when' is the relative tick number (in the future) in which
       to fire the event. Must be a positive value.

       'repeats' is a boolean. Repeating events trigger every 'when'
       ticks.

       'what' is a Function which gets called when the event timer
       is triggered. When called, what's 'this' will be the
       event Object containing 'what'.

       If called with two arguments then it is treated as if
       (when, false, what) are passed in.

       If called with one argument then it is equivalent to calling
       addEvent2().
    
       Returns an Object describing the event. Clients may add
       their own properties to it and use those from the
       what() handler (accessible via this.PROPERTY).

       Callers can control the ordering of events fired at
       the same time by setting a priority numeric property
       on the returned event object. Priority sorts using
       normal numeric comparison, so lower values sort first.
       The default priority is 0 and negative values are legal.

       This class supports the following event object properties
       (and ignores all others):

       id: has no meaning but is used as a fallback option when sorting.
       Clients may set this to what they wish, and a default value
       (incremental integers) is set.

       ts: the current "timestamp", in ticks, of this ticker. It is
       incremented by calling tick().

       when: the tick time at which the event will next fire.

       what: the function to call when the event fires.

       interval: an integer specifying that the event should repeat
       every this-many ticks. If not set, or set to a falsy value,
       the event is a one-time event and will be removed after firing
       by the tick() process.

       priority: an integer value which determines run order for
       events firing at the same tick (lower values sort first). For
       "overlapping" ticks (with a time span of more than one tick)
       this order might be somewhat intuitive: all "overlap" runs of
       a given event handler are processed before the next
       event. i.e. the priority order is maintained, but each event
       may be run multiple times in succession before another event
       with a higher (or the same) priority value.

       client: this property name is reserved solely for use by
       client code. This API promises never to use that property
       key in event objects.
    */
    addEvent:function(when, repeats, what ){
        const argc = argv.length();
        (1===argc) && return this.addEvent2(when);
        if(2===argc && typeinfo(isfunction repeats)){
            what = repeats;
            repeats = false;
        }
        affirm when>0;
        affirm typeinfo(isfunction what);
        this.idCounter || (this.idCounter = 0);
        const ev = {
            id: this.nextEventId(),
            ts: this.ts,
            when: this.ts + when,
            what: what,
            priority: 0
        };
        repeats && (ev.interval = when);
        this.tlist.push(ev);
        return ev;
    },

    /**
       An alternate form of addEvent() which takes an object.

       Returns the object passed to it, after enriching it
       with event state and adding it to the list.

       If the object has a an 'interval' property but no 'when'
       property then its 'when' is set to the interval value.
       Likewise, if 'when' is set and 'repeats' is set to a truthy
       value, 'interval' gets set to 'when'.

       Example:

       ticker.addEvent({interval: 3, what:myFunc});

       will set up a repeating event, firing first in 3 ticks,
       and then ever 3 ticks after that.
    */
    addEvent2:function(obj){
        affirm typeinfo(iscontainer obj);
        affirm typeinfo(isfunction obj.what);
        if(obj.repeats && !obj.interval){
            obj.interval = obj.when;
        }
        unset obj.repeats;
        if(obj.interval && !obj.when){
            obj.when = obj.interval;
        }
        affirm obj.when > 0;
        obj.ts = this.ts;
        obj.priority || (obj.priority = 0);
        obj.id || (obj.id = this.nextEventId());
        this.tlist[] = obj;
        return obj;
    },

    /**
       Removes all event handlers and resets the tick counter to 0.
    */
    reset: function(){
        this.ts = 0;
        this.tlist.clear();
        return this;
    },

    /**
       Increments the tick count by incr (default=1) and triggers any
       events whose time comes. If incr is greater than one and a
       repeating event would normally have been triggered multiple
       times within that span, this function calls it the number of
       times it would have been called had tick() been called in
       increments of 1. The order of event callbacks is unspecified
       by default - they continually get re-sorted based on their
       trigger time. Clients can control the order of like-timed
       events by setting a priority level on an event - see
       addEvent() for details.

       Events added to this object by an event handler will NOT
       be called in this invocation of tick() - they are queued up
       and added to the event list after tick() has finished processing
       any pending handlers.

       Returns this object.
    */
    tick: function(incr=1){
        affirm typeinfo(isinteger incr);
        affirm incr >= 0; // a tick value of 0 might be interesting for something
        const li = this.tlist;
        var n = li.length();
        affirm typeinfo(isarray li);
        this.ts += incr;
        n || return this;

        /* Remove any expired-and-fired events here (as opposed to afterwards)
           because (it turns out) this simplifies things. */
        this.sortEvents(); // moves nulls to the left
        while(n && !li.0){
            /* Remove any expired events */
            li.shift();
            --n;
        }
        n || return this;

        // Move this.tlist so that adding events while looping does not affect us.        
        const listSentry = this.tlist;
        this.tlist = [];

        /*
          Loop over events until we find one with a 'when'
          set in the future. All current events will be to the
          left of that one.
        */
        const tm = this.ts;
        for(var i = 0, e, shiftCount = 0, repeater = 0;
            i<n; ++i){

            e = repeater ||| li[i];
            repeater = 0;
            if(e.when>tm){ break } /* first in-the-future event. */
            else {
                affirm e.when > e.ts;
                if(var err=catch{e.what()}){
                    print(__FLC,"WARNING: tick handler threw. Removing it.",e,err);
                    unset e.interval;
                }
                if(e.interval){ /* Set this one up to run again */
                    e.ts = e.ts + e.interval;
                    e.when = e.ts + e.interval;
                    if(e.when <= tm){
                        /* timespan skipped one or more intervals. Run them now. */
                        //print(__FLC,"firing again for overlap:",e);
                        --i /* fudge the loop counter to repeat this entry.
                               May break this.tlist is modified by
                               callback. */;
                        repeater = e;
                        continue;
                    }
                }else{ /* remove the event */
                    li[i] = undefined;
                    ++shiftCount;
                }
            }
        }
        /* Move this.tlist back... */
        if(this.tlist.isEmpty()){ /* no changes made during iteration */
            this.tlist = listSentry;
        }else{
            /* Events added while looping. Integrate them now.
               We swap tlist here so that we can keep the original
               array (as a minor potential allocation optimization). */
            this.tlist.eachIndex(integrateTlist);
            this.tlist = listSentry;
        }
        return this;
    }/*tick()*/.importSymbols({
        integrateTlist: proc(i,v){ listSentry.push(v) }
    }),
    /**
       Constructor function for new instances.
    */
    __new: proc(){
        this.tlist = [];
        this.ts = 0;
    }
};

Added bindings/s2/require.d/Ticker.test.s2.

































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
requireS2(['time','nocache!Ticker'], proc(time, Ticker){
    affirm 'Ticker' === typeinfo(name Ticker);
    const t = new Ticker();
    affirm t inherits Ticker;
    affirm 'Ticker' === typeinfo(name t);
    const callback = proc(){print('fired:',this.name)};
    var ev;
    affirm typeinfo(isfunction callback);
    // Repeating event, every 3rd tick:
    ev = t.addEvent(3, true, callback);

    ev.name = "threeer";
    ev.priority = 3;
    /*
      Event priority is used to break ties for events firing at
      the same virtual time. It sorts by value, so a lower value
      has a higher priority. Events have a default priority of 0.
    */

    // Add a repeating event which fires every tick:
    ev = t.addEvent(1, true, callback);
    ev.name = "oner";
    ev.priority = -1;


    // Add a one-time event, fired once, four ticks from now:
    affirm typeinfo(isfunction callback);
    ev = t.addEvent(4, callback);
    ev.name = "fourer";
    ev.priority = 4;

    // Add a repeating event, firing every 2nd tick:
    ev = t.addEvent({interval: 2, what: callback, name: "twoer"});

    print(__FLC,typeinfo(name t),':',t);

    // Now advance our virtual clock a few times...
    var i = 1, max = 10, sleepTimeMs = 100;

    print("Ticking",max,"times, sleeping",sleepTimeMs," millis between ticks...");
    //const timeFmt = {%Y-%m-%d %H:%M:%S}
    //const startTime = strftime(timeFmt, time())
    for( ;i<=max; i=i+1){
        print("TICK #"+i);
        time.mssleep(sleepTimeMs);
        t.tick();
    }
    //const endTime = strftime(timeFmt, time());
    print('After loop: Tick =',t.ts);
    //print('Start time:',startTime,'\nEnd time  :',endTime);
    if(0) {
        print('"Manually" ticking...');
        t.tick(6); // note how priority sorting kinda breaks down here
        // potential workaround: run ticks>1 in a loop of smaller ticks,
        // but how small? Maybe set tickerInstance.timeUnit=integer to
        // set the smallest time unit, and loop in increments of that unit?
    }
    //t.tick(5);
    //print(__FLC,"Done:",t);

    //dumpInternedStrings();
    t.reset();
    affirm t.tlist.isEmpty();
});

Added bindings/s2/require.d/cliargs.s2.






































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   A require.s2 module which returns a utility object for working with
   the CLI arguments provided via s2.ARGV.

   "Script arguments" are those passes to s2sh after any '--' flag.
   Those get imported into s2.ARGV (which is not set if no script
   flags are provided).

   s2.ARGV, if set, is an Array containing all flags passed after
   '--', plus possibly containing these two properties:

   .flags: an object of --flag=value pairs, where --flags with no
   value are treated as boolean true.

   .nonFlags: any script argument which does not start with '-' is
   assumed to be a filename or some other string, and is appended
   to this array.

   Either property will be undefined if no flags resp. non-flags
   are provided.
*/
return {

    /**
       Holds an Array of all arguments passed after '--' to s2sh
       resp. any app which installs that particular binding. It is
       undefined (not an empty array) if there are no such flags.
    */
    args: s2.ARGV,
    /**
       An object (or not) containing any -flags. The keys are stripped
       of any number of leading dashes and if a given flag is
       duplicated, the last one currently wins (as opposed to getting
       an array of values, though that might be a useful addition).
    */
    flags: s2.ARGV ? s2.ARGV.flags : undefined,

    /**
       An array (or not) of any non-flags passed after --,
       in the order they were passed in.
    */
    nonFlags: s2.ARGV ? s2.ARGV.nonFlags : undefined,

    /**
       If the given flag (minus any number of prefixing "-") was passed in
       the "script flags" (any flags passed after '--' to the s2sh
       interpreter), its value is returned, otherwise dflt is returned.
    */
    getFlag: proc(flag, dflt){
        return this.flags
            ? (this.flags[flag] ?: dflt)
            : dflt;
    },

    /**
       If the given flag (minus any number of prefixing "-") was
       passed in the "script flags" (any flags passed after '--' to
       the s2sh interpreter), its is removed from the flags and its
       value is returned, otherwise dflt is returned.

       When the last flag is removed, this.flags is unset.
    */
    takeFlag: proc(flag, dflt){
        this.flags || return dflt;
        if(undefined !== const v = this.flags[flag]){
            unset this.flags[flag];
            this.flags.# || unset this.flags;
            return rc;
        }else return dflt;
    },

    /**
       Returns true if the CLI flags (still) contain any flags,
       otherwise false.
    */
    hasFlags: proc(){
        return this.flags ? this.flags.#>0 : false;
    },

    /**
       Returns true if the CLI flags (still) contain any non-flags,
       otherwise false.
    */
    hasNonFlags: proc(){
        return this.nonFlags ? !this.nonFlags.isEmpty() : false;
    },

    /**
       Removes the first non-flag from the list and returns
       it. Returns undefined if there are no non-flags (or none
       remaining).

       When the last entry is removed, this.nonFlags is unset.
    */
    nextNonFlag: proc(){
        this.nonFlags || return undefined;
        var rc = this.nonFlags.shift();
        (0===this.nonFlags.length()) && unset this.nonFlags;
        return rc;
    }
};

Added bindings/s2/require.d/fsl/context.s2.








1
2
3
4
5
6
7
+
+
+
+
+
+
+
/**
   require() plugin which simply resolves to the shared Fossil.Context
   instance set up by the f-s2sh bootstrap code. This instance should
   be used by all other modules so that everybody is using the same
   database handles.
*/
return requireS2.fsl ||| (requireS2.fsl = new Fossil.Context());

Added bindings/s2/require.d/fsl/db/checkout.s2.















1
2
3
4
5
6
7
8
9
10
11
12
13
14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no checkout has been
   opened when this is called, openCheckout() is called on the
   context, otherwise there are no side effects.
*/
return requireS2(['fsl/context'], proc(F){
    affirm const d = F.db;
    if(!d.checkout){
        F.openCheckout();
    }
    affirm d.repo;
    return d.repo;
});

Added bindings/s2/require.d/fsl/db/config.s2.












1
2
3
4
5
6
7
8
9
10
11
+
+
+
+
+
+
+
+
+
+
+
/* require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no repo has been opened
   when this is called, it opens the repo specified via the
   --repo-db|-R=FILENAME CLI script flag.
*/
return requireS2(['fsl/context'], proc(F,args){
    affirm const d = F.db;
    d.config || F.openConfig();
    affirm d.config;
    return d.config;
});

Added bindings/s2/require.d/fsl/db/main.s2.






1
2
3
4
5
+
+
+
+
+
/**
   require.s2 module which returns the "main" db handle of
   the shared Fossil.Context instance.
*/
return requireS2(['fsl/context']).0.db;

Added bindings/s2/require.d/fsl/db/repo.s2.



















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no repo has been opened
   when this is called, it opens the repo specified via the
   --repo-db|-R=FILENAME CLI script flag.
*/
return requireS2(['fsl/context','cliargs'], proc(F,args){
    affirm const d = F.db;
    if(!d.repo){
        const f = args.takeFlag('repo-db',args.takeFlag('R'))
            ||| throw "No repo db specified. "+
            "Pass the --repo-db|-R=filename "+
            "SCRIPT flag (after --) to the interpreter or open a checkout.";
        ('string' === typename f) || throw "Invalid argument type for the --repo-db|-R=DBFILE flag";
        F.openRepo(f);
    }
    affirm d.repo;
    return d.repo;
});

Added bindings/s2/require.d/fsl/db/repoOrCheckout.s2.

















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   require.s2 module which returns either the repo db (specified by
   the -R|--repo-db=DBFILE script flags) or (if that fails) the repo
   db associated with the current checkout. Throws if it can neither
   open a checkout nor figure out the db from the CLI args.
*/
return requireS2(['fsl/context'], proc(F){
    affirm F.db;
    F.db.repo && return F.db.repo;
    F.db.checkout && return F.db.checkout;
    catch return requireS2(['fsl/db/repo']).0; // fails if no -R|--repo-db=DBFILE flag specified
    requireS2(['fsl/db/checkout']) /* opens the checkout */;
    assert F.db.checkout;
    return F.db.repo;
});

Added bindings/s2/require.d/fsl/extendFossil.s2.


























































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
  A require.s2 module which adds some functionality
  to the various Fossil classes.

  This module is not intended to be used more than once and should not
  be cached. Thus, to use it:

  Fossil.require(['nocache!fsl/extendFossil'],proc(){})
*/


assert Fossil;
assert Fossil.Context;
Fossil.artifactTypes && return Fossil /* already ran this module */;


//Fossil.loadModule = s2.loadModule2;
//Fossil.importScript = s2.import2;

/**
  Counterpart of the C-level fsl_catype_e enum.
*/
Fossil.artifactTypes = {
    ANY: 0,
    CHECKIN: 1,
    CLUSTER: 2,
    CONTROL: 3,
    WIKI: 4,
    TICKET: 5,
    ATTACHMENT: 6,
    EVENT: 7
};

/**
    Fetches the first row from the given SQL statement (String or
    Buffer) and returns it as an Object (if asArray is false) or an
    Array (if asArray is true).

    If the bind argument is passed in then it is passed on to the
    Stmt.bind() method of the underlying statement. Use an array to
    bind multiple values. To specify the third parameter when there is
    nothing to bind, pass the undefined value as the second argument.
    i.e. bind===undefined is treated as "nothing to bind," instead of
    binding undefined/null.

    Throws on error. Returns undefined if no row is found.
*/
Fossil.Db.selectRow = proc(sql, bind = undefined, asArray = false){
    affirm this inherits Fossil.Db;
    const st = this.prepare(sql);
    var rc;
    const ex = catch {
        st.bind(bind);
        rc = asArray ? st.stepArray() : st.stepObject();
    };
    st.finalize();
    ex ? throw ex : return rc;
};

/**
   Returns an array containing the complete results of each row in the
   given SQL's result set. If asArray is true, each row is returned as
   an array of column values in the same order as the result set's,
   else each is returned an an Object of column name/value pairs with
   columns in an an unspecified order.

   The bind parameter may hold value(s) to bind to the given SQL.
*/
Fossil.Db.selectAll = proc(sql, bind = undefined, asArray = true){
    affirm this inherits Fossil.Db;
    const rc = [];
    this.each({
        sql: sql,
        bind: bind,
        mode: asArray ? 1 : 0,
        callback: 'rc[] = this',
    });
    return rc;
};

/**
    Given a SELECT-style query and optional bind parameters
    (either a single value for a single param or an array
    of multiple params), this routine simply dumps out
    the results of the query. bind===undefined is treated
    as "nothing to bind," instead of binding undefined/null.
*/
Fossil.Db.dumpQuery = proc(sql,bind = undefined, separator='\t'){
    affirm this inherits Fossil.Db;
    return this.each({
        sql:sql,
        bind:bind, // note that undefined value is treated as non-existent here
        callback:proc(){
            (1===rowNumber) && print(columnNames.join(separator));
            print(this.join(separator));
        }
    });
};

/**
    Stmt.each() loops over this.step(), calling func(N) on each
    iteration, where N is the current row number (1-based).  If func()
    returns a literal false, looping stops without an error. In the
    context of the call, 'this' is the Stmt object.

    Returns this object.
*/
Fossil.Db.Stmt.each = proc(func){
    affirm this inherits Fossil.Db.Stmt;
    affirm typeinfo(iscallable func) && typeinfo(iscallable func.call);
    for(var rowNum = 1; this.step(); ++rowNum){
        (false === func.call(this, rowNum)) && break;
        //^^^^^^^^ emulate Db.each()
    }
    return this;
};


/**
    Changes to the given dir and pushes the (old) current dir
    to the directory stack. To change back to the pre-pushd()
    directory, call Fossil.file.popd(). This function relies
    on this 'this' object beeing Fossil.file. Throws if it
    cannot change directories.

    The array of pushed directory names is available after calling
    this function one time via the property
    Fossil.file.pushd.dirStack. The most recent directory is at the
    end of that array. If that property is undefined, pushd() has
    never been called (or someone removed the property). If it is an
    empty array, there are currently no directories in the stack.
*/
Fossil.file.pushd = proc callee(dir){
    affirm 'string' === typename dir;
    const curdir = this.currentDir();
    this.chdir(dir);
    (callee.dirStack ||| (callee.dirStack = [])).push( curdir );
};

/**
    Pops the directory mostly recently pushed by Fossil.file.pushd()
    off of the directory stack and changes to that directory. Throws
    if called when no directories can be popped.
*/
Fossil.file.popd = proc(){
    const list = this.pushd.dirStack;
    const len = (list ? list.length() : 0)
        ||| throw "Directory stack is empty.";
    this.chdir(list[len-1]);
    return list.pop()
};

return Fossil;

Added bindings/s2/require.d/fsl/reports/common.s2.




































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   INCOMPLETE helper module for pending timeline/reporting bits.
*/
return requireS2(['fsl/db/repo'],
proc(db){
    const mod = {
        db: db,

        /*eventTypeMap:{
            '*': 'all',
            ci: 'checkins',
            w: 'wiki',
            g: 'tags',
            e: 'events',
            t: 'tickets'
        },*/

        /**
           Returns an array of years (integers) representing
           the years for which there is timeline activity in
           this repo. The entries are sorted ascending.
        */
        getActiveYears:proc(){
            return this.db.selectValues(<<<EOSQL
            SELECT DISTINCT CAST(strftime('%Y',mtime) AS INT) AS t
            FROM /*v_reports*/ event GROUP BY t ORDER BY t EOSQL);
        },

        /**
           Given a year in string or integer form, this function
           returns an array of integers holding the calendar week
           numbers (as determined by sqlite) of all "active" weeks
           during the give year. Returns an empty array if there is
           no timeline activity for that year.
         */
        getActiveWeeksForYear: proc(year){
            year || throw "Expecting a year (string|integer) value.";
            return this.db.selectValues(<<<EOSQL
                SELECT DISTINCT CAST(strftime('%W',mtime) AS INT) AS t
                FROM /*v_reports*/ event
                WHERE strftime('%Y',mtime)=?
                GROUP BY t ORDER BY t
                EOSQL,
                ''+year/*has to be a string for comparison to work*/);
        },

        /**
           Returns an array of objects, each with the properties
           (year, weeks), with year being the year (integer) for that
           entry and weeks being an array of the calendar weeks
           (integers, as determined by sqlite) which had activity
           during that year.
         */
        getActiveYearsAndWeeks: proc(){
            const rc = [], that = this;
            this.getActiveYears().eachIndex(proc(v){
                rc[] = {
                    year: v,
                    weeks: that.getActiveWeeksForYear(v)
                };
            });
            return rc;
        }
    };

    return mod;
});

Added bindings/s2/require.d/fsl/reports/common.test.s2.




































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
requireS2([
    'fsl/db/repoOrCheckout',
    'fsl/util/repo',
    'fsl/reports/common'],
proc(ignored, rutil, rcom){
    affirm rcom.db;

    var x = rcom.getActiveYears();
    affirm 'array' === typename x;
    affirm x.length() > 0;

    const isLibfossil = 'libfossil' === rutil.getConfig('project-name');

    if(isLibfossil){
        affirm x.indexOf(2014) >= 0;
        affirm x.indexOf(1972) < 0;
    }

    x = rcom.getActiveWeeksForYear(2014);
    affirm 'array' === typename x;
    affirm x.length() > 0;

    if(isLibfossil){
        affirm 1 === x.0;
        affirm x.indexOf(22) < 0 /* no activity that week */;
        affirm x.indexOf(41) > 0 /* when this test was added */;
    }

    x = rcom.getActiveYearsAndWeeks();
    affirm 'array' === typename x;
    affirm x.length() > 0;
    affirm 'integer' === typename x.0.year;
    affirm 'array' === typename x.0.weeks;

});

Added bindings/s2/require.d/fsl/timeline/basic.s2.





















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   require() module which returns an array of Objects from the event
   table. Each includes all fields from the event table plus the
   associated blob's uuid.
*/
return requireS2(
    ['fsl/db/repo'],
    proc(repo){
        var rc = [];
        repo.each({
            mode: 0,
            sql:<<<_SQL
            SELECT e.*, b.uuid uuid
            FROM event e JOIN blob b ON e.objid=b.rid
            ORDER BY e.mtime DESC LIMIT 5
            _SQL,
            callback:'rc[] = this'
        });
        return rc;
});

Added bindings/s2/require.d/fsl/util/repo.s2.


























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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   In-progress set of utilities for working with repo db tables.
*/
requireS2(['fsl/db/repoOrCheckout'], proc(repo){
    return {
        db: repo,
        /**
           Fetches an array of strings from this.db, each one being
           the name of a user in the current repository. They are
           sorted by name.
        */
        getUserList: proc(){
            return this.db.selectValues('select login from user order by login');
        },

        /**
           Returns the value of the repoDb.config field with the given key,
           or undefined if no such row is found.
        */
        getConfig: proc(key){
            affirm 'string' === typename key /* expecting a legal repo.config.key value */;
            return this.db.selectValue('SELECT value FROM config WHERE name=?',key);
        }
    };
});

Added bindings/s2/require.d/fsl/util/repo.test.s2.




















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   In-progress set of utilities for working with repo db tables.
*/
requireS2(['fsl/db/checkout'/*force repo db open*/,'fsl/util/repo'],
proc(ignored, repo){
    const db = repo.db;
    affirm db;
    affirm repo.getUserList;
    var x = repo.getUserList();
    affirm 'array' === typename x;
    affirm x.length() > 0;
    affirm x.indexOf('anonymous') >= 0;

    x = repo.getConfig('project-name');
    affirm 'string' === typename x;
    x = repo.getConfig('no-such-key');
    affirm undefined === x;
    affirm catch{repo.getConfig()}.message.indexOf("'string'") >0;
});

Added bindings/s2/require.d/fsl/wiki/pageNames.s2.





1
2
3
4
+
+
+
+
return requireS2(
    ['fsl/wiki/util'],
    proc(util){ return util.getPageNames(); }
);

Added bindings/s2/require.d/fsl/wiki/util.s2.








































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   A set of wiki-related utilities.
*/
return requireS2(
    ['fsl/context', 'fsl/db/repo'],
    proc(fsl, repo){
        return {
            getPageNames: proc( caseSensitive ){
                const st = repo.prepare(<<<_SQL
                                        select distinct(substr(tagname,6)) name from tag t,
                                        tagxref x where x.tagid=t.tagid and t.tagname
                                        like 'wiki-%' order by name _SQL
                                        + (caseSensitive
                                           ? '' : ' collate nocase'));
                var rc = [];
                const ex = catch {
                    while(st.step()) rc[] = st.get(0);
                };
                st.finalize();
                ex ? throw ex : return rc;
            }.importSymbols(nameof repo),
            
            getLatestRid: proc(pageName){
                affirm pageName && 'string' === typename pageName;
                return repo.selectValue(<<<_SQL
                                        SELECT x.rid FROM tag t, tagxref x 
                                        WHERE x.tagid=t.tagid
                                        AND t.tagname=?
                                        ORDER BY mtime DESC LIMIT 1
                                        _SQL,
                                        'wiki-'+pageName
                                       );
            }.importSymbols(nameof repo),
            
            loadPageArtifact: proc(pageName){
                return fsl.loadManifest( this.getLatestRid(pageName) );
            }.importSymbols(nameof fsl)
        }
    });

Added bindings/s2/require.d/io.s2.






1
2
3
4
5
+
+
+
+
+
/* a require.s2 module which "hides" s2.io via the module
   interface. It could optionally load 'dll!mod_io', but that's
   an internal impl detail.
*/
return s2.io;

Added bindings/s2/require.d/json.s2.






1
2
3
4
5
+
+
+
+
+
/* a require.s2 module which "hides" s2.json via the module
   interface. It could optionally load 'dll!mod_json', but that's
   an internal impl detail.
*/
return s2.json;

Added bindings/s2/require.d/json2.s2.













































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
affirm s2.json /* we need this as our basis */;
affirm s2.json.stringify /* was added later, might not be in all (two?) trees yet (ha!) */;
/**
   This object basically acts as a (mostly) drop-in replacement for
   s2.json, and it uses s2.json to implement most of its
   functionality. It uses a custom, script-side stringify() which is
   orders of magnitude less efficient (on several levels) than
   s2.json.stringify() (which is implemented in C), but allows
   overriding of to-JSON behaviour on a per-container or per-prototype
   basis.
*/
return {
    /** See s2.json.parse(). */
    parse: s2.json.parse,
    /** See s2.json.parseFile(). */
    parseFile: s2.json.parseFile,
    /**
       Converts the value v into a JSON string (or throws while trying).

       indention may be either a falsy value (for no intenation), a
       string (which gets prepended N times for N levels of
       indentation), or an integer: a positive value indents that many
       spaces and a negative value indents that many tabs.

       v need not be a root-level value (Object or Array), but may be
       a string, number, or boolean.

       Returns a string on success, throws on error.

       Notes about special cases:

       - If v or a prototype of v contains a function property named
       toJSON() then v.toJSON() is used in place of v for
       to-JSON-string conversion. The function must return some
       JSON-able form of v. e.g. an implementation for a Hashtable
       might return an Object in the form {keys:[...], values:[...]}.

       - Object _keys_ which are _not_ of type (string, integer, double)
       are elided from the output. Keys of numeric types are converted
       to strings for JSON key purposes.

       - Objects elide any keys which have value counterpart of
       undefined. JSON does not know 'undefined'. We "could" translate
       it to null here, but we instead opt to elide it.

       - The undefined value: if passed to this function, the string
       'null' is returned. undefined is also translated to 'null' in
       the context of array empty entries.
    */
    stringify: proc stringify(v, indention = stringify.config.indention){
        affirm ++stringify.level > 0;
        const ex = catch{
            typeinfo(isderefable v) && typeinfo(iscallable v.toJSON) && (v = v.toJSON());
            const f = tmap # typename v;
            affirm f /* Argument must be a known JSON-able type or have a toJSON() method. */;
            if('string'===typename f){
                affirm --stringify.level>=0;
                return f;
            }else if(!f.buffered){
                /* "Simple" conversions which do not recurse */
                const rc = f(v);
                affirm --stringify.level>=0;
                return rc;
            }else if(stringify.level>stringify.config.maxOutputDepth){
                throw exception('CWAL_RC_RANGE',
                                "Output depth limit ("+stringify.config.maxOutputDepth+
                                ") exceeded while generating JSON.");
            }else{
                affirm f.buffered /* f.buffered is set, so... */;
                const jbuf = s2.Buffer.new() /* gets appended to by f() */;
                f(v) /* appends all output to jbuf */;
                affirm --stringify.level>=0;
                affirm !jbuf.isEmpty();
                return jbuf.takeString();
            }
        };
        affirm --stringify.level>=0;
        assert ex /* or we couldn't have gotten this far */;
        throw ex;
    }.withThis(proc(){
        /**
           Public configuration for stringify(). Change these
           options to modify the defaults.
        */
        this.config = {
            /* Default indention used by stringify(). */
            indention: undefined,
            /* Separator for entries in arrays and object lists. */
            commaSeparator: ', ',
            /* Separator for keys and value in objects. */
            keyValSeparator: ': ',
            /* Max object/array depth to allow before erroring
               out. Remember that cycles will generally be detected
               before this happens, so this doesn't necessarily
               indicate that any cycles were encountered.
            */
            maxOutputDepth: 15
        };
        this.level = 0;
        return this;
    }).importSymbols({
        // some crazy scoping and var accesses going on here...
        /**
           Indents the output, if appropriate, based on the current
           call level (or the level specified by the 2nd
           parameter). If addNL is true, a newline is appended before
           the indentation. This is a no-op if stringify() is called
           with a falsy indention parameter.
         */
        indent: proc callee(addNL=true, level = stringify.level){
            indention || return;
            callee.idbuf || (callee.idbuf = s2.Buffer.new(64));
            if(callee.prevLevel !== level){
                callee.prevLevel = level;
                if('integer'===typename indention){
                    const len = (indention<0) ? -indention : indention;
                    affirm len >= 0;
                    callee.idbuf.length( len * level )
                        .fill((indention<0) ? 0x09 : 0x20);
                }else if('string' === typename indention){
                    callee.idbuf.reset();
                    for(var i = 0; i < level; ++i){
                        callee.idbuf << indention;
                    }
                }
            }
            addNL && (jbuf << '\n');
            jbuf << callee.idbuf;
        },
        /**
           A hashtable mapping typenames to either strings (for static
           conversions) or a function taking a value parameter. Those
           functions normally return a string, but if the function has
           a 'buffered' property which is truthy then its return
           result is ignored and instead a Buffer value named jbuf is
           made available to them, and they are expected to append all
           output there.
         */
        tmap: scope {
            const proxy4Obj = proc(v){
                v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected.");
                jbuf << '{';
                proxyEachProp.first = true;
                //proxyEachProp('LEVEL', stringify.level);
                const ex = catch v.eachProperty(proxyEachProp);
                indention && indent(true,stringify.level-1);
                jbuf << '}';
                ex && throw ex;
            }.importSymbols({
                // Object.eachProperty() proxy.
                proxyEachProp: proc callee(k,v){
                    undefined === v && return;
                    callee.first
                        ? callee.first = false
                        : jbuf << stringify.config.commaSeparator;
                    indention && indent();
                    const tk = typename k;
                    if('string'===tk){
                        jbuf << k.toJSONString();
                    }else if('integer'===tk||'double'===tk){
                        jbuf << '"' << k << '"';
                    }else{
                        return;
                    }
                    jbuf << stringify.config.keyValSeparator
                        << stringify(v,indention);
                }
            });
            const proxy4Array = proc(v){
                v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected.");
                jbuf << '[';
                proxyEachIndex.first = true;
                v.eachIndex(proxyEachIndex);
                indention && indent(true,stringify.level-1);
                jbuf << ']';
            }.importSymbols({
                proxyEachIndex: proc callee(v){
                    callee.first
                        ? callee.first = false
                        : jbuf << stringify.config.commaSeparator;
                    indention && indent();
                    jbuf << stringify(v,indention);
                }
            });

            proxy4Obj.buffered =
                proxy4Array.buffered = true
            /* tells stringify() to set up a buffer to send the
               results to. */;
            const nativeImpl = s2.json.stringify;
            {#
                array: proxy4Array,
                bool: nativeImpl,
                double: nativeImpl,
                exception: proxy4Obj,
                integer: nativeImpl,
                null: 'null',
                object: proxy4Obj,
                string: nativeImpl,
                undefined: 'null'
            }; // scope result
        }
    })/*stringify()*/
};

Added bindings/s2/require.d/json2.test.s2.











































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
requireS2(['json2'],proc(JSON){
    //JSON.stringify.config.indention = -1;
    const str = proc(v,indent=-1){
        return JSON.stringify(v,indent);
    };
    var s;
    assert '1' === str(1);
    assert '1.23' === str(1.23);
    assert '"hi, \\"there\\""' === str('hi, "there"');
    assert 'null' === str(null);
    assert 'null' === str(undefined)
    /* undefined will be elided in some contexts (object properties),
       translated to null in others (e.g. arrays). */;
    assert 'false' === str(false);
    assert 'true' === str(true);
    s = str({x:1, y:{z:'hi "there"', a:[1,2,"yo"]}, u: undefined, n: null});
    assert s.indexOf('"u":') < 0;
    assert s.indexOf('"n": null')>0;

    s = JSON.parse(str(exception(-1,"not an error")));
    assert 'not an error' === s.message;
    assert -1 === s.code;

    s = JSON.parse(str({a:{b:{1:2,3:4}}}));
    assert 2 === s.a.b.1;
    assert 4 === s.a.b.3
    /**
       BUT ACHTUNG: the integer _keys_ got converted to strings in the
       round trip because JSON only supports strings as
       keys. Supporting round-trip fideltity for non-string keys
       requires a layer of indirection, as demonstrated next...
    */;

    // Customizing toJSON for a non-POD type...
    var h = s2.Hash.new();

    // A stringify()-compliant toJSON() impl for Hashtables.
    h.toJSON = proc(){
        return {
            keys: this.entryKeys(),
            values: this.entryValues()
        }
    };

    // Just for symmetry (not used by the JSON API)...
    h.fromJSON = proc(jsonObj){
        ('string' === typename jsonObj) && (jsonObj = JSON.parse(jsonObj));
        this.clearEntries();
        if(('array' === typename jsonObj.keys) &&
           ('array' === typename jsonObj.values)){
            const that = this;
            jsonObj.keys.eachIndex(proc(v,i){
                that.insert(v, jsonObj.values[i]);
            });
        }
        return this;
    }.importSymbols(nameof JSON);

    // Now try serializing a hash...
    h.insert(1, "one");
    h.insert(2, "two");
    s = JSON.parse(str(h));
    assert 2 === s.keys.length();
    assert 2 === s.values.length();
    assert s.keys.indexOf(2)>=0;
    assert s.values.indexOf('two')>=0;

    h.clearEntries();
    assert undefined === h # 2;

    h.fromJSON(s);
    assert 'two' === h # 2;
    
});

Added bindings/s2/require.d/ob.s2.






1
2
3
4
5
+
+
+
+
+
/* a require.s2 module which "hides" s2.ob via the module
   interface. It could optionally load 'dll!mod_ob', but that's
   an internal impl detail.
*/
return s2.ob;

Added bindings/s2/require.d/ostream.s2.














1
2
3
4
5
6
7
8
9
10
11
12
13
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   A require() module providing an "ostream" object. It overloads the
   << operator and sends all arguments to s2out.

   This module was written before the s2out keyword existed. That
   keyword does the same thing but does so more efficiently, making
   this module entirely superfluous. It is retained solely as a basic
   example of how to write a require.s2 module.
*/
affirm typeinfo(iscallable s2out);
return {
    'operator<<': proc(){s2out<<argv.0; return this}
};

Added bindings/s2/require.d/plugins/demo.s2.













1
2
3
4
5
6
7
8
9
10
11
12
+
+
+
+
+
+
+
+
+
+
+
+
return {
    isVirtual: true,
    foo: true,
    cacheIt: false,
    counter: 0,
    load: proc(name){
        print(__FLC,"Demo plugin: argv=",argv);
        this.lastArgs = argv;
        ++this.counter;
        return this;
    }
};

Added bindings/s2/require.d/plugins/dll.s2.

































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   Uses s2.loadModule() to load s2 loadable modules. Usage:

   dll!moduleName?options...

   The return value: if moduleProperty option is NOT provided
   and the module produces only a single property (as most
   do), that property's value is returned, otherwise the whole
   "namespace object" passed to the module init routine is
   returned. That possibly has potential backfire cases, but
   none affecting any current modules.

   Options:

   ACHTUNG: Be aware that require() caching will hold the
   first result from this plugin's load() call (and
   generically _not_ caching DLL results is dangergous), so
   any options provided to this function via a require() call
   here are only honored on the first call. On subsequent
   calls require() will use the cached result.

   entryPoint=string: if set, it is used as the second param
   to s2.loadModule().

   moduleProperty=string: if set, the result of the call is
   the given property from the DLL module. The majority of
   DLLs install only a single property, and this function will
   return only that property in such cases (as described
   above).
*/
const mod = {
    cacheIt: true /* _not_ caching DLL-loaded resources is a
                     Reall Bad Idea, as the DLL can change
                     between invocations, leaving us with
                     different binary signatures. Also,
                     functions injected via DLLs need access
                     to the C side, which disappears if a DLL
                     is closed. */,
    prefix: ('string' === typename (var dllTmp = s2.getenv('S2_MODULE_PATH')))
        ? dllTmp.split(dllTmp.indexOf(';') >= 0 ? ';' : ':')
    : ['.'],
    suffix: ('string' === typename (dllTmp = s2.getenv('S2_MODULE_EXTENSIONS')))
        ? dllTmp.split(dllTmp.indexOf(';') >= 0 ? ';' : ':')
    : ['.so','.dll'],
    load: function(name,opt){
        var rc;
        affirm name && 'string' === typename name;
        rc = (opt && opt.entryPoint)
            ? s2.loadModule(name, opt.entryPoint, {})
            : s2.loadModule(name, {});
        affirm rc;
        if(opt && opt.hasOwnProperty('moduleProperty')){
            rc = rc[opt.moduleProperty];
        }else if(var keys=rc.propertyKeys();
                 1===keys.length()){
            rc = rc[keys.0];
        }
        return rc;
    }
};

mod.prefix.push( requireS2.home + s2.io.dirSeparator + 'dll'  );

return mod;

Added bindings/s2/require.d/plugins/fsl/blob.s2.























1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   require() plugin which fetches a blob from the repo db. It
   interprets its argument as a symbolic blob name (anything
   libfossil's symbol-to-RID conversions support).
*/
return requireS2(
    ['fsl/context',
     'fsl/db/repo' // so that we are ensured that repo is opened by this point
    ],         
    proc(fsl){
        return {
            isVirtual: true,
            _F: fsl,
            load: function(sym){
                return this._F.loadBlob(sym)
            }
            /* If we wanted to be really pedantic, we would
               require(['fsl/db/repo']...) inside load().
               Overkill.
            */
        }
    });

Added bindings/s2/require.d/plugins/fsl/manifest.s2.






















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   require() plugin which fetches a Manifest from the repo db,
   in the form of an Object which mimics the fsl_deck structure
   (see Fossil.Context.loadManifest()).

   It interprets its argument as a symbolic blob name (anything
   libfossil's symbol-to-RID conversions support).
*/
return requireS2(
    ['fsl/context',
     'fsl/db/repo' // so that we are ensured that repo is opened by this point
    ],
    proc(fsl){
        return {
        isVirtual: true,
            _F: fsl,
            load: function(sym){
                return this._F.loadManifest(sym);
            }
        }
    });

Added bindings/s2/require.d/plugins/fsl/wikiByName.s2.


























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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   require() plugin which fetches a wiki page from the repo db,
   in the form of an Object which mimics the fsl_deck structure
   (see Fossil.Context.loadManifest()).

   It interprets its argument as a wiki page name and throws if not
   found.
*/
return requireS2(['fsl/context', 'fsl/db/repo'], proc(fsl, repo){
    return {
        isVirtual: true,
        _F: fsl,
        _sql: <<<_SQL
        SELECT x.rid FROM tag t, tagxref x
        WHERE x.tagid=t.tagid 
        AND t.tagname='wiki-%1$q'
        ORDER BY mtime DESC LIMIT 1 _SQL,
        load: function(name){
            return this._F.loadManifest(
                this._F.db.selectValue(this._sql.applyFormat(name))
                ||| throw "No wiki such wiki page: "+name
            );
        }
    };
});

Added bindings/s2/require.d/plugins/json-cached.s2.





1
2
3
4
+
+
+
+
/** A clone of the json plugin, except that cacheIt is set to true. */
const mod = requireS2.getPlugin('json').copyPropertiesTo({});
mod.cacheIt = true;
return mod;

Added bindings/s2/require.d/plugins/json.s2.






















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/** A require.s2 plugin which loads file content as JSON,
    using s2.json.parseFile(). It does _not_ cache its
    results. Use the json-cached plugin for the same
    effect but with cached results.
*/
return {
    cacheIt: false /*
                     We _generally_ don't want these cached because
                     they are "probably" used only once in most cases.

                     Use the 'json-cached' plugin if you want cached
                     JSON files.
                   */,
    suffix: ['.json'],
    prefix: // Append '/json' to all paths in the default plugin's path
        ((const p = [], suffix = s2.io.dirSeparator+'json'),
         requireS2.getPlugin('default').prefix.eachIndex(proc(v){
             p[] = v+suffix;
         }), p),
    load: proc(file){return s2.json.parseFile(file)}
};

Added bindings/s2/require.d/plugins/placeholder.s2.







1
2
3
4
5
6
+
+
+
+
+
+
/* a placeholder. you'll know if you need it. */
return {
    isVirtual: true,
    cacheIt: true,
    load: proc(){ return {} }
};

Added bindings/s2/require.d/plugins/tmpl-compiled.s2.













1
2
3
4
5
6
7
8
9
10
11
12
+
+
+
+
+
+
+
+
+
+
+
+
/**
   Works like the tmpl plugin, but passes the file's contents
   throughs s2.tmpl() before returning it.
*/
return {
    cacheIt: false,
    // prefix: uses the defaults
    suffix: ['.tmpl'],
    load: proc(fn){
        return s2.tmpl(s2.Buffer.readFile(fn))
    }
};

Added bindings/s2/require.d/plugins/tmpl.s2.











1
2
3
4
5
6
7
8
9
10
+
+
+
+
+
+
+
+
+
+
/** Loads file content as a buffer, intended
    for use with .tmpl files (for s2.tmpl()). */
return {
    cacheIt: false,
    // prefix: uses the defaults
    suffix: ['.tmpl'],
    load: function(name){
        return s2.Buffer.readFile(name);
    }
};

Added bindings/s2/require.d/pubsub.s2.






















































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
  A require.s2 module implementing a basic publish-subscriber
  manager.

  Example usage:

  http://fossil.wanderinghorse.net/repos/cwal/index.cgi/finfo?name=s2/require.d/pubsub.test.s2

  Returns a class, each instance of which manages its own list
  of publishers and subscribers.
*/
return {
    __typename: 'PubSub',
    prototype: undefined,
    
    /**
       Constructor for use with the 'new' keyword.
       
       var pubber = new thisObj();
       assert pubber inherits thisObj; // this will hold

       Each instance maintains its own, independent list of
       subscriptions.
    */
    __new: proc(){
        this.reset();
    },

    /**
       Subscribes a callback to events published for a given key.
       key may be of any value type. func must be-a Function.
       
       Returns a unique-per-subscription value (of an unspecified
       type) which can be passed to unsub() to opt out of a
       subscription. For a permanent subscription, simply ignore the
       result value.
    */
    sub:proc callee(key, func){
        affirm typeinfo(iscallable func) /* is Function-like */;
        const m = (this.$map[key] ||| (this.$map[key]={prototype:null})),
              i = enum{k:key}.k;
        m[i] = func;
        return i;
    },

    /**
       Expects id to be a value returned by this.sub() and
       unsubscribes a subscriber registered with that id.

       Returns this object.
    */
    unsub: proc(id){
        affirm typeinfo(isunique id);
        (const c = this.$map[id.value]) && unset c[id];
        return this;
    },

    /**
       Publishes an event to all subscribers (if any) of the key
       (event type) given as the first argument.
       Important notes:

       a) Subscribers are notified in an UNSPECIFIED and (very)
       POSSIBLY CHANGING order.

       b) Any arguments given after the event key are passed to each
       subscriber callback function, in the order they are passed in
       here. e.g. if this function is passed ('foo', 'bar', 1) then
       each subscriber will be called with ('bar', 1). Pedantic
       side-note: each callback gets its own copy of the arguments
       array, to avoid unintended side-effects if a callback modifies
       its argv.

       Returns this object (for lack of a better option).

       It propagates any exceptions thrown by a subscriber, and any
       pending subscribers won't get called.

       Pedantic side-node: each subscriber gets its own copy of the
       arguments array, so it's safe to change it or keep a reference
       to it in the subscribers without affecting others.
    */
    pub:proc(/*key, event args...*/){
        return this.pubWithThis(argv.shift(), undefined, @argv);
    },

    /**
       A special case of pub() useful in certain code constellations.

       For each listener of event type e, its callback is called using
       t as the callback's "this" and passing on all arguments after
       the second. If t is undefined then each callback is its own
       'this' (as is conventional for s2).

       Returns this object.

       See pub() for more details.
    */
    pubWithThis: proc(e,t/*...*/){
        const m = this.$map[e] ||| return this;
        affirm typeinfo(isobject m);
        argv.shift(2);
        foreach(m=>k,f) f.apply(t?:f, argv.slice());
        return this;
    },

    /**
       Removes all subscriptions for the given key or (if no arguments
       are passed) all subscriptions for all keys.
       
       Returns this object (for lack of a better option).
    */
    reset:proc(/*key*/){
        argv.# ? unset this.$map[argv.0] : this.$map = {prototype:null};
        return this;
    }
};

Added bindings/s2/require.d/pubsub.test.s2.
























































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* Short demo of the pubsub require.s2 module. */
requireS2(
    ['nocache!pubsub'],
    proc(P){
        const p = new P();
        assert 'object' === typename p.$map /* testing internals */;
        print(__FLC, 'pubsub:');
        foreach(p=>k,v) print('\t',k,v);
        var counter = 0;
        const id = p.sub('hi', proc(){
            ++counter;
            print(__FLC,'hi handler 1',argv);
        });
        const id2 = p.sub('bye', proc(){
            ++counter;
            print(__FLC,'bye handler',argv);
        });

        const id3 = p.sub('hi', proc(){
            ++counter;
            print(__FLC,'hi handler 2',argv);
        });

        print(__FLC, 'subscription IDs =',id, id2, id3);
        print("Publishing events...");
        p.pub('hi',0, __FLC);
        assert 2 === counter;

        p.pub('nope',1, __FLC);
        assert 2 === counter;

        p.pub('bye', 2, __FLC);
        assert 3 === counter;

        p.pub('hi',3, __FLC);
        assert 5 === counter;
        p.unsub(id);
        p.pub('hi',4, __FLC);
        assert 6 === counter;
        p.pub('bye', 5, __FLC);
        assert 7 === counter;
        print(__FLC, 'done');

        var p2 = new P();
        assert p2.$map;
        assert p2.$map !== p.map;
        assert p2 !== p;
        assert !(p2 inherits p);
        assert p2 inherits p.prototype;
        assert p2.sub === p.sub;

        print(__FLC, 'really done');
        return p;
    }
);

Added bindings/s2/require.d/require.s2.
















































































































































































































































































































































































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   Implements a basic script module loading API similar to
   require.js (http://requirejs.org).

   In short, this module acts as a loader for arbitrary other content,
   be it s2 script code, raw file content, or "virtual" (non-file)
   content plugged in by the user. Its usage is modelled very much of
   off requirejs, and it will look familiar to anyone who has used
   that library.

   See the API docs in this module for more details. For even more
   details (or other details, in any case), see this public Google
   Doc:

   https://docs.google.com/document/d/14gRP4f-WWgWNS64KM_BI7YjQqBLl4WT3jIrmNmFefYU/view

   Example usage:

   Assume the files r1.s2 and r2.s2 live somewhere in this module's
   search path and that both return an Object with a foo() method.
   This is how we might import and use them:

   @code
   const R = import('path/to/require.s2');

   R(['r1', 'r2'], proc(r1, r2){
     r1.counter = r2.counter = 1;
     print(__FLC,r1);
     print(__FLC,r2);
     r1.foo();
     r2.foo();
     ++r1.counter;
     ++r2.counter;
   });

   R(['r2', 'r1'], proc(r2, r1){
     assert 2 === r2.counter;
     assert 2 === r1.counter;
     // ^^^ because imported script results are cached
   });
   @endcode

*/
affirm s2.fs;
affirm s2.Buffer;
affirm typeinfo(isfunction s2.getenv);
affirm typeinfo(isfunction s2.fs.realpath);
affirm typeinfo(isfunction s2.Buffer.readFile);
const  /* saves some var lookup time */
s2 = s2,
realpath = s2.fs.realpath,
getenv = s2.getenv,
cliFlags = (s2.ARGV ? s2.ARGV.flags : 0) ||| {prototype:null}
;

const importFileBuffer = s2.Buffer.readFile;
const importFileText = proc(fn) using(importFileBuffer) {
    return importFileBuffer(fn).takeString();
};
/**
   If a string-type CLI flag OR environment variable (in that
   order) with the given name is found, this function returns an
   array of its contents, tokenized using
   s2.PathFinder.tokenizePath(). If no flag/environment variable
   is found, or a CLI flag with a non-string value is found,
   undefined is returned.

   Interpretation of the arguments is as follows:

   - If only 1 argument is provided, this routine looks for both a
   CLI flag and environment var (in that order) with that name.

   - If 2 arguments are provided, the 1st is checked as a CLI flag
   and the 2nd as an environment variable (in that order).
*/
const pathFromEnv = proc(f,e=f){
    const p = F[f] ||| E(e);
    return typeinfo(isstring p)
        ? P.tokenizePath(p)
        : undefined;
} using {
    E: getenv,
    P: s2.PathFinder,
    F: cliFlags
};

/**
   Internal utility to convert almost-URL-encoded plugin options
   to some value form. Treats non-existent values (===undefined)
   as a boolean true flag.
*/
const convertQValue = proc(v){
    undefined === v && return true;
    'true' === v && return true;
    'false' === v && return false;
    'null' === v && return null;
    return 0.parseNumber(v) ?: v;
};

/**
   Module-internal cache of import() responses.
*/
const modCache = new s2.Hash(0.nthPrime(15));

/**
   The main "require" module object.
*/
const mod = {
    __typename: 'require.s2',
    /**
       Used for doing file lookups. Its search path and extensions
       get continually swapped out by require() and friends to use
       whatever search path the current context requires.
    */
    pf: new s2.PathFinder(),
    pathFromEnv,
    /* Exposed for corner-case use by plugins. */
    modCache
};

/**
   This "default" plugin (A) acts as the default
   implementation for fetching require()'d files, (B) is
   where the interface for other plugins is documented.
*/
const PluginModel ={
    /**
       Clear the prototype - we don't really need it for
       this object.
    */
    prototype: undefined,
    /** If true, require() does not search for the filename it
        is given. (It is assumed to be some form of virtual
        unique identifier, and may be empty.)
    */
    isVirtual: false,
    
    /**
       cacheIt tells require whether or not to cache the results
       of requests to this plugin. If it is true, then this.load()
       will only be called for the first request, and its result
       will be returned on subsequent requests. EXCEPTION: if
       a plugin is called with arguments (see load()) the cache
       is ignored/bypassed.

       The default is expected (by client code) to always be true
       for the default plugin!
    */
    cacheIt: true,

    /**
       For file-based plugins, prefix specifies the search
       directories (array of strings, excluding any trailing slash
       on the directory parts unless they refer to the root
       directory).
    */
    prefix: pathFromEnv('s2.require.path', 'S2_REQUIRE_PATH') ||| ['.'],
    /**
       For file-based plugins, suffix specifies the search
       extensions (array of strings, including any '.' part,
       e.g. use ".foo" instead of "foo" unless you're searching
       for partial extensions).
    */
    suffix: pathFromEnv('s2.require.extensions', 'S2_REQUIRE_EXTENSIONS') ||| ['.s2'],

    /**
       Called by require() to "load" a given module.  load() must
       accept a module name, "load" that module, for a given
       definition of "load", and return its contents or
       result. (It need not actually load anything from anywhere,
       much less a file: it might simply return a predefined
       object or other value.)

       In the call context, 'this' will be this plugin
       configuration object.

       The first argument is the "name" part of the string passed
       to require.

       The second argument part is either undefined or an object:
       if the file string contains '?', anything after the ? is
       assumed to be encoded in the form a=b&c=d... (URL-like, but
       _without_ the URL encoding), and that gets transformed into
       an Object of key/value pairs before passing them to this
       function (the default value, if no "=" is provided, is boolean
       true). HOWEVER: passing any arguments after '?' will
       cause caching to be bypassed, because the arguments presumably
       change how the plugin works. Note that if nothing follows
       the '?' then no options object is created.
       
       Caveats regarding the opt parameter:

       1) all of the values in the opt object will originally be
       of type string, but numeric-looking strings and the strings
       ("true", "false", "null") get converted to their
       script-native type.

       2) Only one level of opt object is supported, not nested
       objects, arrays, etc.
    */
    load: proc(f/*, opt*/) using({
        b: s2.Buffer.readFile,
        r: mod
    }) {
        return b(f).evalContents(f,{requireS2: r});
    }
    /* Reminder to self: we cannot simply alias to s2.import() because
       any extra non-string arguments would be passed to it (doing the
       wrong thing). Reminder #2: adding comments outside of function
       bodies, instead of if them, uses less memory, since they don't
       get allocated as part of the function body. That's especially
       significant when the length of the comments outweigh the rest
       of the source, as in this case. */
};

/**
   The funkiness we do with 'this' vs 'mod' in may places places
   below is so that this stuff still works when clients copy the
   returned module into a property of another object. i.e. the following
   usages are equivalent:

   var x = mod;
   obj.x = mod;
   x([...],...);
   obj.x([...],...);

   Both calls (because of these extra bindings) use the same "this"
   inside the call (the local 'mod' symbol), which is important for
   identical/correct semantics in both uses.
*/

/**
   All the plugins are stored in this object, and any number may
   potentially be loaded (and added here) via client-side use.
*/
mod.plugins = {
    /**
       The name 'default' is magic and assumes certain
       plugin-level defaults. It also provides the default .prefix
       and .suffix properties for other non-isVirtual (file-using)
       plugins (used only if a given plugin does not define them
       itself).
    */
    default: PluginModel,
    /**
       Works just like the default plugin but bypasses
       the cache.
    */
    nocache: {
        cacheIt: false,
        load: PluginModel.load
    },
    /** Loads file content as a string. */
    text: {
        cacheIt: false,
        // prefix: uses the defaults
        suffix: ['.txt', '.s2', '.html'],
        load: importFileText
    },
    /** Loads file content as a buffer. */
    buffer: {
        cacheIt: false,
        // prefix: uses the defaults
        suffix: ['.txt', '.s2', '.html'],
        load: importFileBuffer
    }

    /**
       Demonstration of a "virtual" plugin (one which does not
       use files).
       
       virtualDemo:{
       isVirtual: true,
       cacheIt: true, // not strictly necessary, plugin-dependent
       load: proc f(fn,opt){
       print("Example of a 'virtual' handler. Arguments:",argv);
       return opt ||| this;
       }
       }
    */

};

/**
   Installs one or more plugins into this object.

   If called with an initial string arugment then:

   - Adds a new plugin. name must be a string and pluginObj must
   be an object which follows the PluginModel interface. Returns
   pluginObj on success.

   If called with a non-string argument then:

   - name is assumed to be a container. Each of its properties is
   assumed to be a module definition, and each one gets installed
   via a call back into this function, passing it each key and
   value. Returns this object.

   Throws on error.
*/
mod.addPlugin = proc callee(name, pluginObj){
    if(name && !pluginObj && typeinfo(iscontainer name)){
        foreach(name=>k,v) callee(k,v);
        return this;
    }
    affirm 'string' === typeinfo(name name);
    affirm name /* name must be non-empty */;
    affirm typeinfo(iscontainer pluginObj) && typeinfo(iscallable pluginObj.load);
    mod.plugins[name] = pluginObj;
    return mod;
} using(mod);

/**
   Searches for a plugin script by using this.plugins.default's
   search path and the name ("plugins/"+name). Returns undefined
   if not found, else the result of s2.import()'ing that file. It
   does not check if the plugin is already installed, but installs
   (or overwrites) it into mod.plugins[name].
*/
mod.searchAndInstallPlugin = proc(name){
    mod.pf.prefix = mod.plugins.default.prefix;
    mod.pf.suffix = mod.plugins.default.suffix;
    const fn = mod.pf.search('plugins/'+name);
    return fn
        ? mod.plugins[name] = ((const requireS2=mod), import(false,R(fn)))
        : undefined;
} using {mod, R: realpath},

/**
   If the given plugin name is already installed, it is returned,
   otherwise it is sought for, installed, and returned. An
   exception is thrown if it cannot be found or if installing it
   fails.
*/
mod.getPlugin = proc(name) using(mod) {
    return mod.plugins[name] ||| mod.searchAndInstallPlugin(name);
},

/**
   Attempts to resolve a file name using a given plugin's search
   path. basename is the unresolved name of the file to search for
   and forPlugin is either a plugin object or the name of a
   plugin. The search path/extensions use are those of the given
   plugin or (if that plugin has none), the default plugin. If the
   given plugin has the isVirtual flag, no search is performed and
   the undefined value is returned.
*/
mod.resolveFilename = proc(basename,forPlugin='default') using(mod){
    const pConf = typeinfo(isobject forPlugin) ? forPlugin : mod.getPlugin(forPlugin);
    affirm typeinfo(isobject pConf);
    const pf = (pConf.isVirtual ? undefined : mod.pf) ||| return;
    pf.prefix = pConf.prefix ||| mod.plugins.default.prefix;
    pf.suffix = pConf.suffix ||| mod.plugins.default.suffix;
    return pf.search(basename,0);
};

/**
   Given a base filename...

   1) If the given name does not contain a '!' character,
   it searches for an exact-match name in the cache. If it
   finds one, it returns that value.

   2) It searches for the file using the configured search
   paths/extensions (from this.plugins). If found, it is passed to
   the import() function specified for the import type (see
   below).

   By default this.plugins.default is used to search for and
   import the file. If a "special" type of name is provided to
   this function, though (meaning the base name looks like
   with "SOMETHING!basename"), then this.plugins[SOMETHING] is
   used (if set), which may change the caching behaviour and
   how the content of the file is interpreted.

   Depending on the configuration options, requests might get
   cached. Subsequent calls which expand to the same file name
   will return the same (cached) result value on each
   subsequent call.
*/
mod.import = proc(basename){
    affirm 'string' === typeinfo(name basename);
    if(basename.indexOf('!')<0 && const c = C#basename){
        return c;
    }
    var pluginName;
    /* Check for pluginName!... */
    if((var ndx = basename.indexOf('!'))>0){
        pluginName = basename.substr(0,ndx);
        basename = basename.substr(ndx+1);
    }
    pluginName || (pluginName ='default');
    /* Configuration for this plugin... */
    const pConf = mod.getPlugin(pluginName);
    pConf || throw "Could not load plugin '"+pluginName+"'.";
    const pf = pConf.isVirtual ? undefined : mod.pf /* PathFinder */;
    if(pf){
        // Set up/reset file lookup paths/suffixes...
        pf.prefix = pConf.prefix ||| mod.plugins.default.prefix;
        pf.suffix = pConf.suffix ||| mod.plugins.default.suffix;
    }
    /* Treat ?a=b&c=d... almost like a URL-encoded
       as query string, but without the URL encoding.
    */
    var qArgs, useCache = pConf.cacheIt;
    if(basename.indexOf('?')>=0){
        /* Parse args. If any are provided, bypass the cache. */
        const sp = basename.split('?',2);
        basename = sp.0;
        (qArgs = Q(sp.1)) && (useCache = false);
    }
    /* Find the file, if necessary... */
    var fn = pConf.isVirtual
        ? basename
        : (basename ? pf.search(basename, 0) : false)
        ||| throw "Plugin '%1$s' cannot find '%2$s' in search path %3$J with extensions %4$J.".
        applyFormat(pluginName, basename, pf.prefix, pf.suffix);
    // expand to the fully qualified path for non-virtual plugins...
    pConf.isVirtual || (fn = P(fn));
    //print(__FLC,"fn=",fn,"useCache=",useCache,"qArgs =",qArgs,pluginName,pConf);
    //print(__FLC,'pConf.cacheIt=',pConf.cacheIt,', fn=',fn);
    //print(__FLC,cache.toJSONString(2));
    const requireS2 = mod /* public API symbol, potentially
                             needed by anything which
                             uses anyPlugin.load() */;
    return useCache
        ? (const k =
                 (pluginName ? pluginName+'!'+fn : fn),
                 cacheCheck = (C # k))
        ? cacheCheck
        : C.insert(k, pConf.load(fn, qArgs))
    : pConf.load(fn, qArgs);
} using(mod, {
    C: modCache,
    P: realpath,
    Q: proc(str) using {q:convertQValue} {
        str || return str;
        var r;
        foreach(@str.split('&')=>v){
            const s = v.split('=',2);
            (r ||| (r = {prototype:null}))[s.0] = s.1 ? q(s.1) : (v.indexOf('=')>0 ? s.1 : true);
        };
        return r;
    }
});

/**
   For each base filename in the given array/tuple, this function
   calls this.import(basename) and appends the results to a new
   array.  Returns an array of the results to each import() call,
   the order of the array's elements being the same as the calls
   to import(). Throws if list is not an array or is empty or if
   loading any resources fails. Propagates any exceptions.
*/
mod.importList = function(list) using(mod) {
    affirm typeinfo(islist list) /* expecting an Array or Tuple */;
    affirm !list.isEmpty() /* expecting a non-empty list of module names */;
    const imps = [];
    foreach(@list=>v) imps.push(mod.import(v));
    return imps;
};

/**
   Imports a list of script modules and optionally calls a
   callback after loading all of them.

   list is an array of strings - script file base names to import.
   func is an optional function or code string which gets called
   resp. eval'd after importing all of the scripts. If it's a
   function, it is passed one argument for each entry in the list,
   in the same order they are imported. If it is a code string
   then it is eval'd in a scope with the array 'modules' defined
   to the resolved list of modules.

   In either case, it declares the const symbol requireS2 to be
   the require() module, so that these callbacks may recursively
   invoke require() via a call to requireS2(). (It is often useful
   to do so, it turns out, and this consolidates the convention
   across modules originally written for different code bases.)

   Returns the result of calling the function (if any) or the
   array of loaded modules (if passed no function/string).

   Example:

   assert 42 === thisObj(
   ['module1', 'module2'],
   function(mod1, mod2){
   print('result of module1 import:' mod1);
   print('result of module2 import:' mod2);
   return 42;
   }
   );


   _Potential_ TODOs:

   - If passed a non-string value where a name is expected, use it
   as-is as the result of the loading. i suspect we might have
   some interesting uses for that, but want a use case before trying
   it out.
*/
mod.require = function(list, func) using(mod) {
    func && affirm (typeinfo(iscallable func) || typeinfo(isstring func));
    list = mod.importList(list);
    func || return list;
    if(typeinfo(iscallable func)){
        const requireS2=mod, s2 = s2;
        return func.apply(func, list);
    }else{
        return func.evalContents('require() script',{
            s2: s2,
            requireS2: mod,
            modules: list
        });
    }
};

/**
   Installs a cached entry for the given module name, such that
   future calls to import() or require() which use that exact name
   will return the given result object. Note that the name need
   not be filesystem-friendly. e.g. "<my-identifier>" is perfectly
   legal. The only limitation is that it "really should not"
   contain an exclamation point, as that may confuse import()
   because that character is used to denote a plugin.
*/
mod.installModule = function(name, result) using(modCache){
    modCache.insert(name, result);
    return this;
};


// Try to determine some useful directories to search for scripts in...
if(typeinfo(isstring var d = cliFlags['s2.require.home'])){
    mod.home = realpath(d);
    mod.plugins.default.prefix.push( mod.home );
}
else if((d=(cliFlags['s2.home']|||getenv('S2_HOME')))
        && (d=realpath(d+'/require.d'))){
    mod.plugins.default.prefix.push( mod.home = d );
}
if((var d = __FILEDIR ? realpath(__FILEDIR) : 0)
   && (mod.home !== d)){
    mod.plugins.default.prefix.push( d );
    mod.home || (mod.home = d);
}
/* __FILEDIR may be a synthetic __FILE name
   (e.g. via eval or Buffer.evalContents()) */
mod.home || (mod.home="");

/* We set mod.require.home so that plugins can construct paths via
   requireS2.home. */

/* Make it so that call()ing this object calls mod.require() */
mod.prototype = mod.require /** Holy cow! We've just inherited our
                                own member function. */;
mod /* script result */;

Added bindings/s2/require.d/require.test.s2.



















































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

affirm requireS2;
affirm 'string' === typename requireS2.home;
affirm requireS2.home.indexOf('/')>-1 || requireS2.home.indexOf('\\')>-1;
//print(__FLC,'requireS2.home =',requireS2.home);
/**
   A quite incomplete test of require.s2. Since this cannot even
   be loaded by require.s2 if require.s2 isn't at least basically
   working, just getting here already tells us much.
*/
requireS2.addPlugin('dummy',{
    isVirtual: true,
    cacheIt: false,
    load: proc(n,opt){
        return {
            name: n,
            opt: opt
        };
    }
});

requireS2.addPlugin({
    dummy2:{
        isVirtual: true,
        chachIt: true,
        load: proc(n,opt){
            return 1;
        }
    },
    dummy3:{
        isVirtual: true,
        chachIt: true,
        load: proc(n,opt){
            return 0;
        }
    }
});

requireS2(['dummy!fred?a=1'],proc(obj){
    affirm 'fred' === obj.name;
    affirm 1 === obj.opt.a;

    assert true === requireS2(['dummy!barny', 'dummy2!', 'dummy3!'],proc(d, d2, d3){
        assert 'barny' === d.name;
        assert !d.opt;
        assert 1 === d2;
        assert 0 === d3;
        return true;
    });
});

Added bindings/s2/require.d/time.s2.






1
2
3
4
5
+
+
+
+
+
/* a require.s2 module which "hides" s2.time via the module
   interface. It could optionally load 'dll!mod_time', but that's
   an internal impl detail.
*/
return s2.time;

Added bindings/s2/require.d/tmpl.s2.





























































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   The tmpl module (as distinct from the tmpl plugin!) provides
   utilities for working with s2.tmpl().

*/
return {
    /**
       "Processes" a s2.tmpl() template as follows...

       The first argument is a tmpl()-compiled script (of type Buffer)
       or a non-compiled script of type String (in which case this
       function compiles it and uses the original value as the 3rd
       parameter).

       The second parameter is an optional container holding key/value
       pairs which get imported into the current scope before
       evaluating the script. This allows one to easily create
       mini-templates for use in loops and such. If you _have_
       to pass a value but don't have an object, any falsy value
       will do.

       The final argument is intended to hold the uncompiled script
       and is only used in error reporting, and is stored in any
       exception propagated via evaluating a template. It is ignored
       when the first argument has a typename of 'string'.
    */
    process: proc(template, opt, tmplUncompiled){
        if(typeinfo(isstring template)){
            tmplUncompiled = template;
            template = this.compile(template);
        }
        affirm typeinfo(isbuffer template);
        if(const ex = catch template.evalContents(opt|||{})){
            ex && throw {
            message: "Error evaluating compiled template (location info is relative to the compiled script).",
            exception: ex,
                template: {
                    compiled: template.toString(),
                    uncompiled: tmplUncompiled ? tmplUncompiled.toString() : undefined
                }
            };
        }
    },

    processFile: proc(fn, opt){
        return this.process( this.load(fn, true), opt );
    },

    /**
       A proxy for s2.tmpl().
    */
    compile: s2.tmpl,
    /**
       Uses the tmpl! plugin to load the given file
       and optionally compile it using this.compile()
    */
    load: proc(fn, compile){
        return requireS2([(compile ? 'tmpl-compiled!' : 'tmpl!')+fn]).0;
    } using (requireS2)
};

Added bindings/s2/require.d/tmpl.test.s2.











































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* A brief test of the 'tmpl' require.s2 module. */
requireS2(
['ob',
 'nocache!tmpl'
 // bug:  ^^^^ reading a directory (by accident) is reported as OOM.
 // Fixed by not allowing PathFinder to resolve directory names, but
 // that support is missing for Windows (patches welcomed).
],
proc(ob, t){
    const src = 'a=<% a %>, b=<%b%>, c=<%c%>\n';
    const obLevel = ob.level();
    var str;
    ob.push();
    var ex = catch{
        t.process(src,{
            a:'hi',
            b:'there',
            c:'world'
        });
        str = ob.pop(-1);
    };
    while(ob.level()>obLevel) ob.pop();
    ex && throw ex;
    affirm "a=hi, b=there, c=world\n" === str;

    scope {
        const src2 = '<%a%>,<%b%>,<%c%>\n',
          compiled = t.compile(src2),
          opt = { a:-1, b:0, c:1 }
          ,TMPLOUT = proc(){} /* eval'd template uses this func, if defined */
        ;
        const XYZ = 'hi';
        t.process('<% XYZ %>\n', null);
        for(var i = 0; i < 5; ++i, ++opt.a, ++opt.b, ++opt.c ){
            t.process(compiled, opt, src);
        }
        affirm 4==opt.a;
        affirm 5==opt.b;
        affirm 6==opt.c;
    };
    print(__FLC,'done!');
});

Added bindings/s2/s2_amalgamation.c.

more than 10,000 changes

Added bindings/s2/s2_amalgamation.h.

more than 10,000 changes

Added bindings/s2/shell2.c.


















































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/**
   s2sh2 - a cleanup of s2sh.

   s2sh2 is functionally *nearly* identical to s2sh, the primary
   difference being that s2sh2 has more conventional/cleaned-up CLI
   argument handling. Many of the flags which are toggles in s2sh have
   only a single form in s2sh2, to turn something on or off, but not
   toggle it.

   Other minor differences:

   - $2 is a UKWD referring to the global s2 object.
*/

#define S2SH_VERSION 2
#include "shell_common.c"

/**
   gcc bug: if this function is marked static here, it does
   not recognize that shell_common.c uses it and declares that
   it's unused.
*/
static CliAppSwitch const * s2sh_cliapp_switches(){
  static const CliAppSwitch cliAppSwitches[] = {
  /* {opaque, dash, key, value, brief, details, callback, reserved} */
  /** Fills out most of a CliAppSwitch entry */
#define F6(IDFLAGS,DASHES,KEY,VAL,BRIEF,PFLAGS)     \
  {IDFLAGS, DASHES, KEY, VAL, BRIEF, 0, 0, PFLAGS}
  /** Continues the help text for the previous entry. */
#define FCONTINUE(LABEL) {SW_CONTINUE, 0, 0, 0, LABEL, 0, 0, 0}
  /** Adds an "additional info" line, not indented. */
#define FINFOLINE(LABEL) {SW_INFOLINE, 0, 0, 0, LABEL, 0, 0, 0}
  /** -short | --long[=VALUE]*/
#define FX(IDFLAGS,SHORT,LONG,VALUE,BRIEF,PFLAGS)   \
  F6(IDFLAGS,2,LONG,VALUE,BRIEF,PFLAGS),            \
  F6(IDFLAGS,1,SHORT,VALUE,0,PFLAGS)
  /** -short, --long on/off switches ...*/
#define F01(IDFLAGS,SHORT,LONG,BRIEF)                       \
  {IDFLAGS+1, 2, LONG,   0, "Enable "  BRIEF, 0, 0, 0},     \
  {IDFLAGS+1, 1, SHORT,  0, "Enable "  BRIEF, 0, 0, 0},     \
  {IDFLAGS,   2, "no-"LONG, 0, "Disable " BRIEF, 0, 0, 0},  \
  {IDFLAGS,   1, "no"SHORT, 0, "Disable " BRIEF, 0, 0, 0}
  /** Start the next group (for --help grouping). */
#define GROUP(GID,DESC) {GID, 0, 0, 0, DESC, 0, 0, 0}

  /***********************************************************************/
  GROUP(SW_GROUP_0,0),

  /**
     Keep these arranged so that flags with short and long forms are
     ordered consecutively, long one first. The help system uses that to
     collapse the flags into a single listing. In such cases, the help
     text from the first entry is used and the second is ignored.
  */
  FX(SW_HELP, "?", "help", 0,
     "Send help text to stdout and exit with code 0.", 0),
  FCONTINUE("The -v flag can be used once or twice to get more info."),

  FX(SW_VERBOSE,"v","verbose",0,
     "Increases verbosity level by 1.", 0),

  FX(SW_VERSION,"V","version",0,
     "Show version info and exit with code 0. ", 0),
  FCONTINUE("Use with -v for more details."),

  FX(SW_ESCRIPT,"e","eval","SCRIPT",
     "Evaluates the given script code in the global scope. "
     "May be used multiple times.", CLIAPP_F_SPACE_VALUE),

  FX(SW_INFILE,
     "f","file","infile",
     "Evaluates the given file. The first non-flag argument is used "
     "by default.", CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  FX(SW_OUTFILE, "o", "output", "outfile",
     "Sets cwal's output channel to the given file. Default is stdout.",
     CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  /***********************************************************************/
  GROUP(SW_GROUP_1, "Less-common options:"),

  FX(SW_ASSERT_1,"A", "assert", 0, "Increase assertion trace level by one: "
     "1=trace assert, 2=also trace affirm.",0),

  FX(SW_INTERACTIVE,"i", "interactive", 0,
     "Force-enter interactive mode after running -e/-f scripts.",0),

  FX(SW_AUTOLOAD_0,"I","no-init-script", 0,
     "Disables auto-loading of the init script.",0),
  FCONTINUE("Init script name = same as this binary plus \".s2\" "),
  FCONTINUE("or set via the S2SH_INIT_SCRIPT environment variable."),
  
  /***********************************************************************/
  GROUP(SW_GROUP_2, "Esoteric options:"),

  FX(SW_METRICS, "m", "metrics", 0,
     "Show various cwal/s2 metrics before shutdown.",0),
  FCONTINUE("Use -v once or twice for more info."),

  F01(SW_MOD_INIT_0, "mi", "module-init",
      "automatic initialization of static modules."),

  F6(SW_SHELL_API_1,2,"s2.shell",0,
     "Forces installation of the s2.shell API unless trumped by --cleanroom.",0),
  FCONTINUE("By default s2.shell is only installed in interactive mode."),

  F6(SW_TRACE_CWAL,2,"trace-cwal", 0,
     "Enables tremendous amounts of cwal_engine "
     "tracing if it's compiled in.",0),
  
  F01(SW_SCOPE_H0, "hash", "scope-hashes",
      "hashes for scope property storage (else objects)."),

  FX(SW_HIST_FILE_1,
     "h", "history", "filename",
     "Sets the interactive-mode edit history filename.",
     CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  F6(SW_HIST_FILE_0,2, "no-history", 0,
     "Disables interactive-mode edit history.", 0),
  F6(SW_HIST_FILE_0,1, "noh", 0, 0, 0),

  F6(SW_DISABLE, 2, "s2-disable", "comma,list",
     "Disables certain s2 API features by name.",0),
  FCONTINUE("Use a comma-and/or-space-separated list with any of "
            "the following entries:"),
  FCONTINUE("fs-stat, fs-read, fs-write, fs-io, fs-all"),
  
  F6(SW_CLEANROOM,2,"cleanroom",0,
     "Only core language functionality with no prototype-level methods.",0),
  FCONTINUE("Disables the s2/$2 global object and auto-loading of the "
            "init script."),

  FINFOLINE("Recycling-related options:"),
  F01(SW_RE_V0,"rv", "recycle-values", "recycling of value instances."),
  F01(SW_RE_C0,"rc","recycle-chunks", "the memory chunk recycler."),
  F01(SW_RE_S0,"si","string-interning", "string interning."),

  FINFOLINE("Memory-tracking/capping options:"),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-ta", "memcap-total-allocs", "N",
     "* Limit the (T)otal number of (A)llocations to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-tb", "memcap-total-bytes", "N",
     "* Limit the (T)otal number of allocated (B)ytes to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-ca", "memcap-concurrent-allocs", "N",
     "Limit the (C)oncurrent number of (A)llocations to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-cb", "memcap-concurrent-bytes", "N",
     "Limit the (C)oncurrent number of allocated (B)ytes to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-sb", "memcap-single-alloc", "N",
     "Limit the size of any (S)ingle allocation to N (B)ytes.",
     CLIAPP_F_SPACE_VALUE),
  FINFOLINE("The allocator starts failing (returning NULL) when a capping constraint is violated."),
  FCONTINUE("The 'tb' and 'ta' constraint violations are permanent (recovery requires resetting the engine)."),
  FCONTINUE("The 'ca' and 'cb' constraints are recoverable once some cwal-managed memory gets freed."),
  FCONTINUE("Capping only applies to memory allocated by/via the scripting\n"
            "engine, not 3rd-party APIs."),

  F6(SW_MEM_FAST1,2,"fast",0,"(F)orce (A)lloc (S)ize (T)racking.",0),
  FCONTINUE("Enables more precise tracking and reuse of recycled memory."),
  FCONTINUE("Several memcap options enable --fast implicitly."),
  
#undef GROUP
#undef F01
#undef F6
#undef FX
#undef FPLUS
#undef FPLUSX
#undef FINFOLINE
    CliAppSwitch_sentinel /* MUST be the last entry in the list */
  };
  return cliAppSwitches;
}

Added bindings/s2/shell_common.c.