/cmd/holo/internal/scan.go

https://github.com/holocm/holo · Go · 135 lines · 83 code · 16 blank · 36 comment · 24 complexity · 05b5d24d665e78788f452e89d5598966 MD5 · raw file

  1. /*******************************************************************************
  2. *
  3. * Copyright 2015 Stefan Majewsky <majewsky@gmx.net>
  4. *
  5. * This file is part of Holo.
  6. *
  7. * Holo is free software: you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation, either version 3 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * Holo is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * Holo. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. *******************************************************************************/
  20. package impl
  21. import (
  22. "bytes"
  23. "fmt"
  24. "regexp"
  25. "sort"
  26. "strings"
  27. )
  28. //Scan discovers entities available for the given entity. Errors are reported
  29. //immediately and will result in nil being returned. "No entities found" will
  30. //be reported as a non-nil empty slice.
  31. //there are no entities.
  32. func (p *Plugin) Scan() []*Entity {
  33. //invoke scan operation
  34. stdout, hadError := p.runScanOperation()
  35. if hadError {
  36. return nil
  37. }
  38. //parse scan output
  39. lines := strings.Split(strings.TrimSpace(stdout), "\n")
  40. lineRx := regexp.MustCompile(`^\s*([^:]+): (.+)\s*$`)
  41. actionRx := regexp.MustCompile(`^([^()]+) \((.+)\)$`)
  42. hadError = false
  43. var currentEntity *Entity
  44. var result []*Entity
  45. for idx, line := range lines {
  46. //skip empty lines
  47. if line == "" {
  48. continue
  49. }
  50. //keep format strings from getting too long
  51. errorIntro := fmt.Sprintf("error in scan report of %s, line %d", p.ID(), idx+1)
  52. //general line format is "key: value"
  53. match := lineRx.FindStringSubmatch(line)
  54. if match == nil {
  55. Errorf(Stderr, "%s: parse error (line was \"%s\")", errorIntro, line)
  56. hadError = true
  57. continue
  58. }
  59. key, value := match[1], match[2]
  60. switch {
  61. case key == "ENTITY":
  62. //starting new entity
  63. if currentEntity != nil {
  64. result = append(result, currentEntity)
  65. }
  66. currentEntity = &Entity{plugin: p, id: value, actionVerb: "Working on"}
  67. case currentEntity == nil:
  68. //if not, we need to be inside an entity
  69. //(i.e. line with idx = 0 must start an entity)
  70. Errorf(Stderr, "%s: expected entity ID, found attribute \"%s\"", errorIntro, line)
  71. hadError = true
  72. case key == "SOURCE":
  73. value = TranslateIfResourcePath(value)
  74. currentEntity.sourceFiles = append(currentEntity.sourceFiles, value)
  75. case key == "ACTION":
  76. //parse action verb/reason
  77. match = actionRx.FindStringSubmatch(value)
  78. if match == nil {
  79. currentEntity.actionVerb = value
  80. currentEntity.actionReason = ""
  81. } else {
  82. currentEntity.actionVerb = match[1]
  83. currentEntity.actionReason = match[2]
  84. }
  85. default:
  86. //store unrecognized keys as info lines
  87. value = TranslateIfResourcePath(value)
  88. currentEntity.infoLines = append(currentEntity.infoLines,
  89. InfoLine{key, value},
  90. )
  91. }
  92. }
  93. //store last entity
  94. if currentEntity != nil {
  95. result = append(result, currentEntity)
  96. }
  97. //report errors
  98. if hadError {
  99. return nil
  100. }
  101. //on success, ensure non-nil return value
  102. if result == nil {
  103. result = []*Entity{}
  104. }
  105. sort.Sort(entitiesByID(result))
  106. return result
  107. }
  108. func (p *Plugin) runScanOperation() (stdout string, hadError bool) {
  109. var stdoutBuffer bytes.Buffer
  110. err := p.Command([]string{"scan"}, &stdoutBuffer, Stderr, nil).Run()
  111. if err != nil {
  112. Errorf(Stderr, "scan with plugin %s failed: %s", p.ID(), err.Error())
  113. }
  114. return string(stdoutBuffer.Bytes()), err != nil
  115. }
  116. type entitiesByID []*Entity
  117. func (e entitiesByID) Len() int { return len(e) }
  118. func (e entitiesByID) Less(i, j int) bool { return e[i].EntityID() < e[j].EntityID() }
  119. func (e entitiesByID) Swap(i, j int) { e[i], e[j] = e[j], e[i] }