heredoc.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. // Copyright (c) 2014-2017 TSUYUSATO Kitsune
  2. // This software is released under the MIT License.
  3. // http://opensource.org/licenses/mit-license.php
  4. // Package heredoc provides creation of here-documents from raw strings.
  5. //
  6. // Golang supports raw-string syntax.
  7. // doc := `
  8. // Foo
  9. // Bar
  10. // `
  11. // But raw-string cannot recognize indentation. Thus such content is an indented string, equivalent to
  12. // "\n\tFoo\n\tBar\n"
  13. // I dont't want this!
  14. //
  15. // However this problem is solved by package heredoc.
  16. // doc := heredoc.Doc(`
  17. // Foo
  18. // Bar
  19. // `)
  20. // Is equivalent to
  21. // "Foo\nBar\n"
  22. package heredoc
  23. import (
  24. "fmt"
  25. "strings"
  26. "unicode"
  27. )
  28. const maxInt = int(^uint(0) >> 1)
  29. // Doc returns un-indented string as here-document.
  30. func Doc(raw string) string {
  31. skipFirstLine := false
  32. if raw[0] == '\n' {
  33. raw = raw[1:]
  34. } else {
  35. skipFirstLine = true
  36. }
  37. lines := strings.Split(raw, "\n")
  38. minIndentSize := getMinIndent(lines, skipFirstLine)
  39. lines = removeIndentation(lines, minIndentSize, skipFirstLine)
  40. return strings.Join(lines, "\n")
  41. }
  42. // getMinIndent calculates the minimum indentation in lines, excluding empty lines.
  43. func getMinIndent(lines []string, skipFirstLine bool) int {
  44. minIndentSize := maxInt
  45. for i, line := range lines {
  46. if i == 0 && skipFirstLine {
  47. continue
  48. }
  49. indentSize := 0
  50. for _, r := range []rune(line) {
  51. if unicode.IsSpace(r) {
  52. indentSize += 1
  53. } else {
  54. break
  55. }
  56. }
  57. if len(line) == indentSize {
  58. if i == len(lines)-1 && indentSize < minIndentSize {
  59. lines[i] = ""
  60. }
  61. } else if indentSize < minIndentSize {
  62. minIndentSize = indentSize
  63. }
  64. }
  65. return minIndentSize
  66. }
  67. // removeIndentation removes n characters from the front of each line in lines.
  68. // Skips first line if skipFirstLine is true, skips empty lines.
  69. func removeIndentation(lines []string, n int, skipFirstLine bool) []string {
  70. for i, line := range lines {
  71. if i == 0 && skipFirstLine {
  72. continue
  73. }
  74. if len(lines[i]) >= n {
  75. lines[i] = line[n:]
  76. }
  77. }
  78. return lines
  79. }
  80. // Docf returns unindented and formatted string as here-document.
  81. // Formatting is done as for fmt.Printf().
  82. func Docf(raw string, args ...interface{}) string {
  83. return fmt.Sprintf(Doc(raw), args...)
  84. }