diff --git a/CHANGES.md b/CHANGES.md index 125feea..4473e85 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ * Added `traverse-all` which returns a transducer that traverses over all elements matching the given path. * `select-first` and `select-any` now avoid traversal beyond the first value matched by the path (like when using `ALL`), so they are faster now for those use cases. * Add `NAME` and `NAMESPACE` navigators +* Extend `srange`, `BEGINNING`, `END`, `FIRST`, and `LAST` on strings to navigate to substrings * Improved `ALL` performance for PersistentHashSet * Dynamic navs automatically compile sequence returns if completely static * Eliminate reflection warnings for clj (thanks @mpenet) diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index 786ba7a..dc23ae6 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -706,10 +706,12 @@ BEGINNING [] (select* [this structure next-fn] - (next-fn [])) + (next-fn (if (string? structure) "" []))) (transform* [this structure next-fn] - (let [to-prepend (next-fn [])] - (n/prepend-all structure to-prepend)))) + (if (string? structure) + (str (next-fn "") structure) + (let [to-prepend (next-fn [])] + (n/prepend-all structure to-prepend))))) (defnav @@ -717,11 +719,12 @@ END [] (select* [this structure next-fn] - (next-fn [])) + (next-fn (if (string? structure) "" []))) (transform* [this structure next-fn] - (let [to-append (next-fn [])] - (n/append-all structure to-append)))) - + (if (string? structure) + (str structure (next-fn "")) + (let [to-append (next-fn [])] + (n/append-all structure to-append))))) (defnav ^{:doc "Navigates to the specified subset (by taking an intersection). diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index c8030c5..ab2c8e5 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -521,14 +521,21 @@ (mk-comp-navs) (defn srange-transform* [structure start end next-fn] - (let [structurev (vec structure) - newpart (next-fn (-> structurev (subvec start end))) - res (concat (subvec structurev 0 start) - newpart - (subvec structurev end (count structure)))] - (if (vector? structure) - (vec res) - res))) + (if (string? structure) + (let [newss (next-fn (subs structure start end))] + (str (subs structure 0 start) + newss + (subs structure end (count structure)) + )) + (let [structurev (vec structure) + newpart (next-fn (-> structurev (subvec start end))) + res (concat (subvec structurev 0 start) + newpart + (subvec structurev end (count structure)))] + (if (vector? structure) + (vec res) + res + )))) (defn- matching-indices [aseq p] (keep-indexed (fn [i e] (if (p e) i)) aseq)) diff --git a/src/clj/com/rpl/specter/navs.cljc b/src/clj/com/rpl/specter/navs.cljc index e9692dc..7be0aac 100644 --- a/src/clj/com/rpl/specter/navs.cljc +++ b/src/clj/com/rpl/specter/navs.cljc @@ -298,7 +298,11 @@ (defn srange-select [structure start end next-fn] - (next-fn (-> structure vec (subvec start end)))) + (next-fn + (if (string? structure) + (subs structure start end) + (-> structure vec (subvec start end)) + ))) (def srange-transform i/srange-transform*) @@ -430,6 +434,15 @@ (let [i (dec c)] (assoc v i (afn (nth v i))))))) + #?(:clj String :cljs string) + (update-first [s afn] + (str (afn (nth s 0)) (subs s 1 (count s)))) + + (update-last [s afn] + (let [last-idx (-> s count dec)] + (str (subs s 0 last-idx) (afn (nth s last-idx))) + )) + #?(:clj Object :cljs default) (update-first [l val] (update-first-list l val)) @@ -443,11 +456,19 @@ (nth v 0)) (get-last [v] (peek v)) + #?(:clj Object :cljs default) (get-first [s] (first s)) (get-last [s] - (last s))) + (last s)) + + #?(:clj String :cljs string) + (get-first [s] + (nth s 0)) + (get-last [s] + (nth s (-> s count dec)) + )) diff --git a/test/com/rpl/specter/core_test.cljc b/test/com/rpl/specter/core_test.cljc index ff51446..2fcdb4e 100644 --- a/test/com/rpl/specter/core_test.cljc +++ b/test/com/rpl/specter/core_test.cljc @@ -1419,3 +1419,16 @@ (= 'a/e (setval s/NAMESPACE "a" 'e)) (= 'a/e (setval s/NAMESPACE "a" 'f/e)) ) + +(deftest string-navigation-test + (= "ad" (setval (s/srange 1 4) "" "abcd")) + (= "bc" (select-any (s/srange 1 4) "abcd")) + (= "ab" (setval s/END "b" "a")) + (= "ba" (setval s/BEGINNING "b" "a")) + (= "" (select-any s/BEGINNING "abc")) + (= "" (select-any s/END "abc")) + (= \a (select-any s/FIRST "abc")) + (= \c (select-any s/LAST "abc")) + (= "qbc" (setval s/FIRST \q "abc")) + (= "abq" (setval s/FIRST "q" "abc")) + )