diff --git a/coerce.rkt b/coerce.rkt
index 9ad2042..d18c92d 100644
--- a/coerce.rkt
+++ b/coerce.rkt
@@ -1,6 +1,6 @@
#lang racket/base
(require (for-syntax racket/base racket/syntax))
-(require net/url racket/set racket/sequence "len.rkt" "define.rkt")
+(require net/url racket/set racket/sequence "unstable/len.rkt" "define.rkt")
(define-syntax-rule (make-coercion-error-handler target-format x)
(λ(e) (error (string->symbol (format "->~a" target-format)) (format "Can't convert ~s to ~a" x target-format))))
@@ -14,7 +14,7 @@
[(complex? x) (->int (real-part x))]
[(string? x) (let ([strnum (string->number x)])
(if (real? strnum) (->int strnum) (error 'ineligible-string)))]
- [(or (symbol? x) (path? x)) (->int (->string x))]
+ [(or (symbol? x) (path? x) (bytes? x)) (->int (->string x))]
[(char? x) (char->integer x)]
[else (len x)]))) ; covers Lengthable types
@@ -29,7 +29,7 @@
[(symbol? x) (symbol->string x)]
[(number? x) (number->string x)]
[(path? x) (path->string x)]
- [(char? x) (format "~a" x)]
+ [(or (char? x) (bytes? x)) (format "~a" x)]
[(url? x) (url->string x)]
[else (error 'bad-type)]))))
diff --git a/container.rkt b/container.rkt
deleted file mode 100644
index 7704eff..0000000
--- a/container.rkt
+++ /dev/null
@@ -1,46 +0,0 @@
-#lang racket/base
-(require "define.rkt" "coerce.rkt" "len.rkt" racket/list racket/set racket/sequence racket/stream racket/dict)
-
-(define (sliceable-container? x)
- (ormap (λ(proc) (proc x)) (list list? string? symbol? vector? path? (λ(i) (and (not (dict? i)) (sequence? i))))))
-
-(define (gettable-container? x)
- (ormap (λ(proc) (proc x)) (list sliceable-container? dict?)))
-
-
-(define+provide+safe (get container start [end #f])
- ((gettable-container? any/c) ((or/c (and/c integer? positive?) #f)) . ->* . any)
-
- (define result
- ;; use handler to capture error & print localized error message
- (with-handlers ([exn:fail? (λ(exn) (error (format "get: couldn't retrieve ~a from ~a" (if end (format "items ~a through ~a" start end) (format "item ~a" start)) container)))])
- (let ([end (if (and (equal? end #f) (sliceable-container? container)) (add1 start) end)])
- (cond
- [(list? container) (for/list ([i (in-range start end)]) (list-ref container i))]
- [(vector? container) (for/vector ([i (in-range start end)]) (vector-ref container i))]
- [(string? container) (substring container start end)]
- [(symbol? container) (->symbol (get (->string container) start end))]
- [(path? container) (get (explode-path container) start end)]
- [(dict? container) (dict-ref container start)]
- [(sequence? container) (get (->list container) start end)]
- [else (error)]))))
-
- ;; don't return single-item results inside a list
- ;; check for integer because integers don't have length
- (if (and (not (integer? result)) (= (len result) 1) (sliceable-container? container))
- (car (->list result))
- result))
-
-(define (listlike-container? container)
- (ormap (λ(pred) (pred container)) (list vector? set? sequence?)))
-
-(define+provide+safe (in? item container)
- (any/c any/c . -> . boolean?)
- (->boolean (cond
- [(list? container) (member item container)]
- [(dict? container) (dict-has-key? container item)]
- [(path? container) (in? (->path item) (explode-path container))]
- [(stringish? container) (regexp-match (->string item) (->string container))]
- ;; location relevant because dicts and strings are also listlike (= sequences)
- [(listlike-container? container) (in? item (->list container))]
- [else #f])))
diff --git a/define.rkt b/define.rkt
index 715a236..3c15665 100644
--- a/define.rkt
+++ b/define.rkt
@@ -13,7 +13,7 @@
;; need to use stx as context to get correct require behavior
(datum->syntax stx `(begin
(module mod-name typed/racket/base/no-check
- (require sugar/include)
+ (require sugar/unstable/include)
(include-without-lang-line ,(syntax->datum #'path-spec)))
(require (quote mod-name)))))]))
diff --git a/file.rkt b/file.rkt
index 0d61f04..3d8b5b9 100644
--- a/file.rkt
+++ b/file.rkt
@@ -1,5 +1,5 @@
#lang racket/base
-(require "define.rkt" racket/set "coerce.rkt" racket/path "string.rkt")
+(require "define.rkt" racket/set "coerce.rkt" racket/path "unstable/string.rkt")
(define+provide+safe (get-enclosing-dir p)
@@ -45,7 +45,7 @@
(coerce/path? . -> . path?)
;; pass through hidden files (those starting with a dot)
(let ([x (->path x)])
- (if (x . starts-with? . ".")
+ (if ((->string x) . starts-with? . ".")
x
(path-replace-suffix x ""))))
@@ -55,7 +55,7 @@
(coerce/path? . -> . path?)
;; pass through hidden files (those starting with a dot)
(let ([x (->path x)])
- (if (x . starts-with? . ".")
+ (if ((->string x) . starts-with? . ".")
x
(let ([path-with-removed-ext (remove-ext x)])
(if (equal? x path-with-removed-ext)
diff --git a/len.rkt b/len.rkt
deleted file mode 100644
index 863c944..0000000
--- a/len.rkt
+++ /dev/null
@@ -1,15 +0,0 @@
-#lang racket/base
-(require "define.rkt" racket/set racket/sequence)
-
-(define+provide+safe (len x)
- ((or/c list? vector? set? sequence? string? symbol? path? hash?) . -> . integer?)
- (cond
- [(list? x) (length x)]
- [(string? x) (string-length x)]
- [(symbol? x) (len (symbol->string x))]
- [(path? x) (len (path->string x))]
- [(vector? x) (vector-length x)]
- [(hash? x) (len (hash-keys x))]
- [(set? x) (len (set->list x))]
- [(and (sequence? x) (not (integer? x))) (len (sequence->list x))]
- [else (error "len: can't calculate length of" x)]))
\ No newline at end of file
diff --git a/list.rkt b/list.rkt
index c85afd0..a363ac3 100644
--- a/list.rkt
+++ b/list.rkt
@@ -1,6 +1,6 @@
#lang racket/base
(require (for-syntax racket/base) racket/list racket/set racket/function)
-(require "len.rkt" "coerce.rkt" "define.rkt")
+(require "unstable/len.rkt" "coerce.rkt" "define.rkt")
(define (list-of-lists? xs) (and (list? xs) (andmap list? xs)))
(define (index? x) (and (integer? x) (not (negative? x))))
diff --git a/main.rkt b/main.rkt
index 84a6761..5739942 100644
--- a/main.rkt
+++ b/main.rkt
@@ -10,14 +10,8 @@
(r+p "cache.rkt")
(r+p "coerce.rkt")
-(r+p "container.rkt")
(r+p "debug.rkt")
(r+p "define.rkt")
(r+p "file.rkt")
-(r+p "include.rkt")
-(r+p "len.rkt")
(r+p "list.rkt")
-(r+p "misc.rkt")
-(r+p "string.rkt")
(r+p "test.rkt")
-(r+p "xml.rkt")
\ No newline at end of file
diff --git a/scribblings/sugar.scrbl b/scribblings/sugar.scrbl
index 006d15a..ca26bc9 100644
--- a/scribblings/sugar.scrbl
+++ b/scribblings/sugar.scrbl
@@ -12,7 +12,7 @@
@defmodule[#:multi (sugar (submod sugar safe))]
-A collection of small functions to help make Racket code simpler & more readable.
+A collection of small functions to help make Racket code simpler & more readable. Well, according to me, anyhow.
Sugar can be invoked two ways: as an ordinary library, or as a library with contracts (using the @tt{safe} submodule).
@@ -25,22 +25,12 @@ Sugar can be invoked two ways: as an ordinary library, or as a library with cont
@include-section["coerce.scrbl"]
-@include-section["container.scrbl"]
-
@include-section["debug.scrbl"]
@include-section["file.scrbl"]
-@include-section["include.scrbl"]
-
-@include-section["len.scrbl"]
-
@include-section["list.scrbl"]
-@include-section["string.scrbl"]
-
-@include-section["xml.scrbl"]
-
@include-section["license.scrbl"]
@;index-section[]
diff --git a/scribblings/xml.scrbl b/scribblings/xml.scrbl
index f84a7cc..78b77f2 100644
--- a/scribblings/xml.scrbl
+++ b/scribblings/xml.scrbl
@@ -9,7 +9,7 @@
@defmodule[#:multi (sugar/xml (submod sugar/xml safe))]
-Making it easier to do the simplest kind of round-trip with XML: convert an XML string to X-expressions, manipulate, and then convert these X-expressions back to an XML string. @bold{This submodule is untyped only.}
+Making it easier to do the simplest kind of round-trip with XML: convert an XML string to X-expressions, manipulate, and then convert these X-expressions back to an XML string.
@defproc[
(xml-string->xexprs
diff --git a/string.rkt b/string.rkt
deleted file mode 100644
index d9bd934..0000000
--- a/string.rkt
+++ /dev/null
@@ -1,25 +0,0 @@
-#lang racket/base
-(require "define.rkt" "coerce.rkt")
-
-
-(define+provide+safe (starts-with? str starter)
- (string? string? . -> . coerce/boolean?)
- (let ([str (->string str)]
- [starter (->string starter)])
- (and (<= (string-length starter) (string-length str))
- (equal? (substring str 0 (string-length starter)) starter))))
-
-
-(define+provide+safe (ends-with? str ender)
- (string? string? . -> . coerce/boolean?)
- (let ([str (->string str)]
- [ender (->string ender)])
- (and (<= (string-length ender) (string-length str))
- (equal? (substring str (- (string-length str) (string-length ender)) (string-length str)) ender))))
-
-
-(define+provide+safe (capitalized? str)
- (string? . -> . coerce/boolean?)
- (let ([str (->string str)])
- (char-upper-case? (car (string->list str)))))
-
diff --git a/test/main.rkt b/test/main.rkt
index 99d3188..fbc4dd7 100644
--- a/test/main.rkt
+++ b/test/main.rkt
@@ -30,6 +30,7 @@
(check-equal? (->int (make-list 42 null)) 42)
(check-equal? (->string "foo") "foo")
+ (check-equal? (->string #"foo") "foo")
(check-equal? (->string '()) "")
(check-equal? (->string (void)) "")
(check-equal? (->string 'foo) "foo")
@@ -40,6 +41,7 @@
(check-equal? (->string #\¶) "¶")
(check-equal? (->path "foo") (string->path "foo"))
+ (check-equal? (->path #"foo") (string->path "foo"))
(check-equal? (->path 'foo) (string->path "foo"))
(check-equal? (->path 123) (string->path "123"))
(check-equal? (->path (string->url "foo/bar.html")) (string->path "foo/bar.html"))
@@ -55,30 +57,6 @@
(check-true (->boolean "foo"))
(check-true (->boolean '()))
(check-true (->boolean '(1 2 3)))
-
- (check-equal? (len '(1 2 3)) 3)
- (check-not-equal? (len '(1 2)) 3) ; len 2
- (check-equal? (len "foo") 3)
- (check-not-equal? (len "fo") 3) ; len 2
- (check-equal? (len 'foo) 3)
- (check-not-equal? (len 'fo) 3) ; len 2
- (check-equal? (len (list->vector '(1 2 3))) 3)
- (check-not-equal? (len (list->vector '(1 2))) 3) ; len 2
- (check-equal? (len (set 1 2 3)) 3)
- (check-not-equal? (len (set 1 2)) 3) ; len 2
- (check-equal? (len (make-hash '((a . 1) (b . 2) (c . 3)))) 3)
- (check-not-equal? (len (make-hash '((a . 1) (b . 2)))) 3) ; len 2
-
- (check-true ("foobar" . starts-with? . "foo"))
- (check-true ("foobar" . starts-with? . "f"))
- (check-true ("foobar" . starts-with? . "foobar"))
- (check-false ("foobar" . starts-with? . "bar"))
- (check-false ("foobar" . starts-with? . "."))
- (check-true ("foobar" . ends-with? . "bar"))
- (check-true ("foobar" . ends-with? . "r"))
- (check-true ("foobar" . ends-with? . "foobar"))
- (check-false ("foobar" . ends-with? . "foo"))
-
(check-true (members-unique? '(a b c)))
(check-false (members-unique? '(a b c c)))
(check-true (members-unique? "zoey"))
@@ -122,17 +100,6 @@
(check-true (has-binary-ext? "foo.MP3"))
(check-false (has-binary-ext? "foo.py"))
- (check-true (starts-with? "foobar" "foo"))
- (check-true (starts-with? "foobar" "foobar"))
- (check-false (starts-with? "foobar" "zam"))
- (check-false (starts-with? "foobar" "foobars"))
- (check-true (ends-with? "foobar" "bar"))
- (check-false (ends-with? "foobar" "zam"))
- (check-true (ends-with? "foobar" "foobar"))
- (check-false (ends-with? "foobar" "foobars"))
- (check-true (capitalized? "Brennan"))
- (check-false (capitalized? "foobar"))
-
(check-equal? (slice-at (range 5) 1) '((0) (1) (2) (3) (4)))
(check-equal? (slice-at (range 5) 2) '((0 1) (2 3) (4)))
(check-equal? (slice-at (range 5) 2 #t) '((0 1) (2 3)))
@@ -183,68 +150,10 @@
(check-exn exn:fail? (λ _ (slice-at (range 5) 0))) ; needs a positive integer as second arg
(check-exn exn:fail? (λ _ (slicef-at (range 5) 3))) ; needs a procedure as second arg
- (check-equal? (get '(0 1 2 3 4 5) 2) 2)
- (check-exn exn:fail? (λ() (get '(0 1 2 3 4 5) 100))) ; index too big
- (check-equal? (get `(0 1 ,(list 2) 3 4 5) 2) (list 2))
- (check-equal? (get '(0 1 2 3 4 5) 0 2) '(0 1))
- (check-equal? (get (list->vector '(0 1 2 3 4 5)) 2) 2)
- (check-equal? (get (list->vector'(0 1 2 3 4 5)) 0 2) (list->vector '(0 1)))
- (check-equal? (get "purple" 2) "r")
- (check-equal? (get "purple" 0 2) "pu")
- (check-equal? (get 'purple 2) 'r)
- (check-equal? (get 'purple 0 2) 'pu)
- (check-equal? (get (string->path "/root/foo/bar/file.txt") 2) (string->path "foo"))
- (check-equal? (get (string->path "/root/foo/bar/file.txt") 0 2) (list (string->path "/") (string->path "root")))
- (check-equal? (get (make-hash `((a . ,(list 1)) (b . ,(list 2)) (c . ,(list 3)))) 'a) (list 1))
- (check-exn exn:fail? (λ() (get (make-hash `((a . ,(list 1)) (b . ,(list 2)) (c . ,(list 3)))) 'z))) ; nonexistent key
-
- (check-equal? (get (string->path "/root/foo/bar/file.txt") 1) (string->path "root"))
- (check-equal? (get (string->path "/root/foo/bar/file.txt") 0 3)
- (map string->path '("/" "root" "foo")))
-
- (check-equal? (get (make-hash '((a . 1) (b . 2) (c . 3))) 'b) 2)
-
- (check-true (2 . in? . '(1 2 3)))
- (check-false (4 . in? . '(1 2 3)))
- (check-true (2 . in? . (list->vector '(1 2 3))))
- (check-false (4 . in? . (list->vector '(1 2 3))))
- (check-true ('a . in? . (make-hash '((a . 1) (b . 2) (c . 3)))))
- (check-false ('x . in? . (make-hash '((a . 1) (b . 2) (c . 3)))))
- (check-true ("o" . in? . "foobar"))
- (check-false ("z" . in? . "foobar"))
- (check-true ('o . in? . 'foobar))
- (check-false ('z . in? . 'foobar))
- (check-true ("F" . in? . #\F))
-
- (check-true (in? "foo" (string->path "/root/foo/bar/file.txt")))
- (check-false (in? "zam" (string->path "/root/foo/bar/file.txt")))
(define ys (range 5))
(check-equal? (values->list (shift/values ys -1 'boing)) '(1 2 3 4 boing))
- (check-equal? (values->list (shift/values ys '(-1 0 1) 'boing)) `((1 2 3 4 boing) ,xs (boing 0 1 2 3)))
-
- (require xml)
- (define str "\nhello world")
- (define-values (str-prolog str-doc) (xml-string->xexprs str))
- (check-equal? str-prolog (prolog (list (p-i (location 1 0 1) (location 1 38 39) 'xml "version=\"1.0\" encoding=\"utf-8\"")) #f null))
- (check-equal? str-doc '(root () "hello world"))
- (check-equal? (xexprs->xml-string str-prolog str-doc) str)
-
- (module include-test racket/base
- (require sugar/include)
- (include-without-lang-line "source.rkt")
- (provide included-symbol))
-
- (require 'include-test)
- (check-equal? included-symbol 'bar)
-
- (module no-lang-line-include-test racket/base
- (require sugar/include)
- (include-without-lang-line "no-lang-line-source.txt")
- (provide no-lang-symbol))
-
- (require 'no-lang-line-include-test)
- (check-equal? no-lang-symbol 'bar))
+ (check-equal? (values->list (shift/values ys '(-1 0 1) 'boing)) `((1 2 3 4 boing) ,xs (boing 0 1 2 3))))
diff --git a/unstable/container.rkt b/unstable/container.rkt
new file mode 100644
index 0000000..d5902d7
--- /dev/null
+++ b/unstable/container.rkt
@@ -0,0 +1,86 @@
+#lang racket/base
+(require "../define.rkt" "../coerce.rkt" "len.rkt" racket/list racket/set racket/sequence racket/stream racket/dict)
+
+(define (sliceable-container? x)
+ (ormap (λ(proc) (proc x)) (list list? string? symbol? vector? path? (λ(i) (and (not (dict? i)) (sequence? i))))))
+
+(define (gettable-container? x)
+ (ormap (λ(proc) (proc x)) (list sliceable-container? dict?)))
+
+
+(define+provide+safe (get container start [end #f])
+ ((gettable-container? any/c) ((or/c (and/c integer? positive?) #f)) . ->* . any)
+
+ (define result
+ ;; use handler to capture error & print localized error message
+ (with-handlers ([exn:fail? (λ(exn) (error (format "get: couldn't retrieve ~a from ~a" (if end (format "items ~a through ~a" start end) (format "item ~a" start)) container)))])
+ (let ([end (if (and (equal? end #f) (sliceable-container? container)) (add1 start) end)])
+ (cond
+ [(list? container) (for/list ([i (in-range start end)]) (list-ref container i))]
+ [(vector? container) (for/vector ([i (in-range start end)]) (vector-ref container i))]
+ [(string? container) (substring container start end)]
+ [(symbol? container) (->symbol (get (->string container) start end))]
+ [(path? container) (get (explode-path container) start end)]
+ [(dict? container) (dict-ref container start)]
+ [(sequence? container) (get (->list container) start end)]
+ [else (error)]))))
+
+ ;; don't return single-item results inside a list
+ ;; check for integer because integers don't have length
+ (if (and (not (integer? result)) (= (len result) 1) (sliceable-container? container))
+ (car (->list result))
+ result))
+
+(define (listlike-container? container)
+ (ormap (λ(pred) (pred container)) (list vector? set? sequence?)))
+
+(define+provide+safe (in? item container)
+ (any/c any/c . -> . boolean?)
+ (->boolean (cond
+ [(list? container) (member item container)]
+ [(dict? container) (dict-has-key? container item)]
+ [(path? container) (in? (->path item) (explode-path container))]
+ [(stringish? container) (regexp-match (->string item) (->string container))]
+ ;; location relevant because dicts and strings are also listlike (= sequences)
+ [(listlike-container? container) (in? item (->list container))]
+ [else #f])))
+
+
+
+(module+ test
+ (require rackunit)
+ (check-equal? (get '(0 1 2 3 4 5) 2) 2)
+ (check-exn exn:fail? (λ() (get '(0 1 2 3 4 5) 100))) ; index too big
+ (check-equal? (get `(0 1 ,(list 2) 3 4 5) 2) (list 2))
+ (check-equal? (get '(0 1 2 3 4 5) 0 2) '(0 1))
+ (check-equal? (get (list->vector '(0 1 2 3 4 5)) 2) 2)
+ (check-equal? (get (list->vector'(0 1 2 3 4 5)) 0 2) (list->vector '(0 1)))
+ (check-equal? (get "purple" 2) "r")
+ (check-equal? (get "purple" 0 2) "pu")
+ (check-equal? (get 'purple 2) 'r)
+ (check-equal? (get 'purple 0 2) 'pu)
+ (check-equal? (get (string->path "/root/foo/bar/file.txt") 2) (string->path "foo"))
+ (check-equal? (get (string->path "/root/foo/bar/file.txt") 0 2) (list (string->path "/") (string->path "root")))
+ (check-equal? (get (make-hash `((a . ,(list 1)) (b . ,(list 2)) (c . ,(list 3)))) 'a) (list 1))
+ (check-exn exn:fail? (λ() (get (make-hash `((a . ,(list 1)) (b . ,(list 2)) (c . ,(list 3)))) 'z))) ; nonexistent key
+
+ (check-equal? (get (string->path "/root/foo/bar/file.txt") 1) (string->path "root"))
+ (check-equal? (get (string->path "/root/foo/bar/file.txt") 0 3)
+ (map string->path '("/" "root" "foo")))
+
+ (check-equal? (get (make-hash '((a . 1) (b . 2) (c . 3))) 'b) 2)
+
+ (check-true (2 . in? . '(1 2 3)))
+ (check-false (4 . in? . '(1 2 3)))
+ (check-true (2 . in? . (list->vector '(1 2 3))))
+ (check-false (4 . in? . (list->vector '(1 2 3))))
+ (check-true ('a . in? . (make-hash '((a . 1) (b . 2) (c . 3)))))
+ (check-false ('x . in? . (make-hash '((a . 1) (b . 2) (c . 3)))))
+ (check-true ("o" . in? . "foobar"))
+ (check-false ("z" . in? . "foobar"))
+ (check-true ('o . in? . 'foobar))
+ (check-false ('z . in? . 'foobar))
+ (check-true ("F" . in? . #\F))
+
+ (check-true (in? "foo" (string->path "/root/foo/bar/file.txt")))
+ (check-false (in? "zam" (string->path "/root/foo/bar/file.txt"))))
diff --git a/include.rkt b/unstable/include.rkt
similarity index 93%
rename from include.rkt
rename to unstable/include.rkt
index 72d2acf..83304ef 100644
--- a/include.rkt
+++ b/unstable/include.rkt
@@ -4,7 +4,25 @@
racket/private/increader
compiler/cm-accomplice
racket/match racket/function)
- "define.rkt")
+ "../define.rkt")
+
+(module+ test
+ (require rackunit)
+ (module include-test racket/base
+ (require sugar/include)
+ (include-without-lang-line "source.rkt")
+ (provide included-symbol))
+
+ (require 'include-test)
+ (check-equal? included-symbol 'bar)
+
+ (module no-lang-line-include-test racket/base
+ (require sugar/include)
+ (include-without-lang-line "no-lang-line-source.txt")
+ (provide no-lang-symbol))
+
+ (require 'no-lang-line-include-test)
+ (check-equal? no-lang-symbol 'bar))
(provide+safe include-without-lang-line)
diff --git a/unstable/len.rkt b/unstable/len.rkt
new file mode 100644
index 0000000..1d948c4
--- /dev/null
+++ b/unstable/len.rkt
@@ -0,0 +1,31 @@
+#lang racket/base
+(require "../define.rkt" racket/set racket/sequence)
+
+(define+provide+safe (len x)
+ ((or/c list? vector? set? sequence? string? symbol? path? hash?) . -> . integer?)
+ (cond
+ [(list? x) (length x)]
+ [(string? x) (string-length x)]
+ [(symbol? x) (len (symbol->string x))]
+ [(path? x) (len (path->string x))]
+ [(vector? x) (vector-length x)]
+ [(hash? x) (len (hash-keys x))]
+ [(set? x) (len (set->list x))]
+ [(and (sequence? x) (not (integer? x))) (len (sequence->list x))]
+ [else (error "len: can't calculate length of" x)]))
+
+
+(module+ test
+ (require rackunit)
+ (check-equal? (len '(1 2 3)) 3)
+ (check-not-equal? (len '(1 2)) 3) ; len 2
+ (check-equal? (len "foo") 3)
+ (check-not-equal? (len "fo") 3) ; len 2
+ (check-equal? (len 'foo) 3)
+ (check-not-equal? (len 'fo) 3) ; len 2
+ (check-equal? (len (list->vector '(1 2 3))) 3)
+ (check-not-equal? (len (list->vector '(1 2))) 3) ; len 2
+ (check-equal? (len (set 1 2 3)) 3)
+ (check-not-equal? (len (set 1 2)) 3) ; len 2
+ (check-equal? (len (make-hash '((a . 1) (b . 2) (c . 3)))) 3)
+ (check-not-equal? (len (make-hash '((a . 1) (b . 2)))) 3)) ; len 2
\ No newline at end of file
diff --git a/misc.rkt b/unstable/misc.rkt
similarity index 100%
rename from misc.rkt
rename to unstable/misc.rkt
diff --git a/test/no-lang-line-source.txt b/unstable/no-lang-line-source.txt
similarity index 100%
rename from test/no-lang-line-source.txt
rename to unstable/no-lang-line-source.txt
diff --git a/test/source.rkt b/unstable/source.rkt
similarity index 100%
rename from test/source.rkt
rename to unstable/source.rkt
diff --git a/unstable/string.rkt b/unstable/string.rkt
new file mode 100644
index 0000000..4052764
--- /dev/null
+++ b/unstable/string.rkt
@@ -0,0 +1,37 @@
+#lang racket/base
+(require "../define.rkt" "../coerce.rkt")
+
+
+(define+provide+safe (starts-with? str starter)
+ (string? string? . -> . coerce/boolean?)
+ (define pat (regexp (format "^~a" (regexp-quote starter))))
+ (and (regexp-match pat (->string str)) #t))
+
+
+(define+provide+safe (ends-with? str ender)
+ (string? string? . -> . coerce/boolean?)
+ (define pat (regexp (format "~a$" (regexp-quote ender))))
+ (and (regexp-match pat (->string str)) #t))
+
+
+(define+provide+safe (capitalized? str-in)
+ (string? . -> . coerce/boolean?)
+ (define str (->string str-in))
+ (and (positive? (string-length str))
+ (char-upper-case? (car (string->list (car (regexp-match "." str)))))))
+
+
+(module+ test
+ (require rackunit)
+ (check-true (starts-with? "foobar" "foo"))
+ (check-true (starts-with? "foobar" "foobar"))
+ (check-false (starts-with? "foobar" "zam"))
+ (check-false (starts-with? "foobar" "foobars"))
+ (check-false (starts-with? "foo" "."))
+ (check-true (ends-with? "foobar" "bar"))
+ (check-false (ends-with? "foobar" "zam"))
+ (check-true (ends-with? "foobar" "foobar"))
+ (check-false (ends-with? "foobar" "foobars"))
+ (check-true (capitalized? "Brennan"))
+ (check-false (capitalized? "foobar")))
+
diff --git a/xml.rkt b/xml.rkt
index 0dbd260..4ed4a5f 100644
--- a/xml.rkt
+++ b/xml.rkt
@@ -9,4 +9,13 @@
(define+provide+safe (xexprs->xml-string prolog-xexpr root-xexpr)
(xexpr? xexpr? . -> . string?)
- (with-output-to-string (λ _ (write-xml (document (xexpr->xml prolog-xexpr) (xexpr->xml root-xexpr) null)))))
\ No newline at end of file
+ (with-output-to-string (λ _ (write-xml (document (xexpr->xml prolog-xexpr) (xexpr->xml root-xexpr) null)))))
+
+(module+ test
+ (require rackunit)
+ (require xml)
+ (define str "\nhello world")
+ (define-values (str-prolog str-doc) (xml-string->xexprs str))
+ (check-equal? str-prolog (prolog (list (p-i (location 1 0 1) (location 1 38 39) 'xml "version=\"1.0\" encoding=\"utf-8\"")) #f null))
+ (check-equal? str-doc '(root () "hello world"))
+ (check-equal? (xexprs->xml-string str-prolog str-doc) str))
\ No newline at end of file