penv.go 5.7 KB

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