penv.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package penv
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "reflect"
  7. "strconv"
  8. "strings"
  9. "golang.org/x/text/cases"
  10. "golang.org/x/text/language"
  11. )
  12. const NoDescription = "No description"
  13. var Prefix = "ENV_"
  14. type DumpVar struct {
  15. Name string
  16. NameEnv string
  17. NameFlag string
  18. Desc string
  19. Type string
  20. Value string
  21. Secret bool
  22. Required bool
  23. }
  24. func generate(name string) string {
  25. n := ""
  26. prevBig := true
  27. for i, char := range name {
  28. if i > 0 && string(char) == strings.ToUpper(string(char)) && !prevBig {
  29. n += "_"
  30. }
  31. if string(char) == strings.ToUpper(string(char)) {
  32. prevBig = true
  33. } else {
  34. prevBig = false
  35. }
  36. n += string(char)
  37. }
  38. return n
  39. }
  40. func generateEnvName(name string) string {
  41. return strings.ToUpper(Prefix + generate(name))
  42. }
  43. func generateFlagName(name string) string {
  44. return strings.ToLower(generate(name))
  45. }
  46. func isEnvPassed(name string) bool {
  47. if _, ok := os.LookupEnv(name); ok {
  48. return true
  49. }
  50. return false
  51. }
  52. func isFlagPassed(name string) bool {
  53. res := false
  54. flag.Visit(func(f *flag.Flag) {
  55. if f.Name == name {
  56. res = true
  57. return
  58. }
  59. })
  60. return res
  61. }
  62. func stringToInt(value string) (int, error) {
  63. return strconv.Atoi(value)
  64. }
  65. func stringToInt64(value string) (int64, error) {
  66. return strconv.ParseInt(value, 10, 64)
  67. }
  68. func DumpConfig(config any) map[string]DumpVar {
  69. res := map[string]DumpVar{}
  70. v := reflect.ValueOf(config).Elem()
  71. t := v.Type()
  72. for i := 0; i < t.NumField(); i++ {
  73. nameEnv := generateEnvName(t.Field(i).Name)
  74. fieldType := t.Field(i).Type.Kind().String()
  75. nameFlag := generateFlagName(t.Field(i).Name)
  76. description := t.Field(i).Tag.Get("description")
  77. if description == "" {
  78. description = NoDescription
  79. }
  80. secret := t.Field(i).Tag.Get("secret")
  81. required := t.Field(i).Tag.Get("required")
  82. if fieldType == "string" {
  83. res[t.Field(i).Name] = DumpVar{
  84. Name: t.Field(i).Name,
  85. NameEnv: nameEnv,
  86. NameFlag: nameFlag,
  87. Desc: description,
  88. Type: cases.Title(language.English).String(fieldType),
  89. Value: *v.Field(i).Addr().Interface().(*string),
  90. Secret: secret == "1" || secret == "true",
  91. Required: required == "1" || required == "true",
  92. }
  93. } else if fieldType == "int" {
  94. res[t.Field(i).Name] = DumpVar{
  95. Name: t.Field(i).Name,
  96. NameEnv: nameEnv,
  97. NameFlag: nameFlag,
  98. Desc: description,
  99. Type: cases.Title(language.English).String(fieldType),
  100. Value: fmt.Sprintf("%d", *v.Field(i).Addr().Interface().(*int)),
  101. Secret: secret == "1" || secret == "true",
  102. Required: required == "1" || required == "true",
  103. }
  104. } else if fieldType == "int64" {
  105. res[t.Field(i).Name] = DumpVar{
  106. Name: t.Field(i).Name,
  107. NameEnv: nameEnv,
  108. NameFlag: nameFlag,
  109. Desc: description,
  110. Type: cases.Title(language.English).String(fieldType),
  111. Value: fmt.Sprintf("%d", *v.Field(i).Addr().Interface().(*int64)),
  112. Secret: secret == "1" || secret == "true",
  113. Required: required == "1" || required == "true",
  114. }
  115. }
  116. }
  117. return res
  118. }
  119. // ProcessConfig automaticaly read flags and ENVs to structure.
  120. //
  121. // config - must be a pointer to structure
  122. //
  123. // var Config struct {
  124. // Deployment string `default:"development"`
  125. // Host string `default:"127.0.0.1"`
  126. // Port string `default:"8080"`
  127. // }
  128. //
  129. // func init() {
  130. // if err := penv.ProcessConfig(&Config); err != nil {
  131. // panic(err)
  132. // }
  133. // }
  134. func ProcessConfig(config any) error {
  135. v := reflect.ValueOf(config).Elem()
  136. t := v.Type()
  137. // Flags
  138. for i := 0; i < t.NumField(); i++ {
  139. nameEnv := generateEnvName(t.Field(i).Name)
  140. nameFlag := generateFlagName(t.Field(i).Name)
  141. fieldType := t.Field(i).Type.Kind().String()
  142. defvalue := t.Field(i).Tag.Get("default")
  143. description := t.Field(i).Tag.Get("description")
  144. if description == "" {
  145. description = NoDescription
  146. }
  147. if fieldType == "string" {
  148. value := v.Field(i).Addr().Interface().(*string)
  149. flag.StringVar(value, nameFlag, defvalue, "Or "+nameEnv+": "+description)
  150. } else if fieldType == "int" {
  151. if ndefvalue, err := stringToInt(defvalue); err == nil {
  152. value := v.Field(i).Addr().Interface().(*int)
  153. flag.IntVar(value, nameFlag, ndefvalue, "Or "+nameEnv+": "+description)
  154. } else {
  155. return err
  156. }
  157. } else if fieldType == "int64" {
  158. if ndefvalue, err := stringToInt64(defvalue); err == nil {
  159. value := v.Field(i).Addr().Interface().(*int64)
  160. flag.Int64Var(value, nameFlag, ndefvalue, "Or "+nameEnv+": "+description)
  161. } else {
  162. return err
  163. }
  164. }
  165. }
  166. flag.Parse()
  167. // ENVs
  168. for i := 0; i < t.NumField(); i++ {
  169. nameEnv := generateEnvName(t.Field(i).Name)
  170. fieldType := t.Field(i).Type.Kind().String()
  171. if os.Getenv(nameEnv) != "" {
  172. if fieldType == "string" {
  173. value := v.Field(i).Addr().Interface().(*string)
  174. *value = os.Getenv(nameEnv)
  175. } else if fieldType == "int" {
  176. if nvalue, err := stringToInt(os.Getenv(nameEnv)); err == nil {
  177. value := v.Field(i).Addr().Interface().(*int)
  178. *value = nvalue
  179. } else {
  180. return err
  181. }
  182. } else if fieldType == "int64" {
  183. if nvalue, err := stringToInt64(os.Getenv(nameEnv)); err == nil {
  184. value := v.Field(i).Addr().Interface().(*int64)
  185. *value = nvalue
  186. } else {
  187. return err
  188. }
  189. }
  190. }
  191. }
  192. // Required
  193. for i := 0; i < t.NumField(); i++ {
  194. nameEnv := generateEnvName(t.Field(i).Name)
  195. nameFlag := generateFlagName(t.Field(i).Name)
  196. required := t.Field(i).Tag.Get("required")
  197. if required == "1" || required == "true" {
  198. if !(isEnvPassed(nameEnv) || isFlagPassed(nameFlag)) {
  199. return fmt.Errorf("variable '" + nameEnv + "' or flag '-" + nameFlag + "' is not set")
  200. }
  201. }
  202. }
  203. return nil
  204. }