Current section

Files

Jump to
imagine_cms lib crutches string.ex
Raw

lib/crutches/string.ex

defmodule Crutches.String do
alias Crutches.Option
import String,
only: [
replace: 3,
slice: 2,
trim: 1
]
@moduledoc ~s"""
Convenience functions for strings.
This module provides several convenience functions operating on strings.
Simply call any function (with any options if applicable) to make use of it.
"""
# Access
@doc ~S"""
Gives a substring of `string` from the given `position`.
If `position` is positive, counts from the start of the string.
If `position` is negative, count from the end of the string.
## Examples
iex> String.from("hello", 0)
"hello"
iex> String.from("hello", 3)
"lo"
iex> String.from("hello", -2)
"lo"
iex> String.from("hello", -7)
""
You can mix it with +to+ method and do fun things like:
iex> "hello"
iex> |> String.from(0)
iex> |> String.to(-1)
"hello"
iex> "hello"
iex> |> String.from(1)
iex> |> String.to(-2)
"ell"
iex> "a"
iex> |> String.from(1)
iex> |> String.to(1500)
""
iex> "elixir"
iex> |> String.from(-10)
iex> |> String.to(-7)
""
"""
@spec from(String.t(), integer) :: String.t()
def from(string, position) when position >= 0 do
slice(string, position..(String.length(string) - 1))
end
def from(string, position) when position < 0 do
new_position = String.length(string) + position
case new_position < 0 do
true -> ""
false -> slice(string, new_position..(String.length(string) - 1))
end
end
@doc ~S"""
Returns a substring from the beginning of the string to the given position.
If the position is negative, it is counted from the end of the string.
## Examples
iex> String.to("hello", 0)
"h"
iex> String.to("hello", 3)
"hell"
iex> String.to("hello", -2)
"hell"
You can mix it with +from+ method and do fun things like:
iex> "hello"
iex> |> String.from(0)
iex> |> String.to(-1)
"hello"
iex> "hello"
iex> |> String.from(1)
iex> |> String.to(-2)
"ell"
"""
@spec to(String.t(), integer) :: String.t()
def to(string, length) when length >= 0 do
slice(string, 0..length)
end
def to(string, length) when length < 0 do
slice(string, 0..(String.length(string) + length))
end
# Filters
@doc ~S"""
Returns the string, first removing all whitespace on both ends of
the string, and then changing remaining consecutive whitespace
groups into one space each.
## Examples
iex> str = "A multi line
iex> string"
iex> String.squish(str)
"A multi line string"
iex> str = " foo bar \n \t boo"
iex> String.squish(str)
"foo bar boo"
"""
@spec squish(String.t()) :: String.t()
def squish(string) do
string |> replace(~r/[[:space:]]+/, " ") |> trim
end
@doc ~S"""
Remove all occurrences of `pattern` from `string`.
Can also take a list of `patterns`.
## Examples
iex> String.remove("foo bar test", " test")
"foo bar"
iex> String.remove("foo bar test", ~r/foo /)
"bar test"
iex> String.remove("foo bar test", [~r/foo /, " test"])
"bar"
"""
@spec remove(String.t(), String.t() | Regex.t() | list(any)) :: String.t()
def remove(string, patterns) when is_list(patterns) do
patterns |> Enum.reduce(string, &remove(&2, &1))
end
def remove(string, pattern) do
replace(string, pattern, "")
end
@doc ~S"""
Capitalizes every word in a string. Similar to ActiveSupport's #titleize.
iex> String.titlecase("the truth is rarely pure and never simple.")
"The Truth Is Rarely Pure And Never Simple."
iex> String.titlecase("THE TRUTH IS RARELY PURE AND NEVER SIMPLE.")
"The Truth Is Rarely Pure And Never Simple."
iex> String.titlecase("the truth is rarely pure and NEVER simple.")
"The Truth Is Rarely Pure And Never Simple."
"""
@spec titlecase(String.t()) :: String.t()
def titlecase(string) when is_binary(string) do
string
|> String.split(" ")
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
end
@doc ~S"""
Truncates a given `text` after a given `length` if `text` is longer than `length`:
Truncates a given text after a given `len`gth if text is longer than `len`th.
The last characters will be replaced with the `:omission` (defaults to “...”) for a total length not exceeding `len`.
Pass a `:separator` to truncate text at a natural break (the first occurence of that separator before the provided length).
## Examples
iex> String.truncate("Once upon a time in a world far far away", 27)
"Once upon a time in a wo..."
iex> String.truncate("Once upon a time in a world far far away", 27, separator: " ")
"Once upon a time in a..."
iex> String.truncate("Once upon a time in a world far far away", 27, separator: ~r/\s/)
"Once upon a time in a..."
iex> String.truncate("Once upon a time in a world far far away", 35, separator: "far ")
"Once upon a time in a world far..."
iex> String.truncate("And they found that many people were sleeping better.", 25, omission: "... (continued)")
"And they f... (continued)"
iex> String.truncate("Supercalifragilisticexpialidocious", 24, separator: ~r/\s/)
"Supercalifragilistice..."
"""
@truncate [
valid: ~w(separator omission)a,
defaults: [
separator: nil,
omission: "..."
]
]
def truncate(string, len, opts \\ [])
def truncate(nil, _, _), do: nil
def truncate(string, len, opts) when is_binary(string) and is_integer(len) do
opts = Option.combine!(opts, @truncate)
if String.length(string) > len do
length_with_room = len - String.length(opts[:omission])
do_truncate(string, length_with_room, opts[:separator], opts[:omission])
else
string
end
end
defp do_truncate(string, length, sep, omission) when is_nil(sep) do
String.slice(string, 0, length) <> omission
end
defp do_truncate(string, length, sep, omission) when is_binary(sep) do
sep_size = String.length(sep)
chunk_indexes =
string
|> String.codepoints()
|> Enum.take(length)
|> Enum.with_index()
|> Enum.chunk_every(sep_size)
|> Enum.reverse()
|> Enum.find(fn chars ->
str = chars |> Enum.map(&elem(&1, 0)) |> Enum.join("")
str == sep
end)
{_, index} = if chunk_indexes, do: List.last(chunk_indexes), else: {nil, length}
do_truncate(string, index, nil, omission)
end
defp do_truncate(string, length, sep, omission) do
case Regex.scan(sep, string, return: :index) do
[_ | _] = captures ->
[{index, _}] = captures |> Enum.reverse() |> Enum.find(fn [{i, _}] -> i <= length end)
do_truncate(string, index, nil, omission)
[] ->
do_truncate(string, length, nil, omission)
end
end
end