function upper_first(text) return text:sub(1, 1):upper() .. text:sub(2) end

function lower_first(text) return text:sub(1, 1):lower() .. text:sub(2) end

function starts_with(text, start) return text:sub(1, start:len()) == start end

function ends_with(text, suffix) return text:sub(text:len() - suffix:len() + 1) == suffix end

function trim(text, to_remove)
	local j = 1
	for i = 1, string.len(text) do
		if text:sub(i, i) ~= to_remove then
			j = i
			break
		end
	end
	local k = 1
	for i = string.len(text), j, -1 do
		if text:sub(i, i) ~= to_remove then
			k = i
			break
		end
	end
	return text:sub(j, k)
end

function trim_begin(text, to_remove)
	local j = 1
	for i = 1, string.len(text) do
		if text:sub(i, i) ~= to_remove then
			j = i
			break
		end
	end
	return text:sub(j)
end

trim_left = trim_begin

function trim_end(text, to_remove)
	local k = 1
	for i = string.len(text), 1, -1 do
		if text:sub(i, i) ~= to_remove then
			k = i
			break
		end
	end
	return text:sub(1, k)
end

trim_right = trim_end

function split(text, delimiter, limit, is_regex)
	limit = limit or math.huge
	local no_regex = not is_regex
	local parts = {}
	local occurences = 1
	local last_index = 1
	local index = string.find(text, delimiter, 1, no_regex)
	while index and occurences < limit do
		table.insert(parts, string.sub(text, last_index, index - 1))
		last_index = index + string.len(delimiter)
		index = string.find(text, delimiter, index + string.len(delimiter), no_regex)
		occurences = occurences + 1
	end
	table.insert(parts, string.sub(text, last_index))
	return parts
end

function split_without_limit(text, delimiter, is_regex) return split(text, delimiter, nil, is_regex) end

split_unlimited = split_without_limit

function split_lines(text, limit) return modlib.text.split(text, "\r?\n", limit, true) end

function lines(text) return text:gmatch"[^\r\n]*" end

local hashtag = string.byte"#"
local zero = string.byte"0"
local nine = string.byte"9"
local letter_a = string.byte"A"
local letter_f = string.byte"F"

function is_hexadecimal(byte)
	return byte >= zero and byte <= nine or byte >= letter_a and byte <= letter_f
end

magic_chars = {
	"%",
	"(",
	")",
	".",
	"+",
	"-",
	"*",
	"?",
	"[",
	"^",
	"$"
}
local magic_charset = {}
for _, magic_char in pairs(magic_chars) do table.insert(magic_charset, "%" .. magic_char) end
magic_charset = "[" .. table.concat(magic_charset) .. "]"

function escape_magic_chars(text) return text:gsub("(" .. magic_charset .. ")", "%%%1") end

function utf8(number)
	if number < 0x007F then
		-- Single byte
		return string.char(number)
	end
	if number < 0x00A0 or number > 0x10FFFF then
		-- Out of range
		return
	end
	local result = ""
	local i = 0
	while true do
		local remainder = number % 64
		result = string.char(128 + remainder) .. result
		number = (number - remainder) / 64
		i = i + 1
		if number <= 2 ^ (8 - i - 2) then break end
	end
	return string.char(256 - 2 ^ (8 - i - 1) + number) .. result
end

--+ deprecated
function handle_ifdefs(code, vars)
	local finalcode = {}
	local endif
	local after_endif = -1
	local ifdef_pos, after_ifdef = string.find(code, "--IFDEF", 1, true)
	while ifdef_pos do
		table.insert(finalcode, string.sub(code, after_endif + 2, ifdef_pos - 1))
		local linebreak = string.find(code, "\n", after_ifdef + 1, true)
		local varname = string.sub(code, after_ifdef + 2, linebreak - 1)
		endif, after_endif = string.find(code, "--ENDIF", linebreak + 1, true)
		if not endif then break end
		if vars[varname] then
			table.insert(finalcode, string.sub(code, linebreak + 1, endif - 1))
		end
		ifdef_pos, after_ifdef = string.find(code, "--IFDEF", after_endif + 1, true)
	end
	table.insert(finalcode, string.sub(code, after_endif + 2))
	return table.concat(finalcode, "")
end