-
Notifications
You must be signed in to change notification settings - Fork 4
/
qm.r
4617 lines (4036 loc) · 111 KB
/
qm.r
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
Rebol [
Title: "QuarterMaster"
Author: "Christopher Ross-Gill"
Version: 0.7.6
Notes: {Warning: Work-In-Progress - no liabilities for damage, etc.}
License: http://opensource.org/licenses/Apache-2.0
Needs: [2.7.8 shell]
]
;--## APPLICATION NAMESPACE
;-------------------------------------------------------------------##
qm: context [
binding: 'self
profile: settings:
controller: metadata: action:
models: db: request: response: none
alerts: []
errors: []
notices: []
handler: %.rsp
view-path: none
title: ""
date: now
code: []
live?: string? system/options/cgi/server-software
cheyenne?: parse any [system/options/cgi/server-software ""][thru "Cheyenne" to end]
probe: func [data /with prefix][
either with [
with: join data mold prefix
data: :prefix
][
with: mold data
]
if response [
response/log: append any [response/log "^/"] :with
append response/log newline
]
return :data
]
]
;--## SETTINGS
;-------------------------------------------------------------------##
if all [qm/live? parse/all system/options/cgi/query-string [thru "?" any [thru "&"] "debug=" end]][
print "Content-Type: text/plain; charset=utf-8^/"
]
system/options/binary-base: 64
system/error/user/type: "QuarterMaster Error"
range!: :pair! ; until REBOL v3
else: true ; for 'case statements
qm/profile: any [
system/script/args
system/script/parent/header
]
settings: qm/settings: construct/with any [
qm/profile/settings
make error! "No Settings Provided"
] context [
get: none
]
settings/get: func [key [word!]][
all [not key = 'self key: in settings key get key]
]
date: qm/date: qm/date - qm/date/zone + settings/zone
qm/date/zone: settings/zone
parse settings/spaces use [location][
[some [string! [
location: url! |
file! (change location clean-path location/1)
]]]
]
use [seed][
seed: either any [
not qm/live?
settings/get 'no-mod-unique
][""][
any [
get-env "UNIQUE_ID"
make error! "Missing Apache Mod_Unique_ID"
]
]
append seed now/precise
random/seed to integer! checksum/secure seed
]
;--## EXTENDED CORE FUNCTIONS
;-------------------------------------------------------------------##
context [
func: make function! [spec [block!] body [block!]][make function! spec body]
does: func [body [block!]][make function! [] body]
uses: func [
"Defines a function with a finite context"
proto [block!]
spec [block!] "Function Body"
][
proto: context proto
func [args [block! object!]] compose/only [
args: make (proto) args
do bind (spec) args
]
]
try-else: func [
"Tries to DO a block, returns its value or DOes the fallback block."
[throw] 'block [block!] fallback [block!] /local reason
][
either error? reason: try :block bind :fallback 'reason [:reason]
]
verify: assert-all: func [
"Steps through a series of cases/resolutions. Returns last case result where all cases are positive."
[throw] cases [block!] /local value
][
until [
set [value cases] do/next cases
unless value cases/1
cases: next cases
any [not value tail? cases]
]
any [value]
]
with: func [
"Binds and evaluates a block to a specified context."
object [any-word! object! port!] "Target context."
block [any-block!] "Block to be bound."
/only "Returns the block unevaluated."
][
block: bind block object
either only [block] :block
]
envelop: func [
"Returns a block, encloses any value not already of any-block type."
values [any-type!]
][
case [
any-block? values [values]
none? values [make block! 0]
else [reduce [values]]
]
]
press: func [
"Evaluates and joins a block of values omitting unset and none values."
values [any-block! string! none!]
/local out
][
any [values return none]
values: reduce envelop values
remove-each value values [any [unset? get/any 'value none? value]]
append copy "" values
]
raise: func [[throw] reason][throw make error! press reason]
form-error: func [reason [error!] /local type message][
reason: make disarm reason []
type: system/error/(reason/type)/type
message: reform bind envelop system/error/(reason/type)/(reason/id) reason
reason: rejoin [
"** " type ": " message
"^/** Where: " mold reason/where
"^/** Near: " mold reason/near
]
]
true?: func [test][not not test]
export: func [words [word! block!] /to dest [object!] /local word][
dest: any [dest system/words]
foreach word words [if word? word [set/any in dest word get/any word]]
]
export [func does uses try-else verify assert-all with envelop press raise form-error true? export]
]
;--## SERIES HELPERS
;-------------------------------------------------------------------##
context [
push: func [stack [series! port!] value [any-type!] /only][
head either only [insert/only stack :value][insert stack :value]
]
append: func [
[catch]
{Appends a value to the tail of a series and returns the series head.}
series [series! port!] value
/only "Appends a block value as a block"
][
throw-on-error [
head either only [
insert/only tail series :value
][
insert tail series :value
]
]
]
flatten: func [block [any-block!] /once][
once: either once [
[(block: insert block take block)]
][
[(insert block take block)]
]
parse block [
any [block: any-block! (insert block take block) :block | skip]
]
head block
]
map: func [series [any-block! port!] action [any-function!] /only /copy /local new][
if copy [series: system/words/copy/deep series]
while [not tail? series][
series: either only [
change/part/only series action series/1 1
][
change/part series action series/1 1
]
]
head series
]
each: func [[catch throw] 'word [word! block!] series [any-block!] body [block!] /copy /local new][
case/all [
word? word [word: envelop word]
not parse word [some word!][
raise "WORDS argument should be a word or block of words"
]
copy [series: system/words/copy/deep series]
]
use word compose/deep [
while [not tail? series][
set [(word)] series (body)
series: change/part series reduce [(word)] (length? word)
]
]
head series
]
categorize: func [items [block!] test [any-function! block!] /local out value target][
out: copy []
if block? :test [test: func [item] :test]
foreach item items [
value: test item
unless target: select out value [
repend out [value target: copy []]
]
append target item
]
foreach [value items] out [new-line/all items true]
new-line/all/skip out true 2
]
get-choice: func [word [string! word!] words [any-block!]][
all [
word: attempt [to word! word]
find words word
word
]
]
get-class: func [classes [block!] item /local type][
all [
type: type? classes/1
classes: find classes item
first find/reverse classes type
]
]
link-to: func ['path [any-block!] /full /local out][
out: copy %""
path: compose to block! path
foreach val path [
case [
issue? val [append out mold val]
get-word? val [repend out ["/" get/any :val]]
parse/all form val [["." | ","] to end][append out form val]
parse/all form val [["`" | "!"] to end][append out back change form val ","]
refinement? val [append out replace mold val "/" OOGIEBOOGIE]
val [repend out ["/" form val]]
]
]
either full [join settings/home either find/match out %/ [next out][out]][out]
]
compose-path: func [
"Evaluates a path and reduces contained paren values"
'path [path! lit-path! word! lit-word!]
][
to path! new-line/all compose to block! path none
]
paginate: func [
"Paginate a Series of Known Length"
series [series! port!]
page [integer! none!]
/window padding /size length
][
page: any [page 1]
length: any [length 15]
padding: any [padding 2]
context [
last: max 1 to integer! (length? series) - 1 / length + 1
current: max 1 min last page
next: either last > current [current + 1][false]
previous: either 1 < current [current - 1][false]
records: copy/part skip series offset: current - 1 * length length
upper: copy [] lower: copy []
repeat cnt padding [
insert lower current - cnt
append upper current + cnt
]
remove-each val lower [val <= 1]
remove-each val upper [val >= last]
start: current - 2 <= padding
end: last - current - 1 <= padding
]
]
prepare: use [rule flat nest val][
rule: [val: paren! (do val/1 remove val) :val | get-word! (change/part val get/any val/1 1)]
flat: [some [rule | skip]]
nest: [some [rule | into nest | skip]]
func [block [any-block!] /deep][
parse block: copy/deep block either deep [nest][flat]
block
]
]
some: func [series [block!] block [block!] /empty else [block!]][
else: any [else [none]]
either empty? series :else :block
]
change-status: func [current target conditions [block!]][
foreach [old new permission action] :conditions [
if all [
old = current
new = target
all to block! :permission
][
break/return do action
]
]
]
neaten: func [block [block!] /pairs /flat][
new-line/all/skip block not flat either pairs [2][1]
]
export [
push append flatten map each categorize
get-choice get-class compose-path
prepare link-to paginate some change-status neaten
]
]
;--## KEY-VALUE HELPERS
;-------------------------------------------------------------------##
context [
add-to: func [ser key val][
key: envelop key
map key func [key][as word! key]
if find key none! [return none]
until [
ser: any [
find/tail ser key/1
insert tail ser key/1
]
key: next key
switch type?/word ser/1 [
none! [unless tail? key [insert/only ser ser: copy []]]
string! [change/only ser ser: envelop ser/1]
block! [ser: ser/1]
]
if tail? key [append ser val]
]
]
get-from: func [series 'key][
key: copy envelop key
while [all [not tail? key any-block? series]][
series: select series take key
]
all [tail? key series]
]
export [add-to get-from]
]
;--## GRAMMAR SETS
;-------------------------------------------------------------------##
context [
ascii: charset ["^/^-" #"^(20)" - #"^(7E)"]
digit: charset [#"0" - #"9"]
upper: charset [#"A" - #"Z"]
lower: charset [#"a" - #"z"]
alpha: union upper lower
alphanum: union alpha digit
hex: union digit charset [#"A" - #"F" #"a" - #"f"]
symbol: file*: union alphanum charset "_-"
url-: union alphanum charset "!'*,-._~" ; "!*-._"
url*: union url- charset ":+%&=?"
space: charset " ^-"
ws: charset " ^-^/"
word1: union alpha charset "!&*+-.?_|"
word*: union word1 digit
html*: exclude ascii charset {&<>"}
para*: path*: union alphanum charset "!%'+-._"
extended: charset [#"^(80)" - #"^(FF)"]
chars: complement nochar: charset " ^-^/^@^M"
ascii+: charset [#"^(20)" - #"^(7E)"]
wiki*: complement charset [#"^(00)" - #"^(1F)" {:*.<>} #"{" #"}"]
name: union union lower digit charset "*!',()_-"
wordify-punct: charset "-_()!"
ucs: charset ""
utf-8: use [utf-2 utf-3 utf-4 utf-5 utf-b][
utf-2: #[bitset! 64#{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wAAAAA=}]
utf-3: #[bitset! 64#{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAA=}]
utf-4: #[bitset! 64#{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wA=}]
utf-5: #[bitset! 64#{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8=}]
utf-b: #[bitset! 64#{AAAAAAAAAAAAAAAAAAAAAP//////////AAAAAAAAAAA=}]
[utf-2 1 utf-b | utf-3 2 utf-b | utf-4 3 utf-b | utf-5 4 utf-b]
]
get-ucs-code: decode-utf: use [utf-os utf-fc int][
utf-os: [0 192 224 240 248 252]
utf-fc: [1 64 4096 262144 16777216]
func [char][
int: 0
char: change char char/1 xor pick utf-os length? char
forskip char 1 [change char char/1 xor 128]
char: head reverse head char
forskip char 1 [int: (to integer! char/1) * (pick utf-fc index? char) + int]
all [int > 127 int <= 65535 int]
]
]
inline: [ascii+ | utf-8]
text-row: [chars any [chars | space]]
text: [ascii | utf-8]
ident: [alpha 0 14 file*]
wordify: [alphanum 0 99 [wordify-punct | alphanum]]
word: [word1 0 25 word*]
number: [some digit]
integer: [opt #"-" number]
wiki: [some [wiki* | utf-8]]
ws*: white-space: [some ws]
encode-utf8: func [
"Encode a code point in UTF-8 format"
char [integer!] "Unicode code point"
][
as-string to binary! reduce case [
char <= 127 [[char]]
char <= 2047 [[
char and 1984 / 64 + 192
char and 63 + 128
]]
char <= 65535 [[
char and 61440 / 4096 + 224
char and 4032 / 64 + 128
char and 63 + 128
]]
char <= 2097151 [[
char and 1835008 / 262144 + 240
char and 258048 / 4096 + 128
char and 4032 / 64 + 128
char and 63 + 128
]]
; true [[]]
true [[40 63 41]]
]
]
amend: func [rule [block!]][
bind rule 'self
]
export [get-ucs-code decode-utf encode-utf8 amend]
]
;--## STRING HELPERS
;-------------------------------------------------------------------##
context [
pad: func [text length [integer!] /with padding [char!]][
padding: any [padding #"0"]
text: form text
skip tail insert/dup text padding length negate length
]
url-encode: use [ch sp encode][
ch: charset {!'*-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~}
encode: func [text][insert next text enbase/base form text/1 16 change text "%"]
func [text [any-string!] /wiki][
sp: either wiki [#"_"][#"+"]
parse/all copy text [
copy text any [
text: some ch | #" " (change text sp)
| [#"_" | #"." | #","] (all [wiki encode text]) | skip (encode text)
]
]
text
]
]
url-decode: use [deplus sp decrlf][
deplus: func [text][
parse/all text [
some [to sp text: (text: change text #" ") :text] to end
]
head text
]
decrlf: func [text][
parse/all text [
some [to crlf text: (text: change/part text #"^/" 2) :text] to end
]
head text
]
func [text [any-string!] /wiki][
sp: either wiki [#"_"][#"+"]
decrlf dehex deplus to string! text
]
]
load-webform: func [query [string! none!] /loose /local result name value][
query: any [query ""]
result: copy []
if query [
remove-each value query: parse/all query "&" [empty? value]
map/only query func [value][parse/all value "="]
foreach value query [change/only value parse/all value/1 "."]
]
parse query [
any [
into [
set name block! set value opt string! (
add-to result name any [all [value url-decode value] ""]
)
]
]
]
result
]
to-webform: use [
webform form-key emit
here path value block array object
][
path: []
form-key: does [
remove head foreach key path [insert "" reduce ["." key]]
]
emit: func [data][
repend webform ["&" form-key "=" url-encode data]
]
value: [
here: number! (emit form here/1)
| [logic! | 'true | 'false] (emit form here/1)
| [none! | 'none]
| date! (replace form date "/" "T")
| [any-string! | tuple! | money! | time!] (emit form here/1)
]
array: [any value end]
object: [
any [
here: [word! | set-word!] (insert path to word! here/1)
[value | block] (remove path)
] end
]
block: [
here: [
any-block! (change/only here copy here/1)
| object! (change/only here body-of here/1)
] :here into [object | mk: array]
]
func [
"Serializes block data as URL-Encoded Web Form string"
data [block! object!] /prefix
][
clear path
webform: copy ""
data: either object? data [body-of data][copy data]
if parse copy data object [
either prefix [back change webform "?"][remove webform]
]
]
]
; doesn't work right, yet
; text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5
decode-options: func [options [string! none!] type [datatype!]][
options: any [options ""]
options: parse lowercase options ";,"
map options func [val [string!] /local weight][
either parse/all val ["q=" weight: "0." integer!][
load weight
][
as :type val
]
]
]
compose-tags: func [body [string!] callback [any-function!] /local out tag block][
out: make string! length? body
while [tag: find body "=["][
insert/part tail out body offset? body tag
body: either error? err: try [
block: load/next tag: next tag
][
append out "**Tag Loading Error: #"
tag
][
append out any [callback first block ""]
second block
]
]
append out body
]
prep: func [value [any-type!]][
form any [value ""]
]
interpolate: func [body [string!] escapes [any-block!] /local out][
body: out: copy body
parse/all body [
any [
to #"%" body: (
body: change/part body reduce any [
select/case escapes body/2 body/2
] 2
) :body
]
]
out
]
sanitize: func [text [any-string!] /local char] amend [
parse/all copy text [
copy text any [
text: some html*
| #"&" (text: change/part text "&" 1) :text
| #"<" (text: change/part text "<" 1) :text
| #">" (text: change/part text ">" 1) :text
| #"^"" (text: change/part text """ 1) :text
| #"^M" (remove text) :text
| copy char utf-8 (text: change/part text rejoin ["&#" get-ucs-code char ";"] length? char)
| skip (text: change/part text rejoin ["#(" to integer! text/1 ")"] 1) :text
; | skip (text: change text "#") :text
]
]
any [text make string! 32]
]
load-multipart: func [
[catch] data [binary!] boundary
/local store name content filetype filename
file-prototype qchars nchars dchars
][
store: copy []
file-prototype: context [name: data: type: meta: #[none]]
qchars: #[bitset! 64#{//////v///////////////////////////////////8=}]
nchars: #[bitset! 64#{//////////f///////////////////////////////8=}]
unless parse/all/case data [boundary data: to end][
raise "Postdata not Multipart"
]
boundary: join crlf boundary
unless parse/all/case data [
some [
"--" crlf end |
(name: content: filemime: filetype: filename: none)
crlf {Content-Disposition: form-data; name=}
[{"} copy name some qchars {"} | copy name some nchars]
(name: parse/all name ".")
opt [
{; filename=} [
[{"} copy filename any qchars {"} | copy filename any nchars]
crlf {Content-Type: } copy filetype to crlf
]
]
crlf crlf copy content to boundary boundary (
content: any [content ""]
either all [filetype filetype: as path! filetype][
filename: either filename [
as file! any [
find/last/tail filename #"/"
find/last/tail filename #"\"
find/last/tail filename #":"
filename
]
][%file.dat]
content: either filetype/1 = 'text [decrlf content][to binary! content]
add-to store name make file-prototype [
name: :filename type: :filetype data: :content
]
][
add-to store name to string! decrlf content
]
)
]
][
raise "Invalid Multipart Postdata"
]
store
]
string-length?: func [[catch] string [any-string!] /local counter][
either parse/all string amend [
(counter: 0)
any [[ascii | utf-8] (counter: counter + 1)]
][counter][raise "String contains invalid characters."]
]
export [
pad url-encode url-decode load-webform to-webform decode-options
load-multipart compose-tags prep interpolate sanitize string-length?
]
]
;--## PORT HELPERS
;-------------------------------------------------------------------##
context [
add-protocol: func ['name id handler /with block][
unless in system/schemes name [
system/schemes: make system/schemes compose [
(to set-word! name) #[none]
]
]
set in system/schemes name make system/standard/port compose [
scheme: name
port-id: (id)
handler: (handler)
passive: #[none]
cache-size: 5
proxy: make object! [host: port-id: user: pass: type: bypass: #[none]]
(block)
]
]
to-header: func [object [object!] /local header][
header: make string! (20 * length? words-of object)
foreach word words-of object [
if get :word [
insert tail header reduce [word ": " get :word newline]
]
]
header
]
codes: [read 1 write 2 append 4 new 8 binary 32 lines 64 direct 524288]
get-port-flags: func [port words][
remove-each word copy words [
word: select codes word
word <> (port/state/flags and word)
]
]
chars: charset [#"a" - #"z" #"A" - #"Z" #"0" - #"9" "!%()+,-_"]
; #[bitset! 64#{AAAAACIo/wP+//+H/v//BwAAAAAAAAAAAAAAAAAAAAA=}]
space!: context [
root: domain: path: target: folder: file: suffix: #[none]
]
get-space: func [base [url!] location [url!] /local space][
base: form base
space: make space! [uri: :location]
if all with/only space [
parse/all uri [
base
copy domain some chars #"/"
copy path any [some chars opt [#"." 1 10 chars] #"/"]
copy target opt [any chars 1 2 [#"." 1 10 chars]]
]
root: select settings/spaces domain
] with/only space [
path: all [path to file! path]
target: all [target to file! target]
folder: join root any [path ""]
file: join folder any [target ""]
suffix: suffix? file
self
]
]
export [add-protocol to-header get-port-flags get-space]
]
;--## VALUES HELPERS
;-------------------------------------------------------------------##
context [
pad-zone: func [time /flat][
rejoin [
pick "-+" time/hour < 0
pad abs time/hour 2
either flat [""][#":"]
pad time/minute 2
]
]
pad-precise: func [seconds [number!] /local out][
seconds: form make time! seconds
head change copy "00.000000" find/last/tail form seconds ":"
]
to-iso-week: use [get-iso-year][
get-iso-year: func [year [integer!] /local d1 d2][
d1: to-date join "4-Jan-" year
d2: to-date join "28-Dec-" year
reduce [d1 + 1 - d1/weekday d2 + 7 - d2/weekday]
]
func [date [date!] /local out d1 d2][
out: 0x0
set [d1 d2] get-iso-year out/y: date/year
case [
date < d1 [d1: first get-iso-year out/y: date/year - 1]
date > d2 [d1: first get-iso-year out/y: date/year + 1]
]
out/x: date + 8 - date/weekday - d1 / 7
out
]
]
to-epoch-time: func [date [date!]][
; date/time: date/time - date/zone
date: form any [
attempt [to integer! difference date 1-Jan-1970/0:0:0]
date - 1-Jan-1970/0:0:0 * 86400.0
]
clear find/last date "."
date
]
date-codes: [
#"a" [copy/part pick system/locale/days date/weekday 3]
#"A" [pick system/locale/days date/weekday]
#"b" [copy/part pick system/locale/months date/month 3]
#"B" [pick system/locale/months date/month]
#"C" [to integer! date/year / 100]
#"d" [pad date/day 2]
#"D" [date/year #"-" pad date/month 2 #"-" pad date/day 2]
#"e" [date/day]
#"f" [find/tail pad-precise time/second "."]
#"g" [pad (second to-iso-week date) // 100 2]
#"G" [second to-iso-week date]
#"h" [time/hour + 11 // 12 + 1]
#"H" [pad time/hour 2]
#"i" [any [get-class [st 1 21 31 nd 2 22 rd 3 23] date/day "th"]]
#"I" [pad time/hour + 11 // 12 + 1 2]
#"j" [pad date/julian 3]
#"J" [date/julian]
#"m" [pad date/month 2]
#"M" [pad time/minute 2]
#"p" [pick ["am" "pm"] time/hour < 12]
#"P" [pick ["AM" "PM"] time/hour < 12]
#"s" [to-epoch-time date]
#"S" [pad to integer! time/second 2]
#"t" [#"^-"]
#"T" [pad time/hour 2 #":" pad time/minute 2 #":" pad round time/second 2]
#"u" [date/weekday]
#"U" [pad to integer! date/julian + 6 - (date/weekday // 7) / 7 2]
#"V" [pad first to-iso-week date 2]
#"w" [date/weekday // 7]
#"W" [pad to integer! date/julian + 7 - date/weekday / 7 2]
#"y" [pad date/year // 100 2]
#"Y" [date/year]
#"z" [pad-zone/flat zone]
#"Z" [pad-zone zone]
#"c" [
date/year #"-" pad date/month 2 "-" pad date/day 2 "T"
pad time/hour 2 #":" pad time/minute 2 #":" pad to integer! time/second 2
either gmt ["Z"][pad-zone zone]
]
]
form-date: func [date [date!] format [any-string!] /gmt /local time zone nyd][
either date/time [
if date/zone [date/time: date/time - date/zone]
date/zone: either gmt [0:00][settings/zone]
date/time: round date/time + date/zone
][
date/time: 0:00
date/zone: either gmt [0:00][settings/zone]
]
time: date/time
zone: date/zone
interpolate format bind date-codes 'date
]
form-time: func [time [time!] format [any-string!] /local date zone][
date: now/date zone: 0:00
interpolate format bind date-codes 'time
]
color-codes: [
#"r" [color/1] #"1" [to char! color/1]
#"g" [color/2] #"2" [to char! color/2]
#"b" [color/3] #"3" [to char! color/3]
#"a" [color/4] #"4" [to char! color/4]
#"R" [skip tail to-hex color/1 -2]
#"G" [skip tail to-hex color/2 -2]
#"B" [skip tail to-hex color/3 -2]
#"A" [skip tail to-hex color/4 -2]
]
form-color: func [color [tuple!] format [any-string!]][
bind color-codes 'color
color: 0.0.0.0 + color
interpolate format color-codes
]
pluralize: func [string [string!] count [number!]][
unless any [count = 1 count = -1][string: join string "s"]
reform [count string]
]
export [form-date form-time to-local-time form-color pluralize]
]
;--## VALUES FILTER
;-------------------------------------------------------------------##
context [
masks: reduce amend [
issue! [some url*]
logic! ["true" | "on" | "yes" | "1"]
word! [word]
url! [ident #":" some [url* | #":" | #"/"]]
email! [some url* #"@" some url*]
path! [word 1 5 [#"/" [word | integer]]]
integer! [integer]
string! [some [some ascii | utf-8]]
'positive [number]
'id [ident]
'key [word 0 6 [#"." word]]
]
load-rfc3339: func [date [string!]][
if parse/all date amend [
copy date [
3 5 digit "-" 1 2 digit "-" 1 2 digit
opt [
"T" 1 2 digit ":" 1 2 digit (change mark "/")
opt [
":" 1 2 digit opt ["." 1 6 digit]