first versioned release

dev-fixit v0.1
Matthew Butterick 9 years ago
parent e613f0cade
commit d8e538d53e

@ -4,8 +4,9 @@
(define+provide+safe (make-caching-proc base-proc)
(procedure? . -> . procedure?)
(let ([cache (make-hash)])
(λ args
(hash-ref! cache args (λ () (apply base-proc args))))))
(make-keyword-procedure
(λ (kws kw-args . args)
(hash-ref! cache args (λ () (keyword-apply base-proc kws kw-args args)))))))
(provide+safe define/caching)
(define-syntax (define/caching stx)

@ -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)]))))

@ -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])))

@ -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)))))]))

@ -1,10 +1,5 @@
#lang racket/base
(require "define.rkt" racket/set "coerce.rkt" racket/path "string.rkt")
(define+provide+safe (get-enclosing-dir p)
(coerce/path? . -> . path?)
(simplify-path (build-path (->path p) 'up)))
(require "define.rkt" racket/set "coerce.rkt" racket/path "unstable/string.rkt")
;; does path have a certain extension
@ -45,7 +40,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 +50,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)

@ -1,5 +1,6 @@
#lang info
(define collection "sugar")
(define version "0.1")
(define deps '("base" "rackunit-lib"))
(define build-deps '("scribble-lib" "racket-doc"))
(define scribblings '(("scribblings/sugar.scrbl" ())))

@ -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)]))

@ -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))))
@ -33,7 +33,6 @@
(define+provide+safe (slicef-at xs pred [force? #f])
;; with polymorphic function, use cased typing to simulate optional position arguments
((list? procedure?) (boolean?) . ->* . list-of-lists?)
(define-values (last-list list-of-lists)
(for/fold

@ -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")

@ -8,7 +8,7 @@
@title{Container}
@defmodule[#:multi (sugar/container (submod sugar/container safe))]
Type-neutral functions for getting elements out of a container, or testing membership. @bold{This submodule is untyped only.}
Type-neutral functions for getting elements out of a container, or testing membership.
@defproc[

@ -5,10 +5,10 @@
@(define my-eval (make-base-eval))
@(my-eval `(require sugar))
@title{File}
@title{File extensions}
@defmodule[#:multi (sugar/file (submod sugar/file safe))]
File utilities, mostly in the realm of file extensions. These functions don't access the filesystem.
These functions don't access the filesystem.
Arguments that are @racket[pathish?] can take either a string or a path. For clarity below, I've used strings.
@ -72,18 +72,3 @@ Return a new @racket[_file-path] with @racket[_ext] appended. Note that this doe
(add-ext "foo" "txt")
(add-ext "foo.txt" "jpg")
(add-ext (remove-ext "foo.txt") "jpg")]
@defproc[
(get-enclosing-dir
[path pathish?])
path?]
Return the enclosing directory of @racket[_path]. Does not consult the filesystem about whether @racket[_path] is valid. If you reach the @racket[_root] directory, then @racket[(get-enclosing-dir _root)] will just return @racket[_root] again.
@examples[#:eval my-eval
(define bin (string->path "/usr/bin"))
bin
(get-enclosing-dir bin)
(get-enclosing-dir (get-enclosing-dir bin))
(get-enclosing-dir (get-enclosing-dir (get-enclosing-dir bin)))
]

@ -5,7 +5,7 @@
@(define my-eval (make-base-eval))
@(my-eval `(require sugar racket/list))
@title{List}
@title{Lists}
@defmodule[#:multi (sugar/list (submod sugar/list safe))]

@ -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,20 +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["file-extensions.scrbl"]
@include-section["list.scrbl"]
@include-section["string.scrbl"]
@include-section["xml.scrbl"]
@include-section["license.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

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

@ -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"))
@ -116,23 +94,9 @@
(check-not-equal? (remove-ext* foo.bar.txt-path) foo.bar-path) ; removes more than one ext
(check-equal? (remove-ext* foo.bar.txt-path) foo-path)
(check-equal? (get-enclosing-dir "/Users/MB/foo.txt") (->path "/Users/MB/"))
(check-equal? (get-enclosing-dir "/Users/MB/foo/") (->path "/Users/MB/"))
(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 +147,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 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>hello world</root>")
(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))))

@ -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"))))

@ -4,7 +4,7 @@
racket/private/increader
compiler/cm-accomplice
racket/match racket/function)
"define.rkt")
"../define.rkt")
(provide+safe include-without-lang-line)

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

@ -1,5 +1,5 @@
#lang racket/base
(require "define.rkt" racket/set "coerce.rkt")
(require "../define.rkt" racket/set "../coerce.rkt")
(define+provide+safe (bytecount->string bytecount)

@ -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")))

@ -10,3 +10,12 @@
(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)))))
(module+ test
(require rackunit)
(require xml)
(define str "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>hello world</root>")
(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))
Loading…
Cancel
Save