-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
generic.ex
147 lines (122 loc) · 4.3 KB
/
generic.ex
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
defmodule Recase.Generic do
@moduledoc """
Generic module to split and join strings back or convert strings to atoms.
This module should not be used directly.
"""
@splitters Application.compile_env(:recase, :delimiters, [
?\s,
?\n,
?\t,
?_,
?.,
?-,
?#,
??,
?!
])
@delimiters (case @splitters do
list when is_list(list) ->
list
:symbol ->
[all, digits, down, up] =
Enum.map(
[32..127, ?0..?9, ?a..?z, ?A..?Z],
&Enum.to_list/1
)
all -- (digits ++ down ++ up)
end)
@doc """
Splits the input into **`list`**. Utility function.
## Examples
iex> Recase.Generic.split "foo_barBaz-λambdaΛambda-привет-Мир"
["foo", "bar", "Baz", "λambda", "Λambda", "привет", "Мир"]
"""
@spec split(input :: String.t()) :: [String.t()]
def split(input) when is_binary(input), do: do_split(input)
@doc """
Splits the input and **`rejoins`** it with a separator given. Optionally
converts parts to `downcase`, `upcase` or `titlecase`.
- `opts[:case] :: [:down | :up | :title | :none]`
- `opts[:separator] :: binary() | integer()`
Default separator is `?_`, default conversion is `:downcase` so that
it behaves the same way as `to_snake/1`.
## Examples
iex> Recase.Generic.rejoin "foo_barBaz-λambdaΛambda-привет-Мир", separator: "__"
"foo__bar__baz__λambda__λambda__привет__мир"
"""
@spec rejoin(input :: String.t(), opts :: Keyword.t()) :: String.t()
def rejoin(input, opts \\ []) when is_binary(input) do
mapper =
case Keyword.get(opts, :case, :down) do
:down ->
&String.downcase/1
:title ->
fn <<char::utf8, rest::binary>> ->
String.upcase(<<char::utf8>>) <> String.downcase(rest)
end
:up ->
&String.upcase/1
_ ->
& &1
end
input
|> do_split()
|> Enum.map_join(Keyword.get(opts, :separator, ?_), mapper)
end
@doc """
Atomizes a string value.
Uses an existing atom if possible.
"""
@spec safe_atom(String.t()) :: atom()
def safe_atom(string_value) do
String.to_existing_atom(string_value)
rescue
ArgumentError -> String.to_atom(string_value)
end
##############################################################################
@spec do_split(input :: String.t(), {binary(), acc :: [String.t()]}) :: [
String.t()
]
defp do_split(string, acc \\ {"", []})
defp do_split("", {"", acc}), do: Enum.reverse(acc)
defp do_split("", {curr, acc}),
do: do_split("", {"", [curr | acc]})
Enum.each(@delimiters, fn delim ->
defp do_split(<<unquote(delim)::utf8, rest::binary>>, {"", acc}),
do: do_split(rest, {"", acc})
defp do_split(<<unquote(delim), rest::binary>>, {curr, acc}),
do: do_split(rest, {"", [curr | acc]})
end)
Enum.each(?A..?Z, fn char ->
defp do_split(<<unquote(char), rest::binary>>, {"", acc}),
do: do_split(rest, {<<unquote(char)::utf8>>, acc})
defp do_split(<<unquote(char), rest::binary>>, {curr, acc}) do
<<c::utf8, _::binary>> = String.reverse(curr)
if c in ?A..?Z do
do_split(rest, {curr <> <<unquote(char)::utf8>>, acc})
else
do_split(rest, {<<unquote(char)::utf8>>, [curr | acc]})
end
end
end)
[32..64, 91..127]
|> Enum.map(&Enum.to_list/1)
|> Enum.reduce(&Kernel.++/2)
|> Kernel.--(@delimiters)
|> Enum.each(fn char ->
defp do_split(<<unquote(char)::utf8, rest::binary>>, {"", acc}),
do: do_split(rest, {<<unquote(char)::utf8>>, acc})
defp do_split(<<unquote(char), rest::binary>>, {curr, acc}),
do: do_split(rest, {curr <> <<unquote(char)::utf8>>, acc})
end)
defp do_split(<<char::utf8, rest::binary>>, {"", acc}),
do: do_split(rest, {<<char::utf8>>, acc})
@upcase ~r/(?<!\p{Lu})\p{Lu}/u
defp do_split(<<char::utf8, rest::binary>>, {curr, acc}) do
if Regex.match?(@upcase, <<char::utf8>>) do
do_split(rest, {<<char::utf8>>, [curr | acc]})
else
do_split(rest, {curr <> <<char::utf8>>, acc})
end
end
end