import gleam/int
import gleam/list
import gleam/result
import gleam/string
/// Returns the part of a string before the first occurrence of a given substring.
///
/// If the substring is not found, it returns `Error(Nil)`.
///
/// ## Examples
///
/// > before("hello world", on: " ")
/// Ok("hello")
///
/// > before("gleam is fun", on: "!")
/// Error(Nil)
///
pub fn before(string: String, on pattern: String) -> Result(String, Nil) {
string
|> string.split_once(on: pattern)
|> result.map(fn(parts) { parts.0 })
}
/// Returns the part of a string after the first occurrence of a given substring.
///
/// If the substring is not found, it returns `Error(Nil)`.
///
/// ## Examples
///
/// > after("hello world", on: " ")
/// Ok("world")
///
/// > after("gleam is fun", on: "!")
/// Error(Nil)
///
pub fn after(string: String, on pattern: String) -> Result(String, Nil) {
string
|> string.split_once(on: pattern)
|> result.map(fn(parts) { parts.1 })
}
/// Returns the part of a string between two given substrings.
///
/// It finds the first occurrence of `start` and then the first
/// occurrence of `end` after `start`. If either is not found in the
/// correct order, it returns `Error(Nil)`.
///
/// ## Examples
///
/// > between("b", from: "", to: "")
/// Ok("b")
///
/// > between("
title
", from: "", to: "")
/// Error(Nil)
///
pub fn between(
string: String,
// The starting substring
from start: String,
// The ending substring
to end: String,
) -> Result(String, Nil) {
case after(string, on: start) {
Ok(after_start) -> before(after_start, on: end)
Error(e) -> Error(e)
}
}
/// Counts the number of occurrences of a substring in a string.
///
/// ## Examples
///
/// > count("hello hello world", of: "hello")
/// 2
///
/// > count("gleam is fun", of: "rust")
/// 0
///
pub fn count(string: String, of pattern: String) -> Int {
case pattern {
"" -> 0
_ -> {
string
|> string.split(on: pattern)
|> list.length
|> int.subtract(1)
|> int.max(0)
}
}
}
/// Returns the part of a string before the nth occurrence of a given substring.
///
/// Index is 0-based. If the pattern doesn't occur enough times, returns `Error(Nil)`.
///
/// ## Examples
///
/// > before_at("a.b.c.d", on: ".", at: 1)
/// Ok("a.b")
///
/// > before_at("hello world", on: " ", at: 5)
/// Error(Nil)
///
pub fn before_at(
string: String,
on pattern: String,
at index: Int,
) -> Result(String, Nil) {
case pattern == "" || index < 0 {
True -> Error(Nil)
False -> {
let parts = string.split(string, on: pattern)
case list.length(parts) > index + 1 {
True -> {
parts
|> list.take(index + 1)
|> string.join(with: pattern)
|> Ok
}
False -> Error(Nil)
}
}
}
}
/// Returns the part of a string after the nth occurrence of a given substring.
///
/// Index is 0-based. If the pattern doesn't occur enough times, returns `Error(Nil)`.
///
/// ## Examples
///
/// > after_at("a.b.c.d", on: ".", at: 1)
/// Ok("c.d")
///
/// > after_at("hello world", on: " ", at: 5)
/// Error(Nil)
///
pub fn after_at(
string: String,
on pattern: String,
at index: Int,
) -> Result(String, Nil) {
case pattern == "" || index < 0 {
True -> Error(Nil)
False -> {
let parts = string.split(string, on: pattern)
case list.length(parts) > index + 1 {
True -> {
parts
|> list.drop(index + 1)
|> string.join(with: pattern)
|> Ok
}
False -> Error(Nil)
}
}
}
}
/// Returns the part of a string between the nth occurrence of start and the first occurrence of end after that.
///
/// Index is 0-based for the start pattern. If patterns don't occur enough times, returns `Error(Nil)`.
///
/// ## Examples
///
/// > between_at("12", from: "", to: "", at: 1)
/// Ok("2")
///
/// > between_at("title
", from: "", to: "", at: 0)
/// Error(Nil)
///
pub fn between_at(
string: String,
from start: String,
to end: String,
at index: Int,
) -> Result(String, Nil) {
case start == "" || end == "" || index < 0 {
True -> Error(Nil)
False -> {
case after_at(string, on: start, at: index) {
Ok(after_start) -> before(after_start, on: end)
Error(e) -> Error(e)
}
}
}
}
/// Returns all parts of a string before each occurrence of a given substring.
///
/// ## Examples
///
/// > before_all("a.b.c.d", on: ".")
/// ["a", "a.b", "a.b.c"]
///
/// > before_all("hello world", on: "!")
/// []
///
pub fn before_all(string: String, on pattern: String) -> List(String) {
case pattern == "" {
True -> []
False -> {
let parts = string.split(string, on: pattern)
case list.length(parts) {
1 -> []
_ -> {
int.range(from: 1, to: list.length(parts), with: [], run: fn(acc, i) {
let part =
parts
|> list.take(i)
|> string.join(with: pattern)
[part, ..acc]
})
|> list.reverse
}
}
}
}
}
/// Returns all parts of a string after each occurrence of a given substring.
///
/// ## Examples
///
/// > after_all("a.b.c.d", on: ".")
/// ["b.c.d", "c.d", "d"]
///
/// > after_all("hello world", on: "!")
/// []
///
pub fn after_all(string: String, on pattern: String) -> List(String) {
case pattern == "" {
True -> []
False -> {
let parts = string.split(string, on: pattern)
case list.length(parts) {
1 -> []
_ -> {
int.range(from: 1, to: list.length(parts), with: [], run: fn(acc, i) {
let part =
parts
|> list.drop(i)
|> string.join(with: pattern)
[part, ..acc]
})
|> list.reverse
}
}
}
}
}
/// Returns all parts of a string between each occurrence of start and the next occurrence of end.
///
/// ## Examples
///
/// > between_all("123", from: "", to: "")
/// ["1", "3"]
///
/// > between_all("no matches here", from: "
", to: "
")
/// []
///
pub fn between_all(
string: String,
from start: String,
to end: String,
) -> List(String) {
case start == "" || end == "" {
True -> []
False -> {
let after_parts = after_all(string, on: start)
after_parts
|> list.filter_map(fn(part) { before(part, on: end) })
}
}
}