From f36979aa62eb038bc5da7aed8ef07df8eaead95d Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 31 May 2024 15:52:30 +0530 Subject: [PATCH] Implement code generation for config parsing. (#1893) * Create a new package called "cfg" where the generated code will reside. In future, this code will be moved into the config package to complete the migration. * Add an import in main.go to force compile the new package. Otherwise, we'll not know of any compile errors in the codegen and generated code. * Add a limited set of params in params.yaml to verify that the code generation works. We'll add the entire set of params in a subsequent PR. --- cfg/config.go | 97 +++++++++++++ cfg/types.go | 25 ++++ go.mod | 21 ++- go.sum | 37 ++++- main.go | 5 + tools/config-gen/config.tpl | 58 ++++++++ tools/config-gen/flag_template_data_gen.go | 105 ++++++++++++++ tools/config-gen/main.go | 112 +++++++++++++++ tools/config-gen/params.yaml | 39 ++++++ tools/config-gen/parser.go | 120 ++++++++++++++++ tools/config-gen/type_template_data_gen.go | 152 +++++++++++++++++++++ 11 files changed, 764 insertions(+), 7 deletions(-) create mode 100644 cfg/config.go create mode 100644 cfg/types.go create mode 100644 tools/config-gen/config.tpl create mode 100644 tools/config-gen/flag_template_data_gen.go create mode 100644 tools/config-gen/main.go create mode 100644 tools/config-gen/params.yaml create mode 100644 tools/config-gen/parser.go create mode 100644 tools/config-gen/type_template_data_gen.go diff --git a/cfg/config.go b/cfg/config.go new file mode 100644 index 0000000000..bd21139ef8 --- /dev/null +++ b/cfg/config.go @@ -0,0 +1,97 @@ +// Copyright 2024 Google Inc. All Rights Reserved. +// +// 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. + +// GENERATED CODE - DO NOT EDIT MANUALLY. + +package cfg + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +type Config struct { + AppName string `yaml:"app-name"` + + Debug DebugConfig `yaml:"debug"` + + FileSystem FileSystemConfig `yaml:"file-system"` +} + +type DebugConfig struct { + ExitOnInvariantViolation bool `yaml:"exit-on-invariant-violation"` + + LogMutex bool `yaml:"log-mutex"` +} + +type FileSystemConfig struct { + FileMode Octal `yaml:"file-mode"` + + Uid int `yaml:"uid"` +} + +func BindFlags(flagSet *pflag.FlagSet) error { + var err error + + flagSet.StringP("app-name", "", "", "The application name of this mount.") + + err = viper.BindPFlag("app-name", flagSet.Lookup("app-name")) + if err != nil { + return err + } + + flagSet.BoolP("debug_fuse", "", true, "This flag is currently unused.") + + err = flagSet.MarkDeprecated("debug_fuse", "This flag is currently unused.") + if err != nil { + return err + } + + flagSet.BoolP("debug_fuse_errors", "", true, "This flag is currently unused.") + + err = flagSet.MarkDeprecated("debug_fuse_errors", "This flag is currently unused.") + if err != nil { + return err + } + + flagSet.BoolP("debug_invariants", "", false, "Exit when internal invariants are violated.") + + err = viper.BindPFlag("debug.exit-on-invariant-violation", flagSet.Lookup("debug_invariants")) + if err != nil { + return err + } + + flagSet.BoolP("debug_mutex", "", false, "Print debug messages when a mutex is held too long.") + + err = viper.BindPFlag("debug.log-mutex", flagSet.Lookup("debug_mutex")) + if err != nil { + return err + } + + flagSet.IntP("file-mode", "", 0, "Permissions bits for files, in octal.") + + err = viper.BindPFlag("file-system.file-mode", flagSet.Lookup("file-mode")) + if err != nil { + return err + } + + flagSet.IntP("uid", "", -1, "UID owner of all inodes.") + + err = viper.BindPFlag("file-system.uid", flagSet.Lookup("uid")) + if err != nil { + return err + } + + return nil +} diff --git a/cfg/types.go b/cfg/types.go new file mode 100644 index 0000000000..8642d092d8 --- /dev/null +++ b/cfg/types.go @@ -0,0 +1,25 @@ +// Copyright 2024 Google Inc. All Rights Reserved. +// +// 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 cfg + +import ( + "fmt" +) + +type Octal int + +func (oi Octal) String() string { + return fmt.Sprintf("%o", oi) +} diff --git a/go.mod b/go.mod index d292716651..d52501c8be 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,8 @@ require ( github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/spf13/cobra v1.8.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/urfave/cli v1.22.15 go.opencensus.io v0.24.0 @@ -49,10 +51,11 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/envoyproxy/go-control-plane v0.12.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -63,23 +66,35 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/xattr v0.4.9 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/prometheus v0.35.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect go.opentelemetry.io/otel v1.26.0 // indirect go.opentelemetry.io/otel/metric v1.26.0 // indirect go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 // indirect google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index d60702312c..97632d0ca8 100644 --- a/go.sum +++ b/go.sum @@ -347,8 +347,9 @@ github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -411,9 +412,13 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsouza/fake-gcs-server v1.49.0 h1:4x1RxKuqoqhZrXogtj5nInQnIjQylxld43tKrkPHnmE= github.com/fsouza/fake-gcs-server v1.49.0/go.mod h1:FJYZxdHQk2nGxrczFjLbDv8h6SnYXxSxcnM14eeespA= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= @@ -693,6 +698,7 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -798,6 +804,8 @@ github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -850,6 +858,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= @@ -939,6 +949,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -947,8 +959,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= @@ -1015,6 +1028,10 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= @@ -1039,13 +1056,19 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= @@ -1061,6 +1084,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1085,6 +1110,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1213,6 +1240,8 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1252,6 +1281,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 h1:Mi0bCswbz+9cXmwFAdxoo5GPFMKONUpua6iUdtQS7lk= +golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1789,8 +1820,6 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/main.go b/main.go index 5c410c7848..e3f6092315 100644 --- a/main.go +++ b/main.go @@ -499,6 +499,11 @@ func handlePanicWhileMounting() { } } +// Don't remove the comment below. It's used to generate config.go file +// which is used for flag and config file parsing. +// Refer https://go.dev/blog/generate for details. +// +//go:generate go run -C tools/config-gen . --paramsFile=params.yaml --outFile=../../cfg/config.go --templateFile=config.tpl func main() { if strings.ToLower(os.Getenv("ENABLE_GCSFUSE_VIPER_CONFIG")) == "true" { cmd.Execute() diff --git a/tools/config-gen/config.tpl b/tools/config-gen/config.tpl new file mode 100644 index 0000000000..242e8b34c7 --- /dev/null +++ b/tools/config-gen/config.tpl @@ -0,0 +1,58 @@ +// Copyright 2024 Google Inc. All Rights Reserved. +// +// 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. + +// GENERATED CODE - DO NOT EDIT MANUALLY. + +package cfg + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +{{$bt := .Backticks}} +{{range .TypeTemplateData}} +type {{ .TypeName}} struct { + {{- range $idx, $fld := .Fields}} + {{ $fld.FieldName}} {{ $fld.DataType}} {{$bt}}yaml:"{{$fld.ConfigPath}}"{{$bt}} +{{end}} +} +{{end}} + +func BindFlags(flagSet *pflag.FlagSet) error { + var err error + {{range .FlagTemplateData}} + flagSet.{{ .Fn}}("{{ .FlagName}}", "{{ .Shorthand}}", {{ .DefaultValue}}, {{ .Usage}}) + {{if .IsDeprecated}} + err = flagSet.MarkDeprecated("{{ .FlagName}}", "{{ .DeprecationWarning}}") + if err != nil { + return err + } + {{end}} + {{if .HideFlag}} + err = flagSet.MarkHidden("{{ .FlagName}}") + if err != nil { + return err + } + {{end}} + {{if .HideShorthand}}flagSet.ShorthandLookup("{{ .Shorthand}}").Hidden = true{{end}} + {{if ne .ConfigPath ""}} + err = viper.BindPFlag("{{ .ConfigPath}}", flagSet.Lookup("{{ .FlagName}}")) + if err != nil { + return err + } + {{end}} + {{end}} + return nil +} diff --git a/tools/config-gen/flag_template_data_gen.go b/tools/config-gen/flag_template_data_gen.go new file mode 100644 index 0000000000..c1cfff5d83 --- /dev/null +++ b/tools/config-gen/flag_template_data_gen.go @@ -0,0 +1,105 @@ +/* + * Copyright 2024 Google Inc. All Rights Reserved. + * + * 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 main + +import ( + "fmt" + "time" +) + +type flagTemplateData struct { + Param + // The pFlag function to invoke in order to add the flag. + Fn string +} + +func computeFlagTemplateData(paramsConfig []Param) ([]flagTemplateData, error) { + var flgTemplate []flagTemplateData + for _, p := range paramsConfig { + td, err := computeFlagTemplateDataForParam(p) + if err != nil { + return nil, err + } + flgTemplate = append(flgTemplate, td) + } + return flgTemplate, nil +} + +func computeFlagTemplateDataForParam(p Param) (flagTemplateData, error) { + var defaultValue string + var fn string + switch p.Type { + case "int": + if p.DefaultValue == "" { + defaultValue = "0" + } else { + defaultValue = p.DefaultValue + } + fn = "IntP" + case "octal": + if p.DefaultValue == "" { + defaultValue = "0" + } else { + defaultValue = p.DefaultValue + } + fn = "IntP" + case "float64": + if p.DefaultValue == "" { + defaultValue = "0.0" + } else { + defaultValue = p.DefaultValue + } + fn = "Float64P" + case "bool": + if p.DefaultValue == "" { + defaultValue = "false" + } else { + defaultValue = p.DefaultValue + } + fn = "BoolP" + case "duration": + if p.DefaultValue == "" { + defaultValue = "0s" + } else { + defaultValue = p.DefaultValue + } + dur, err := time.ParseDuration(defaultValue) + if err != nil { + return flagTemplateData{}, err + } + defaultValue = fmt.Sprintf("%d * time.Nanosecond", dur.Nanoseconds()) + fn = "DurationP" + case "string": + defaultValue = fmt.Sprintf("%q", p.DefaultValue) + fn = "StringP" + case "[]int": + defaultValue = fmt.Sprintf("[]int{%s}", p.DefaultValue) + fn = "IntSliceP" + case "[]string": + defaultValue = fmt.Sprintf("[]string{%s}", p.DefaultValue) + fn = "StringSliceP" + default: + return flagTemplateData{}, fmt.Errorf("unhandled type: %s", p.Type) + } + p.DefaultValue = defaultValue + // Usage string safely escaped with Go syntax. + p.Usage = fmt.Sprintf("%q", p.Usage) + return flagTemplateData{ + Param: p, + Fn: fn, + }, nil +} diff --git a/tools/config-gen/main.go b/tools/config-gen/main.go new file mode 100644 index 0000000000..7e7d9844ea --- /dev/null +++ b/tools/config-gen/main.go @@ -0,0 +1,112 @@ +/* + * Copyright 2024 Google Inc. All Rights Reserved. + * + * 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 main + +import ( + "cmp" + "flag" + "fmt" + "os" + "slices" + "text/template" +) + +var ( + outFile = flag.String("outFile", "", "Output file") + paramsFile = flag.String("paramsFile", "", "Params YAML file") + templateFile = flag.String("templateFile", "", "Template file") +) + +type templateData struct { + TypeTemplateData []typeTemplateData + FlagTemplateData []flagTemplateData + // Back-ticks are not supported in templates. So, passing as a parameter. + Backticks string +} + +func validateFlags() error { + if *paramsFile == "" { + return fmt.Errorf("input filename cannot be empty") + } + if *outFile == "" { + return fmt.Errorf("output filename cannot be empty") + } + if *templateFile == "" { + return fmt.Errorf("template filename cannot be empty") + } + return nil +} + +func write(data templateData) error { + outputFile, err := os.Create(*outFile) + if err != nil { + defer outputFile.Close() + } + if err != nil { + return err + } + tmpl, err := template.New("config.tpl").ParseFiles(*templateFile) + if err != nil { + return err + } + err = tmpl.Execute(outputFile, data) + if err != nil { + return err + } + return nil +} + +func main() { + flag.Parse() + err := validateFlags() + if err != nil { + panic(err) + } + + paramsConfig, err := parseParamsConfig() + if err != nil { + panic(err) + } + + td, err := constructTypeTemplateData(paramsConfig) + if err != nil { + panic(err) + } + + fd, err := computeFlagTemplateData(paramsConfig) + if err != nil { + panic(err) + } + + // Sort to have reliable ordering. + slices.SortFunc(td, func(i, j typeTemplateData) int { + return cmp.Compare(i.TypeName, j.TypeName) + }) + slices.SortFunc(fd, func(i, j flagTemplateData) int { + return cmp.Compare(i.FlagName, j.FlagName) + }) + + err = write(templateData{ + FlagTemplateData: fd, + TypeTemplateData: td, + Backticks: "`", + }) + if err != nil { + panic(err) + } + +} diff --git a/tools/config-gen/params.yaml b/tools/config-gen/params.yaml new file mode 100644 index 0000000000..67639d1d88 --- /dev/null +++ b/tools/config-gen/params.yaml @@ -0,0 +1,39 @@ +- flag-name: "app-name" + config-path: "app-name" + type: "string" + usage: "The application name of this mount." + +- flag-name: "file-mode" + config-path: "file-system.file-mode" + type: "octal" + usage: "Permissions bits for files, in octal." + +- flag-name: "uid" + config-path: "file-system.uid" + type: "int" + default: -1 + usage: "UID owner of all inodes." + +- flag-name: "debug_fuse_errors" + type: "bool" + default: "true" + usage: "This flag is currently unused." + deprecated: true + deprecation-warning: "This flag is currently unused." + +- flag-name: "debug_fuse" + type: "bool" + default: "true" + usage: "This flag is currently unused." + deprecated: true + deprecation-warning: "This flag is currently unused." + +- flag-name: "debug_invariants" + config-path: "debug.exit-on-invariant-violation" + type: "bool" + usage: "Exit when internal invariants are violated." + +- flag-name: "debug_mutex" + config-path: "debug.log-mutex" + type: "bool" + usage: "Print debug messages when a mutex is held too long." diff --git a/tools/config-gen/parser.go b/tools/config-gen/parser.go new file mode 100644 index 0000000000..166ebd4a9a --- /dev/null +++ b/tools/config-gen/parser.go @@ -0,0 +1,120 @@ +/* + * Copyright 2024 Google Inc. All Rights Reserved. + * + * 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 main + +import ( + "fmt" + "os" + "slices" + + "gopkg.in/yaml.v3" +) + +type Param struct { + FlagName string `yaml:"flag-name"` + Shorthand string + Type string + DefaultValue string `yaml:"default"` + ConfigPath string `yaml:"config-path"` + IsDeprecated bool `yaml:"deprecated"` + DeprecationWarning string `yaml:"deprecation-warning"` + Usage string + HideFlag bool `yaml:"hide-flag"` + HideShorthand bool `yaml:"hide-shorthand"` +} + +func parseParamsConfig() ([]Param, error) { + yamlFile, err := os.ReadFile(*paramsFile) + if err != nil { + return nil, err + } + var paramsConfig []Param + err = yaml.Unmarshal(yamlFile, ¶msConfig) + if err != nil { + return nil, err + } + err = validateParams(paramsConfig) + if err != nil { + return nil, err + } + return paramsConfig, nil +} + +func validateParam(param Param) error { + if param.IsDeprecated && param.DeprecationWarning == "" { + return fmt.Errorf("param %s is marked deprecated but deprecation-warning is not set", param.FlagName) + } + + if param.ConfigPath == "" && !param.IsDeprecated { + return fmt.Errorf("config-path is empty for flag-name: %s", param.FlagName) + } + for k, v := range map[string]string{ + "flag-name": param.FlagName, + "usage": param.Usage, + "type": param.Type, + } { + if v == "" { + return fmt.Errorf("%s is empty for flag-name: %s", k, param.FlagName) + } + } + + // Validate the data type. + idx := slices.IndexFunc( + []string{"int", "float64", "bool", "string", "duration", "octal", "[]int", "[]string"}, + func(dt string) bool { + return dt == param.Type + }, + ) + if idx == -1 { + return fmt.Errorf("unsupported datatype: %s", param.Type) + } + + return nil +} + +func validateParams(params []Param) error { + err := validateForDuplicates(params, func(param Param) string { return param.FlagName }) + if err != nil { + return fmt.Errorf("duplicate flag names found: %w", err) + } + err = validateForDuplicates(params, func(param Param) string { return param.ConfigPath }) + if err != nil { + return fmt.Errorf("duplicate config-paths found: %w", err) + } + for _, param := range params { + err := validateParam(param) + if err != nil { + return err + } + } + return nil +} + +func validateForDuplicates(params []Param, fn func(param Param) string) error { + lookup := make(map[string]bool) + for _, p := range params { + value := fn(p) + if value == "" { + continue + } + if _, ok := lookup[value]; ok { + return fmt.Errorf("%s is present more than once", value) + } + lookup[value] = true + } + return nil +} diff --git a/tools/config-gen/type_template_data_gen.go b/tools/config-gen/type_template_data_gen.go new file mode 100644 index 0000000000..df9f4be578 --- /dev/null +++ b/tools/config-gen/type_template_data_gen.go @@ -0,0 +1,152 @@ +/* + * Copyright 2024 Google Inc. All Rights Reserved. + * + * 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 main + +import ( + "cmp" + "fmt" + "regexp" + "slices" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +var ( + cfgSegmentRegex = regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9\-]*`) +) + +type fieldInfo struct { + TypeName string + FieldName string + DataType string + ConfigPath string +} + +type typeTemplateData struct { + // Name of the type + TypeName string + // Fields that are to be included in the type. + Fields []fieldInfo +} + +func capitalizeIdentifier(name string) (string, error) { + if !cfgSegmentRegex.MatchString(name) { + return "", fmt.Errorf("%s is not a supported name", name) + } + + // For the purposes of capitalization, both "." and "-" are equivalent. + name = strings.ReplaceAll(name, ".", "-") + var buf strings.Builder + for _, w := range strings.Split(name, "-") { + // Capitalize the first letter and concatenate. + buf.WriteString(cases.Title(language.English).String(w)) + } + return buf.String(), nil +} + +func getGoDataType(dt string) string { + switch dt { + case "octal": + return "Octal" + case "duration": + return "time.Duration" + default: + return dt + } +} + +// Returns a flat list with one entry for each field that needs to be created and the corresponding type. +// A config path of x.y.z for a param of type int would return the follow entries +// 1. {TypeName: Config, FieldName: X, DataType: XConfig, ConfigPath: x} +// 2. {TypeName: XConfig, FieldName: Y, DataType: YXConfig, ConfigPath: y} +// 3. {TypeName: YXConfig, FieldName: Z, DataType: int, ConfigPath: z} +func computeFields(param Param) ([]fieldInfo, error) { + segments := strings.Split(param.ConfigPath, ".") + fieldInfos := make([]fieldInfo, 0, len(segments)) + typeName := "Config" + for idx, s := range segments { + fld, err := capitalizeIdentifier(s) + if err != nil { + return nil, err + } + + var dt string + if idx == len(segments)-1 { + // Dealing with leaf field here. + dt = getGoDataType(param.Type) + } else { + // Not a leaf field. + tn, err := capitalizeIdentifier(s) + if err != nil { + return nil, err + } + + dt = tn + typeName + } + fieldInfos = append(fieldInfos, fieldInfo{ + TypeName: typeName, + FieldName: fld, + DataType: dt, + ConfigPath: s, + }) + typeName = dt + } + + return fieldInfos, nil +} + +func constructTypeTemplateData(paramsConfig []Param) ([]typeTemplateData, error) { + var fields []fieldInfo + for _, p := range paramsConfig { + // ConfigPath can be empty for deprecated flags. + if p.ConfigPath == "" { + continue + } + f, err := computeFields(p) + if err != nil { + return nil, err + } + + fields = append(fields, f...) + } + + ttf := make(map[string][]fieldInfo) + for _, f := range fields { + ttf[f.TypeName] = append(ttf[f.TypeName], f) + } + + var ttd []typeTemplateData + for k, v := range ttf { + // Sort field names for reliable ordering. + slices.SortFunc(v, func(i, j fieldInfo) int { + return cmp.Compare(i.FieldName, j.FieldName) + }) + + ttd = append(ttd, typeTemplateData{ + TypeName: k, + Fields: slices.Compact(v), + }, + ) + } + // Sort type names for reliable ordering. + slices.SortFunc(ttd, func(i, j typeTemplateData) int { + return cmp.Compare(i.TypeName, j.TypeName) + }) + return ttd, nil +}