Skip to content

Commit 8fa46c0

Browse files
authored
Add chopprefix, chopsuffix (#825)
1 parent 17dc60f commit 8fa46c0

File tree

4 files changed

+166
-6
lines changed

4 files changed

+166
-6
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Compat"
22
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
3-
version = "4.14.0"
3+
version = "4.15.0"
44

55
[deps]
66
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,12 @@ changes in `julia`.
7070

7171
## Supported features
7272

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

7575
* `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).
7676

77+
* `allequal(f, itr)` and `allunique(f, itr)` methods. ([#47679]) (since Compat 4.13.0)
78+
7779
* `Iterators.cycle(itr, n)` is the lazy version of `repeat(vector, n)`. ([#47354]) (since Compat 4.13.0)
7880

7981
* `@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)
@@ -171,6 +173,7 @@ Note that you should specify the correct minimum version for `Compat` in the
171173
[#39794]: https://github.com/JuliaLang/julia/issues/39794
172174
[#40729]: https://github.com/JuliaLang/julia/issues/40729
173175
[#40803]: https://github.com/JuliaLang/julia/issues/40803
176+
[#40995]: https://github.com/JuliaLang/julia/pull/40995
174177
[#41007]: https://github.com/JuliaLang/julia/issues/41007
175178
[#41032]: https://github.com/JuliaLang/julia/issues/41032
176179
[#41076]: https://github.com/JuliaLang/julia/issues/41076
@@ -184,6 +187,6 @@ Note that you should specify the correct minimum version for `Compat` in the
184187
[#45052]: https://github.com/JuliaLang/julia/issues/45052
185188
[#45607]: https://github.com/JuliaLang/julia/issues/45607
186189
[#47354]: https://github.com/JuliaLang/julia/issues/47354
190+
[#47679]: https://github.com/JuliaLang/julia/pull/47679
187191
[#48038]: https://github.com/JuliaLang/julia/issues/48038
188192
[#50105]: https://github.com/JuliaLang/julia/issues/50105
189-
[#47679]: https://github.com/JuliaLang/julia/pull/47679

src/Compat.jl

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ if VERSION < v"1.11.0-DEV.1562"
784784
length(t) < 32 || return Base._hashed_allunique(Base.Generator(f, t))
785785
return allunique(map(f, t))
786786
end
787-
787+
788788
# allequal is either imported or defined above
789789
allequal(f, xs) = allequal(Base.Generator(f, xs))
790790
function allequal(f, xs::Tuple)
@@ -1001,9 +1001,9 @@ if !isdefined(Base, :logrange) # VERSION < v"1.12.0-DEV.2" or appropriate 1.11.
10011001
_exp_allowing_twice64(x::Number) = exp(x)
10021002

10031003
if VERSION >= v"1.9.0-DEV.318" # Julia PR #44717 allows this high-precision path:
1004-
1004+
10051005
_exp_allowing_twice64(x::Base.TwicePrecision{Float64}) = Base.Math.exp_impl(x.hi, x.lo, Val(:ℯ))
1006-
1006+
10071007
function _log_twice64_unchecked(x::Float64)
10081008
xu = reinterpret(UInt64, x)
10091009
if xu < (UInt64(1)<<52) # x is subnormal
@@ -1027,6 +1027,101 @@ else
10271027
using Base: LogRange
10281028
end
10291029

1030+
# https://github.com/JuliaLang/julia/pull/40995: add chopprefix, chopsuffix
1031+
if VERSION < v"1.8.0-DEV.1016"
1032+
function chopprefix(s::AbstractString, prefix::Regex)
1033+
m = match(prefix, s, firstindex(s), Base.PCRE.ANCHORED)
1034+
m === nothing && return SubString(s)
1035+
return SubString(s, ncodeunits(m.match) + 1)
1036+
end
1037+
1038+
function chopsuffix(s::AbstractString, suffix::Regex)
1039+
m = match(suffix, s, firstindex(s), Base.PCRE.ENDANCHORED)
1040+
m === nothing && return SubString(s)
1041+
isempty(m.match) && return SubString(s)
1042+
return SubString(s, firstindex(s), prevind(s, m.offset))
1043+
end
1044+
1045+
"""
1046+
chopprefix(s::AbstractString, prefix::Union{AbstractString,Regex}) -> SubString
1047+
1048+
Remove the prefix `prefix` from `s`. If `s` does not start with `prefix`, a string equal to `s` is returned.
1049+
1050+
See also [`chopsuffix`](@ref).
1051+
1052+
# Examples
1053+
```jldoctest
1054+
julia> chopprefix("Hamburger", "Ham")
1055+
"burger"
1056+
1057+
julia> chopprefix("Hamburger", "hotdog")
1058+
"Hamburger"
1059+
```
1060+
"""
1061+
function chopprefix(s::AbstractString, prefix::AbstractString)
1062+
k = firstindex(s)
1063+
i, j = iterate(s), iterate(prefix)
1064+
while true
1065+
j === nothing && i === nothing && return SubString(s, 1, 0) # s == prefix: empty result
1066+
j === nothing && return @inbounds SubString(s, k) # ran out of prefix: success!
1067+
i === nothing && return SubString(s) # ran out of source: failure
1068+
i[1] == j[1] || return SubString(s) # mismatch: failure
1069+
k = i[2]
1070+
i, j = iterate(s, k), iterate(prefix, j[2])
1071+
end
1072+
end
1073+
1074+
function chopprefix(s::Union{String, SubString{String}},
1075+
prefix::Union{String, SubString{String}})
1076+
if startswith(s, prefix)
1077+
SubString(s, 1 + ncodeunits(prefix))
1078+
else
1079+
SubString(s)
1080+
end
1081+
end
1082+
1083+
"""
1084+
chopsuffix(s::AbstractString, suffix::Union{AbstractString,Regex}) -> SubString
1085+
1086+
Remove the suffix `suffix` from `s`. If `s` does not end with `suffix`, a string equal to `s` is returned.
1087+
1088+
See also [`chopprefix`](@ref).
1089+
1090+
# Examples
1091+
```jldoctest
1092+
julia> chopsuffix("Hamburger", "er")
1093+
"Hamburg"
1094+
julia> chopsuffix("Hamburger", "hotdog")
1095+
"Hamburger"
1096+
```
1097+
"""
1098+
function chopsuffix(s::AbstractString, suffix::AbstractString)
1099+
a, b = Iterators.Reverse(s), Iterators.Reverse(suffix)
1100+
k = lastindex(s)
1101+
i, j = iterate(a), iterate(b)
1102+
while true
1103+
j === nothing && i === nothing && return SubString(s, 1, 0) # s == suffix: empty result
1104+
j === nothing && return @inbounds SubString(s, firstindex(s), k) # ran out of suffix: success!
1105+
i === nothing && return SubString(s) # ran out of source: failure
1106+
i[1] == j[1] || return SubString(s) # mismatch: failure
1107+
k = i[2]
1108+
i, j = iterate(a, k), iterate(b, j[2])
1109+
end
1110+
end
1111+
1112+
function chopsuffix(s::Union{String, SubString{String}},
1113+
suffix::Union{String, SubString{String}})
1114+
if !isempty(suffix) && endswith(s, suffix)
1115+
astart = ncodeunits(s) - ncodeunits(suffix) + 1
1116+
@inbounds SubString(s, firstindex(s), prevind(s, astart))
1117+
else
1118+
SubString(s)
1119+
end
1120+
end
1121+
1122+
export chopprefix, chopsuffix
1123+
end
1124+
10301125
include("deprecated.jl")
10311126

10321127
end # module Compat

test/runtests.jl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,3 +845,65 @@ end
845845
@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"
846846
@test_skip repr("text/plain", Compat.LogRange(1,2,0)) == "LogRange{Float64}(1.0, 2.0, 0)" # empty case
847847
end
848+
849+
# https://github.com/JuliaLang/julia/pull/40995
850+
@testset "chopprefix, chopsuffix" begin
851+
SubStr(s) = SubString("abc$(s)de", firstindex(s) + 3, lastindex(s) + 3)
852+
853+
for S in (String, SubStr, Test.GenericString)
854+
for T in (String, SubStr, Test.GenericString, Regex)
855+
S === Test.GenericString && T === Regex && continue # not supported
856+
@test chopprefix(S("fo∀\n"), T("bog")) == "fo∀\n"
857+
@test chopprefix(S("fo∀\n"), T("\n∀foΔ")) == "fo∀\n"
858+
@test chopprefix(S("fo∀\n"), T("∀foΔ")) == "fo∀\n"
859+
@test chopprefix(S("fo∀\n"), T("f")) == "o∀\n"
860+
@test chopprefix(S("fo∀\n"), T("fo")) == "\n"
861+
@test chopprefix(S("fo∀\n"), T("fo∀")) == "\n"
862+
@test chopprefix(S("fo∀\n"), T("fo∀\n")) == ""
863+
@test chopprefix(S("\nfo∀"), T("bog")) == "\nfo∀"
864+
@test chopprefix(S("\nfo∀"), T("\n∀foΔ")) == "\nfo∀"
865+
@test chopprefix(S("\nfo∀"), T("\nfo∀")) == ""
866+
@test chopprefix(S("\nfo∀"), T("\n")) == "fo∀"
867+
@test chopprefix(S("\nfo∀"), T("\nf")) == "o∀"
868+
@test chopprefix(S("\nfo∀"), T("\nfo")) == ""
869+
@test chopprefix(S("\nfo∀"), T("\nfo∀")) == ""
870+
@test chopprefix(S(""), T("")) == ""
871+
@test chopprefix(S(""), T("asdf")) == ""
872+
@test chopprefix(S(""), T("∃∃∃")) == ""
873+
@test chopprefix(S("εfoo"), T("ε")) == "foo"
874+
@test chopprefix(S("ofoε"), T("o")) == "foε"
875+
@test chopprefix(S("∃∃∃∃"), T("")) == "∃∃∃"
876+
@test chopprefix(S("∃∃∃∃"), T("")) == "∃∃∃∃"
877+
878+
@test chopsuffix(S("fo∀\n"), T("bog")) == "fo∀\n"
879+
@test chopsuffix(S("fo∀\n"), T("\n∀foΔ")) == "fo∀\n"
880+
@test chopsuffix(S("fo∀\n"), T("∀foΔ")) == "fo∀\n"
881+
@test chopsuffix(S("fo∀\n"), T("\n")) == "fo∀"
882+
@test chopsuffix(S("fo∀\n"), T("\n")) == "fo"
883+
@test chopsuffix(S("fo∀\n"), T("o∀\n")) == "f"
884+
@test chopsuffix(S("fo∀\n"), T("fo∀\n")) == ""
885+
@test chopsuffix(S("\nfo∀"), T("bog")) == "\nfo∀"
886+
@test chopsuffix(S("\nfo∀"), T("\n∀foΔ")) == "\nfo∀"
887+
@test chopsuffix(S("\nfo∀"), T("\nfo∀")) == ""
888+
@test chopsuffix(S("\nfo∀"), T("")) == "\nfo"
889+
@test chopsuffix(S("\nfo∀"), T("o∀")) == "\nf"
890+
@test chopsuffix(S("\nfo∀"), T("fo∀")) == "\n"
891+
@test chopsuffix(S("\nfo∀"), T("\nfo∀")) == ""
892+
@test chopsuffix(S(""), T("")) == ""
893+
@test chopsuffix(S(""), T("asdf")) == ""
894+
@test chopsuffix(S(""), T("∃∃∃")) == ""
895+
@test chopsuffix(S("fooε"), T("ε")) == "foo"
896+
@test chopsuffix(S("εofo"), T("o")) == "εof"
897+
@test chopsuffix(S("∃∃∃∃"), T("")) == "∃∃∃"
898+
@test chopsuffix(S("∃∃∃∃"), T("")) == "∃∃∃∃"
899+
end
900+
901+
if S !== Test.GenericString
902+
@test chopprefix(S("∃∃∃b∃"), r"∃+") == "b∃"
903+
@test chopsuffix(S("∃b∃∃∃"), r"∃+") == "∃b"
904+
end
905+
906+
@test isa(chopprefix(S("foo"), "fo"), SubString)
907+
@test isa(chopsuffix(S("foo"), "oo"), SubString)
908+
end
909+
end

0 commit comments

Comments
 (0)