Skip to content

Commit b049265

Browse files
committed
render only copied files
1 parent fce5b92 commit b049265

4 files changed

Lines changed: 172 additions & 34 deletions

File tree

internal/deploy.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,11 @@ func cloneFromGit(ctx context.Context, client gitclient.Client, url string) (pro
209209
return tmpDir, nil
210210
}
211211

212-
func CopyTree(src string, dest string) ([]string, error) {
213-
var files []string
212+
func CopyTree(src string, dest string) (*FSTree, error) {
213+
var root = &FSTree{
214+
Name: dest,
215+
Dir: true,
216+
}
214217
err := filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
215218
if err != nil {
216219
return err
@@ -220,10 +223,14 @@ func CopyTree(src string, dest string) ([]string, error) {
220223
}
221224
relPath, err := filepath.Rel(src, path)
222225
destPath := filepath.Join(dest, relPath)
226+
root.Add(relPath, info.IsDir())
223227
if info.IsDir() {
224-
return os.Mkdir(destPath, info.Mode())
228+
err := os.Mkdir(destPath, info.Mode())
229+
if os.IsExist(err) {
230+
return nil
231+
}
232+
return err
225233
}
226-
files = append(files, relPath)
227234
srcFile, err := os.Open(path)
228235
if err != nil {
229236
return fmt.Errorf("open source (%s): %w", path, err)
@@ -243,7 +250,7 @@ func CopyTree(src string, dest string) ([]string, error) {
243250

244251
return destFile.Close()
245252
})
246-
return files, err
253+
return root, err
247254
}
248255

249256
func splitAbbreviation(text string) (abbrev, repo string) {

internal/fs_tree.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
Copyright 2022 Aleksandr Baryshnikov
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package internal
18+
19+
import (
20+
"os"
21+
"path/filepath"
22+
"strings"
23+
)
24+
25+
// FSTree is general tree which reflects hierarchy of modified files.
26+
// It's used to track modified files even after rendering and copying.
27+
type FSTree struct {
28+
Name string // path or base name
29+
Dir bool // is it directory (used to skip rendering content)
30+
Children []*FSTree // child nodes (files or dirs)
31+
parent *FSTree // ref to parent (could be null for root)
32+
}
33+
34+
// Paths returns list of all paths from this node and children.
35+
// This is very slow operation.
36+
func (fs *FSTree) Paths() []string {
37+
var ans = []string{fs.Path()}
38+
for _, c := range fs.Children {
39+
ans = append(ans, c.Paths()...)
40+
}
41+
return ans
42+
}
43+
44+
// Render node name.
45+
// Rules for returned string:
46+
// 1. same name -> do nothing
47+
// 2. empty name -> remove node and children
48+
// 3. rename
49+
func (fs *FSTree) Render(renderName func(node *FSTree) (string, error)) error {
50+
newName, err := renderName(fs)
51+
if err != nil {
52+
return err
53+
}
54+
if newName == fs.Name {
55+
// nothing changed, continue walk
56+
cp := fs.Children
57+
for _, child := range cp {
58+
if err := child.Render(renderName); err != nil {
59+
return err
60+
}
61+
}
62+
return nil
63+
}
64+
65+
if newName == "" {
66+
//remove
67+
if err := os.RemoveAll(fs.Path()); err != nil {
68+
return err
69+
}
70+
var filtered []*FSTree
71+
for _, child := range fs.parent.Children {
72+
if child != fs {
73+
filtered = append(filtered, child)
74+
}
75+
}
76+
fs.parent.Children = filtered
77+
return nil
78+
}
79+
80+
// name changed
81+
// we are walking top-down, so it's enough just to update corresponding node
82+
oldPath := fs.Path()
83+
fs.Name = newName
84+
newPath := fs.Path()
85+
if err := os.Rename(oldPath, newPath); err != nil {
86+
return err
87+
}
88+
// continue walk
89+
cp := fs.Children
90+
for _, child := range cp {
91+
if err := child.Render(renderName); err != nil {
92+
return err
93+
}
94+
}
95+
return nil
96+
}
97+
98+
// Add node to tree. It's naive implementation and requires O(N*M) complexity, where N is number of sections in path,
99+
// and M is number of elements per section.
100+
func (fs *FSTree) Add(path string, dir bool) *FSTree {
101+
parts := strings.Split(path, string(filepath.Separator))
102+
current := fs
103+
for i, p := range parts {
104+
isDir := i != len(parts)-1 || dir
105+
current = current.child(p, isDir)
106+
}
107+
return current
108+
}
109+
110+
// Path from the root node.
111+
func (fs *FSTree) Path() string {
112+
if fs.parent == nil {
113+
return fs.Name
114+
}
115+
return filepath.Join(fs.parent.Path(), fs.Name)
116+
}
117+
118+
func (fs *FSTree) child(name string, dir bool) *FSTree {
119+
for _, c := range fs.Children {
120+
if c.Name == name {
121+
return c
122+
}
123+
}
124+
child := &FSTree{
125+
Name: name,
126+
Dir: dir,
127+
parent: fs,
128+
}
129+
fs.Children = append(fs.Children, child)
130+
return child
131+
}

internal/manifest.go

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"bytes"
2121
"context"
2222
"fmt"
23-
"io/fs"
2423
"io/ioutil"
2524
"os"
2625
"path/filepath"
@@ -94,10 +93,15 @@ func (m *Manifest) renderTo(ctx context.Context, display ui.UI, destinationDir,
9493
return fmt.Errorf("create destination: %w", err)
9594
}
9695

97-
if _, err := CopyTree(filepath.Join(layoutDir, ContentDir), destinationDir); err != nil {
96+
tree, err := CopyTree(filepath.Join(layoutDir, ContentDir), destinationDir)
97+
if err != nil {
9898
return fmt.Errorf("copy content: %w", err)
9999
}
100100

101+
if debug {
102+
spew.Dump(tree)
103+
}
104+
101105
// execute pre-generate
102106
for i, h := range m.Before {
103107
if ok, err := h.When.Ok(ctx, state); err != nil {
@@ -113,48 +117,37 @@ func (m *Manifest) renderTo(ctx context.Context, display ui.UI, destinationDir,
113117
}
114118
}
115119

116-
// render template
120+
// render template based on tree
117121
// rename files and dirs, empty entries removed
118-
err := walk(destinationDir, func(dir string, d fs.DirEntry) error {
119-
renderedName, err := renderer.Render(d.Name())
120-
if err != nil {
121-
return err
122-
}
123-
renderedName = strings.TrimSpace(renderedName)
124-
oldPath := filepath.Join(dir, d.Name())
125-
newPath := filepath.Join(dir, renderedName)
126-
if len(renderedName) == 0 {
127-
return os.RemoveAll(oldPath)
128-
}
129-
if oldPath == newPath {
130-
return nil
131-
}
132-
return os.Rename(oldPath, newPath)
122+
err = tree.Render(func(node *FSTree) (string, error) {
123+
return renderer.Render(node.Name)
133124
})
134125
if err != nil {
135126
return fmt.Errorf("render files names: %w", err)
136127
}
128+
137129
// render file contents as template, except ignored
138130
ignoredFiles, err := m.filesToIgnore(destinationDir)
139131
if err != nil {
140132
return fmt.Errorf("calculate which files to ignore: %w", err)
141133
}
142-
err = filepath.Walk(destinationDir, func(path string, info fs.FileInfo, err error) error {
143-
if err != nil {
144-
return err
134+
err = tree.Render(func(node *FSTree) (string, error) {
135+
if node.Dir {
136+
return node.Name, nil
145137
}
146-
if info.IsDir() || ignoredFiles[path] {
147-
return nil
138+
path := node.Path()
139+
if ignoredFiles[path] {
140+
return node.Name, nil
148141
}
149142
templateData, err := ioutil.ReadFile(path)
150143
if err != nil {
151-
return fmt.Errorf("read content of %s: %w", path, err)
144+
return node.Name, fmt.Errorf("read content of %s: %w", path, err)
152145
}
153146
data, err := renderer.Render(string(templateData))
154147
if err != nil {
155-
return fmt.Errorf("render %s: %w", path, err)
148+
return node.Name, fmt.Errorf("render %s: %w", path, err)
156149
}
157-
return ioutil.WriteFile(path, []byte(data), info.Mode())
150+
return node.Name, ioutil.WriteFile(path, []byte(data), 0755)
158151
})
159152
if err != nil {
160153
return fmt.Errorf("render: %w", err)

layout_test.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"bytes"
2323
"context"
2424
"io"
25+
"io/fs"
2526
"io/ioutil"
2627
"os"
2728
"path/filepath"
@@ -53,6 +54,11 @@ func TestRender_basic(t *testing.T) {
5354
})
5455
require.NoError(t, err)
5556

57+
filepath.Walk(tempDir, func(path string, info fs.FileInfo, err error) error {
58+
t.Log(path)
59+
return err
60+
})
61+
5662
assert.FileExists(t, filepath.Join(tempDir, "created.txt"))
5763
assert.FileExists(t, filepath.Join(tempDir, "README.md"))
5864
assert.DirExists(t, filepath.Join(tempDir, "alice"))
@@ -96,9 +102,10 @@ func TestRender_gitClone(t *testing.T) {
96102
files, err := internal.CopyTree("test-data/projectA", repoDir)
97103
require.NoError(t, err)
98104

99-
for _, f := range files {
100-
t.Log("+", f)
101-
_, err = w.Add(f)
105+
for _, f := range files.Paths() {
106+
rel, _ := filepath.Rel(repoDir, f)
107+
t.Log("+", rel)
108+
_, err = w.Add(rel)
102109
require.NoError(t, err)
103110
}
104111

0 commit comments

Comments
 (0)