Skip to content

Commit cab0c90

Browse files
authored
[feat] add new lib: string (#46)
* basic const * testing consts now * for all printable * check length * reverse and test * for more string funcs * func wroks * for quoertew * add sp test * for str * add cases * another checks
1 parent e8bb4a1 commit cab0c90

File tree

6 files changed

+391
-3
lines changed

6 files changed

+391
-3
lines changed

config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
libjson "github.com/1set/starlet/lib/json"
99
librand "github.com/1set/starlet/lib/random"
1010
libre "github.com/1set/starlet/lib/re"
11+
libstr "github.com/1set/starlet/lib/string"
1112
stdmath "go.starlark.net/lib/math"
1213
stdtime "go.starlark.net/lib/time"
1314
"go.starlark.net/resolve"
@@ -41,6 +42,7 @@ var allBuiltinModules = ModuleLoaderMap{
4142
libb64.ModuleName: libb64.LoadModule,
4243
librand.ModuleName: librand.LoadModule,
4344
libjson.ModuleName: libjson.LoadModule,
45+
libstr.ModuleName: libstr.LoadModule,
4446
}
4547

4648
// GetAllBuiltinModuleNames returns a list of all builtin module names.

lib/goidiomatic/idiomatic.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func length(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, k
8181
if sv, ok := v.(starlark.Sequence); ok {
8282
return starlark.MakeInt(sv.Len()), nil
8383
}
84-
return none, fmt.Errorf(`object of type '%s' has no length()`, v.Type())
84+
return none, fmt.Errorf(`length() function isn't supported for '%s' type object`, v.Type())
8585
}
8686
}
8787

