diff --git a/gs/strings/godoc.txt b/gs/strings/godoc.txt index dc47e99aa..34f36d4a2 100644 --- a/gs/strings/godoc.txt +++ b/gs/strings/godoc.txt @@ -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 diff --git a/gs/strings/parity.json b/gs/strings/parity.json index e3761b1ba..e3358eef9 100644 --- a/gs/strings/parity.json +++ b/gs/strings/parity.json @@ -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" }, diff --git a/gs/strings/strings.test.ts b/gs/strings/strings.test.ts index 60e208ebb..cd367e102 100644 --- a/gs/strings/strings.test.ts +++ b/gs/strings/strings.test.ts @@ -10,6 +10,7 @@ import { ContainsRune, Count, Cut, + CutLast, CutPrefix, CutSuffix, EqualFold, @@ -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]) diff --git a/gs/strings/strings.ts b/gs/strings/strings.ts index 25dffdb8f..6e9d99208 100644 --- a/gs/strings/strings.ts +++ b/gs/strings/strings.ts @@ -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)) { diff --git a/tests/tests/strings_cutlast/expected.log b/tests/tests/strings_cutlast/expected.log new file mode 100644 index 000000000..3447d1c72 --- /dev/null +++ b/tests/tests/strings_cutlast/expected.log @@ -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 diff --git a/tests/tests/strings_cutlast/index.ts b/tests/tests/strings_cutlast/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests/strings_cutlast/strings_cutlast.go b/tests/tests/strings_cutlast/strings_cutlast.go new file mode 100644 index 000000000..c4671a630 --- /dev/null +++ b/tests/tests/strings_cutlast/strings_cutlast.go @@ -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) +} diff --git a/tests/tests/strings_cutlast/strings_cutlast.gs.ts b/tests/tests/strings_cutlast/strings_cutlast.gs.ts new file mode 100644 index 000000000..5879aa4cc --- /dev/null +++ b/tests/tests/strings_cutlast/strings_cutlast.gs.ts @@ -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 { + // 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() +} diff --git a/tests/tests/strings_cutlast/tsconfig.json b/tests/tests/strings_cutlast/tsconfig.json new file mode 100644 index 000000000..0023539c6 --- /dev/null +++ b/tests/tests/strings_cutlast/tsconfig.json @@ -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" + ] +}