This repository has been archived by the owner on Apr 29, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
slugify.clj
80 lines (68 loc) · 2.67 KB
/
slugify.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
(ns oc.lib.slugify
"Functions for creating URL safe slugs from text strings."
(:require [clojure.string :as s]
[oc.lib.db.common :as db-common])
(:import [java.text Normalizer Normalizer$Form]))
(def max-slug-length 128)
(defn- replace-whitespace [slug]
(s/join "-" (s/split slug #"[\p{Space}]+")))
(defn- normalize-characters [slug]
(Normalizer/normalize slug Normalizer$Form/NFD))
(defn- replace-punctuation [slug]
(s/replace slug #"[\p{Punct}]" "-"))
(defn- remove-non-alpha-numeric [slug]
(s/replace slug #"[^\w^\-]+" ""))
(defn- normalize-dashes [slug]
(s/replace (s/join "-" (s/split slug #"\-+")) #"^-+" ""))
(defn- truncate [slug n]
(s/join (take n slug)))
;; Slugify Rules:
;; trim prefixed and trailing white space
;; replace internal white space with dash
;; replace accented characters with normalized characters
;; replace any punctuation with dash
;; remove any remaining non-alpha-numberic characters
;; replace A-Z with a-z
;; replace multiple dashes with dash and dash at the beginning and end with nothing
;; truncate
;; replace dash at the end with nothing (in case we left a - at the end by truncating)
(defn slugify
([resource-name] (slugify resource-name max-slug-length))
([resource-name max-length]
(let [slug (-> resource-name
s/trim
replace-whitespace
normalize-characters
replace-punctuation
remove-non-alpha-numeric
s/lower-case
normalize-dashes
(truncate max-length)
normalize-dashes)]
;; if the slug was sanitized to nothing, use a UUID
(if (s/blank? slug) (db-common/unique-id) slug))))
;; In some places we are exployiting the fact that UUID are actually valid slugs
;; to make them interchangable. See oc.storage.resources.board/get-board for example.
(defn valid-slug?
"Return `true` if the specified slug is potentially a valid slug (follows the rules), otherwise return `false`."
[slug]
(try
(and (string? slug)
(not (s/blank? slug))
(= slug (slugify slug))) ; a valid slug slugifys to itself
(catch Exception e
false)))
(defn- positive-numbers
([] (positive-numbers 1))
([n] (lazy-seq (cons n (positive-numbers (inc n))))))
(defn find-available-slug
"Create a slug from `name` but find alternatives
if the resulting slug is part of the set given as `taken`"
[name taken]
{:pre [(set? taken)]}
(let [base-slug (slugify name)]
(if (taken base-slug)
(->> (map #(str base-slug "-" %) (positive-numbers))
(filter (fn [candidate] (not (contains? taken candidate))))
first)
base-slug)))