lib/goidiomatic/idiomatic_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ func TestLoadModule_GoIdiomatic(t *testing.T) {
259259
load('go_idiomatic', 'length')
260260
length(True)
261261
`),
262-
wantErr: `object of type 'bool' has no length()`,
262+
wantErr: `length() function isn't supported for 'bool' type object`,
263263
},
264264
{
265265
name: `sum()`,

lib/string/string.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Package string defines functions that manipulate strings, it's intended to be a drop-in subset of Python's string module for Starlark.
2+
// See https://docs.python.org/3/library/string.html and https://github.com/python/cpython/blob/main/Lib/string.py for reference.
3+
package string
4+
5+
import (
6+
"fmt"
7+
"html"
8+
"strconv"
9+
"sync"
10+
"unicode/utf8"
11+
12+
"go.starlark.net/starlark"
13+
"go.starlark.net/starlarkstruct"
14+
)
15+
16+
// ModuleName defines the expected name for this Module when used
17+
// in starlark's load() function, eg: load('string', 'length')
18+
const ModuleName = "string"
19+
20+
var (
21+
once sync.Once
22+
strModule starlark.StringDict
23+
)
24+
25+
const (
26+
whitespace = " \t\n\r\v\f"
27+
asciiLowerCase = `abcdefghijklmnopqrstuvwxyz`
28+
asciiUpperCase = `ABCDEFGHIJKLMNOPQRSTUVWXYZ`
29+
asciiLetters = asciiLowerCase + asciiUpperCase
30+
decDigits = `0123456789`
31+
hexDigits = decDigits + `abcdefABCDEF`
32+
octDigits = `01234567`
33+
punctuation = `!"#$%&'()*+,-./:;<=>?@[\]^_` + "{|}~`"
34+
printable = decDigits + asciiLetters + punctuation + whitespace
35+
)
36+
37+
// LoadModule loads the time module. It is concurrency-safe and idempotent.
38+
func LoadModule() (starlark.StringDict, error) {
39+
once.Do(func() {
40+
strModule = starlark.StringDict{
41+
ModuleName: &starlarkstruct.Module{
42+
Name: ModuleName,
43+
Members: starlark.StringDict{
44+
// constants
45+
"ascii_lowercase": starlark.String(asciiLowerCase),
46+
"ascii_uppercase": starlark.String(asciiUpperCase),
47+
"ascii_letters": starlark.String(asciiLetters),
48+
"digits": starlark.String(decDigits),
49+
"hexdigits": starlark.String(hexDigits),
50+
"octdigits": starlark.String(octDigits),
51+
"punctuation": starlark.String(punctuation),
52+
"whitespace": starlark.String(whitespace),
53+
"printable": starlark.String(printable),
54+
55+
// functions
56+
"length": starlark.NewBuiltin(ModuleName+".length", length),
57+
"reverse": starlark.NewBuiltin(ModuleName+".reverse", reverse),
58+
"escape": genStarStrBuiltin("escape", html.EscapeString),
59+
"unescape": genStarStrBuiltin("unescape", html.UnescapeString),
60+
"quote": genStarStrBuiltin("quote", strconv.Quote),
61+
"unquote": genStarStrBuiltin("unquote", robustUnquote),
62+
},
63+
},
64+
}
65+
})
66+
return strModule, nil
67+
}
68+
69+
// for convenience
70+
var (
71+
emptyStr string
72+
none = starlark.None
73+
)
74+
75+
type (
76+
starFn func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
77+
)
78+
79+
// length returns the length of the given value.
80+
func length(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
81+
if l := len(args); l != 1 {
82+
return none, fmt.Errorf(`length() takes exactly one argument (%d given)`, l)
83+
}
84+
85+
switch r := args[0]; v := r.(type) {
86+
case starlark.String:
87+
return starlark.MakeInt(utf8.RuneCountInString(v.GoString())), nil
88+
case starlark.Bytes:
89+
return starlark.MakeInt(len(v)), nil
90+
default:
91+
if sv, ok := v.(starlark.Sequence); ok {
92+
return starlark.MakeInt(sv.Len()), nil
93+
}
94+
return none, fmt.Errorf(`length() function isn't supported for '%s' type object`, v.Type())
95+
}
96+
}
97+
98+
// reverse returns the reversed string of the given value.
99+
func reverse(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
100+
if l := len(args); l != 1 {
101+
return none, fmt.Errorf(`reverse() takes exactly one argument (%d given)`, l)
102+
}
103+
104+
switch r := args[0]; v := r.(type) {
105+
case starlark.String:
106+
rs := []rune(v.GoString())
107+
for i, j := 0, len(rs)-1; i < j; i, j = i+1, j-1 {
108+
rs[i], rs[j] = rs[j], rs[i]
109+
}
110+
return starlark.String(rs), nil
111+
case starlark.Bytes:
112+
bs := []byte(v)
113+
for i, j := 0, len(v)-1; i < j; i, j = i+1, j-1 {
114+
bs[i], bs[j] = bs[j], bs[i]
115+
}
116+
return starlark.Bytes(bs), nil
117+
default:
118+
return none, fmt.Errorf(`reverse() function isn't supported for '%s' type object`, v.Type())
119+
}
120+
}
121+
122+
// genStarStrBuiltin generates the string operation builtin for Starlark.
123+
func genStarStrBuiltin(fn string, opFn func(string) string) *starlark.Builtin {
124+
sf := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
125+
if l := len(args); l != 1 {
126+
return none, fmt.Errorf(`%s() takes exactly one argument (%d given)`, fn, l)
127+
}
128+
129+
switch r := args[0]; v := r.(type) {
130+
case starlark.String:
131+
return starlark.String(opFn(v.GoString())), nil
132+
case starlark.Bytes:
133+
return starlark.Bytes(opFn(string(v))), nil
134+
default:
135+
return none, fmt.Errorf(`%s() function isn't supported for '%s' type object`, fn, v.Type())
136+
}
137+
}
138+
return starlark.NewBuiltin(ModuleName+"."+fn, sf)
139+
}
140+
141+
// robustUnquote unquotes a string, even if it's not quoted.
142+
func robustUnquote(s string) string {
143+
if len(s) < 2 {
144+
return s
145+
}
146+
147+
// if it's not quoted, quote it
148+
old := s
149+
if !(s[0] == '"' && s[len(s)-1] == '"') {
150+
s = `"` + s + `"`
151+
}
152+
153+
// try to unquote
154+
ns, err := strconv.Unquote(s)
155+
if err != nil {
156+
// if failed, return original string
157+
return old
158+
}
159+
// return unmodified string
160+
return ns
161+
}

0 commit comments

Comments
 (0)