whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bitfield converts annotated structs into integer values.
|
||||
//
|
||||
// Any field that is marked with a bitfield tag is compacted. The tag value has
|
||||
// two parts. The part before the comma determines the method name for a
|
||||
// generated type. If left blank the name of the field is used.
|
||||
// The part after the comma determines the number of bits to use for the
|
||||
// representation.
|
||||
package bitfield
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config determines settings for packing and generation. If a Config is used,
|
||||
// the same Config should be used for packing and generation.
|
||||
type Config struct {
|
||||
// NumBits fixes the maximum allowed bits for the integer representation.
|
||||
// If NumBits is not 8, 16, 32, or 64, the actual underlying integer size
|
||||
// will be the next largest available.
|
||||
NumBits uint
|
||||
|
||||
// If Package is set, code generation will write a package clause.
|
||||
Package string
|
||||
|
||||
// TypeName is the name for the generated type. By default it is the name
|
||||
// of the type of the value passed to Gen.
|
||||
TypeName string
|
||||
}
|
||||
|
||||
var nullConfig = &Config{}
|
||||
|
||||
// Pack packs annotated bit ranges of struct x in an integer.
|
||||
//
|
||||
// Only fields that have a "bitfield" tag are compacted.
|
||||
func Pack(x interface{}, c *Config) (packed uint64, err error) {
|
||||
packed, _, err = pack(x, c)
|
||||
return
|
||||
}
|
||||
|
||||
func pack(x interface{}, c *Config) (packed uint64, nBit uint, err error) {
|
||||
if c == nil {
|
||||
c = nullConfig
|
||||
}
|
||||
nBits := c.NumBits
|
||||
v := reflect.ValueOf(x)
|
||||
v = reflect.Indirect(v)
|
||||
t := v.Type()
|
||||
pos := 64 - nBits
|
||||
if nBits == 0 {
|
||||
pos = 0
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
v := v.Field(i)
|
||||
field := t.Field(i)
|
||||
f, err := parseField(field)
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if f.nBits == 0 {
|
||||
continue
|
||||
}
|
||||
value := uint64(0)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
value = 1
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
value = v.Uint()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x := v.Int()
|
||||
if x < 0 {
|
||||
return 0, 0, fmt.Errorf("bitfield: negative value for field %q not allowed", field.Name)
|
||||
}
|
||||
value = uint64(x)
|
||||
}
|
||||
if value > (1<<f.nBits)-1 {
|
||||
return 0, 0, fmt.Errorf("bitfield: value %#x of field %q does not fit in %d bits", value, field.Name, f.nBits)
|
||||
}
|
||||
shift := 64 - pos - f.nBits
|
||||
if pos += f.nBits; pos > 64 {
|
||||
return 0, 0, fmt.Errorf("bitfield: no more bits left for field %q", field.Name)
|
||||
}
|
||||
packed |= value << shift
|
||||
}
|
||||
if nBits == 0 {
|
||||
nBits = posToBits(pos)
|
||||
packed >>= (64 - nBits)
|
||||
}
|
||||
return packed, nBits, nil
|
||||
}
|
||||
|
||||
type field struct {
|
||||
name string
|
||||
value uint64
|
||||
nBits uint
|
||||
}
|
||||
|
||||
// parseField parses a tag of the form [<name>][:<nBits>][,<pos>[..<end>]]
|
||||
func parseField(field reflect.StructField) (f field, err error) {
|
||||
s, ok := field.Tag.Lookup("bitfield")
|
||||
if !ok {
|
||||
return f, nil
|
||||
}
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
default:
|
||||
return f, fmt.Errorf("bitfield: field %q is not an integer or bool type", field.Name)
|
||||
}
|
||||
bits := s
|
||||
f.name = ""
|
||||
|
||||
if i := strings.IndexByte(s, ','); i >= 0 {
|
||||
bits = s[:i]
|
||||
f.name = s[i+1:]
|
||||
}
|
||||
if bits != "" {
|
||||
nBits, err := strconv.ParseUint(bits, 10, 8)
|
||||
if err != nil {
|
||||
return f, fmt.Errorf("bitfield: invalid bit size for field %q: %v", field.Name, err)
|
||||
}
|
||||
f.nBits = uint(nBits)
|
||||
}
|
||||
if f.nBits == 0 {
|
||||
if field.Type.Kind() == reflect.Bool {
|
||||
f.nBits = 1
|
||||
} else {
|
||||
f.nBits = uint(field.Type.Bits())
|
||||
}
|
||||
}
|
||||
if f.name == "" {
|
||||
f.name = field.Name
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
func posToBits(pos uint) (bits uint) {
|
||||
switch {
|
||||
case pos <= 8:
|
||||
bits = 8
|
||||
case pos <= 16:
|
||||
bits = 16
|
||||
case pos <= 32:
|
||||
bits = 32
|
||||
case pos <= 64:
|
||||
bits = 64
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
// Gen generates code for unpacking integers created with Pack.
|
||||
func Gen(w io.Writer, x interface{}, c *Config) error {
|
||||
if c == nil {
|
||||
c = nullConfig
|
||||
}
|
||||
_, nBits, err := pack(x, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(x)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if c.TypeName == "" {
|
||||
c.TypeName = t.Name()
|
||||
}
|
||||
firstChar := []rune(c.TypeName)[0]
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
print := func(w io.Writer, format string, args ...interface{}) {
|
||||
if _, e := fmt.Fprintf(w, format+"\n", args...); e != nil && err == nil {
|
||||
err = fmt.Errorf("bitfield: write failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
pos := uint(0)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
f, _ := parseField(field)
|
||||
if f.nBits == 0 {
|
||||
continue
|
||||
}
|
||||
shift := nBits - pos - f.nBits
|
||||
pos += f.nBits
|
||||
|
||||
retType := field.Type.Name()
|
||||
print(buf, "\nfunc (%c %s) %s() %s {", firstChar, c.TypeName, f.name, retType)
|
||||
if field.Type.Kind() == reflect.Bool {
|
||||
print(buf, "\tconst bit = 1 << %d", shift)
|
||||
print(buf, "\treturn %c&bit == bit", firstChar)
|
||||
} else {
|
||||
print(buf, "\treturn %s((%c >> %d) & %#x)", retType, firstChar, shift, (1<<f.nBits)-1)
|
||||
}
|
||||
print(buf, "}")
|
||||
}
|
||||
|
||||
if c.Package != "" {
|
||||
print(w, "// Code generated by golang.org/x/text/internal/gen/bitfield. DO NOT EDIT.\n")
|
||||
print(w, "package %s\n", c.Package)
|
||||
}
|
||||
|
||||
bits := posToBits(pos)
|
||||
|
||||
print(w, "type %s uint%d", c.TypeName, bits)
|
||||
|
||||
if _, err := io.Copy(w, buf); err != nil {
|
||||
return fmt.Errorf("bitfield: write failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bitfield
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type myUint8 uint8
|
||||
|
||||
type test1 struct { // 28 bits
|
||||
foo uint16 `bitfield:",fob"`
|
||||
Bar int8 `bitfield:"5,baz"`
|
||||
Foo uint64
|
||||
bar myUint8 `bitfield:"3"`
|
||||
Bool bool `bitfield:""`
|
||||
Baz int8 `bitfield:"3"`
|
||||
}
|
||||
|
||||
type test2 struct {
|
||||
larger1 uint16 `bitfield:"32"`
|
||||
larger2 uint16 `bitfield:"32"`
|
||||
}
|
||||
|
||||
type tooManyBits struct {
|
||||
u1 uint16 `bitfield:"12"`
|
||||
u2 uint16 `bitfield:"12"`
|
||||
u3 uint16 `bitfield:"12"`
|
||||
u4 uint16 `bitfield:"12"`
|
||||
u5 uint16 `bitfield:"12"`
|
||||
u6 uint16 `bitfield:"12"`
|
||||
}
|
||||
|
||||
type just64 struct {
|
||||
foo uint64 `bitfield:""`
|
||||
}
|
||||
|
||||
type toUint8 struct {
|
||||
foo bool `bitfield:""`
|
||||
}
|
||||
|
||||
type toUint16 struct {
|
||||
foo int `bitfield:"9"`
|
||||
}
|
||||
|
||||
type faultySize struct {
|
||||
foo uint64 `bitfield:"a"`
|
||||
}
|
||||
|
||||
type faultyType struct {
|
||||
foo *int `bitfield:"5"`
|
||||
}
|
||||
|
||||
var (
|
||||
maxed = test1{
|
||||
foo: 0xffff,
|
||||
Bar: 0x1f,
|
||||
Foo: 0xffff,
|
||||
bar: 0x7,
|
||||
Bool: true,
|
||||
Baz: 0x7,
|
||||
}
|
||||
alternate1 = test1{
|
||||
foo: 0xffff,
|
||||
bar: 0x7,
|
||||
Baz: 0x7,
|
||||
}
|
||||
alternate2 = test1{
|
||||
Bar: 0x1f,
|
||||
Bool: true,
|
||||
}
|
||||
overflow = test1{
|
||||
Bar: 0x3f,
|
||||
}
|
||||
negative = test1{
|
||||
Bar: -1,
|
||||
}
|
||||
)
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
x interface{}
|
||||
nBits uint
|
||||
out uint64
|
||||
ok bool
|
||||
}{
|
||||
{"maxed out fields", maxed, 0, 0xfffffff0, true},
|
||||
{"maxed using less bits", maxed, 28, 0x0fffffff, true},
|
||||
|
||||
{"alternate1", alternate1, 0, 0xffff0770, true},
|
||||
{"alternate2", alternate2, 0, 0x0000f880, true},
|
||||
|
||||
{"just64", &just64{0x0f0f0f0f}, 00, 0xf0f0f0f, true},
|
||||
{"just64", &just64{0x0f0f0f0f}, 64, 0xf0f0f0f, true},
|
||||
{"just64", &just64{0xffffFFFF}, 64, 0xffffffff, true},
|
||||
{"to uint8", &toUint8{true}, 0, 0x80, true},
|
||||
{"to uint16", &toUint16{1}, 0, 0x0080, true},
|
||||
// errors
|
||||
{"overflow", overflow, 0, 0, false},
|
||||
{"too many bits", &tooManyBits{}, 0, 0, false},
|
||||
{"fault size", &faultySize{}, 0, 0, false},
|
||||
{"fault type", &faultyType{}, 0, 0, false},
|
||||
{"negative", negative, 0, 0, false},
|
||||
{"not enough bits", maxed, 27, 0, false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%T/%s", tc.x, tc.desc), func(t *testing.T) {
|
||||
v, err := Pack(tc.x, &Config{NumBits: tc.nBits})
|
||||
if ok := err == nil; v != tc.out || ok != tc.ok {
|
||||
t.Errorf("got %#x, %v; want %#x, %v (%v)", v, ok, tc.out, tc.ok, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip(t *testing.T) {
|
||||
testCases := []struct {
|
||||
x test1
|
||||
}{
|
||||
{maxed},
|
||||
{alternate1},
|
||||
{alternate2},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
v, err := Pack(tc.x, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := tc.x
|
||||
want.Foo = 0 // not stored
|
||||
x := myInt(v)
|
||||
got := test1{
|
||||
foo: x.fob(),
|
||||
Bar: x.baz(),
|
||||
bar: x.bar(),
|
||||
Bool: x.Bool(),
|
||||
Baz: x.Baz(),
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("\ngot %#v\nwant %#v (%#x)", got, want, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGen(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
x interface{}
|
||||
config *Config
|
||||
ok bool
|
||||
out string
|
||||
}{{
|
||||
desc: "test1",
|
||||
x: &test1{},
|
||||
ok: true,
|
||||
out: test1Gen,
|
||||
}, {
|
||||
desc: "test1 with options",
|
||||
x: &test1{},
|
||||
config: &Config{Package: "bitfield", TypeName: "myInt"},
|
||||
ok: true,
|
||||
out: mustRead("gen1_test.go"),
|
||||
}, {
|
||||
desc: "test1 with alternative bits",
|
||||
x: &test1{},
|
||||
config: &Config{NumBits: 28, Package: "bitfield", TypeName: "myInt2"},
|
||||
ok: true,
|
||||
out: mustRead("gen2_test.go"),
|
||||
}, {
|
||||
desc: "failure",
|
||||
x: &test1{},
|
||||
config: &Config{NumBits: 27}, // Too few bits.
|
||||
ok: false,
|
||||
out: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
err := Gen(w, tc.x, tc.config)
|
||||
if ok := err == nil; ok != tc.ok {
|
||||
t.Fatalf("got %v; want %v (%v)", ok, tc.ok, err)
|
||||
}
|
||||
got := w.String()
|
||||
if got != tc.out {
|
||||
t.Errorf("got:\n%s\nwant:\n%s", got, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const test1Gen = `type test1 uint32
|
||||
|
||||
func (t test1) fob() uint16 {
|
||||
return uint16((t >> 16) & 0xffff)
|
||||
}
|
||||
|
||||
func (t test1) baz() int8 {
|
||||
return int8((t >> 11) & 0x1f)
|
||||
}
|
||||
|
||||
func (t test1) bar() myUint8 {
|
||||
return myUint8((t >> 8) & 0x7)
|
||||
}
|
||||
|
||||
func (t test1) Bool() bool {
|
||||
const bit = 1 << 7
|
||||
return t&bit == bit
|
||||
}
|
||||
|
||||
func (t test1) Baz() int8 {
|
||||
return int8((t >> 4) & 0x7)
|
||||
}
|
||||
`
|
||||
|
||||
func mustRead(filename string) string {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Code generated by golang.org/x/text/internal/gen/bitfield. DO NOT EDIT.
|
||||
|
||||
package bitfield
|
||||
|
||||
type myInt uint32
|
||||
|
||||
func (m myInt) fob() uint16 {
|
||||
return uint16((m >> 16) & 0xffff)
|
||||
}
|
||||
|
||||
func (m myInt) baz() int8 {
|
||||
return int8((m >> 11) & 0x1f)
|
||||
}
|
||||
|
||||
func (m myInt) bar() myUint8 {
|
||||
return myUint8((m >> 8) & 0x7)
|
||||
}
|
||||
|
||||
func (m myInt) Bool() bool {
|
||||
const bit = 1 << 7
|
||||
return m&bit == bit
|
||||
}
|
||||
|
||||
func (m myInt) Baz() int8 {
|
||||
return int8((m >> 4) & 0x7)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Code generated by golang.org/x/text/internal/gen/bitfield. DO NOT EDIT.
|
||||
|
||||
package bitfield
|
||||
|
||||
type myInt2 uint32
|
||||
|
||||
func (m myInt2) fob() uint16 {
|
||||
return uint16((m >> 12) & 0xffff)
|
||||
}
|
||||
|
||||
func (m myInt2) baz() int8 {
|
||||
return int8((m >> 7) & 0x1f)
|
||||
}
|
||||
|
||||
func (m myInt2) bar() myUint8 {
|
||||
return myUint8((m >> 4) & 0x7)
|
||||
}
|
||||
|
||||
func (m myInt2) Bool() bool {
|
||||
const bit = 1 << 3
|
||||
return m&bit == bit
|
||||
}
|
||||
|
||||
func (m myInt2) Baz() int8 {
|
||||
return int8((m >> 0) & 0x7)
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// This file contains utilities for generating code.
|
||||
|
||||
// TODO: other write methods like:
|
||||
// - slices, maps, types, etc.
|
||||
|
||||
// CodeWriter is a utility for writing structured code. It computes the content
|
||||
// hash and size of written content. It ensures there are newlines between
|
||||
// written code blocks.
|
||||
type CodeWriter struct {
|
||||
buf bytes.Buffer
|
||||
Size int
|
||||
Hash hash.Hash32 // content hash
|
||||
gob *gob.Encoder
|
||||
// For comments we skip the usual one-line separator if they are followed by
|
||||
// a code block.
|
||||
skipSep bool
|
||||
}
|
||||
|
||||
func (w *CodeWriter) Write(p []byte) (n int, err error) {
|
||||
return w.buf.Write(p)
|
||||
}
|
||||
|
||||
// NewCodeWriter returns a new CodeWriter.
|
||||
func NewCodeWriter() *CodeWriter {
|
||||
h := fnv.New32()
|
||||
return &CodeWriter{Hash: h, gob: gob.NewEncoder(h)}
|
||||
}
|
||||
|
||||
// WriteGoFile appends the buffer with the total size of all created structures
|
||||
// and writes it as a Go file to the given file with the given package name.
|
||||
func (w *CodeWriter) WriteGoFile(filename, pkg string) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create file %s: %v", filename, err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err = w.WriteGo(f, pkg, ""); err != nil {
|
||||
log.Fatalf("Error writing file %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteVersionedGoFile appends the buffer with the total size of all created
|
||||
// structures and writes it as a Go file to the given file with the given
|
||||
// package name and build tags for the current Unicode version,
|
||||
func (w *CodeWriter) WriteVersionedGoFile(filename, pkg string) {
|
||||
tags := buildTags()
|
||||
if tags != "" {
|
||||
pattern := fileToPattern(filename)
|
||||
updateBuildTags(pattern)
|
||||
filename = fmt.Sprintf(pattern, UnicodeVersion())
|
||||
}
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create file %s: %v", filename, err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err = w.WriteGo(f, pkg, tags); err != nil {
|
||||
log.Fatalf("Error writing file %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteGo appends the buffer with the total size of all created structures and
|
||||
// writes it as a Go file to the given writer with the given package name.
|
||||
func (w *CodeWriter) WriteGo(out io.Writer, pkg, tags string) (n int, err error) {
|
||||
sz := w.Size
|
||||
if sz > 0 {
|
||||
w.WriteComment("Total table size %d bytes (%dKiB); checksum: %X\n", sz, sz/1024, w.Hash.Sum32())
|
||||
}
|
||||
defer w.buf.Reset()
|
||||
return WriteGo(out, pkg, tags, w.buf.Bytes())
|
||||
}
|
||||
|
||||
func (w *CodeWriter) printf(f string, x ...interface{}) {
|
||||
fmt.Fprintf(w, f, x...)
|
||||
}
|
||||
|
||||
func (w *CodeWriter) insertSep() {
|
||||
if w.skipSep {
|
||||
w.skipSep = false
|
||||
return
|
||||
}
|
||||
// Use at least two newlines to ensure a blank space between the previous
|
||||
// block. WriteGoFile will remove extraneous newlines.
|
||||
w.printf("\n\n")
|
||||
}
|
||||
|
||||
// WriteComment writes a comment block. All line starts are prefixed with "//".
|
||||
// Initial empty lines are gobbled. The indentation for the first line is
|
||||
// stripped from consecutive lines.
|
||||
func (w *CodeWriter) WriteComment(comment string, args ...interface{}) {
|
||||
s := fmt.Sprintf(comment, args...)
|
||||
s = strings.Trim(s, "\n")
|
||||
|
||||
// Use at least two newlines to ensure a blank space between the previous
|
||||
// block. WriteGoFile will remove extraneous newlines.
|
||||
w.printf("\n\n// ")
|
||||
w.skipSep = true
|
||||
|
||||
// strip first indent level.
|
||||
sep := "\n"
|
||||
for ; len(s) > 0 && (s[0] == '\t' || s[0] == ' '); s = s[1:] {
|
||||
sep += s[:1]
|
||||
}
|
||||
|
||||
strings.NewReplacer(sep, "\n// ", "\n", "\n// ").WriteString(w, s)
|
||||
|
||||
w.printf("\n")
|
||||
}
|
||||
|
||||
func (w *CodeWriter) writeSizeInfo(size int) {
|
||||
w.printf("// Size: %d bytes\n", size)
|
||||
}
|
||||
|
||||
// WriteConst writes a constant of the given name and value.
|
||||
func (w *CodeWriter) WriteConst(name string, x interface{}) {
|
||||
w.insertSep()
|
||||
v := reflect.ValueOf(x)
|
||||
|
||||
switch v.Type().Kind() {
|
||||
case reflect.String:
|
||||
w.printf("const %s %s = ", name, typeName(x))
|
||||
w.WriteString(v.String())
|
||||
w.printf("\n")
|
||||
default:
|
||||
w.printf("const %s = %#v\n", name, x)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteVar writes a variable of the given name and value.
|
||||
func (w *CodeWriter) WriteVar(name string, x interface{}) {
|
||||
w.insertSep()
|
||||
v := reflect.ValueOf(x)
|
||||
oldSize := w.Size
|
||||
sz := int(v.Type().Size())
|
||||
w.Size += sz
|
||||
|
||||
switch v.Type().Kind() {
|
||||
case reflect.String:
|
||||
w.printf("var %s %s = ", name, typeName(x))
|
||||
w.WriteString(v.String())
|
||||
case reflect.Struct:
|
||||
w.gob.Encode(x)
|
||||
fallthrough
|
||||
case reflect.Slice, reflect.Array:
|
||||
w.printf("var %s = ", name)
|
||||
w.writeValue(v)
|
||||
w.writeSizeInfo(w.Size - oldSize)
|
||||
default:
|
||||
w.printf("var %s %s = ", name, typeName(x))
|
||||
w.gob.Encode(x)
|
||||
w.writeValue(v)
|
||||
w.writeSizeInfo(w.Size - oldSize)
|
||||
}
|
||||
w.printf("\n")
|
||||
}
|
||||
|
||||
func (w *CodeWriter) writeValue(v reflect.Value) {
|
||||
x := v.Interface()
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
w.WriteString(v.String())
|
||||
case reflect.Array:
|
||||
// Don't double count: callers of WriteArray count on the size being
|
||||
// added, so we need to discount it here.
|
||||
w.Size -= int(v.Type().Size())
|
||||
w.writeSlice(x, true)
|
||||
case reflect.Slice:
|
||||
w.writeSlice(x, false)
|
||||
case reflect.Struct:
|
||||
w.printf("%s{\n", typeName(v.Interface()))
|
||||
t := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
w.printf("%s: ", t.Field(i).Name)
|
||||
w.writeValue(v.Field(i))
|
||||
w.printf(",\n")
|
||||
}
|
||||
w.printf("}")
|
||||
default:
|
||||
w.printf("%#v", x)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteString writes a string literal.
|
||||
func (w *CodeWriter) WriteString(s string) {
|
||||
io.WriteString(w.Hash, s) // content hash
|
||||
w.Size += len(s)
|
||||
|
||||
const maxInline = 40
|
||||
if len(s) <= maxInline {
|
||||
w.printf("%q", s)
|
||||
return
|
||||
}
|
||||
|
||||
// We will render the string as a multi-line string.
|
||||
const maxWidth = 80 - 4 - len(`"`) - len(`" +`)
|
||||
|
||||
// When starting on its own line, go fmt indents line 2+ an extra level.
|
||||
n, max := maxWidth, maxWidth-4
|
||||
|
||||
// As per https://golang.org/issue/18078, the compiler has trouble
|
||||
// compiling the concatenation of many strings, s0 + s1 + s2 + ... + sN,
|
||||
// for large N. We insert redundant, explicit parentheses to work around
|
||||
// that, lowering the N at any given step: (s0 + s1 + ... + s63) + (s64 +
|
||||
// ... + s127) + etc + (etc + ... + sN).
|
||||
explicitParens, extraComment := len(s) > 128*1024, ""
|
||||
if explicitParens {
|
||||
w.printf(`(`)
|
||||
extraComment = "; the redundant, explicit parens are for https://golang.org/issue/18078"
|
||||
}
|
||||
|
||||
// Print "" +\n, if a string does not start on its own line.
|
||||
b := w.buf.Bytes()
|
||||
if p := len(bytes.TrimRight(b, " \t")); p > 0 && b[p-1] != '\n' {
|
||||
w.printf("\"\" + // Size: %d bytes%s\n", len(s), extraComment)
|
||||
n, max = maxWidth, maxWidth
|
||||
}
|
||||
|
||||
w.printf(`"`)
|
||||
|
||||
for sz, p, nLines := 0, 0, 0; p < len(s); {
|
||||
var r rune
|
||||
r, sz = utf8.DecodeRuneInString(s[p:])
|
||||
out := s[p : p+sz]
|
||||
chars := 1
|
||||
if !unicode.IsPrint(r) || r == utf8.RuneError || r == '"' {
|
||||
switch sz {
|
||||
case 1:
|
||||
out = fmt.Sprintf("\\x%02x", s[p])
|
||||
case 2, 3:
|
||||
out = fmt.Sprintf("\\u%04x", r)
|
||||
case 4:
|
||||
out = fmt.Sprintf("\\U%08x", r)
|
||||
}
|
||||
chars = len(out)
|
||||
} else if r == '\\' {
|
||||
out = "\\" + string(r)
|
||||
chars = 2
|
||||
}
|
||||
if n -= chars; n < 0 {
|
||||
nLines++
|
||||
if explicitParens && nLines&63 == 63 {
|
||||
w.printf("\") + (\"")
|
||||
}
|
||||
w.printf("\" +\n\"")
|
||||
n = max - len(out)
|
||||
}
|
||||
w.printf("%s", out)
|
||||
p += sz
|
||||
}
|
||||
w.printf(`"`)
|
||||
if explicitParens {
|
||||
w.printf(`)`)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteSlice writes a slice value.
|
||||
func (w *CodeWriter) WriteSlice(x interface{}) {
|
||||
w.writeSlice(x, false)
|
||||
}
|
||||
|
||||
// WriteArray writes an array value.
|
||||
func (w *CodeWriter) WriteArray(x interface{}) {
|
||||
w.writeSlice(x, true)
|
||||
}
|
||||
|
||||
func (w *CodeWriter) writeSlice(x interface{}, isArray bool) {
|
||||
v := reflect.ValueOf(x)
|
||||
w.gob.Encode(v.Len())
|
||||
w.Size += v.Len() * int(v.Type().Elem().Size())
|
||||
name := typeName(x)
|
||||
if isArray {
|
||||
name = fmt.Sprintf("[%d]%s", v.Len(), name[strings.Index(name, "]")+1:])
|
||||
}
|
||||
if isArray {
|
||||
w.printf("%s{\n", name)
|
||||
} else {
|
||||
w.printf("%s{ // %d elements\n", name, v.Len())
|
||||
}
|
||||
|
||||
switch kind := v.Type().Elem().Kind(); kind {
|
||||
case reflect.String:
|
||||
for _, s := range x.([]string) {
|
||||
w.WriteString(s)
|
||||
w.printf(",\n")
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
// nLine and nBlock are the number of elements per line and block.
|
||||
nLine, nBlock, format := 8, 64, "%d,"
|
||||
switch kind {
|
||||
case reflect.Uint8:
|
||||
format = "%#02x,"
|
||||
case reflect.Uint16:
|
||||
format = "%#04x,"
|
||||
case reflect.Uint32:
|
||||
nLine, nBlock, format = 4, 32, "%#08x,"
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
nLine, nBlock, format = 4, 32, "%#016x,"
|
||||
case reflect.Int8:
|
||||
nLine = 16
|
||||
}
|
||||
n := nLine
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i%nBlock == 0 && v.Len() > nBlock {
|
||||
w.printf("// Entry %X - %X\n", i, i+nBlock-1)
|
||||
}
|
||||
x := v.Index(i).Interface()
|
||||
w.gob.Encode(x)
|
||||
w.printf(format, x)
|
||||
if n--; n == 0 {
|
||||
n = nLine
|
||||
w.printf("\n")
|
||||
}
|
||||
}
|
||||
w.printf("\n")
|
||||
case reflect.Struct:
|
||||
zero := reflect.Zero(v.Type().Elem()).Interface()
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
x := v.Index(i).Interface()
|
||||
w.gob.EncodeValue(v)
|
||||
if !reflect.DeepEqual(zero, x) {
|
||||
line := fmt.Sprintf("%#v,\n", x)
|
||||
line = line[strings.IndexByte(line, '{'):]
|
||||
w.printf("%d: ", i)
|
||||
w.printf(line)
|
||||
}
|
||||
}
|
||||
case reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
w.printf("%d: %#v,\n", i, v.Index(i).Interface())
|
||||
}
|
||||
default:
|
||||
panic("gen: slice elem type not supported")
|
||||
}
|
||||
w.printf("}")
|
||||
}
|
||||
|
||||
// WriteType writes a definition of the type of the given value and returns the
|
||||
// type name.
|
||||
func (w *CodeWriter) WriteType(x interface{}) string {
|
||||
t := reflect.TypeOf(x)
|
||||
w.printf("type %s struct {\n", t.Name())
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
w.printf("\t%s %s\n", t.Field(i).Name, t.Field(i).Type)
|
||||
}
|
||||
w.printf("}\n")
|
||||
return t.Name()
|
||||
}
|
||||
|
||||
// typeName returns the name of the go type of x.
|
||||
func typeName(x interface{}) string {
|
||||
t := reflect.ValueOf(x).Type()
|
||||
return strings.Replace(fmt.Sprint(t), "main.", "", 1)
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gen contains common code for the various code generation tools in the
|
||||
// text repository. Its usage ensures consistency between tools.
|
||||
//
|
||||
// This package defines command line flags that are common to most generation
|
||||
// tools. The flags allow for specifying specific Unicode and CLDR versions
|
||||
// in the public Unicode data repository (https://www.unicode.org/Public).
|
||||
//
|
||||
// A local Unicode data mirror can be set through the flag -local or the
|
||||
// environment variable UNICODE_DIR. The former takes precedence. The local
|
||||
// directory should follow the same structure as the public repository.
|
||||
//
|
||||
// IANA data can also optionally be mirrored by putting it in the iana directory
|
||||
// rooted at the top of the local mirror. Beware, though, that IANA data is not
|
||||
// versioned. So it is up to the developer to use the right version.
|
||||
package gen // import "golang.org/x/text/internal/gen"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/unicode/cldr"
|
||||
)
|
||||
|
||||
var (
|
||||
url = flag.String("url",
|
||||
"https://www.unicode.org/Public",
|
||||
"URL of Unicode database directory")
|
||||
iana = flag.String("iana",
|
||||
"http://www.iana.org",
|
||||
"URL of the IANA repository")
|
||||
unicodeVersion = flag.String("unicode",
|
||||
getEnv("UNICODE_VERSION", unicode.Version),
|
||||
"unicode version to use")
|
||||
cldrVersion = flag.String("cldr",
|
||||
getEnv("CLDR_VERSION", cldr.Version),
|
||||
"cldr version to use")
|
||||
)
|
||||
|
||||
func getEnv(name, def string) string {
|
||||
if v := os.Getenv(name); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// Init performs common initialization for a gen command. It parses the flags
|
||||
// and sets up the standard logging parameters.
|
||||
func Init() {
|
||||
log.SetPrefix("")
|
||||
log.SetFlags(log.Lshortfile)
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
const header = `// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
`
|
||||
|
||||
// UnicodeVersion reports the requested Unicode version.
|
||||
func UnicodeVersion() string {
|
||||
return *unicodeVersion
|
||||
}
|
||||
|
||||
// CLDRVersion reports the requested CLDR version.
|
||||
func CLDRVersion() string {
|
||||
return *cldrVersion
|
||||
}
|
||||
|
||||
var tags = []struct{ version, buildTags string }{
|
||||
{"9.0.0", "!go1.10"},
|
||||
{"10.0.0", "go1.10,!go1.13"},
|
||||
{"11.0.0", "go1.13,!go1.14"},
|
||||
{"12.0.0", "go1.14,!go1.16"},
|
||||
{"13.0.0", "go1.16,!go1.21"},
|
||||
{"15.0.0", "go1.21"},
|
||||
}
|
||||
|
||||
// buildTags reports the build tags used for the current Unicode version.
|
||||
func buildTags() string {
|
||||
v := UnicodeVersion()
|
||||
for _, e := range tags {
|
||||
if e.version == v {
|
||||
return e.buildTags
|
||||
}
|
||||
}
|
||||
log.Fatalf("Unknown build tags for Unicode version %q.", v)
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsLocal reports whether data files are available locally.
|
||||
func IsLocal() bool {
|
||||
dir, err := localReadmeFile()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err = os.Stat(dir); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// OpenUCDFile opens the requested UCD file. The file is specified relative to
|
||||
// the public Unicode root directory. It will call log.Fatal if there are any
|
||||
// errors.
|
||||
func OpenUCDFile(file string) io.ReadCloser {
|
||||
return openUnicode(path.Join(*unicodeVersion, "ucd", file))
|
||||
}
|
||||
|
||||
// OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there
|
||||
// are any errors.
|
||||
func OpenCLDRCoreZip() io.ReadCloser {
|
||||
return OpenUnicodeFile("cldr", *cldrVersion, "core.zip")
|
||||
}
|
||||
|
||||
// OpenUnicodeFile opens the requested file of the requested category from the
|
||||
// root of the Unicode data archive. The file is specified relative to the
|
||||
// public Unicode root directory. If version is "", it will use the default
|
||||
// Unicode version. It will call log.Fatal if there are any errors.
|
||||
func OpenUnicodeFile(category, version, file string) io.ReadCloser {
|
||||
if version == "" {
|
||||
version = UnicodeVersion()
|
||||
}
|
||||
return openUnicode(path.Join(category, version, file))
|
||||
}
|
||||
|
||||
// OpenIANAFile opens the requested IANA file. The file is specified relative
|
||||
// to the IANA root, which is typically either http://www.iana.org or the
|
||||
// iana directory in the local mirror. It will call log.Fatal if there are any
|
||||
// errors.
|
||||
func OpenIANAFile(path string) io.ReadCloser {
|
||||
return Open(*iana, "iana", path)
|
||||
}
|
||||
|
||||
var (
|
||||
dirMutex sync.Mutex
|
||||
localDir string
|
||||
)
|
||||
|
||||
const permissions = 0755
|
||||
|
||||
func localReadmeFile() (string, error) {
|
||||
p, err := build.Import("golang.org/x/text", "", build.FindOnly)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not locate package: %v", err)
|
||||
}
|
||||
return filepath.Join(p.Dir, "DATA", "README"), nil
|
||||
}
|
||||
|
||||
func getLocalDir() string {
|
||||
dirMutex.Lock()
|
||||
defer dirMutex.Unlock()
|
||||
|
||||
readme, err := localReadmeFile()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
dir := filepath.Dir(readme)
|
||||
if _, err := os.Stat(readme); err != nil {
|
||||
if err := os.MkdirAll(dir, permissions); err != nil {
|
||||
log.Fatalf("Could not create directory: %v", err)
|
||||
}
|
||||
os.WriteFile(readme, []byte(readmeTxt), permissions)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT.
|
||||
|
||||
This directory contains downloaded files used to generate the various tables
|
||||
in the golang.org/x/text subrepo.
|
||||
|
||||
Note that the language subtag repo (iana/assignments/language-subtag-registry)
|
||||
and all other times in the iana subdirectory are not versioned and will need
|
||||
to be periodically manually updated. The easiest way to do this is to remove
|
||||
the entire iana directory. This is mostly of concern when updating the language
|
||||
package.
|
||||
`
|
||||
|
||||
// Open opens subdir/path if a local directory is specified and the file exists,
|
||||
// where subdir is a directory relative to the local root, or fetches it from
|
||||
// urlRoot/path otherwise. It will call log.Fatal if there are any errors.
|
||||
func Open(urlRoot, subdir, path string) io.ReadCloser {
|
||||
file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path))
|
||||
return open(file, urlRoot, path)
|
||||
}
|
||||
|
||||
func openUnicode(path string) io.ReadCloser {
|
||||
file := filepath.Join(getLocalDir(), filepath.FromSlash(path))
|
||||
return open(file, *url, path)
|
||||
}
|
||||
|
||||
// TODO: automatically periodically update non-versioned files.
|
||||
|
||||
func open(file, urlRoot, path string) io.ReadCloser {
|
||||
if f, err := os.Open(file); err == nil {
|
||||
return f
|
||||
}
|
||||
r := get(urlRoot, path)
|
||||
defer r.Close()
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not download file: %v", err)
|
||||
}
|
||||
os.MkdirAll(filepath.Dir(file), permissions)
|
||||
if err := os.WriteFile(file, b, permissions); err != nil {
|
||||
log.Fatalf("Could not create file: %v", err)
|
||||
}
|
||||
return io.NopCloser(bytes.NewReader(b))
|
||||
}
|
||||
|
||||
func get(root, path string) io.ReadCloser {
|
||||
url := root + "/" + path
|
||||
fmt.Printf("Fetching %s...", url)
|
||||
defer fmt.Println(" done.")
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatalf("HTTP GET: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
log.Fatalf("Bad GET status for %q: %q", url, resp.Status)
|
||||
}
|
||||
return resp.Body
|
||||
}
|
||||
|
||||
// TODO: use Write*Version in all applicable packages.
|
||||
|
||||
// WriteUnicodeVersion writes a constant for the Unicode version from which the
|
||||
// tables are generated.
|
||||
func WriteUnicodeVersion(w io.Writer) {
|
||||
fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n")
|
||||
fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion())
|
||||
}
|
||||
|
||||
// WriteCLDRVersion writes a constant for the CLDR version from which the
|
||||
// tables are generated.
|
||||
func WriteCLDRVersion(w io.Writer) {
|
||||
fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n")
|
||||
fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion())
|
||||
}
|
||||
|
||||
// WriteGoFile prepends a standard file comment and package statement to the
|
||||
// given bytes, applies gofmt, and writes them to a file with the given name.
|
||||
// It will call log.Fatal if there are any errors.
|
||||
func WriteGoFile(filename, pkg string, b []byte) {
|
||||
w, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create file %s: %v", filename, err)
|
||||
}
|
||||
defer w.Close()
|
||||
if _, err = WriteGo(w, pkg, "", b); err != nil {
|
||||
log.Fatalf("Error writing file %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
func fileToPattern(filename string) string {
|
||||
suffix := ".go"
|
||||
if strings.HasSuffix(filename, "_test.go") {
|
||||
suffix = "_test.go"
|
||||
}
|
||||
prefix := filename[:len(filename)-len(suffix)]
|
||||
return fmt.Sprint(prefix, "%s", suffix)
|
||||
}
|
||||
|
||||
// tagLines returns the //go:build lines to add to the file.
|
||||
func tagLines(tags string) string {
|
||||
return "//go:build " + strings.ReplaceAll(tags, ",", " && ") + "\n"
|
||||
}
|
||||
|
||||
func updateBuildTags(pattern string) {
|
||||
for _, t := range tags {
|
||||
oldFile := fmt.Sprintf(pattern, t.version)
|
||||
b, err := os.ReadFile(oldFile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
b = regexp.MustCompile(`//go:build.*\n`).ReplaceAll(b, []byte(tagLines(t.buildTags)))
|
||||
err = os.WriteFile(oldFile, b, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteVersionedGoFile prepends a standard file comment, adds build tags to
|
||||
// version the file for the current Unicode version, and package statement to
|
||||
// the given bytes, applies gofmt, and writes them to a file with the given
|
||||
// name. It will call log.Fatal if there are any errors.
|
||||
func WriteVersionedGoFile(filename, pkg string, b []byte) {
|
||||
pattern := fileToPattern(filename)
|
||||
updateBuildTags(pattern)
|
||||
filename = fmt.Sprintf(pattern, UnicodeVersion())
|
||||
|
||||
w, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create file %s: %v", filename, err)
|
||||
}
|
||||
defer w.Close()
|
||||
if _, err = WriteGo(w, pkg, buildTags(), b); err != nil {
|
||||
log.Fatalf("Error writing file %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteGo prepends a standard file comment and package statement to the given
|
||||
// bytes, applies gofmt, and writes them to w.
|
||||
func WriteGo(w io.Writer, pkg, tags string, b []byte) (n int, err error) {
|
||||
src := []byte(header)
|
||||
if tags != "" {
|
||||
src = append(src, tagLines(tags)...)
|
||||
src = append(src, '\n')
|
||||
}
|
||||
src = append(src, fmt.Sprintf("package %s\n\n", pkg)...)
|
||||
src = append(src, b...)
|
||||
formatted, err := format.Source(src)
|
||||
if err != nil {
|
||||
// Print the generated code even in case of an error so that the
|
||||
// returned error can be meaningfully interpreted.
|
||||
n, _ = w.Write(src)
|
||||
return n, err
|
||||
}
|
||||
return w.Write(formatted)
|
||||
}
|
||||
|
||||
// Repackage rewrites a Go file from belonging to package main to belonging to
|
||||
// the given package.
|
||||
func Repackage(inFile, outFile, pkg string) {
|
||||
src, err := os.ReadFile(inFile)
|
||||
if err != nil {
|
||||
log.Fatalf("reading %s: %v", inFile, err)
|
||||
}
|
||||
const toDelete = "package main\n\n"
|
||||
i := bytes.Index(src, []byte(toDelete))
|
||||
if i < 0 {
|
||||
log.Fatalf("Could not find %q in %s.", toDelete, inFile)
|
||||
}
|
||||
w := &bytes.Buffer{}
|
||||
w.Write(src[i+len(toDelete):])
|
||||
WriteGoFile(outFile, pkg, w.Bytes())
|
||||
}
|
||||
Reference in New Issue
Block a user