forked from GoogleCloudPlatform/k8s-config-connector
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request GoogleCloudPlatform#3475 from yuwenma/apply-cmd-1
feat: add cmd to merge two go files (struct only)
- Loading branch information
Showing
5 changed files
with
782 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Copyright 2025 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package apply | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
kccio "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/io" | ||
"github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/options" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type ApplyOptions struct { | ||
*options.GenerateOptions | ||
SrcFile string | ||
DestFile string | ||
} | ||
|
||
func (o *ApplyOptions) BindFlags(cmd *cobra.Command) { | ||
cmd.Flags().StringVar(&o.DestFile, "dest-file", o.DestFile, "destination file path to write the gocode to, default to ./apis/ to update the Config Connector types") | ||
cmd.Flags().StringVar(&o.SrcFile, "src-file", o.SrcFile, "src file path to read the new gocode from") | ||
} | ||
|
||
func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { | ||
opt := &ApplyOptions{ | ||
GenerateOptions: baseOptions, | ||
} | ||
|
||
if err := opt.InitDefaults(); err != nil { | ||
fmt.Fprintf(os.Stderr, "Error initializing defaults: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "apply", | ||
Short: "[ALPHA] Write go code from src dir to dest dir", | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
if opt.SrcFile == "" { | ||
return fmt.Errorf("--src-file is required") | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
ctx := cmd.Context() | ||
if err := Apply(ctx, opt); err != nil { | ||
return err | ||
} | ||
return nil | ||
}, | ||
} | ||
opt.BindFlags(cmd) | ||
|
||
return cmd | ||
} | ||
|
||
func Apply(ctx context.Context, o *ApplyOptions) error { | ||
rawData, err := os.ReadFile(o.SrcFile) | ||
if err != nil { | ||
return err | ||
} | ||
return kccio.UpdateGoFile(ctx, o.DestFile, bytes.NewBuffer(rawData)) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package io | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
) | ||
|
||
// WriteToCache write `out` to a temporary file under `dir`. | ||
func WriteToCache(ctx context.Context, dir string, out string, namePattern string) (string, error) { | ||
if _, err := os.Stat(dir); os.IsNotExist(err) { | ||
if err := os.Mkdir(dir, 0755); err != nil { | ||
return "", fmt.Errorf("create dir %s: %w", dir, err) | ||
} | ||
} | ||
if namePattern == "" { | ||
namePattern = "unnamed-*" | ||
} | ||
tmpFile, err := os.CreateTemp(dir, namePattern) | ||
if err != nil { | ||
return "", fmt.Errorf("create file under %s: %w", dir, err) | ||
} | ||
defer tmpFile.Close() | ||
|
||
fmt.Printf("out %s\n", out) | ||
if _, err := tmpFile.WriteString(out); err != nil { | ||
return "", fmt.Errorf("write to file %s: %w", tmpFile.Name(), err) | ||
} | ||
|
||
return tmpFile.Name(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package io | ||
|
||
import ( | ||
"go/ast" | ||
"go/token" | ||
) | ||
|
||
type StructComment struct { | ||
Name *ast.Ident | ||
Comment *ast.CommentGroup | ||
Fields map[string]*ast.CommentGroup | ||
} | ||
|
||
func (s *StructComment) SetCommentPos(offset token.Pos) token.Pos { | ||
newComments := []*ast.Comment{} | ||
if s.Comment == nil { | ||
return offset | ||
} | ||
for _, c := range s.Comment.List { | ||
c.Slash = offset | ||
offset += c.End() + 1 | ||
newComments = append(newComments, c) | ||
} | ||
s.Comment = &ast.CommentGroup{List: newComments} | ||
return offset | ||
} | ||
|
||
func MergeStructComments(oldComments, newComments map[string]*StructComment) map[string]*StructComment { | ||
merged := make(map[string]*StructComment) | ||
for k, v := range oldComments { | ||
merged[k] = v | ||
} | ||
|
||
// If the new Comment is non empty, override the old comment. | ||
for k, v := range newComments { | ||
merged[k] = v | ||
} | ||
return merged | ||
} | ||
|
||
// ast Docs and comments are not binding with the Decl but the relative position of the Node, | ||
// so we have to handle it specifically. | ||
func MapGoComments(f ast.Node) map[string]*StructComment { | ||
comments := make(map[string]*StructComment) | ||
|
||
ast.Inspect(f, func(n ast.Node) bool { | ||
genDecl, ok := n.(*ast.GenDecl) | ||
if !ok || genDecl.Tok != token.TYPE { | ||
// TODO: support IMPORT, CONST, VAR | ||
return true | ||
} | ||
c := mapStructComments(genDecl) | ||
if c == nil { | ||
return true | ||
} | ||
comments[c.Name.Name] = c | ||
return true | ||
}) | ||
return comments | ||
} | ||
|
||
func mapStructComments(g *ast.GenDecl) *StructComment { | ||
if g == nil { | ||
return nil | ||
} | ||
c := &StructComment{ | ||
Fields: make(map[string]*ast.CommentGroup), | ||
} | ||
|
||
if g.Doc != nil { | ||
c.Comment = ResetCommentsPos(g.Doc) | ||
} | ||
|
||
for _, spec := range g.Specs { | ||
typeSpec, ok := spec.(*ast.TypeSpec) | ||
if !ok { | ||
continue | ||
} | ||
c.Name = typeSpec.Name | ||
for _, field := range typeSpec.Type.(*ast.StructType).Fields.List { | ||
if field.Doc == nil { | ||
continue | ||
} | ||
var name *ast.Ident | ||
|
||
if field.Names == nil { | ||
// Anonymous name | ||
name = field.Type.(*ast.SelectorExpr).Sel | ||
} else { | ||
// Field could has multiple names like `A, B string`. Use the first one | ||
name = field.Names[0] | ||
} | ||
c.Fields[name.Name] = field.Doc | ||
c.Comment = ResetCommentsPos(field.Doc) | ||
} | ||
} | ||
if c.Name == nil { | ||
return nil | ||
} | ||
return c | ||
} | ||
|
||
func ResetCommentsPos(comments *ast.CommentGroup) *ast.CommentGroup { | ||
if comments == nil { | ||
return nil | ||
} | ||
newComments := []*ast.Comment{} | ||
for _, c := range comments.List { | ||
c.Slash = token.NoPos | ||
newComments = append(newComments, c) | ||
} | ||
return &ast.CommentGroup{ | ||
List: newComments, | ||
} | ||
} |
Oops, something went wrong.