From dfc91cca672832bd321140df530b873b3bd869de Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Thu, 24 Jul 2014 12:17:24 -0700 Subject: [PATCH] allow doc directory --- .gitignore | 1 - container.rkt | 49 ++++++++++------------ len.rkt | 1 + scribblings/coerce.scrbl | 2 + scribblings/container.scrbl | 83 +++++++++++++++++++++++++++++++++++++ tests.rkt | 21 ++++++---- 6 files changed, 121 insertions(+), 36 deletions(-) create mode 100644 scribblings/container.scrbl diff --git a/.gitignore b/.gitignore index efb2dbf..e86c472 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ Icon .Trashes # generated documentation -doc/* scribblings/*.js scribblings/*.css scribblings/*.html diff --git a/container.rkt b/container.rkt index 73feb21..10e7340 100644 --- a/container.rkt +++ b/container.rkt @@ -1,49 +1,46 @@ #lang racket/base (require "define.rkt") -(require "coerce.rkt" "len.rkt" racket/list) +(require "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?))) + (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? hash?))) + (ormap (λ(proc) (proc x)) (list sliceable-container? dict?))) -(define+provide/contract (get container start [end #f]) - ((gettable-container? any/c) ((λ(i)(or (integer? i) (and (symbol? i) (equal? i 'end))))) . ->* . any/c) +(define/contract+provide (get container start [end #f]) + ((gettable-container? any/c) ((or/c (and/c integer? positive?) #f)) . ->* . any/c) (define result - (with-handlers ([exn:fail? (λ(exn) (error (format "Couldn't get item from ~a" container)))]) - (let ([end (if (sliceable-container? container) - (cond - ;; treat negative lengths as offset from end (Python style) - [(and (integer? end) (< end 0)) (+ (len container) end)] - ;; 'end slices to the end - [(equal? end 'end) (len container)] - ;; default to slice length of 1 (i.e, single-item retrieval) - [(equal? end #f) (add1 start)] - [else end]) - end)]) + ;; 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 (range start end)]) (list-ref container i))] - [(vector? container) (for/vector ([i (range start end)]) (vector-ref container i))] + [(vector? container) (for/vector ([i (range start end)]) (vector-ref container i))] [(string? container) (substring container start end)] [(symbol? container) (->symbol (get (->string container) start end))] - [(hash? container) (hash-ref container start)] + [(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 - (if (and (sliceable-container? container) (= (len result) 1)) + (if (and (= (len result) 1) (sliceable-container? container)) (car (->list result)) result)) +(define (listlike-container? container) + (ormap (λ(pred) (pred container)) (list vector? set? sequence?))) -(define+provide/contract (in? item container) +(define/contract+provide (in? item container) (any/c any/c . -> . coerce/boolean?) (cond - [(list? container) (member item container)] ; returns #f or sublist beginning with item - [(vector? container) (member item (vector->list container))] ; returns #f or sublist beginning with item - [(hash? container) (and (hash-has-key? container item) (get container item))] ; returns #f or hash value - [(string? container) (regexp-match (->string item) container)] ; returns #f or substring beginning with item - [(symbol? container) ((->string item) . in? . (->string container))] ; returns #f or subsymbol (?!) beginning with item - [else #f])) \ No newline at end of file + [(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/len.rkt b/len.rkt index 13687ec..b869f0f 100644 --- a/len.rkt +++ b/len.rkt @@ -8,6 +8,7 @@ [(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))] [(integer? x) (len (number->string x))] diff --git a/scribblings/coerce.scrbl b/scribblings/coerce.scrbl index adb004d..5798961 100644 --- a/scribblings/coerce.scrbl +++ b/scribblings/coerce.scrbl @@ -8,6 +8,8 @@ @title{Coercion} @defmodule[sugar/coerce] +Functions that coerce the datatype of a value to another type. Racket already has type-specific conversion functions. But if you're handling values of indeterminate type — as sometimes happens in an untyped language — then handling the possible cases individually gets to be a drag. + @section{Values} @defproc[ diff --git a/scribblings/container.scrbl b/scribblings/container.scrbl new file mode 100644 index 0000000..d85183e --- /dev/null +++ b/scribblings/container.scrbl @@ -0,0 +1,83 @@ +#lang scribble/manual + +@(require scribble/eval (for-label racket sugar)) + +@(define my-eval (make-base-eval)) +@(my-eval `(require sugar)) + +@title{Container} +@defmodule[sugar/container] + +Type-neutral functions for getting elements out of a container, or testing membership. + + +@defproc[ +(get +[container (or/c list? vector? sequence? dict? string? symbol? path?)] +[which any/c] +[end_which (or/c (and/c integer? positive?) #f) #f]) +any/c] +For a @racket[_container] that's a @racket[dict?], retrieve the element associated with the key @racket[_which]. Raise an error if the key doesn't exist. + +@examples[#:eval my-eval +(get (make-hash '((a . 1) (b . 2) (c . 3))) 'b) +(get (make-hash '((a . 1) (b . 2) (c . 3))) 'z) +] + +For other @racket[_container] types — which are all sequence-like — retrieve the element located at @racket[_which]. Or if the optional @racket[_end_which] argument is provided, retrieve the elements from @racket[_which] to @racket[(sub1 _end_which)], inclusive (i.e., make a slice). Raise an error if @racket[_which] or @racket[_end_which] is out of bounds. + +@examples[#:eval my-eval +(get '(0 1 2 3 4 5) 2) +(get '(0 1 2 3 4 5) 2 4) +(get '(0 1 2 3 4 5) 100) +(get '(0 1 2 3 4 5) 2 100) +(get (list->vector '(0 1 2 3 4 5)) 2) +(get (list->vector '(0 1 2 3 4 5)) 2 4) +(get "purple" 2) +(get "purple" 2 4) +(get 'purple 2) +(get 'purple 2 4) +] + +When @racket[_container] is a path, it's treated as a list of exploded path elements, not as a stringlike value. + +@examples[#:eval my-eval +(get (string->path "/root/foo/bar/file.txt") 1) +(get (string->path "/root/foo/bar/file.txt") 0 3) +] + +To slice to the end of @racket[_container], use @racket[(len _container)] as the value of @racket[_end_which]. + +@examples[#:eval my-eval +(define xs '(0 1 2 3 4 5)) +(get xs 2 (len xs)) +(get (list->vector xs) 2 (len (list->vector xs))) +(define color "purple") +(get color 2 (len color)) +] + + +@defproc[ +(in? +[item any/c] +[container (or/c list? vector? sequence? set? dict? string? symbol? path?)]) +boolean?] +Return @racket[#t] if @racket[_item] is in @racket[_container], or @racket[#f] otherwise. + +@examples[#:eval my-eval +(in? 2 '(0 1 2 3 4 5)) +(in? 'a '(0 1 2 3 4 5)) +(in? 2 (list->vector '(0 1 2 3 4 5))) +(in? "pu" "purple") +(in? "zig" "purple") +(in? 'b (make-hash '((a . 1) (b . 2) (c . 3)))) +(in? 'z (make-hash '((a . 1) (b . 2) (c . 3)))) +] + +As with @racket[get], when @racket[_container] is a path, it's treated as a list of exploded path elements, not as a stringlike value. + +@examples[#:eval my-eval +(in? "foo" (string->path "/root/foo/bar/file.txt")) +(in? "zam" (string->path "/root/foo/bar/file.txt")) +] + diff --git a/tests.rkt b/tests.rkt index b842c0e..ec32562 100644 --- a/tests.rkt +++ b/tests.rkt @@ -47,23 +47,23 @@ (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 '(0 1 2 3 4 5) 2 -1) '(2 3 4)) -(check-equal? (get '(0 1 2 3 4 5) 2 'end) '(2 3 4 5)) (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 (list->vector'(0 1 2 3 4 5)) 2 -1) (list->vector '(2 3 4))) -(check-equal? (get (list->vector'(0 1 2 3 4 5)) 2 'end) (list->vector '(2 3 4 5))) (check-equal? (get "purple" 2) "r") (check-equal? (get "purple" 0 2) "pu") -(check-equal? (get "purple" 2 -1) "rpl") -(check-equal? (get "purple" 2 'end) "rple") (check-equal? (get 'purple 2) 'r) (check-equal? (get 'purple 0 2) 'pu) -(check-equal? (get 'purple 2 -1) 'rpl) -(check-equal? (get 'purple 2 'end) 'rple) +(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-true (2 . in? . '(1 2 3))) @@ -76,7 +76,10 @@ (check-false ("z" . in? . "foobar")) (check-true ('o . in? . 'foobar)) (check-false ('z . in? . 'foobar)) -(check-false ("F" . in? . #\F)) +(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"))) (check-true ("foobar" . starts-with? . "foo"))