Packages
moar
0.1.0
5.0.0
4.3.0
4.2.0
4.1.0
4.0.0
3.2.0
3.1.0
3.0.0
2.7.0
2.6.0
2.5.0
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.1
2.0.0
1.64.0
1.63.1
1.63.0
1.62.0
1.61.2
1.61.1
1.61.0
1.60.0
1.59.2
1.59.1
1.59.0
1.58.0
1.57.0
1.56.2
1.56.1
1.56.0
1.55.0
1.54.0
1.53.0
1.52.1
1.52.0
1.51.0
1.50.0
1.49.0
1.48.0
1.47.0
1.46.0
1.45.1
1.45.0
1.44.0
1.43.0
1.42.0
1.41.0
1.40.0
1.39.0
1.38.0
1.37.0
1.36.0
1.35.0
1.34.0
1.33.0
1.32.0
1.31.0
1.30.0
1.29.0
1.28.0
1.27.0
1.26.0
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.3
1.19.2
1.19.1
1.19.0
1.18.1
1.18.0
1.17.0
1.16.0
1.15.0
1.14.0
1.13.1
1.13.0
1.12.0
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.0
1.5.0
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.1.0
A dependency-free utility library containing 100+ useful functions.
Current section
Files
Jump to
Current section
Files
lib/string.ex
defmodule Moar.String do
# @related [test](/test/string_test.exs)
@moduledoc "String-related functions"
use Bitwise
@doc """
Convert strings and atoms to dash-case (kebab-case) and trims leading and trailing non-alphanumeric characters.
The following all get converted to `"foo"`: `"foo"`, `"FOO"`, `:foo`.
The following all get converted to `"foo-bar"`: `"foo-bar"`, `:foo_bar`, `" fooBar "`, `" ?foo ! bar "`
"""
@spec dasherize(atom() | binary()) :: binary()
def dasherize(term) do
term
|> Moar.Atom.to_string()
|> String.replace(~r/([A-Z]+)([A-Z][a-z])/, "\\1_\\2")
|> String.replace(~r/([a-z\d])([A-Z])/, "\\1_\\2")
|> String.replace(~r{[^a-z0-9]+}i, "-")
|> String.trim_leading("-")
|> String.trim_trailing("-")
|> String.downcase()
end
@doc "Truncate `s` to `max_length` by removing the middle of the string"
@spec inner_truncate(binary(), integer()) :: binary()
def inner_truncate(nil, _),
do: nil
def inner_truncate(s, max_length) do
case String.length(s) <= max_length do
true ->
s
false ->
left_length = (max_length / 2) |> Float.ceil() |> round()
right_length = (max_length / 2) |> Float.floor() |> round()
"#{String.slice(s, 0, left_length)}…#{String.slice(s, -right_length, right_length)}"
end
end
@doc """
Compares the two binaries in constant-time to avoid timing attacks.
See: http://codahale.com/a-lesson-in-timing-attacks/
"""
@spec secure_compare(binary(), binary()) :: boolean()
def secure_compare(left, right) when is_nil(left) or is_nil(right),
do: false
def secure_compare(left, right) when is_binary(left) and is_binary(right),
do: byte_size(left) == byte_size(right) and secure_compare(left, right, 0)
defp secure_compare(<<x, left::binary>>, <<y, right::binary>>, acc) do
xorred = Bitwise.bxor(x, y)
secure_compare(left, right, acc ||| xorred)
end
defp secure_compare(<<>>, <<>>, acc),
do: acc === 0
@doc "Replace consecutive whitespace characters with a single space"
@spec squish(binary()) :: binary()
def squish(nil),
do: nil
def squish(s),
do: s |> trim() |> Elixir.String.replace(~r/\s+/, " ")
@doc "Add `surrounder` to the beginning and end of `s`"
@spec surround(binary(), binary()) :: binary()
def surround(s, surrounder),
do: surrounder <> s <> surrounder
@doc "Add `prefix` to the beginning of `s` and `suffix` to the end"
@spec surround(binary(), binary(), binary()) :: binary()
def surround(s, prefix, suffix),
do: prefix <> s <> suffix
@doc "Convert a string to an integer. Returns `nil` if the argument is `nil` or empty string"
@spec to_integer(nil | binary()) :: integer()
def to_integer(nil),
do: nil
def to_integer(""),
do: nil
def to_integer(s) when is_binary(s),
do: s |> trim() |> Elixir.String.replace(",", "") |> Elixir.String.to_integer()
@doc """
Like `to_integer/1` but with options:
* `:lenient` option removes non-digit characters first
* `default:` option specifies a default in case `s` is nil
"""
@spec to_integer(binary(), :lenient | [default: binary()]) :: integer()
def to_integer(s, :lenient) when is_binary(s),
do: s |> String.replace(~r|\D|, "") |> Elixir.String.to_integer()
def to_integer(s, default: default),
do: s |> to_integer() |> Moar.Term.or_default(default)
@doc "Like `String.trim/1` but returns `nil` if the argument is nil"
@spec trim(nil | binary()) :: binary()
def trim(nil),
do: nil
def trim(s) when is_binary(s),
do: Elixir.String.trim(s)
@doc """
Truncates `s` at the last instance of `at`, causing the string to be at most `limit` characters.
```elixir
iex> Moar.String.truncate_at("I like apples. I like bananas. I like cherries.", ".", 35)
"I like apples. I like bananas."
```
"""
def truncate_at(s, at, limit) do
s
|> String.graphemes()
|> Enum.take(limit)
|> Enum.reverse()
|> Enum.split_while(fn c -> c != at end)
|> case do
{a, []} -> a
{[], b} -> b
{_a, b} -> b
end
|> Enum.reverse()
|> Enum.join("")
end
end