meta.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. package metadata
  2. import (
  3. "fmt"
  4. "strings"
  5. "unicode"
  6. )
  7. type CommentSyntax struct {
  8. Dash bool
  9. Hash bool
  10. SlashStar bool
  11. }
  12. const (
  13. CmdExec = ":exec"
  14. CmdExecResult = ":execresult"
  15. CmdExecRows = ":execrows"
  16. CmdMany = ":many"
  17. CmdOne = ":one"
  18. CmdCopyFrom = ":copyfrom"
  19. )
  20. // A query name must be a valid Go identifier
  21. //
  22. // https://golang.org/ref/spec#Identifiers
  23. func validateQueryName(name string) error {
  24. if len(name) == 0 {
  25. return fmt.Errorf("invalid query name: %q", name)
  26. }
  27. for i, c := range name {
  28. isLetter := unicode.IsLetter(c) || c == '_'
  29. isDigit := unicode.IsDigit(c)
  30. if i == 0 && !isLetter {
  31. return fmt.Errorf("invalid query name %q", name)
  32. } else if !(isLetter || isDigit) {
  33. return fmt.Errorf("invalid query name %q", name)
  34. }
  35. }
  36. return nil
  37. }
  38. func Parse(t string, commentStyle CommentSyntax) (string, string, error) {
  39. for _, line := range strings.Split(t, "\n") {
  40. var prefix string
  41. if strings.HasPrefix(line, "--") {
  42. if !commentStyle.Dash {
  43. continue
  44. }
  45. prefix = "--"
  46. }
  47. if strings.HasPrefix(line, "/*") {
  48. if !commentStyle.SlashStar {
  49. continue
  50. }
  51. prefix = "/*"
  52. }
  53. if strings.HasPrefix(line, "#") {
  54. if !commentStyle.Hash {
  55. continue
  56. }
  57. prefix = "#"
  58. }
  59. if prefix == "" {
  60. continue
  61. }
  62. rest := line[len(prefix):]
  63. if !strings.HasPrefix(strings.TrimSpace(rest), "name") {
  64. continue
  65. }
  66. if !strings.Contains(rest, ":") {
  67. continue
  68. }
  69. if !strings.HasPrefix(rest, " name: ") {
  70. return "", "", fmt.Errorf("invalid metadata: %s", line)
  71. }
  72. part := strings.Split(strings.TrimSpace(line), " ")
  73. if prefix == "/*" {
  74. part = part[:len(part)-1] // removes the trailing "*/" element
  75. }
  76. if len(part) == 2 {
  77. return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows', ':execresult', ':copyfrom']: %s", line)
  78. }
  79. if len(part) != 4 {
  80. return "", "", fmt.Errorf("invalid query comment: %s", line)
  81. }
  82. queryName := part[2]
  83. queryType := strings.TrimSpace(part[3])
  84. switch queryType {
  85. case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdCopyFrom:
  86. default:
  87. return "", "", fmt.Errorf("invalid query type: %s", queryType)
  88. }
  89. if err := validateQueryName(queryName); err != nil {
  90. return "", "", err
  91. }
  92. return queryName, queryType, nil
  93. }
  94. return "", "", nil
  95. }