/source/file/source.go

https://gitlab.com/actions/go-actions · Go · 187 lines · 140 code · 24 blank · 23 comment · 31 complexity · 06bb2bb7b86ae2d1250678e2329ad613 MD5 · raw file

  1. // Package file defines file action source.
  2. //
  3. // It registers file source in github.com/crackcomm/go-actions/source.
  4. package file
  5. import "os"
  6. import "regexp"
  7. import "strings"
  8. import "path/filepath"
  9. import "github.com/crackcomm/go-actions/action"
  10. import "github.com/crackcomm/go-actions/source"
  11. import "github.com/crackcomm/go-actions/encoding/json"
  12. import "github.com/crackcomm/go-actions/encoding/yaml"
  13. // DefaultExtension - Default format when saving actions.
  14. var DefaultExtension = ".json"
  15. // PathSeparator - Used to replace dots (.) in action name to find them in directories.
  16. var PathSeparator = string(os.PathSeparator)
  17. // Source - File actions source.
  18. type Source struct {
  19. Addr string
  20. files map[string]string
  21. }
  22. // acceptedExtensions - Accepted extensions.
  23. var acceptedExtensions = []string{".json", ".yaml", ".yml"}
  24. // All - Returns all available actions.
  25. func (s *Source) All() (res action.Actions, err error) {
  26. // Get all files in source path
  27. files, err := s.allFiles()
  28. if err != nil {
  29. return
  30. }
  31. // Read all files
  32. res = make(action.Actions)
  33. for name, filename := range files {
  34. res[name], err = s.readFile(filename)
  35. if err != nil {
  36. return
  37. }
  38. }
  39. return
  40. }
  41. // Add - Adds action to file store.
  42. func (s *Source) Add(name string, a *action.Action) (err error) {
  43. name = strings.Replace(name, ".", PathSeparator, -1)
  44. return s.writeFile(name+DefaultExtension, a)
  45. }
  46. // Get - Gets action from file source.
  47. func (s *Source) Get(name string) (a *action.Action, err error) {
  48. filename, err := s.getFilename(name)
  49. if err != nil {
  50. return
  51. }
  52. a, err = s.readFile(filename)
  53. return
  54. }
  55. // Delete - Deletes action from file source.
  56. func (s *Source) Delete(name string) (err error) {
  57. filename, err := s.getFilename(name)
  58. if err != nil {
  59. return
  60. }
  61. filename = filepath.Join(s.Addr, filename)
  62. return os.Remove(filename)
  63. }
  64. // readFile - Reads action from file, accepts relative path.
  65. func (s *Source) readFile(filename string) (a *action.Action, err error) {
  66. filename = filepath.Join(s.Addr, filename)
  67. file, err := os.Open(filename)
  68. if err != nil {
  69. return
  70. }
  71. defer file.Close()
  72. switch filepath.Ext(filename) {
  73. case ".json":
  74. a, err = json.NewDecoder(file).Decode()
  75. case ".yaml", ".yml":
  76. a, err = yaml.NewDecoder(file).Decode()
  77. }
  78. return
  79. }
  80. // writeFile - Writes action to file, encodes using DefaultExtension marshaler (yaml or json).
  81. func (s *Source) writeFile(filename string, a *action.Action) (err error) {
  82. filename = filepath.Join(s.Addr, filename)
  83. err = os.MkdirAll(filepath.Dir(filename), os.ModeDir)
  84. if err != nil {
  85. return
  86. }
  87. file, err := os.Create(filename)
  88. if err != nil {
  89. return
  90. }
  91. defer file.Close()
  92. switch DefaultExtension {
  93. case ".json":
  94. err = json.NewEncoder(file).Encode(a)
  95. case ".yaml", ".yml":
  96. err = yaml.NewEncoder(file).Encode(a)
  97. }
  98. return
  99. }
  100. // getFilename - Gets action filename by its name, it returns source.ErrNotFound when its not found.
  101. func (s *Source) getFilename(name string) (filename string, err error) {
  102. // Get all files in source path
  103. files, err := s.allFiles()
  104. if err != nil {
  105. return
  106. }
  107. // Get action filename
  108. filename = files[name]
  109. if filename == "" {
  110. err = source.ErrNotFound
  111. }
  112. return
  113. }
  114. // allFiles - Finds all action files in source path, returns in map with action name in key and filename in value.
  115. func (s *Source) allFiles() (res map[string]string, err error) {
  116. path := filepath.Join(s.Addr, "**/*")
  117. files, err := filepath.Glob(path)
  118. if err != nil {
  119. return
  120. }
  121. // Create a map with action names in keys and filenames in value
  122. res = make(map[string]string)
  123. for _, filename := range files {
  124. filename, _ = filepath.Rel(s.Addr, filename)
  125. name, ok := fileActionName(filename) // minus base path
  126. if ok {
  127. res[name] = filename
  128. }
  129. }
  130. return
  131. }
  132. // reformat - formats filename from `../../test/me/now/../../` to `test/me/now` and from `.test.me.` to `test.me`
  133. var reformat = regexp.MustCompile(`(^([./\\]+)|([./\\]+)$)`)
  134. // fileActionName - Converts filename to action name. Ok can be false when filename extension is not supported.
  135. func fileActionName(filename string) (name string, ok bool) {
  136. ext := filepath.Ext(filename)
  137. if !isAccepted(ext) {
  138. return
  139. }
  140. filename = filename[:len(filename)-len(ext)]
  141. name = reformat.ReplaceAllString(filename, "")
  142. name = strings.Replace(name, PathSeparator, ".", -1)
  143. ok = true
  144. return
  145. }
  146. // isAccepted - checks if extension appears in acceptedExtensions list.
  147. func isAccepted(ext string) bool {
  148. for _, accept := range acceptedExtensions {
  149. if accept == ext {
  150. return true
  151. }
  152. }
  153. return false
  154. }
  155. func init() {
  156. source.Register("file", func(address string, authorization ...string) (source.Source, error) {
  157. src := &Source{
  158. Addr: address,
  159. files: make(map[string]string),
  160. }
  161. return src, nil
  162. })
  163. }