diff --git a/cmd/app/factory.go b/cmd/app/factory.go index 79acb7d2..92f78349 100644 --- a/cmd/app/factory.go +++ b/cmd/app/factory.go @@ -14,6 +14,7 @@ import ( //"github.com/gardener/docforge/pkg/metrics" "github.com/gardener/docforge/pkg/processors" "github.com/gardener/docforge/pkg/reactor" + "github.com/gardener/docforge/pkg/resourcehandlers/fs" ghrs "github.com/gardener/docforge/pkg/resourcehandlers/github" "github.com/google/go-github/v32/github" @@ -112,7 +113,9 @@ func WithHugo(reactorOptions *reactor.Options, o *Options) { // initResourceHandlers initializes the resource handler // objects used by reactors func initResourceHandlers(ctx context.Context, o *Options) []resourcehandlers.ResourceHandler { - rhs := []resourcehandlers.ResourceHandler{} + rhs := []resourcehandlers.ResourceHandler{ + fs.NewFSResourceHandler(), + } if o.GitHubTokens != nil { if token, ok := o.GitHubTokens["github.com"]; ok { ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) diff --git a/pkg/reactor/content_processor.go b/pkg/reactor/content_processor.go index 277a98cf..d150ba93 100644 --- a/pkg/reactor/content_processor.go +++ b/pkg/reactor/content_processor.go @@ -228,7 +228,9 @@ func (c *nodeContentProcessor) resolveLink(ctx context.Context, node *api.Node, if handler == nil { return &destination, text, title, nil, nil } - absLink, err = handler.BuildAbsLink(contentSourcePath, destination) + if !u.IsAbs() { + absLink, err = handler.BuildAbsLink(contentSourcePath, destination) + } if err != nil { return nil, text, title, nil, err } diff --git a/pkg/reactor/document_worker.go b/pkg/reactor/document_worker.go index 7c7544b2..4e4fe82c 100644 --- a/pkg/reactor/document_worker.go +++ b/pkg/reactor/document_worker.go @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" "path/filepath" - "strings" "text/template" "github.com/gardener/docforge/pkg/api" @@ -97,17 +96,10 @@ func (w *DocumentWorker) Work(ctx context.Context, task interface{}, wq jobs.Wor tmpl *template.Template ) if tmpl, ok = w.templates[task.Node.Template.Path]; !ok { - // TODO: temporary solution for local templates - if !strings.HasPrefix(task.Node.Template.Path, "https") { - if templateBlob, err = ioutil.ReadFile(task.Node.Template.Path); err != nil { - return jobs.NewWorkerError(err, 0) - } - } else { - if templateBlob, err = w.Reader.Read(ctx, task.Node.Template.Path); err != nil { - return jobs.NewWorkerError(err, 0) - } + if templateBlob, err = w.Reader.Read(ctx, task.Node.Template.Path); err != nil { + return jobs.NewWorkerError(err, 0) } - if tmpl, err = template.New("test").Parse(string(templateBlob)); err != nil { + if tmpl, err = template.New(task.Node.Template.Path).Parse(string(templateBlob)); err != nil { return jobs.NewWorkerError(err, 0) } w.templates[task.Node.Template.Path] = tmpl diff --git a/pkg/resourcehandlers/fs/fs.go b/pkg/resourcehandlers/fs/fs.go new file mode 100644 index 00000000..10465edc --- /dev/null +++ b/pkg/resourcehandlers/fs/fs.go @@ -0,0 +1,229 @@ +package fs + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/gardener/docforge/pkg/api" + "github.com/gardener/docforge/pkg/git" + "github.com/gardener/docforge/pkg/resourcehandlers" + "github.com/google/go-github/v32/github" +) + +type fsHandler struct{} + +// NewFSResourceHandler create file system ResourceHandler +func NewFSResourceHandler() resourcehandlers.ResourceHandler { + return &fsHandler{} +} + +// Accept implements resourcehandlers.ResourceHandler#Accept +func (fs *fsHandler) Accept(uri string) bool { + if _, err := os.Stat(uri); err == nil { + return true + } + return false +} + +// ResolveNodeSelector implements resourcehandlers.ResourceHandler#ResolveNodeSelector +func (fs *fsHandler) ResolveNodeSelector(ctx context.Context, node *api.Node, excludePaths []string, frontMatter map[string]interface{}, excludeFrontMatter map[string]interface{}, depth int32) error { + var ( + fileInfo os.FileInfo + err error + ) + if node.NodeSelector == nil { + return nil + } + if fileInfo, err = os.Stat(node.NodeSelector.Path); err != nil { + return err + } + if !fileInfo.IsDir() { + return fmt.Errorf("nodeSelector path is not directory") + } + _node := &api.Node{ + Nodes: []*api.Node{}, + } + filepath.Walk(node.NodeSelector.Path, func(node *api.Node, parentPath string) filepath.WalkFunc { + return func(path string, info os.FileInfo, err error) error { + if node.NodeSelector != nil { + return nil + } + if path != parentPath { + if len(strings.Split(path, "/"))-len(strings.Split(parentPath, "/")) != 1 { + node = node.Parent() + parentPathSegments := strings.Split(path, "/") + if len(parentPathSegments) > 0 { + parentPathSegments = parentPathSegments[:len(parentPathSegments)-1] + parentPath = filepath.Join(parentPathSegments...) + } + } + } + n := &api.Node{ + Name: info.Name(), + } + n.SetParent(node) + node.Nodes = append(node.Nodes, n) + if info.IsDir() { + node = n + node.Nodes = []*api.Node{} + parentPath = path + } else { + n.Source = path + } + return nil + } + }(_node, node.NodeSelector.Path)) + if len(_node.Nodes) > 0 && len(_node.Nodes[0].Nodes) > 0 { + node.Nodes = _node.Nodes[0].Nodes + for _, n := range node.Nodes { + n.SetParent(node) + } + } + return nil +} + +func buildNodes(path string, info os.FileInfo) *api.Node { + return &api.Node{ + Name: info.Name(), + Source: path, + } +} + +// Read implements resourcehandlers.ResourceHandler#Read +func (fs *fsHandler) Read(ctx context.Context, uri string) ([]byte, error) { + return ioutil.ReadFile(uri) +} + +// ReadGitInfo implements resourcehandlers.ResourceHandler#ReadGitInfo +func (fs *fsHandler) ReadGitInfo(ctx context.Context, uri string) ([]byte, error) { + var ( + log []*gitLogEntry + blob []byte + err error + ) + if log, err = gitLog(uri); err != nil { + return nil, err + } + + if len(log) == 0 { + return []byte(""), nil + } + + for _, logEntry := range log { + logEntry.Name = strings.Split(logEntry.Name, "<")[0] + logEntry.Name = strings.TrimSpace(logEntry.Name) + } + authorName := log[len(log)-1].Name + authorEmail := log[len(log)-1].Email + publishD := log[len(log)-1].Date + lastModD := log[0].Date + gitInfo := &git.GitInfo{ + PublishDate: &publishD, + LastModifiedDate: &lastModD, + Author: &github.User{ + Name: &authorName, + Email: &authorEmail, + }, + Contributors: []*github.User{}, + } + + for _, logEntry := range log { + if logEntry.Email != *gitInfo.Author.Email { + name := logEntry.Name + email := logEntry.Email + gitInfo.Contributors = append(gitInfo.Contributors, &github.User{ + Name: &name, + Email: &email, + }) + } + } + + if blob, err = json.MarshalIndent(gitInfo, "", " "); err != nil { + return nil, err + } + + return blob, nil +} + +// ResourceName implements resourcehandlers.ResourceHandler#ResourceName +func (fs *fsHandler) ResourceName(link string) (name string, extension string) { + _, name = filepath.Split(link) + if len(name) > 0 { + if e := filepath.Ext(name); len(e) > 0 { + extension = e[1:] + name = strings.TrimSuffix(name, e) + } + } + return +} + +// BuildAbsLink implements resourcehandlers.ResourceHandler#BuildAbsLink +func (fs *fsHandler) BuildAbsLink(source, link string) (string, error) { + if filepath.IsAbs(link) { + return link, nil + } + p := filepath.Join(source, link) + p = filepath.Clean(p) + if filepath.IsAbs(p) { + return p, nil + } + return filepath.Abs(p) +} + +// GetRawFormatLink implements resourcehandlers.ResourceHandler#GetRawFormatLink +func (fs *fsHandler) GetRawFormatLink(absLink string) (string, error) { + return absLink, nil +} + +// SetVersion implements resourcehandlers.ResourceHandler#SetVersion +func (fs *fsHandler) SetVersion(absLink, version string) (string, error) { + return absLink, nil +} + +type gitLogEntry struct { + Sha string + Author string + Date string + Message string + Email string + Name string +} + +type gitLogEntryAuthor struct { +} + +func gitLog(path string) ([]*gitLogEntry, error) { + var ( + log []byte + err error + ) + + if _, err := os.Stat(path); err != nil { + return nil, err + } + + git := exec.Command("git", "log", "--date=short", `--pretty=format:'{%n "sha": "%H",%n "author": "%aN <%aE>",%n "date": "%ad",%n "message": "%s",%n "email": "%aE",%n "name": "%aN"%n },'`, "--follow", path) + if log, err = git.CombinedOutput(); err != nil { + return nil, err + } + + logS := string(log) + logS = strings.ReplaceAll(logS, "'{", "{") + logS = strings.ReplaceAll(logS, "},'", "},") + if strings.HasSuffix(logS, ",") { + logS = logS[:len(logS)-1] + } + logS = fmt.Sprintf("[%s]", logS) + + gitLog := []*gitLogEntry{} + if err := json.Unmarshal([]byte(logS), &gitLog); err != nil { + return nil, err + } + return gitLog, nil +} diff --git a/pkg/resourcehandlers/fs/fs_test.go b/pkg/resourcehandlers/fs/fs_test.go new file mode 100644 index 00000000..7db14772 --- /dev/null +++ b/pkg/resourcehandlers/fs/fs_test.go @@ -0,0 +1,79 @@ +package fs + +import ( + "path/filepath" + "testing" + + "github.com/gardener/docforge/pkg/api" + "github.com/stretchr/testify/assert" +) + +func TestGitLog(t *testing.T) { + var ( + log []*gitLogEntry + err error + ) + if log, err = gitLog(filepath.Join("testdata", "f00.md")); err != nil { + t.Fatalf("%s", err.Error()) + } + assert.NotNil(t, log) +} + +func TestReadGitInfo(t *testing.T) { + var ( + log []byte + err error + ) + fs := &fsHandler{} + if log, err = fs.ReadGitInfo(nil, filepath.Join("testdata", "f00.md")); err != nil { + t.Fatalf("%s", err.Error()) + } + t.Logf("%s\n", log) + assert.NotNil(t, log) +} + +func TestResolveNodeSelector(t *testing.T) { + var ( + err error + ) + fs := &fsHandler{} + node := &api.Node{ + NodeSelector: &api.NodeSelector{ + Path: "testdata", + }, + } + expected := &api.Node{ + NodeSelector: &api.NodeSelector{ + Path: "testdata", + }, + Nodes: []*api.Node{ + &api.Node{ + Name: "d00", + Nodes: []*api.Node{ + &api.Node{ + Name: "d02", + Nodes: []*api.Node{ + &api.Node{ + Name: "f020.md", + Source: "testdata/d00/d02/f020.md", + }, + }, + }, + &api.Node{ + Name: "f01.md", + Source: "testdata/d00/f01.md", + }, + }, + }, + &api.Node{ + Name: "f00.md", + Source: "testdata/f00.md", + }, + }, + } + expected.SetParentsDownwards() + if err = fs.ResolveNodeSelector(nil, node, nil, nil, nil, 0); err != nil { + t.Fatalf("%s", err.Error()) + } + assert.Equal(t, expected, node) +} diff --git a/pkg/resourcehandlers/fs/testdata/d00/d02/f020.md b/pkg/resourcehandlers/fs/testdata/d00/d02/f020.md new file mode 100644 index 00000000..e69de29b diff --git a/pkg/resourcehandlers/fs/testdata/d00/f01.md b/pkg/resourcehandlers/fs/testdata/d00/f01.md new file mode 100644 index 00000000..e69de29b diff --git a/pkg/resourcehandlers/fs/testdata/f00.md b/pkg/resourcehandlers/fs/testdata/f00.md new file mode 100644 index 00000000..e69de29b