├── deps.edn ├── ORIGINATOR ├── .github ├── CODEOWNERS └── workflows │ └── test.yml ├── AUTHORS.md ├── .gitignore ├── src └── camel_snake_kebab │ ├── extras.cljc │ ├── internals │ ├── alter_name.cljc │ ├── misc.cljc │ ├── macros.cljc │ └── string_separator.cljc │ └── core.cljc ├── README.md ├── test └── camel_snake_kebab │ ├── test_runner.cljs │ ├── internals │ └── string_separator_test.cljc │ ├── extras_test.cljc │ └── core_test.cljc ├── project.clj ├── NEWS.md └── LICENSE.txt /deps.edn: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /ORIGINATOR: -------------------------------------------------------------------------------- 1 | @qerub 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @qerub 2 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | * Christoffer Sawicki 2 | * ToBeReplaced 3 | * Brendan Bates 4 | * Vincent Pizzo 5 | * Sebastian Bensusan -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | .nrepl-port 7 | /out 8 | *.jar 9 | *.class 10 | .lein-deps-sum 11 | .lein-failures 12 | .lein-plugins 13 | -------------------------------------------------------------------------------- /src/camel_snake_kebab/extras.cljc: -------------------------------------------------------------------------------- 1 | (ns camel-snake-kebab.extras 2 | (:require [clojure.walk :refer [postwalk]])) 3 | 4 | (defn transform-keys 5 | "Recursively transforms all map keys in coll with t." 6 | [t coll] 7 | (letfn [(transform [[k v]] [(t k) v])] 8 | (postwalk (fn [x] (if (map? x) (with-meta (into {} (map transform x)) (meta x)) x)) coll))) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Clojars Project](https://img.shields.io/clojars/v/camel-snake-kebab.svg)](https://clojars.org/camel-snake-kebab) 2 | [![cljdoc badge](https://cljdoc.org/badge/camel-snake-kebab)](https://cljdoc.org/d/camel-snake-kebab/camel-snake-kebab/CURRENT) 3 | [![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://book.babashka.org#badges) 4 | 5 | GOTO https://clj-commons.org/camel-snake-kebab/ 6 | -------------------------------------------------------------------------------- /test/camel_snake_kebab/test_runner.cljs: -------------------------------------------------------------------------------- 1 | (ns camel-snake-kebab.test-runner 2 | (:require [cljs.test :as test] 3 | [doo.runner :refer-macros [doo-all-tests doo-tests]] 4 | [camel-snake-kebab.core-test] 5 | [camel-snake-kebab.extras-test] 6 | [camel-snake-kebab.internals.string-separator-test])) 7 | 8 | (doo-tests 'camel-snake-kebab.core-test 9 | 'camel-snake-kebab.extras-test 10 | 'camel-snake-kebab.internals.string-separator-test) 11 | -------------------------------------------------------------------------------- /src/camel_snake_kebab/internals/alter_name.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc camel-snake-kebab.internals.alter-name 2 | #?(:clj (:import (clojure.lang Keyword Symbol)))) 3 | 4 | (defprotocol AlterName 5 | (alter-name [this f] "Alters the name of this with f.")) 6 | 7 | (extend-protocol AlterName 8 | #?(:clj String 9 | :cljs string) 10 | (alter-name [this f] 11 | (-> this f)) 12 | 13 | Keyword 14 | (alter-name [this f] 15 | (if (namespace this) 16 | (throw (ex-info "Namespaced keywords are not supported" {:input this})) 17 | (-> this name f keyword))) 18 | 19 | Symbol 20 | (alter-name [this f] 21 | (if (namespace this) 22 | (throw (ex-info "Namespaced symbols are not supported" {:input this})) 23 | (-> this name f symbol)))) 24 | -------------------------------------------------------------------------------- /src/camel_snake_kebab/internals/misc.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc camel-snake-kebab.internals.misc 2 | (:require [camel-snake-kebab.internals.string-separator :refer [split generic-separator]] 3 | [clojure.string :refer [join upper-case capitalize]])) 4 | 5 | (defn convert-case [first-fn rest-fn sep s & {:keys [separator] 6 | :or {separator generic-separator}}] 7 | (if-let [[first & rest] (seq (split separator s))] 8 | (join sep (cons (first-fn first) (map rest-fn rest))) 9 | "")) 10 | 11 | (def upper-case-http-headers 12 | #{"CSP" "ATT" "WAP" "IP" "HTTP" "CPU" "DNT" "SSL" "UA" "TE" "WWW" "XSS" "MD5"}) 13 | 14 | (defn capitalize-http-header [s] 15 | (or (upper-case-http-headers (upper-case s)) 16 | (capitalize s))) 17 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject camel-snake-kebab "0.4.4-SNAPSHOT" 2 | :min-lein-version "2.5.2" 3 | 4 | :description "A library for word case conversions." 5 | :url "https://clj-commons.org/camel-snake-kebab/" 6 | :license {:name "Eclipse Public License 1.0" 7 | :url "http://www.eclipse.org/legal/epl-v10.html"} 8 | :scm {:name "git" 9 | :url "https://github.com/clj-commons/camel-snake-kebab"} 10 | 11 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.10.0"] 12 | [org.clojure/clojurescript "1.10.520" :scope "provided"]] 13 | :plugins [[lein-cljsbuild "1.1.3"] 14 | [lein-doo "0.1.11"]]}} 15 | 16 | :jar-exclusions [#"\.DS_Store"] 17 | 18 | :source-paths ["src"] 19 | 20 | :test-paths ["test"] 21 | 22 | :cljsbuild {:builds [{:id "test" 23 | :source-paths ["src" "test"] 24 | :compiler {:output-to "target/testable.js" 25 | :main "camel-snake-kebab.test-runner" 26 | :target :nodejs 27 | :optimizations :simple}}]} 28 | 29 | :aliases {"test-all" ["do" "test," "doo" "node" "test" "once," "doo" "planck" "test" "once"]}) 30 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # News 2 | 3 | ## 2022-05-23: 0.4.3 4 | 5 | * `transform-keys` now keeps metadata. 6 | 7 | ## 2020-10-18: 0.4.2 8 | 9 | * Supports self-hosted ClojureScript. 10 | * Splits strings consistently between CLJ and CLJS. 11 | * Doesn't throw NPE when input only consists of separator(s). 12 | * Doesn't throw AssertionError on `nil` input. 13 | 14 | ## 2019-11-24: 0.4.1 15 | 16 | * Declares macro-generated symbols so that tools like Cursive that use static analysis can find them. 17 | 18 | ## 2016-04-16: 0.4.0 - The Malmö Release 19 | 20 | * Uses reader conditionals instead of cljx. 21 | * Requires Clojure[Script] version >= 1.7. 22 | 23 | ## 2015-01-17: 0.3.0 24 | 25 | * Optional new argument `:separator` allows you to control how words are separated. 26 | * **Breaking changes:** 27 | * `CamelCase` has been renamed to `PascalCase`. 28 | * `SNAKE_CASE` has been renamed to `SCREAMING_SNAKE_CASE`. 29 | * `Snake_case` has been removed. 30 | * The above changes makes the library AOT compilable on case-insensitive filesystems. 31 | In practice, this means you can e.g. use `lein ring uberwar` on Windows and OS X. 32 | 33 | ## 2014-07-28: 0.2.0 34 | 35 | * Supports ClojureScript! 36 | * Has new regex-free internals. 37 | * Handles non-ASCII chars better. 38 | * **Breaking change:** The namespace `camel-snake-kebab` has been renamed to `camel-snake-kebab.core`. 39 | -------------------------------------------------------------------------------- /test/camel_snake_kebab/internals/string_separator_test.cljc: -------------------------------------------------------------------------------- 1 | (ns camel-snake-kebab.internals.string-separator-test 2 | (:require [camel-snake-kebab.internals.string-separator :refer [split generic-separator]] 3 | #?(:clj [clojure.test :refer :all] 4 | :cljs [cljs.test :refer-macros [deftest testing is are]]))) 5 | 6 | (deftest split-test 7 | (testing "regex, string and character separators" 8 | (are [sep] 9 | (and (= ["foo" "bar"] (split sep "foo.bar")) 10 | (= [""] (split sep ""))) 11 | #"\." "." \.)) 12 | 13 | (testing "input consisting of separator(s)" 14 | (is (empty? (split "x" "x"))) 15 | (is (empty? (split "x" "xx")))) 16 | 17 | (testing "generic separator" 18 | (are [x y] 19 | (= x (split generic-separator y)) 20 | 21 | [""] "" 22 | [""] " " 23 | ["x"] " x " 24 | 25 | ["foo" "bar"] "foo bar" 26 | ["foo" "bar"] "foo\n\tbar" 27 | ["foo" "bar"] "foo-bar" 28 | ["foo" "Bar"] "fooBar" 29 | ["Foo" "Bar"] "FooBar" 30 | ["foo" "bar"] "foo_bar" 31 | ["FOO" "BAR"] "FOO_BAR" 32 | 33 | ["räksmörgås"] "räksmörgås" 34 | 35 | ["IP" "Address"] "IPAddress" 36 | 37 | ["Adler" "32"] "Adler32" 38 | ["Inet" "4" "Address"] "Inet4Address" 39 | ["Arc" "2" "D"] "Arc2D" 40 | ["a" "123b"] "a123b" 41 | ["A" "123" "B"] "A123B"))) 42 | -------------------------------------------------------------------------------- /src/camel_snake_kebab/internals/macros.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc camel-snake-kebab.internals.macros 2 | #?(:cljs (:refer-clojure :exclude [resolve])) 3 | (:require [camel-snake-kebab.internals.alter-name :refer [alter-name]] 4 | [camel-snake-kebab.internals.misc :refer [convert-case]])) 5 | 6 | #?(:cljs 7 | (defn resolve [sym] 8 | ;; On self-hosted ClojureScript, macros are evaluated under the `:cljs` conditional branch 9 | ;; In that case, we need to use `eval` in order to resolve variables instead of `resolve` 10 | (eval `(~'var ~sym)))) 11 | 12 | (defn type-preserving-function [case-label first-fn rest-fn sep] 13 | `(defn ~(symbol (str "->" case-label)) [s# & rest#] 14 | (let [convert-case# #(apply convert-case ~first-fn ~rest-fn ~sep % rest#)] 15 | (alter-name s# convert-case#)))) 16 | 17 | (defn type-converting-functions [case-label first-fn rest-fn sep] 18 | (letfn [(make-name [type-label] 19 | (->> (str case-label " " type-label) 20 | (convert-case (resolve first-fn) (resolve rest-fn) sep) 21 | (str "->") 22 | (symbol)))] 23 | (for [[type-label type-converter] {"string" `identity "symbol" `symbol "keyword" `keyword}] 24 | `(defn ~(make-name type-label) [s# & rest#] 25 | (~type-converter (apply convert-case ~first-fn ~rest-fn ~sep (name s#) rest#)))))) 26 | 27 | (defmacro defconversion [case-label first-fn rest-fn sep] 28 | `(do ~(type-preserving-function case-label first-fn rest-fn sep) 29 | ~@(type-converting-functions case-label first-fn rest-fn sep))) 30 | -------------------------------------------------------------------------------- /test/camel_snake_kebab/extras_test.cljc: -------------------------------------------------------------------------------- 1 | (ns camel-snake-kebab.extras-test 2 | (:require [camel-snake-kebab.core :as csk] 3 | [camel-snake-kebab.extras :refer [transform-keys]] 4 | #?(:clj [clojure.test :refer :all] 5 | :cljs [cljs.test :refer-macros [deftest testing is are]]))) 6 | 7 | (deftest transform-keys-test 8 | (are [x y] (= x (transform-keys csk/->kebab-case-keyword y)) 9 | nil nil 10 | {} {} 11 | [] [] 12 | {:total-books 0 :all-books []} {'total_books 0 "allBooks" []} 13 | [{:the-author "Dr. Seuss" :the-title "Green Eggs and Ham"}] 14 | [{'the-Author "Dr. Seuss" "The_Title" "Green Eggs and Ham"}] 15 | {:total-books 1 :all-books [{:the-author "Dr. Seuss" :the-title "Green Eggs and Ham"}]} 16 | {'total_books 1 "allBooks" [{'THE_AUTHOR "Dr. Seuss" "the_Title" "Green Eggs and Ham"}]})) 17 | 18 | (deftest transform-keys-with-metadata-test 19 | (are [x y metadata] 20 | (let [y-with-metadata (with-meta y metadata) 21 | y-transformed (transform-keys csk/->kebab-case-keyword y-with-metadata)] 22 | (and (= x y-transformed) 23 | (= metadata (meta y-transformed)))) 24 | {} {} {:type-name :metadata-type} 25 | [] [] {:type-name :check} 26 | {:total-books 0 :all-books []} {'total_books 0 "allBooks" []} {:type-name :metadata-type} 27 | 28 | [{:the-author "Dr. Seuss" :the-title "Green Eggs and Ham"}] 29 | [{'the-Author "Dr. Seuss" "The_Title" "Green Eggs and Ham"}] 30 | {:type-name :metadata-type} 31 | 32 | {:total-books 1 :all-books [{:the-author "Dr. Seuss" :the-title "Green Eggs and Ham"}]} 33 | {'total_books 1 "allBooks" [{'THE_AUTHOR "Dr. Seuss" "the_Title" "Green Eggs and Ham"}]} 34 | {:type-name :metadata-type})) 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.os }}-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ ubuntu ] 18 | java-version: [ '8', '11', '17', '21', '23' ] 19 | 20 | name: test ${{matrix.os}} jdk${{matrix.java-version}} 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Clojure deps cache 27 | uses: actions/cache@v4 28 | with: 29 | path: | 30 | ~/.m2/repository 31 | key: ${{ runner.os }}-cljdeps-${{ hashFiles('project.clj') }} 32 | restore-keys: ${{ runner.os }}-cljdeps- 33 | 34 | - name: Setup Java 35 | uses: actions/setup-java@v4 36 | with: 37 | distribution: 'temurin' 38 | java-version: ${{ matrix.java-version }} 39 | 40 | - name: Install Babashka & Clojure 41 | uses: DeLaGuardo/setup-clojure@ada62bb3282a01a296659d48378b812b8e097360 42 | with: 43 | lein: 'latest' 44 | 45 | - name: Install planck (linux) 46 | run: | 47 | # There are not planck binaries for jammy yet, so hack-around to use focal release 48 | sudo add-apt-repository -y "deb http://cz.archive.ubuntu.com/ubuntu focal main universe" 49 | sudo add-apt-repository -y "deb http://security.ubuntu.com/ubuntu focal-security main" 50 | 51 | # is missing after installing planck so compensate 52 | DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libicu66 libjavascriptcoregtk-4.0-18 53 | 54 | wget https://launchpad.net/~mfikes/+archive/ubuntu/planck/+files/planck_2.25.0-1ppa1~focal1_amd64.deb 55 | sudo apt-get install ./planck_2.25.0-1ppa1~focal1_amd64.deb 56 | if: runner.os == 'Linux' 57 | 58 | - name: Download deps 59 | run: lein deps 60 | 61 | - name: Tools Versions 62 | run: | 63 | echo "java -version" 64 | java -version 65 | echo "lein --version" 66 | lein --version 67 | echo "planck --version" 68 | planck --version 69 | 70 | - name: Run Tests 71 | run: lein test-all 72 | -------------------------------------------------------------------------------- /src/camel_snake_kebab/core.cljc: -------------------------------------------------------------------------------- 1 | (ns camel-snake-kebab.core 2 | (:require [clojure.string] 3 | [camel-snake-kebab.internals.misc :as misc] 4 | #?(:clj [camel-snake-kebab.internals.macros :refer [defconversion]] 5 | :cljs [camel-snake-kebab.internals.alter-name])) ;; Needed for expansion of defconversion 6 | #?(:cljs (:require-macros [camel-snake-kebab.internals.macros :refer [defconversion]]))) 7 | 8 | (declare 9 | ->PascalCase 10 | ->Camel_Snake_Case 11 | ->camelCase 12 | ->SCREAMING_SNAKE_CASE 13 | ->snake_case 14 | ->kebab-case 15 | ->HTTP-Header-Case 16 | 17 | ->PascalCaseKeyword 18 | ->camelCaseKeyword 19 | ->SCREAMING_SNAKE_CASE_KEYWORD 20 | ->snake_case_keyword 21 | ->kebab-case-keyword 22 | ->Camel_Snake_Case_Keyword 23 | ->HTTP-Header-Case-Keyword 24 | 25 | ->PascalCaseString 26 | ->camelCaseString 27 | ->SCREAMING_SNAKE_CASE_STRING 28 | ->snake_case_string 29 | ->kebab-case-string 30 | ->Camel_Snake_Case_String 31 | ->HTTP-Header-Case-String 32 | 33 | ->PascalCaseSymbol 34 | ->camelCaseSymbol 35 | ->SCREAMING_SNAKE_CASE_SYMBOL 36 | ->snake_case_symbol 37 | ->kebab-case-symbol 38 | ->Camel_Snake_Case_Symbol 39 | ->HTTP-Header-Case-Symbol) 40 | 41 | (defn convert-case 42 | "Converts the case of a string according to the rule for the first 43 | word, remaining words, and the separator." 44 | [first-fn rest-fn sep s & rest] 45 | (apply misc/convert-case first-fn rest-fn sep s rest)) 46 | 47 | ;; These are fully qualified to workaround some issue with ClojureScript: 48 | 49 | (defconversion "PascalCase" clojure.string/capitalize clojure.string/capitalize "") 50 | (defconversion "Camel_Snake_Case" clojure.string/capitalize clojure.string/capitalize "_") 51 | (defconversion "camelCase" clojure.string/lower-case clojure.string/capitalize "" ) 52 | (defconversion "SCREAMING_SNAKE_CASE" clojure.string/upper-case clojure.string/upper-case "_") 53 | (defconversion "snake_case" clojure.string/lower-case clojure.string/lower-case "_") 54 | (defconversion "kebab-case" clojure.string/lower-case clojure.string/lower-case "-") 55 | (defconversion "HTTP-Header-Case" camel-snake-kebab.internals.misc/capitalize-http-header camel-snake-kebab.internals.misc/capitalize-http-header "-") 56 | -------------------------------------------------------------------------------- /test/camel_snake_kebab/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns camel-snake-kebab.core-test 2 | (:require [camel-snake-kebab.core :as csk] 3 | #?(:clj [clojure.test :refer :all] 4 | :cljs [cljs.test :refer-macros [deftest testing is are]])) 5 | #?(:clj (:import (clojure.lang ExceptionInfo)))) 6 | 7 | (def zip (partial map vector)) 8 | 9 | (deftest format-case-test 10 | (testing "examples" 11 | (are [x y] (= x y) 12 | 'fluxCapacitor (csk/->camelCase 'flux-capacitor) 13 | "I_AM_CONSTANT" (csk/->SCREAMING_SNAKE_CASE "I am constant") 14 | :object-id (csk/->kebab-case :object_id) 15 | "X-SSL-Cipher" (csk/->HTTP-Header-Case "x-ssl-cipher") 16 | :object-id (csk/->kebab-case-keyword "object_id")) 17 | :s3_key (csk/->snake_case :s3-key :separator \-)) 18 | 19 | (testing "rejection of namespaced keywords and symbols" 20 | (is (thrown? ExceptionInfo (csk/->PascalCase (keyword "a" "b")))) 21 | (is (thrown? ExceptionInfo (csk/->PascalCase (symbol "a" "b"))))) 22 | 23 | (testing "all the type preserving functions" 24 | (let 25 | [inputs ["FooBar" 26 | "fooBar" 27 | "FOO_BAR" 28 | "foo_bar" 29 | "foo-bar" 30 | "Foo_Bar"] 31 | functions [csk/->PascalCase 32 | csk/->camelCase 33 | csk/->SCREAMING_SNAKE_CASE 34 | csk/->snake_case 35 | csk/->kebab-case 36 | csk/->Camel_Snake_Case] 37 | formats [identity keyword symbol]] 38 | 39 | (doseq [input inputs, format formats, [output function] (zip inputs functions)] 40 | (is (= (format output) (function (format input))))))) 41 | 42 | (testing "some of the type converting functions" 43 | (are [x y] (= x y) 44 | :FooBar (csk/->PascalCaseKeyword 'foo-bar) 45 | "FOO_BAR" (csk/->SCREAMING_SNAKE_CASE_STRING :foo-bar) 46 | 'foo-bar (csk/->kebab-case-symbol "foo bar"))) 47 | 48 | (testing "handling of blank input string" 49 | (is (= "" (csk/->kebab-case ""))) 50 | (is (= "" (csk/->kebab-case " ")))) 51 | 52 | (testing "handling of input consisting of only separator(s)" 53 | (is (= "" (csk/->kebab-case "a" :separator \a))) 54 | (is (= "" (csk/->kebab-case "aa" :separator \a))))) 55 | 56 | (deftest http-header-case-test 57 | (are [x y] (= x (csk/->HTTP-Header-Case y)) 58 | "User-Agent" "user-agent" 59 | "DNT" "dnt" 60 | "Remote-IP" "remote-ip" 61 | "TE" "te" 62 | "UA-CPU" "ua-cpu" 63 | "X-SSL-Cipher" "x-ssl-cipher" 64 | "X-WAP-Profile" "x-wap-profile" 65 | "X-XSS-Protection" "x-xss-protection")) 66 | -------------------------------------------------------------------------------- /src/camel_snake_kebab/internals/string_separator.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc camel-snake-kebab.internals.string-separator 2 | (:require [clojure.string :as string]) 3 | #?(:clj (:import (java.util.regex Pattern)))) 4 | 5 | #?(:clj (set! *warn-on-reflection* true)) 6 | 7 | (defprotocol StringSeparator 8 | (split [this s] ": StringSeparator -> String -> NonEmptySeq[String]")) 9 | 10 | #?(:clj 11 | (letfn [(split-by-pattern [^Pattern p, ^String s] 12 | (string/split s p)) 13 | ;; These could be optimized e.g. by using StringUtils in Apache Commons: 14 | (split-by-string [^String p, ^String s] 15 | (split-by-pattern (-> p Pattern/quote Pattern/compile) s)) 16 | (split-by-char [^Character p, ^String s] 17 | (split-by-string (String/valueOf p) s))] 18 | (extend Pattern StringSeparator {:split split-by-pattern}) 19 | (extend String StringSeparator {:split split-by-string}) 20 | (extend Character StringSeparator {:split split-by-char})) 21 | 22 | :cljs 23 | (extend-protocol StringSeparator 24 | ;; Notes: 25 | ;; * Characters are just strings in ClojureScript. 26 | ;; * Using js/RegExp generates a warning, but what's the right way? 27 | 28 | js/RegExp 29 | (split [this s] (string/split s this)) 30 | 31 | string 32 | (split [this s] (string/split s this)))) 33 | 34 | (defn classify-char [c] 35 | (case c 36 | (\0 \1 \2 \3 \4 \5 \6 \7 \8 \9) :number 37 | (\- \_ \space \tab \newline \o013 \formfeed \return) :whitespace 38 | (\a \b \c \d \e \f \g \h \i \j \k \l \m \n \o \p \q \r \s \t \u \v \w \x \y \z) :lower 39 | (\A \B \C \D \E \F \G \H \I \J \K \L \M \N \O \P \Q \R \S \T \U \V \W \X \Y \Z) :upper 40 | :other)) 41 | 42 | (defn generic-split [ss] 43 | (let [cs (mapv classify-char ss) 44 | ss-length #?(:clj (.length ^String ss) 45 | :cljs (.-length ss))] 46 | (loop [result (transient []), start 0, current 0] 47 | (let [next (inc current) 48 | result+new (fn [end] 49 | (if (> end start) 50 | (conj! result (.substring ^String ss start end)) 51 | result))] 52 | (cond (>= current ss-length) 53 | (or (seq (persistent! (result+new current))) 54 | ;; Return this instead of an empty seq: 55 | [""]) 56 | 57 | (= (nth cs current) :whitespace) 58 | (recur (result+new current) next next) 59 | 60 | (let [[a b c] (subvec cs current)] 61 | ;; This expression is not pretty, 62 | ;; but it compiles down to sane JavaScript. 63 | (or (and (not= a :upper) (= b :upper)) 64 | (and (not= a :number) (= b :number)) 65 | (and (= a :upper) (= b :upper) (= c :lower)))) 66 | (recur (result+new next) next next) 67 | 68 | :else 69 | (recur result start next)))))) 70 | 71 | (def generic-separator 72 | (reify StringSeparator 73 | (split [_ s] (generic-split s)))) 74 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | 16 | i) changes to the Program, and 17 | 18 | ii) additions to the Program; 19 | 20 | where such changes and/or additions to the Program originate from and are 21 | distributed by that particular Contributor. A Contribution 'originates' from 22 | a Contributor if it was added to the Program by such Contributor itself or 23 | anyone acting on such Contributor's behalf. Contributions do not include additions 24 | to the Program which: (i) are separate modules of software distributed in 25 | conjunction with the Program under their own license agreement, and (ii) are 26 | not derivative works of the Program. 27 | 28 | "Contributor" means any person or entity that distributes the Program. 29 | 30 | "Licensed Patents" mean patent claims licensable by a Contributor which are 31 | necessarily infringed by the use or sale of its Contribution alone or when 32 | combined with the Program. 33 | 34 | "Program" means the Contributions distributed in accordance with this Agreement. 35 | 36 | "Recipient" means anyone who receives the Program under this Agreement, including 37 | all Contributors. 38 | 39 | 2. GRANT OF RIGHTS 40 | 41 | a) Subject to the terms of this Agreement, each Contributor hereby grants 42 | Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, 43 | prepare derivative works of, publicly display, publicly perform, distribute 44 | and sublicense the Contribution of such Contributor, if any, and such derivative 45 | works, in source code and object code form. 46 | 47 | b) Subject to the terms of this Agreement, each Contributor hereby grants 48 | Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed 49 | Patents to make, use, sell, offer to sell, import and otherwise transfer the 50 | Contribution of such Contributor, if any, in source code and object code form. 51 | This patent license shall apply to the combination of the Contribution and 52 | the Program if, at the time the Contribution is added by the Contributor, 53 | such addition of the Contribution causes such combination to be covered by 54 | the Licensed Patents. The patent license shall not apply to any other combinations 55 | which include the Contribution. No hardware per se is licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any Contributor 59 | that the Program does not infringe the patent or other intellectual property 60 | rights of any other entity. Each Contributor disclaims any liability to Recipient 61 | for claims brought by any other entity based on infringement of intellectual 62 | property rights or otherwise. As a condition to exercising the rights and 63 | licenses granted hereunder, each Recipient hereby assumes sole responsibility 64 | to secure any other intellectual property rights needed, if any. For example, 65 | if a third party patent license is required to allow Recipient to distribute 66 | the Program, it is Recipient's responsibility to acquire that license before 67 | distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient copyright 70 | rights in its Contribution, if any, to grant the copyright license set forth 71 | in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for damages, 88 | including direct, indirect, special, incidental and consequential damages, 89 | such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such Contributor, 95 | and informs licensees how to obtain it in a reasonable manner on or through 96 | a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor 116 | who includes the Program in a commercial product offering should do so in 117 | a manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product offering, 119 | such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 120 | every other Contributor ("Indemnified Contributor") against any losses, damages 121 | and costs (collectively "Losses") arising from claims, lawsuits and other 122 | legal actions brought by a third party against the Indemnified Contributor 123 | to the extent caused by the acts or omissions of such Commercial Contributor 124 | in connection with its distribution of the Program in a commercial product 125 | offering. The obligations in this section do not apply to any claims or Losses 126 | relating to any actual or alleged intellectual property infringement. In order 127 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 128 | Contributor in writing of such claim, and b) allow the Commercial Contributor 129 | to control, and cooperate with the Commercial Contributor in, the defense 130 | and any related settlement negotiations. The Indemnified Contributor may participate 131 | in any such claim at its own expense. 132 | 133 | For example, a Contributor might include the Program in a commercial product 134 | offering, Product X. That Contributor is then a Commercial Contributor. If 135 | that Commercial Contributor then makes performance claims, or offers warranties 136 | related to Product X, those performance claims and warranties are such Commercial 137 | Contributor's responsibility alone. Under this section, the Commercial Contributor 138 | would have to defend claims against the other Contributors related to those 139 | performance claims and warranties, and if a court requires any other Contributor 140 | to pay any damages as a result, the Commercial Contributor must pay those 141 | damages. 142 | 143 | 5. NO WARRANTY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 146 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS 147 | OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 148 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 149 | Each Recipient is solely responsible for determining the appropriateness of 150 | using and distributing the Program and assumes all risks associated with its 151 | exercise of rights under this Agreement, including but not limited to the 152 | risks and costs of program errors, compliance with applicable laws, damage 153 | to or loss of data, programs or equipment, and unavailability or interruption 154 | of operations. 155 | 156 | 6. DISCLAIMER OF LIABILITY 157 | 158 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 159 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 160 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 161 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 162 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 163 | WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 164 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 165 | 166 | 7. GENERAL 167 | 168 | If any provision of this Agreement is invalid or unenforceable under applicable 169 | law, it shall not affect the validity or enforceability of the remainder of 170 | the terms of this Agreement, and without further action by the parties hereto, 171 | such provision shall be reformed to the minimum extent necessary to make such 172 | provision valid and enforceable. 173 | 174 | If Recipient institutes patent litigation against any entity (including a 175 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 176 | (excluding combinations of the Program with other software or hardware) infringes 177 | such Recipient's patent(s), then such Recipient's rights granted under Section 178 | 2(b) shall terminate as of the date such litigation is filed. 179 | 180 | All Recipient's rights under this Agreement shall terminate if it fails to 181 | comply with any of the material terms or conditions of this Agreement and 182 | does not cure such failure in a reasonable period of time after becoming aware 183 | of such noncompliance. If all Recipient's rights under this Agreement terminate, 184 | Recipient agrees to cease use and distribution of the Program as soon as reasonably 185 | practicable. However, Recipient's obligations under this Agreement and any 186 | licenses granted by Recipient relating to the Program shall continue and survive. 187 | 188 | Everyone is permitted to copy and distribute copies of this Agreement, but 189 | in order to avoid inconsistency the Agreement is copyrighted and may only 190 | be modified in the following manner. The Agreement Steward reserves the right 191 | to publish new versions (including revisions) of this Agreement from time 192 | to time. No one other than the Agreement Steward has the right to modify this 193 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse 194 | Foundation may assign the responsibility to serve as the Agreement Steward 195 | to a suitable separate entity. Each new version of the Agreement will be given 196 | a distinguishing version number. The Program (including Contributions) may 197 | always be distributed subject to the version of the Agreement under which 198 | it was received. In addition, after a new version of the Agreement is published, 199 | Contributor may elect to distribute the Program (including its Contributions) 200 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 201 | above, Recipient receives no rights or licenses to the intellectual property 202 | of any Contributor under this Agreement, whether expressly, by implication, 203 | estoppel or otherwise. All rights in the Program not expressly granted under 204 | this Agreement are reserved. 205 | 206 | This Agreement is governed by the laws of the State of New York and the intellectual 207 | property laws of the United States of America. No party to this Agreement 208 | will bring a legal action under this Agreement more than one year after the 209 | cause of action arose. Each party waives its rights to a jury trial in any 210 | resulting litigation. --------------------------------------------------------------------------------