Skip to content

Commit

Permalink
Add chopprefix, chopsuffix (JuliaLang#788)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhanak committed May 2, 2024
1 parent 220f4d7 commit 947f830
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Compat"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "4.14.0"
version = "4.15.0"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ changes in `julia`.

## Supported features

* `allequal(f, itr)` and `allunique(f, itr)` methods. ([#47679]) (since Compat 4.13.0)
* `chopprefix(s, prefix)` and `chopsuffix(s, suffix)` ([#40995]) (since Compat 4.15.0)

* `logrange(lo, hi; length)` is like `range` but with a constant ratio, not difference. ([#39071]) (since Compat 4.14.0) Note that on Julia 1.8 and earlier, the version from Compat has slightly lower floating-point accuracy than the one in Base (Julia 1.11 and later).

* `allequal(f, itr)` and `allunique(f, itr)` methods. ([#47679]) (since Compat 4.13.0)

* `Iterators.cycle(itr, n)` is the lazy version of `repeat(vector, n)`. ([#47354]) (since Compat 4.13.0)

* `@compat public foo, bar` marks `foo` and `bar` as public in Julia 1.11+ and is a no-op in Julia 1.10 and earlier. ([#50105]) (since Compat 3.47.0, 4.10.0)
Expand Down Expand Up @@ -171,6 +173,7 @@ Note that you should specify the correct minimum version for `Compat` in the
[#39794]: https://github.com/JuliaLang/julia/issues/39794
[#40729]: https://github.com/JuliaLang/julia/issues/40729
[#40803]: https://github.com/JuliaLang/julia/issues/40803
[#40995]: https://github.com/JuliaLang/julia/pull/40995
[#41007]: https://github.com/JuliaLang/julia/issues/41007
[#41032]: https://github.com/JuliaLang/julia/issues/41032
[#41076]: https://github.com/JuliaLang/julia/issues/41076
Expand All @@ -184,6 +187,6 @@ Note that you should specify the correct minimum version for `Compat` in the
[#45052]: https://github.com/JuliaLang/julia/issues/45052
[#45607]: https://github.com/JuliaLang/julia/issues/45607
[#47354]: https://github.com/JuliaLang/julia/issues/47354
[#47679]: https://github.com/JuliaLang/julia/pull/47679
[#48038]: https://github.com/JuliaLang/julia/issues/48038
[#50105]: https://github.com/JuliaLang/julia/issues/50105
[#47679]: https://github.com/JuliaLang/julia/pull/47679
101 changes: 98 additions & 3 deletions src/Compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ if VERSION < v"1.11.0-DEV.1562"
length(t) < 32 || return Base._hashed_allunique(Base.Generator(f, t))
return allunique(map(f, t))
end

# allequal is either imported or defined above
allequal(f, xs) = allequal(Base.Generator(f, xs))
function allequal(f, xs::Tuple)
Expand Down Expand Up @@ -1001,9 +1001,9 @@ if !isdefined(Base, :logrange) # VERSION < v"1.12.0-DEV.2" or appropriate 1.11.
_exp_allowing_twice64(x::Number) = exp(x)

if VERSION >= v"1.9.0-DEV.318" # Julia PR #44717 allows this high-precision path:

_exp_allowing_twice64(x::Base.TwicePrecision{Float64}) = Base.Math.exp_impl(x.hi, x.lo, Val(:ℯ))

function _log_twice64_unchecked(x::Float64)
xu = reinterpret(UInt64, x)
if xu < (UInt64(1)<<52) # x is subnormal
Expand All @@ -1027,6 +1027,101 @@ else
using Base: LogRange
end

# https://github.com/JuliaLang/julia/pull/40995: add chopprefix, chopsuffix
if VERSION < v"1.8.0-DEV.1016"
function chopprefix(s::AbstractString, prefix::Regex)
m = match(prefix, s, firstindex(s), Base.PCRE.ANCHORED)
m === nothing && return SubString(s)
return SubString(s, ncodeunits(m.match) + 1)
end

function chopsuffix(s::AbstractString, suffix::Regex)
m = match(suffix, s, firstindex(s), Base.PCRE.ENDANCHORED)
m === nothing && return SubString(s)
isempty(m.match) && return SubString(s)
return SubString(s, firstindex(s), prevind(s, m.offset))
end

"""
chopprefix(s::AbstractString, prefix::Union{AbstractString,Regex}) -> SubString
Remove the prefix `prefix` from `s`. If `s` does not start with `prefix`, a string equal to `s` is returned.
See also [`chopsuffix`](@ref).
# Examples
```jldoctest
julia> chopprefix("Hamburger", "Ham")
"burger"
julia> chopprefix("Hamburger", "hotdog")
"Hamburger"
```
"""
function chopprefix(s::AbstractString, prefix::AbstractString)
k = firstindex(s)
i, j = iterate(s), iterate(prefix)
while true
j === nothing && i === nothing && return SubString(s, 1, 0) # s == prefix: empty result
j === nothing && return @inbounds SubString(s, k) # ran out of prefix: success!
i === nothing && return SubString(s) # ran out of source: failure
i[1] == j[1] || return SubString(s) # mismatch: failure
k = i[2]
i, j = iterate(s, k), iterate(prefix, j[2])
end
end

function chopprefix(s::Union{String, SubString{String}},
prefix::Union{String, SubString{String}})
if startswith(s, prefix)
SubString(s, 1 + ncodeunits(prefix))
else
SubString(s)
end
end

"""
chopsuffix(s::AbstractString, suffix::Union{AbstractString,Regex}) -> SubString
Remove the suffix `suffix` from `s`. If `s` does not end with `suffix`, a string equal to `s` is returned.
See also [`chopprefix`](@ref).
# Examples
```jldoctest
julia> chopsuffix("Hamburger", "er")
"Hamburg"
julia> chopsuffix("Hamburger", "hotdog")
"Hamburger"
```
"""
function chopsuffix(s::AbstractString, suffix::AbstractString)
a, b = Iterators.Reverse(s), Iterators.Reverse(suffix)
k = lastindex(s)
i, j = iterate(a), iterate(b)
while true
j === nothing && i === nothing && return SubString(s, 1, 0) # s == suffix: empty result
j === nothing && return @inbounds SubString(s, firstindex(s), k) # ran out of suffix: success!
i === nothing && return SubString(s) # ran out of source: failure
i[1] == j[1] || return SubString(s) # mismatch: failure
k = i[2]
i, j = iterate(a, k), iterate(b, j[2])
end
end

function chopsuffix(s::Union{String, SubString{String}},
suffix::Union{String, SubString{String}})
if !isempty(suffix) && endswith(s, suffix)
astart = ncodeunits(s) - ncodeunits(suffix) + 1
@inbounds SubString(s, firstindex(s), prevind(s, astart))
else
SubString(s)
end
end

export chopprefix, chopsuffix
end

include("deprecated.jl")

end # module Compat
62 changes: 62 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -845,3 +845,65 @@ end
@test_skip repr("text/plain", Compat.LogRange(1,2,3)) == "3-element Compat.LogRange{Float64, Base.TwicePrecision{Float64}}:\n 1.0, 1.41421, 2.0"
@test_skip repr("text/plain", Compat.LogRange(1,2,0)) == "LogRange{Float64}(1.0, 2.0, 0)" # empty case
end

# https://github.com/JuliaLang/julia/pull/40995
@testset "chopprefix, chopsuffix" begin
SubStr(s) = SubString("abc$(s)de", firstindex(s) + 3, lastindex(s) + 3)

for S in (String, SubStr, Test.GenericString)
for T in (String, SubStr, Test.GenericString, Regex)
S === Test.GenericString && T === Regex && continue # not supported
@test chopprefix(S("fo∀\n"), T("bog")) == "fo∀\n"
@test chopprefix(S("fo∀\n"), T("\n∀foΔ")) == "fo∀\n"
@test chopprefix(S("fo∀\n"), T("∀foΔ")) == "fo∀\n"
@test chopprefix(S("fo∀\n"), T("f")) == "o∀\n"
@test chopprefix(S("fo∀\n"), T("fo")) == "\n"
@test chopprefix(S("fo∀\n"), T("fo∀")) == "\n"
@test chopprefix(S("fo∀\n"), T("fo∀\n")) == ""
@test chopprefix(S("\nfo∀"), T("bog")) == "\nfo∀"
@test chopprefix(S("\nfo∀"), T("\n∀foΔ")) == "\nfo∀"
@test chopprefix(S("\nfo∀"), T("\nfo∀")) == ""
@test chopprefix(S("\nfo∀"), T("\n")) == "fo∀"
@test chopprefix(S("\nfo∀"), T("\nf")) == "o∀"
@test chopprefix(S("\nfo∀"), T("\nfo")) == ""
@test chopprefix(S("\nfo∀"), T("\nfo∀")) == ""
@test chopprefix(S(""), T("")) == ""
@test chopprefix(S(""), T("asdf")) == ""
@test chopprefix(S(""), T("∃∃∃")) == ""
@test chopprefix(S("εfoo"), T("ε")) == "foo"
@test chopprefix(S("ofoε"), T("o")) == "foε"
@test chopprefix(S("∃∃∃∃"), T("")) == "∃∃∃"
@test chopprefix(S("∃∃∃∃"), T("")) == "∃∃∃∃"

@test chopsuffix(S("fo∀\n"), T("bog")) == "fo∀\n"
@test chopsuffix(S("fo∀\n"), T("\n∀foΔ")) == "fo∀\n"
@test chopsuffix(S("fo∀\n"), T("∀foΔ")) == "fo∀\n"
@test chopsuffix(S("fo∀\n"), T("\n")) == "fo∀"
@test chopsuffix(S("fo∀\n"), T("\n")) == "fo"
@test chopsuffix(S("fo∀\n"), T("o∀\n")) == "f"
@test chopsuffix(S("fo∀\n"), T("fo∀\n")) == ""
@test chopsuffix(S("\nfo∀"), T("bog")) == "\nfo∀"
@test chopsuffix(S("\nfo∀"), T("\n∀foΔ")) == "\nfo∀"
@test chopsuffix(S("\nfo∀"), T("\nfo∀")) == ""
@test chopsuffix(S("\nfo∀"), T("")) == "\nfo"
@test chopsuffix(S("\nfo∀"), T("o∀")) == "\nf"
@test chopsuffix(S("\nfo∀"), T("fo∀")) == "\n"
@test chopsuffix(S("\nfo∀"), T("\nfo∀")) == ""
@test chopsuffix(S(""), T("")) == ""
@test chopsuffix(S(""), T("asdf")) == ""
@test chopsuffix(S(""), T("∃∃∃")) == ""
@test chopsuffix(S("fooε"), T("ε")) == "foo"
@test chopsuffix(S("εofo"), T("o")) == "εof"
@test chopsuffix(S("∃∃∃∃"), T("")) == "∃∃∃"
@test chopsuffix(S("∃∃∃∃"), T("")) == "∃∃∃∃"
end

if S !== Test.GenericString
@test chopprefix(S("∃∃∃b∃"), r"∃+") == "b∃"
@test chopsuffix(S("∃b∃∃∃"), r"∃+") == "∃b"
end

@test isa(chopprefix(S("foo"), "fo"), SubString)
@test isa(chopsuffix(S("foo"), "oo"), SubString)
end
end

0 comments on commit 947f830

Please sign in to comment.