beego bee tool mirror
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. // Copyright 2013 bee authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. package utils
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "os/exec"
  21. "path"
  22. "path/filepath"
  23. "regexp"
  24. "runtime"
  25. "strings"
  26. "text/template"
  27. "time"
  28. "unicode"
  29. beeLogger "github.com/beego/bee/logger"
  30. "github.com/beego/bee/logger/colors"
  31. )
  32. // Go is a basic promise implementation: it wraps calls a function in a goroutine
  33. // and returns a channel which will later return the function's return value.
  34. func Go(f func() error) chan error {
  35. ch := make(chan error)
  36. go func() {
  37. ch <- f()
  38. }()
  39. return ch
  40. }
  41. // IsExist returns whether a file or directory exists.
  42. func IsExist(path string) bool {
  43. _, err := os.Stat(path)
  44. return err == nil || os.IsExist(err)
  45. }
  46. // GetGOPATHs returns all paths in GOPATH variable.
  47. func GetGOPATHs() []string {
  48. gopath := os.Getenv("GOPATH")
  49. if gopath == "" && strings.Compare(runtime.Version(), "go1.8") >= 0 {
  50. gopath = defaultGOPATH()
  51. }
  52. return filepath.SplitList(gopath)
  53. }
  54. // IsInGOPATH checks whether the path is inside of any GOPATH or not
  55. func IsInGOPATH(thePath string) bool {
  56. for _, gopath := range GetGOPATHs() {
  57. if strings.Contains(thePath, filepath.Join(gopath, "src")) {
  58. return true
  59. }
  60. }
  61. return false
  62. }
  63. // IsBeegoProject checks whether the current path is a Beego application or not
  64. func IsBeegoProject(thePath string) bool {
  65. mainFiles := []string{}
  66. hasBeegoRegex := regexp.MustCompile(`(?s)package main.*?import.*?\(.*?github.com/astaxie/beego".*?\).*func main()`)
  67. c := make(chan error)
  68. // Walk the application path tree to look for main files.
  69. // Main files must satisfy the 'hasBeegoRegex' regular expression.
  70. go func() {
  71. filepath.Walk(thePath, func(fpath string, f os.FileInfo, err error) error {
  72. if err != nil {
  73. return nil
  74. }
  75. // Skip sub-directories
  76. if !f.IsDir() {
  77. var data []byte
  78. data, err = ioutil.ReadFile(fpath)
  79. if err != nil {
  80. c <- err
  81. return nil
  82. }
  83. if len(hasBeegoRegex.Find(data)) > 0 {
  84. mainFiles = append(mainFiles, fpath)
  85. }
  86. }
  87. return nil
  88. })
  89. close(c)
  90. }()
  91. if err := <-c; err != nil {
  92. beeLogger.Log.Fatalf("Unable to walk '%s' tree: %s", thePath, err)
  93. }
  94. if len(mainFiles) > 0 {
  95. return true
  96. }
  97. return false
  98. }
  99. // SearchGOPATHs searchs the user GOPATH(s) for the specified application name.
  100. // It returns a boolean, the application's GOPATH and its full path.
  101. func SearchGOPATHs(app string) (bool, string, string) {
  102. gps := GetGOPATHs()
  103. if len(gps) == 0 {
  104. beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
  105. }
  106. // Lookup the application inside the user workspace(s)
  107. for _, gopath := range gps {
  108. var currentPath string
  109. if !strings.Contains(app, "src") {
  110. gopathsrc := path.Join(gopath, "src")
  111. currentPath = path.Join(gopathsrc, app)
  112. } else {
  113. currentPath = app
  114. }
  115. if IsExist(currentPath) {
  116. return true, gopath, currentPath
  117. }
  118. }
  119. return false, "", ""
  120. }
  121. // askForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and
  122. // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as
  123. // confirmations. If the input is not recognized, it will ask again. The function does not return
  124. // until it gets a valid response from the user. Typically, you should use fmt to print out a question
  125. // before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)")
  126. func AskForConfirmation() bool {
  127. var response string
  128. _, err := fmt.Scanln(&response)
  129. if err != nil {
  130. beeLogger.Log.Fatalf("%s", err)
  131. }
  132. okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
  133. nokayResponses := []string{"n", "N", "no", "No", "NO"}
  134. if containsString(okayResponses, response) {
  135. return true
  136. } else if containsString(nokayResponses, response) {
  137. return false
  138. } else {
  139. fmt.Println("Please type yes or no and then press enter:")
  140. return AskForConfirmation()
  141. }
  142. }
  143. func containsString(slice []string, element string) bool {
  144. for _, elem := range slice {
  145. if elem == element {
  146. return true
  147. }
  148. }
  149. return false
  150. }
  151. // snake string, XxYy to xx_yy
  152. func SnakeString(s string) string {
  153. data := make([]byte, 0, len(s)*2)
  154. j := false
  155. num := len(s)
  156. for i := 0; i < num; i++ {
  157. d := s[i]
  158. if i > 0 && d >= 'A' && d <= 'Z' && j {
  159. data = append(data, '_')
  160. }
  161. if d != '_' {
  162. j = true
  163. }
  164. data = append(data, d)
  165. }
  166. return strings.ToLower(string(data[:]))
  167. }
  168. func CamelString(s string) string {
  169. data := make([]byte, 0, len(s))
  170. j := false
  171. k := false
  172. num := len(s) - 1
  173. for i := 0; i <= num; i++ {
  174. d := s[i]
  175. if !k && d >= 'A' && d <= 'Z' {
  176. k = true
  177. }
  178. if d >= 'a' && d <= 'z' && (j || !k) {
  179. d = d - 32
  180. j = false
  181. k = true
  182. }
  183. if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' {
  184. j = true
  185. continue
  186. }
  187. data = append(data, d)
  188. }
  189. return string(data[:])
  190. }
  191. // camelCase converts a _ delimited string to camel case
  192. // e.g. very_important_person => VeryImportantPerson
  193. func CamelCase(in string) string {
  194. tokens := strings.Split(in, "_")
  195. for i := range tokens {
  196. tokens[i] = strings.Title(strings.Trim(tokens[i], " "))
  197. }
  198. return strings.Join(tokens, "")
  199. }
  200. // formatSourceCode formats source files
  201. func FormatSourceCode(filename string) {
  202. cmd := exec.Command("gofmt", "-w", filename)
  203. if err := cmd.Run(); err != nil {
  204. beeLogger.Log.Warnf("Error while running gofmt: %s", err)
  205. }
  206. }
  207. // CloseFile attempts to close the passed file
  208. // or panics with the actual error
  209. func CloseFile(f *os.File) {
  210. err := f.Close()
  211. MustCheck(err)
  212. }
  213. // MustCheck panics when the error is not nil
  214. func MustCheck(err error) {
  215. if err != nil {
  216. panic(err)
  217. }
  218. }
  219. // WriteToFile creates a file and writes content to it
  220. func WriteToFile(filename, content string) {
  221. f, err := os.Create(filename)
  222. MustCheck(err)
  223. defer CloseFile(f)
  224. _, err = f.WriteString(content)
  225. MustCheck(err)
  226. }
  227. // __FILE__ returns the file name in which the function was invoked
  228. func FILE() string {
  229. _, file, _, _ := runtime.Caller(1)
  230. return file
  231. }
  232. // __LINE__ returns the line number at which the function was invoked
  233. func LINE() int {
  234. _, _, line, _ := runtime.Caller(1)
  235. return line
  236. }
  237. // BeeFuncMap returns a FuncMap of functions used in different templates.
  238. func BeeFuncMap() template.FuncMap {
  239. return template.FuncMap{
  240. "trim": strings.TrimSpace,
  241. "bold": colors.Bold,
  242. "headline": colors.MagentaBold,
  243. "foldername": colors.RedBold,
  244. "endline": EndLine,
  245. "tmpltostr": TmplToString,
  246. }
  247. }
  248. // TmplToString parses a text template and return the result as a string.
  249. func TmplToString(tmpl string, data interface{}) string {
  250. t := template.New("tmpl").Funcs(BeeFuncMap())
  251. template.Must(t.Parse(tmpl))
  252. var doc bytes.Buffer
  253. err := t.Execute(&doc, data)
  254. MustCheck(err)
  255. return doc.String()
  256. }
  257. // EndLine returns the a newline escape character
  258. func EndLine() string {
  259. return "\n"
  260. }
  261. func Tmpl(text string, data interface{}) {
  262. output := colors.NewColorWriter(os.Stderr)
  263. t := template.New("Usage").Funcs(BeeFuncMap())
  264. template.Must(t.Parse(text))
  265. err := t.Execute(output, data)
  266. if err != nil {
  267. beeLogger.Log.Error(err.Error())
  268. }
  269. }
  270. func CheckEnv(appname string) (apppath, packpath string, err error) {
  271. gps := GetGOPATHs()
  272. if len(gps) == 0 {
  273. beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
  274. }
  275. currpath, _ := os.Getwd()
  276. currpath = filepath.Join(currpath, appname)
  277. for _, gpath := range gps {
  278. gsrcpath := filepath.Join(gpath, "src")
  279. if strings.HasPrefix(strings.ToLower(currpath), strings.ToLower(gsrcpath)) {
  280. packpath = strings.Replace(currpath[len(gsrcpath)+1:], string(filepath.Separator), "/", -1)
  281. return currpath, packpath, nil
  282. }
  283. }
  284. // In case of multiple paths in the GOPATH, by default
  285. // we use the first path
  286. gopath := gps[0]
  287. beeLogger.Log.Warn("You current workdir is not inside $GOPATH/src.")
  288. beeLogger.Log.Debugf("GOPATH: %s", FILE(), LINE(), gopath)
  289. gosrcpath := filepath.Join(gopath, "src")
  290. apppath = filepath.Join(gosrcpath, appname)
  291. if _, e := os.Stat(apppath); !os.IsNotExist(e) {
  292. err = fmt.Errorf("Cannot create application without removing '%s' first", apppath)
  293. beeLogger.Log.Errorf("Path '%s' already exists", apppath)
  294. return
  295. }
  296. packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(filepath.Separator)), "/")
  297. return
  298. }
  299. func PrintErrorAndExit(message, errorTemplate string) {
  300. Tmpl(fmt.Sprintf(errorTemplate, message), nil)
  301. os.Exit(2)
  302. }
  303. // GoCommand executes the passed command using Go tool
  304. func GoCommand(command string, args ...string) error {
  305. allargs := []string{command}
  306. allargs = append(allargs, args...)
  307. goBuild := exec.Command("go", allargs...)
  308. goBuild.Stderr = os.Stderr
  309. return goBuild.Run()
  310. }
  311. // SplitQuotedFields is like strings.Fields but ignores spaces
  312. // inside areas surrounded by single quotes.
  313. // To specify a single quote use backslash to escape it: '\''
  314. func SplitQuotedFields(in string) []string {
  315. type stateEnum int
  316. const (
  317. inSpace stateEnum = iota
  318. inField
  319. inQuote
  320. inQuoteEscaped
  321. )
  322. state := inSpace
  323. r := []string{}
  324. var buf bytes.Buffer
  325. for _, ch := range in {
  326. switch state {
  327. case inSpace:
  328. if ch == '\'' {
  329. state = inQuote
  330. } else if !unicode.IsSpace(ch) {
  331. buf.WriteRune(ch)
  332. state = inField
  333. }
  334. case inField:
  335. if ch == '\'' {
  336. state = inQuote
  337. } else if unicode.IsSpace(ch) {
  338. r = append(r, buf.String())
  339. buf.Reset()
  340. } else {
  341. buf.WriteRune(ch)
  342. }
  343. case inQuote:
  344. if ch == '\'' {
  345. state = inField
  346. } else if ch == '\\' {
  347. state = inQuoteEscaped
  348. } else {
  349. buf.WriteRune(ch)
  350. }
  351. case inQuoteEscaped:
  352. buf.WriteRune(ch)
  353. state = inQuote
  354. }
  355. }
  356. if buf.Len() != 0 {
  357. r = append(r, buf.String())
  358. }
  359. return r
  360. }
  361. // GetFileModTime returns unix timestamp of `os.File.ModTime` for the given path.
  362. func GetFileModTime(path string) int64 {
  363. path = strings.Replace(path, "\\", "/", -1)
  364. f, err := os.Open(path)
  365. if err != nil {
  366. beeLogger.Log.Errorf("Failed to open file on '%s': %s", path, err)
  367. return time.Now().Unix()
  368. }
  369. defer f.Close()
  370. fi, err := f.Stat()
  371. if err != nil {
  372. beeLogger.Log.Errorf("Failed to get file stats: %s", err)
  373. return time.Now().Unix()
  374. }
  375. return fi.ModTime().Unix()
  376. }
  377. func defaultGOPATH() string {
  378. env := "HOME"
  379. if runtime.GOOS == "windows" {
  380. env = "USERPROFILE"
  381. } else if runtime.GOOS == "plan9" {
  382. env = "home"
  383. }
  384. if home := os.Getenv(env); home != "" {
  385. return filepath.Join(home, "go")
  386. }
  387. return ""
  388. }