File: readfile.go

package info (click to toggle)
deck 1.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 1,100 kB
  • sloc: makefile: 19; sh: 3
file content (134 lines) | stat: -rw-r--r-- 3,457 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package file

import (
	"bufio"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	ghodssyaml "github.com/ghodss/yaml"
	"github.com/imdario/mergo"
	"github.com/pkg/errors"
)

// getContent reads all the YAML and JSON files in the directory or the
// file, depending on the type of each item in filenames, merges the content of
// these files and renders a Content.
func getContent(filenames []string) (*Content, error) {
	var allReaders []io.Reader
	for _, fileOrDir := range filenames {
		readers, err := getReaders(fileOrDir)
		if err != nil {
			return nil, err
		}
		allReaders = append(allReaders, readers...)
	}
	var res Content
	for _, r := range allReaders {
		content, err := readContent(r)
		if err != nil {
			return nil, errors.Wrap(err, "reading file")
		}
		err = mergo.Merge(&res, content, mergo.WithAppendSlice)
		if err != nil {
			return nil, errors.Wrap(err, "merging file contents")
		}
	}
	return &res, nil
}

// getReaders returns back io.Readers representing all the YAML and JSON
// files in a directory. If fileOrDir is a single file, then it
// returns back the reader for the file.
// If fileOrDir is equal to "-" string, then it returns back a io.Reader
// for the os.Stdin file descriptor.
func getReaders(fileOrDir string) ([]io.Reader, error) {
	// special case where `-` means stdin
	if fileOrDir == "-" {
		return []io.Reader{os.Stdin}, nil
	}

	finfo, err := os.Stat(fileOrDir)
	if err != nil {
		return nil, errors.Wrap(err, "reading state file")
	}

	var files []string
	if finfo.IsDir() {
		files, err = configFilesInDir(fileOrDir)
		if err != nil {
			return nil,
				errors.Wrap(err, "getting files from directory")
		}
	} else {
		files = append(files, fileOrDir)
	}

	var res []io.Reader
	for _, file := range files {
		f, err := os.Open(file)
		if err != nil {
			return nil, errors.Wrap(err, "opening file")
		}
		res = append(res, bufio.NewReader(f))
	}
	return res, nil
}

// readContent reads all the byes until io.EOF and unmarshals the read
// bytes into Content.
func readContent(reader io.Reader) (*Content, error) {
	var content Content
	var bytes []byte
	var err error
	bytes, err = ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	err = validate(bytes)
	if err != nil {
		return nil, errors.Wrap(err, "validating file content")
	}
	err = yamlUnmarshal(bytes, &content)
	if err != nil {
		return nil, err
	}
	return &content, nil
}

// yamlUnmarshal is a wrapper around yaml.Unmarshal to ensure that the right
// yaml package is in use. Using ghodss/yaml ensures that no
// `map[interface{}]interface{}` is present in go-kong.Plugin.Configuration.
// If it is present, then it leads to a silent error. See Github Issue #144.
// The verification for this is done using a test.
func yamlUnmarshal(bytes []byte, v interface{}) error {
	return ghodssyaml.Unmarshal(bytes, v)
}

// configFilesInDir traverses the directory rooted at dir and
// returns all the files with a case-insensitive extension of `yml` or `yaml`.
func configFilesInDir(dir string) ([]string, error) {
	var res []string
	err := filepath.Walk(
		dir,
		func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}
			switch strings.ToLower(filepath.Ext(path)) {
			case ".yaml", ".yml", ".json":
				res = append(res, path)
			}
			return nil
		},
	)
	if err != nil {
		return nil, errors.Wrap(err, "reading state directory")
	}
	return res, nil
}