Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gs/strings/godoc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func ContainsFunc(s string, f func(rune) bool) bool
func ContainsRune(s string, r rune) bool
func Count(s, substr string) int
func Cut(s, sep string) (before, after string, found bool)
func CutLast(s, sep string) (before, after string, found bool)
func CutPrefix(s, prefix string) (after string, found bool)
func CutSuffix(s, suffix string) (before string, found bool)
func EqualFold(s, t string) bool
Expand Down
2 changes: 1 addition & 1 deletion gs/strings/parity.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"Cut": {
"status": "real"
},
"CutLast": { "status": "blocked", "reason": "stdlib function added in a newer toolchain; not implemented by the override" },
"CutLast": { "status": "real", "since": "go1.27" },
"CutPrefix": {
"status": "real"
},
Expand Down
11 changes: 11 additions & 0 deletions gs/strings/strings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ContainsRune,
Count,
Cut,
CutLast,
CutPrefix,
CutSuffix,
EqualFold,
Expand Down Expand Up @@ -128,6 +129,16 @@ describe('strings', () => {
})
})

describe('CutLast', () => {
it('should cut around the last separator', () => {
expect(CutLast('a-b-c', '-')).toEqual(['a-b', 'c', true])
expect(CutLast('hello', '-')).toEqual(['hello', '', false])
expect(CutLast('', '-')).toEqual(['', '', false])
expect(CutLast('a--b', '--')).toEqual(['a', 'b', true])
expect(CutLast('abc', '')).toEqual(['abc', '', true])
})
})

describe('CutPrefix', () => {
it('should cut prefix and report if found', () => {
expect(CutPrefix('hello world', 'hello ')).toEqual(['world', true])
Expand Down
9 changes: 9 additions & 0 deletions gs/strings/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,15 @@ export function Cut(s: string, sep: string): [string, string, boolean] {
return [s.slice(0, index), s.slice(index + sep.length), true]
}

// CutLast slices s around the last instance of sep, returning the text before and after sep.
export function CutLast(s: string, sep: string): [string, string, boolean] {
const index = s.lastIndexOf(sep)
if (index === -1) {
return [s, '', false]
}
return [s.slice(0, index), s.slice(index + sep.length), true]
}

// CutPrefix returns s without the provided leading prefix string and reports whether it found the prefix.
export function CutPrefix(s: string, prefix: string): [string, boolean] {
if (s.startsWith(prefix)) {
Expand Down
4 changes: 4 additions & 0 deletions tests/tests/strings_cutlast/expected.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CutLast: a/b c true
Cut: a b/c true
missing: abc true false
multi: héllo::wörld end true
Empty file.
19 changes: 19 additions & 0 deletions tests/tests/strings_cutlast/strings_cutlast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import "strings"

func main() {
// CutLast splits around the last separator, like Cut does around the first.
before, after, found := strings.CutLast("a/b/c", "/")
println("CutLast:", before, after, found)
before, after, found = strings.Cut("a/b/c", "/")
println("Cut:", before, after, found)

// Without the separator, it returns s, "", false.
before, after, found = strings.CutLast("abc", "/")
println("missing:", before, after == "", found)

// A multi-byte separator and non-ASCII text cut at the last match.
before, after, found = strings.CutLast("héllo::wörld::end", "::")
println("multi:", before, after, found)
}
34 changes: 34 additions & 0 deletions tests/tests/strings_cutlast/strings_cutlast.gs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Generated file based on strings_cutlast.go
// Updated when compliance tests are re-run, DO NOT EDIT!

import * as $ from "@goscript/builtin/index.js"
import * as strings from "@goscript/strings/index.js"

export async function main(): globalThis.Promise<void> {
// CutLast splits around the last separator, like Cut does around the first.
let [before, after, found] = strings.CutLast("a/b/c", "/")
await $.println("CutLast:", before, after, found)
let __goscriptTuple0: any = strings.Cut("a/b/c", "/")
before = __goscriptTuple0[0]
after = __goscriptTuple0[1]
found = __goscriptTuple0[2]
await $.println("Cut:", before, after, found)

// Without the separator, it returns s, "", false.
let __goscriptTuple1: any = strings.CutLast("abc", "/")
before = __goscriptTuple1[0]
after = __goscriptTuple1[1]
found = __goscriptTuple1[2]
await $.println("missing:", before, $.stringEqual(after, ""), found)

// A multi-byte separator and non-ASCII text cut at the last match.
let __goscriptTuple2: any = strings.CutLast("h\xc3\xa9llo::w\xc3\xb6rld::end", "::")
before = __goscriptTuple2[0]
after = __goscriptTuple2[1]
found = __goscriptTuple2[2]
await $.println("multi:", before, after, found)
}

if ($.isMainScript(import.meta)) {
await main()
}
25 changes: 25 additions & 0 deletions tests/tests/strings_cutlast/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"paths": {
"*": [
"./*"
],
"@go/github.com/s4wave/goscript/*": [
"../../../*"
],
"@goscript/*": [
"../../../tests/tests/strings_cutlast/run/output/@goscript/*",
"../../../gs/*",
"../../../tests/deps/*"
],
"@goscript/github.com/s4wave/goscript/tests/tests/strings_cutlast/*": [
"./*"
]
}
},
"extends": "../../../tests/tsconfig.base.json",
"include": [
"index.ts",
"strings_cutlast.gs.ts"
]
}
Loading