-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.clj
160 lines (144 loc) · 6.03 KB
/
util.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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
;
; Copyright © 2020 Peter Monks
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; SPDX-License-Identifier: Apache-2.0
;
(ns discljord-utils.util
(:require [clojure.string :as s]
[clojure.tools.logging :as log]
[java-time :as tm]))
(defn nth-fibonacci
"Returns the nth fibonacci."
[n]
(loop [series [0 1]]
(if (> (count series) n)
(nth series n)
(let [[n-1 n-2] (rseq series)] ; We use rseq here as it is constant time on vectors (vs reverse, which is linear time)
(recur (conj series (+' n-1 n-2)))))))
(defn parse-int
"Parses a value (a string or numeric) into a Clojure integer (Java Long or BigInteger), returning nil if parsing failed. Note: redundant in Clojure v1.11+."
[x]
(cond (integer? x) x
(string? x) (try
(Long/parseLong (s/trim x))
(catch NumberFormatException _
nil))
(float? x) (int (Math/round ^Float x))
(double? x) (int (Math/round ^Double x))
(rational? x) (parse-int (double x))
:else nil))
(defn getrn
"Like get, but also replaces nil values found in the map with the default value."
[m k nf]
(or (get m k nf) nf))
(defn mapfonk
"Returns a new map where f has been applied to all of the keys of m."
[f m]
(when m
(into {}
(for [[k v] m]
[(f k) v]))))
(defn mapfonv
"Returns a new map where f has been applied to all of the values of m."
[f m]
(when m
(into {}
(for [[k v] m]
[k (f v)]))))
(defn clojurise-json-key
"Converts JSON string keys (e.g. \"fullName\") to Clojure keyword keys (e.g. :full-name)."
[k]
(keyword
(s/replace
(s/join "-"
(map s/lower-case
(s/split k #"(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")))
"_"
"-")))
(defn replace-all
"Takes a sequence of replacements, and applies all of them to the given string, in the order provided. Each replacement in the sequence is a pair of values to be passed to clojure.string/replace (the 2nd and 3rd arguments)."
[string replacements]
(when (and string (seq replacements))
(loop [s string
f (first replacements)
r (rest replacements)]
(if f
(recur (s/replace s (first f) (second f))
(first r)
(rest r))
s))))
(defn to-ascii
"Converts the given string to ASCII, mapping a small number of Unicode characters (whitespace, hyphens, single & double quotes) to their ASCII equivalents and stripping the rest."
[s]
(replace-all s
[[#"\p{javaWhitespace}" " "] ; Whitespace
[#"[–‑‒–—]" "-"] ; Hyphens / dashes
[#"[’‘‘‚❛❛❟]" "'"] ; Single quote characters
[#"[„“‟”⹂❝❞〝〞"]" "\""] ; Double quote characters
[#"[^\p{ASCII}]+" ""]])) ; Strip everything else
(defn truncate
"If s is longer than len, truncates it to len-1, trims any whitespace on the right, then adds the Unicode ellipsis (…) character to the end."
[s len]
(if (> (count s) len)
(str (s/trimr (subs s 0 (dec len))) "…")
s))
(defmacro in-tz
"Executes body (assumed to include java-time logic) within the given tzdata timezone (e.g. \"Americas/Los_Angeles\")."
[tz & body]
`(tm/with-clock (tm/system-clock ~tz) ~@body))
(defn human-readable-date-diff
"Returns a string containing the human readable difference between two instants e.g. \"4d 2h 37m 12.379s\""
[^java.time.Instant i1
^java.time.Instant i2]
(format "%dd %dh %dm %d.%03ds" (.until i1 i2 (java.time.temporal.ChronoUnit/DAYS))
(mod (.until i1 i2 (java.time.temporal.ChronoUnit/HOURS)) 24)
(mod (.until i1 i2 (java.time.temporal.ChronoUnit/MINUTES)) 60)
(mod (.until i1 i2 (java.time.temporal.ChronoUnit/SECONDS)) 60)
(mod (.until i1 i2 (java.time.temporal.ChronoUnit/MILLIS)) 1000)))
(def ^:private units ["B" "KB" "MB" "GB" "TB" "PB" "EB" "ZB" "YB"])
(def ^:private ^java.text.DecimalFormat df (java.text.DecimalFormat. "#.##"))
(defn human-readable-size
"Returns a string containing a rounded human readable size of the given number of bytes e.g. 1024 -> \"1KB\", 1234567890 -> \"1.15GB\""
[size]
(let [index (loop [size size
index 0]
(if (< size 1024)
index
(recur (/ size 1024) (inc index))))]
(str (.format df (/ size (Math/pow 1024 index))) (nth units index))))
(defn log-exception
"Logs the given exception and (optional) message at ERROR level."
([^java.lang.Throwable e] (log-exception e nil))
([^java.lang.Throwable e msg]
(let [extra (ex-data e)
m (case [(boolean msg) (boolean extra)]
[true true] (str msg "; data: " extra)
[true false] msg
[false true] (str "Data: " extra)
[false false] (if e (.getMessage e) "No exception information provided (this is probably a bug)"))]
(log/error e m))))
(defn exit
"Exits the program after printing the given message, and returns the given status code."
([] (exit 0 nil))
([status-code] (exit status-code nil))
([status-code message]
(when message
(if (= 0 status-code)
(println message)
(binding [*out* *err*]
(println message))))
(flush)
(shutdown-agents)
(System/exit status-code)))