response.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // Copyright 2021 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain 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,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package response
  15. import (
  16. "io"
  17. "io/ioutil"
  18. "strings"
  19. "unicode"
  20. )
  21. const noQuote = '\x00'
  22. // ReadRspFile reads a file in Ninja's response file format and returns its contents.
  23. func ReadRspFile(r io.Reader) ([]string, error) {
  24. var files []string
  25. var file []byte
  26. buf, err := ioutil.ReadAll(r)
  27. if err != nil {
  28. return nil, err
  29. }
  30. isEscaping := false
  31. quotingStart := byte(noQuote)
  32. for _, c := range buf {
  33. switch {
  34. case isEscaping:
  35. if quotingStart == '"' {
  36. if !(c == '"' || c == '\\') {
  37. // '\"' or '\\' will be escaped under double quoting.
  38. file = append(file, '\\')
  39. }
  40. }
  41. file = append(file, c)
  42. isEscaping = false
  43. case c == '\\' && quotingStart != '\'':
  44. isEscaping = true
  45. case quotingStart == noQuote && (c == '\'' || c == '"'):
  46. quotingStart = c
  47. case quotingStart != noQuote && c == quotingStart:
  48. quotingStart = noQuote
  49. case quotingStart == noQuote && unicode.IsSpace(rune(c)):
  50. // Current character is a space outside quotes
  51. if len(file) != 0 {
  52. files = append(files, string(file))
  53. }
  54. file = file[:0]
  55. default:
  56. file = append(file, c)
  57. }
  58. }
  59. if len(file) != 0 {
  60. files = append(files, string(file))
  61. }
  62. return files, nil
  63. }
  64. func rspUnsafeChar(r rune) bool {
  65. switch {
  66. case 'A' <= r && r <= 'Z',
  67. 'a' <= r && r <= 'z',
  68. '0' <= r && r <= '9',
  69. r == '_',
  70. r == '+',
  71. r == '-',
  72. r == '.',
  73. r == '/':
  74. return false
  75. default:
  76. return true
  77. }
  78. }
  79. var rspEscaper = strings.NewReplacer(`'`, `'\''`)
  80. // WriteRspFile writes a list of files to a file in Ninja's response file format.
  81. func WriteRspFile(w io.Writer, files []string) error {
  82. for i, f := range files {
  83. if i != 0 {
  84. _, err := io.WriteString(w, " ")
  85. if err != nil {
  86. return err
  87. }
  88. }
  89. if strings.IndexFunc(f, rspUnsafeChar) != -1 {
  90. f = `'` + rspEscaper.Replace(f) + `'`
  91. }
  92. _, err := io.WriteString(w, f)
  93. if err != nil {
  94. return err
  95. }
  96. }
  97. return nil
  98. }