File: make_textflag.go

package info (click to toggle)
golang-github-mmcloughlin-avo 0.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 15,024 kB
  • sloc: xml: 71,029; asm: 14,862; sh: 194; makefile: 21; ansic: 11
file content (206 lines) | stat: -rw-r--r-- 4,240 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//go:build ignore
// +build ignore

package main

import (
	"bufio"
	"bytes"
	"errors"
	"flag"
	"fmt"
	"go/format"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

var (
	TextFlagPath = "src/runtime/textflag.h"
	TextFlagName = filepath.Base(TextFlagPath)
)

var (
	download = flag.Bool("download", false, "download new version of "+TextFlagName)
	version  = flag.String("version", runtime.Version(), "go version to download file from")
	textflag = flag.String("textflag", TextFlagName, "path to "+TextFlagName)
	output   = flag.String("output", "", "path to output file")
)

func main() {
	if err := mainerr(); err != nil {
		log.Fatal(err)
	}
}

func mainerr() error {
	flag.Parse()

	// Download new version, if requested.
	if *download {
		if err := DownloadGoSourceFile(*textflag, *version, TextFlagPath); err != nil {
			return err
		}
		log.Printf("downloaded %q from version %s to %q", TextFlagPath, *version, *textflag)
	}

	// Parse text flags header.
	fs, err := ParseFile(*textflag)
	if err != nil {
		return err
	}

	// Determine output.
	w := os.Stdout
	if *output != "" {
		f, err := os.Create(*output)
		if err != nil {
			return err
		}
		defer f.Close()
		w = f
	}

	// Generate code and format it.
	buf := bytes.NewBuffer(nil)
	PrintFlagAttributes(buf, fs)

	src, err := format.Source(buf.Bytes())
	if err != nil {
		return err
	}

	// Write output.
	_, err = w.Write(src)
	if err != nil {
		return err
	}

	return nil
}

// DownloadGoSourceFile downloads a Go source file from a specific version.
func DownloadGoSourceFile(outpath, v, srcpath string) (err error) {
	// Download from github.
	url := "https://github.com/golang/go/raw/" + v + "/" + srcpath
	res, err := http.Get(url)
	if err != nil {
		return err
	}
	defer func() {
		if errc := res.Body.Close(); errc != nil && err == nil {
			err = errc
		}
	}()

	// Write to file.
	buf := bytes.NewBuffer(nil)
	fmt.Fprintf(buf, "// Code generated by downloading from %s. DO NOT EDIT.\n\n", url)

	if _, err := io.Copy(buf, res.Body); err != nil {
		return err
	}

	if err := os.WriteFile(outpath, buf.Bytes(), 0o644); err != nil {
		return err
	}

	return nil
}

type Flag struct {
	Doc   []string
	Name  string
	Value int
}

// Parse text flags header format.
func Parse(r io.Reader) ([]Flag, error) {
	var fs []Flag
	var doc []string

	s := bufio.NewScanner(r)
	for s.Scan() {
		line := s.Text()
		switch {
		case strings.Contains(line, "TODO"):
			// skip

		case strings.HasPrefix(line, "// "):
			doc = append(doc, strings.TrimPrefix(line, "// "))

		case strings.HasPrefix(line, "#define"):
			fields := strings.Fields(line)
			if len(fields) != 3 || fields[0] != "#define" {
				return nil, fmt.Errorf("unexpected line format %q", line)
			}
			v, err := strconv.Atoi(fields[2])
			if err != nil {
				return nil, err
			}

			fs = append(fs, Flag{
				Doc:   doc,
				Name:  fields[1],
				Value: v,
			})
			doc = nil

		case line == "" || line == "//":
			doc = nil

		default:
			return nil, errors.New("unexpected format")
		}
	}

	if err := s.Err(); err != nil {
		return nil, err
	}

	return fs, nil
}

// ParseFile parses text flags header file.
func ParseFile(filename string) ([]Flag, error) {
	b, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return Parse(bytes.NewReader(b))
}

func PrintFlagAttributes(w io.Writer, fs []Flag) {
	_, self, _, _ := runtime.Caller(0)
	fmt.Fprintf(w, "// Code generated by %s. DO NOT EDIT.\n\n", filepath.Base(self))
	fmt.Fprintf(w, "package attr\n")

	// Attribute constants.
	fmt.Fprintf(w, "\n// Attribute values defined in %s.\n", TextFlagName)
	fmt.Fprintf(w, "const (\n")
	for _, f := range fs {
		for _, line := range f.Doc {
			fmt.Fprintf(w, "\t// %s\n", line)
		}
		fmt.Fprintf(w, "\t%s Attribute = %d\n\n", f.Name, f.Value)
	}
	fmt.Fprintf(w, ")\n")

	// String map.
	fmt.Fprintf(w, "\nvar attrname = map[Attribute]string{\n")
	for _, f := range fs {
		fmt.Fprintf(w, "\t%s: %q,\n", f.Name, f.Name)
	}
	fmt.Fprintf(w, "}\n")

	// Flag test methods.
	for _, f := range fs {
		fmt.Fprintf(w, "\n// %s reports whether the %s flag is set.\n", f.Name, f.Name)
		fmt.Fprintf(w, "func (a Attribute) %s() bool { return (a & %s) != 0 }\n", f.Name, f.Name)
	}
}