Skip to content

Commit

Permalink
fix embed, default git branch and git client sharing
Browse files Browse the repository at this point in the history
- Fix embed to properly checkout ref
- Fix import to properly output errors
- Pass git client to executor for easier reuse
- Allow seting default git branch
  • Loading branch information
euforic committed Sep 13, 2023
1 parent f3a29f1 commit 2845773
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 73 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ type DefaultGitClient struct {
### func NewDefaultGitClient

```go
func NewDefaultGitClient(token string) *DefaultGitClient
func NewDefaultGitClient(defaultBranch, token string) *DefaultGitClient
```

NewDefaultGitClient creates a new DefaultGitClient with the given token.
NewDefaultGitClient creates a new DefaultGitClient with the optional defaultBranch and token.

<a name="DefaultGitClient.Checkout"></a>
### func (*DefaultGitClient) Checkout
Expand Down Expand Up @@ -184,7 +184,7 @@ type Executor struct {
### func NewExecutor

```go
func NewExecutor() *Executor
func NewExecutor(client GitClient) *Executor
```

New returns a new Executor
Expand All @@ -193,7 +193,7 @@ New returns a new Executor
### func (*Executor) EmbedFunc

```go
func (e *Executor) EmbedFunc(client GitClient) func(remotePath string, data interface{}) (string, error)
func (e *Executor) EmbedFunc() func(remotePath string, data interface{}) (string, error)
```

EmbedFunc returns a template function that can be used to process and embed a template from a remote git repository. EmbedFunc allows embedding content from a remote repository directly into a Go template.
Expand All @@ -219,7 +219,7 @@ Placeholders:
### func (*Executor) ImportFunc
```go
func (e *Executor) ImportFunc(client GitClient) func(repoAndTag, destPath string, data interface{}) (string, error)
func (e *Executor) ImportFunc() func(repoAndTag, destPath string, data interface{}) (string, error)
```

ImportFunc returns a function that can be used as a template function to import and process a template from a remote git repository. ImportFunc allows embedding content from a remote repository into a Go template.
Expand Down Expand Up @@ -285,5 +285,6 @@ GitClient is an interface that abstracts Git operations.
type GitClient interface {
Clone(host, owner, repo, dest string) error
Checkout(path, branch string) error
DefaultBranch() string
}
```
19 changes: 15 additions & 4 deletions dep.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,32 @@ func extractBlockAndTag(fragment string) (string, string) {
type GitClient interface {
Clone(host, owner, repo, dest string) error
Checkout(path, branch string) error
DefaultBranch() string
}

// DefaultGitClient provides a default implementation for the GitClient interface.
type DefaultGitClient struct {
Token string
BaseURL string
Token string
defaultBranch string
}

// NewDefaultGitClient creates a new DefaultGitClient with the given token.
func NewDefaultGitClient(token string) *DefaultGitClient {
func NewDefaultGitClient(defaultBranch string, token string) *DefaultGitClient {
if defaultBranch == "" {
defaultBranch = "main"
}

return &DefaultGitClient{
Token: token,
Token: token,
defaultBranch: defaultBranch,
}
}

// DefaultBranch returns the default branch name.
func (d *DefaultGitClient) DefaultBranch() string {
return d.defaultBranch
}

// Clone clones a Git repository to the given destination.
func (d *DefaultGitClient) Clone(host, owner, repo, dest string) error {
repoURL := fmt.Sprintf("%s/%s/%s.git", host, owner, repo)
Expand Down
63 changes: 33 additions & 30 deletions embed_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,43 +25,46 @@ import (
// - `<path>`: Path to the desired file or directory within the repository.
// - `<block>`: Specific template block name.
// - `<tag_or_hash_or_branch>`: Specific Git reference (tag, commit hash, or branch name).
func (e *Executor) EmbedFunc(client GitClient) func(remotePath string, data interface{}) (string, error) {
return func(remotePath string, data interface{}) (string, error) {
embedInfo, err := ParseDepURL(remotePath)
if err != nil {
return "", err
}
func (e *Executor) EmbedFunc(remotePath string, data interface{}) (string, error) {
const tempDirPrefix = "templit_clone_"

const tempDirPrefix = "templit_clone_"
tempDir, err := os.MkdirTemp("", tempDirPrefix)
if err != nil {
return "", fmt.Errorf("failed to create temp dir: %w", err)
}
defer os.RemoveAll(tempDir) // Cleanup
depInfo, err := ParseDepURL(remotePath)
if err != nil {
return "", err
}

err = client.Clone(embedInfo.Host, embedInfo.Owner, embedInfo.Repo, tempDir)
if err != nil {
return "", fmt.Errorf("failed to clone repo: %w", err)
}
if depInfo.Tag == "" {
depInfo.Tag = e.git.DefaultBranch()
}

if embedInfo.Tag != "main" {
err = client.Checkout(tempDir, embedInfo.Tag)
if err != nil {
return "", fmt.Errorf("failed to checkout branch: %w", err)
}
}
tempDir, err := os.MkdirTemp("", tempDirPrefix)
if err != nil {
return "", fmt.Errorf("failed to create temp dir: %w", err)
}
defer os.RemoveAll(tempDir) // Cleanup

// templatePath is the path to the template file or directory
templatePath := path.Join(tempDir, embedInfo.Path)
err = e.git.Clone(depInfo.Host, depInfo.Owner, depInfo.Repo, tempDir)
if err != nil {
return "", fmt.Errorf("failed to clone repo: %w", err)
}

if err := e.ParsePath(filepath.Dir(templatePath)); err != nil {
return "", fmt.Errorf("failed to create executor: %w", err)
if depInfo.Tag != "" && depInfo.Tag != e.git.DefaultBranch() {
err = e.git.Checkout(tempDir, depInfo.Tag)
if err != nil {
return "", fmt.Errorf("failed to checkout branch: %w", err)
}
}

if embedInfo.Block != "" {
return e.Render(embedInfo.Block, data)
}
// templatePath is the path to the template file or directory
templatePath := path.Join(tempDir, depInfo.Path)

if err := e.ParsePath(filepath.Dir(templatePath)); err != nil {
return "", fmt.Errorf("failed to create executor: %w", err)
}

return e.Render(path.Join(tempDir, embedInfo.Path), data)
if depInfo.Block != "" {
return e.Render(depInfo.Block, data)
}

return e.Render(path.Join(tempDir, depInfo.Path), data)
}
5 changes: 2 additions & 3 deletions embed_func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ func TestEmbedFunc(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
executor := templit.NewExecutor()
fn := executor.EmbedFunc(client)
result, err := fn(tt.repoAndPath, tt.ctx)
executor := templit.NewExecutor(client)
result, err := executor.EmbedFunc(tt.repoAndPath, tt.ctx)
if err != nil {
if tt.expectedError == nil || err.Error() != tt.expectedError.Error() {
t.Fatalf("expected error %v, got %v", tt.expectedError, err)
Expand Down
34 changes: 15 additions & 19 deletions import_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,29 @@ import (
// - `<repo>`: Repository name.
// - `<path>`: Path to the desired file or directory within the repository.
// - `<tag_or_hash_or_branch>`: Specific Git reference (tag, commit hash, or branch name).
func (e *Executor) ImportFunc(client GitClient, outputDir string) func(repoAndTag, destPath string, data interface{}) error {
return func(repoAndTag, destPath string, data interface{}) error {
func (e *Executor) ImportFunc(outputDir string) func(repoAndTag, destPath string, data interface{}) (string, error) {
return func(repoAndTag, destPath string, data interface{}) (string, error) {
const tempDirPrefix = "temp_clone_"

depInfo, err := ParseDepURL(repoAndTag)
if err != nil {
return fmt.Errorf("failed to parse embed URL: %w", err)
}

if depInfo.Tag == "" {
depInfo.Tag = "main"
return "", fmt.Errorf("failed to parse embed URL: %w", err)
}

tempDir, err := os.MkdirTemp("", tempDirPrefix)
if err != nil {
return fmt.Errorf("failed to create temp dir: %w", err)
return "", fmt.Errorf("failed to create temp dir: %w", err)
}
defer os.RemoveAll(tempDir) // Cleanup

if err := client.Clone(depInfo.Host, depInfo.Owner, depInfo.Repo, tempDir); err != nil {
return fmt.Errorf("failed to clone repo: %w", err)
if err := e.git.Clone(depInfo.Host, depInfo.Owner, depInfo.Repo, tempDir); err != nil {
return "", fmt.Errorf("failed to clone repo: %w", err)
}

if depInfo.Tag != "main" {
err = client.Checkout(tempDir, depInfo.Tag)
if depInfo.Tag != "" && depInfo.Tag != e.git.DefaultBranch() {
err = e.git.Checkout(tempDir, depInfo.Tag)
if err != nil {
return fmt.Errorf("failed to checkout branch: %w", err)
return "", fmt.Errorf("failed to checkout ref %s: %w", depInfo.Tag, err)
}
}

Expand All @@ -60,27 +56,27 @@ func (e *Executor) ImportFunc(client GitClient, outputDir string) func(repoAndTa
if info, err := os.Stat(sourcePath); err == nil && !info.IsDir() {
// parse the file
if err := e.ParsePath(filepath.Dir(sourcePath)); err != nil {
return fmt.Errorf("failed to create executor: %w", err)
return "", fmt.Errorf("failed to create executor: %w", err)
}

// render the file
string, err := e.Render(sourcePath, data)
if err != nil {
return fmt.Errorf("failed to render template: %w", err)
return "", fmt.Errorf("failed to render template: %w", err)
}

// write the file
if err := os.WriteFile(filepath.Join(outputPath, filepath.Base(depInfo.Path)), []byte(string), 0644); err != nil {
return fmt.Errorf("failed to write file: %w", err)
return "", fmt.Errorf("failed to write file: %w", err)
}

return nil
return "", nil
}

if err := e.WalkAndProcessDir(sourcePath, outputPath, data); err != nil {
return fmt.Errorf("failed to process template: %w", err)
return "", fmt.Errorf("failed to process template: %w", err)
}

return nil
return "", nil
}
}
15 changes: 9 additions & 6 deletions import_func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

// TestImportFunc tests the ImportFunc function.
func TestImportFunc(t *testing.T) {
client := &MockGitClient{}

tests := []struct {
name string
Expand All @@ -32,17 +31,21 @@ func TestImportFunc(t *testing.T) {
},
}

// Mocked GitClient
client := &MockGitClient{}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
destPath, err := os.MkdirTemp("", "test_output_"+strings.ReplaceAll(strings.ToLower(tt.name), " ", "_"))
if err != nil {
t.Fatal(fmt.Errorf("failed to create temp dir: %w", err))
}
fmt.Println(destPath)
//defer os.RemoveAll(destPath) // Cleanup
executor := templit.NewExecutor()
fn := executor.ImportFunc(client, destPath)
if err := fn(tt.repoAndTag, "./", tt.data); err != nil {

defer os.RemoveAll(destPath) // Cleanup

executor := templit.NewExecutor(client)
fn := executor.ImportFunc(destPath)
if _, err := fn(tt.repoAndTag, "./", tt.data); err != nil {
if tt.expectedError == nil || err.Error() != tt.expectedError.Error() {
t.Fatalf("expected error %v, got %v", tt.expectedError, err)
}
Expand Down
4 changes: 3 additions & 1 deletion templit.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import (
// Executor is a wrapper around the template.Template type
type Executor struct {
*template.Template
git GitClient
}

// New returns a new Executor
func NewExecutor() *Executor {
func NewExecutor(gitClient GitClient) *Executor {
return &Executor{
Template: template.New("main").Funcs(DefaultFuncMap),
git: gitClient,
}
}

Expand Down
4 changes: 2 additions & 2 deletions templit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestNewExecutor(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
executor := templit.NewExecutor()
executor := templit.NewExecutor(nil)
executor.Funcs(tt.funcMap)
err := executor.ParsePath(tt.input)
if (err != nil) != tt.err {
Expand Down Expand Up @@ -73,7 +73,7 @@ func TestRender(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
executor := templit.NewExecutor()
executor := templit.NewExecutor(nil)
err := executor.ParsePath(tt.inputPath)
if err != nil {
t.Fatalf("failed to create executor: %v", err)
Expand Down
8 changes: 8 additions & 0 deletions test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ import (
"github.com/google/go-cmp/cmp"
)

// MockGitClient is a mock implementation of the GitClient interface.
type MockGitClient struct{}

// DefaultBranch returns the default branch name.
func (m *MockGitClient) DefaultBranch() string {
return "main"
}

// Clone clones a repository to the given destination.
func (m *MockGitClient) Clone(host, owner, repo, dest string) error {
src := filepath.Join(host, owner, repo)
return copyDir(src, dest)
}

// Checkout checks out a branch.
func (m *MockGitClient) Checkout(path, branch string) error {
// Mocked function, doesn't need to do anything for this test.
return nil
Expand Down
6 changes: 3 additions & 3 deletions walk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestExecutor_StringRender(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := templit.NewExecutor()
e := templit.NewExecutor(nil)
got, err := e.StringRender(tt.tmpl, tt.data)
if (err != nil) != tt.wantErr {
t.Fatalf("RenderTemplate() error = %v, wantErr %v", err, tt.wantErr)
Expand Down Expand Up @@ -93,9 +93,9 @@ func TestWalkAndProcessDir(t *testing.T) {
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
//defer os.RemoveAll(tempOutputDir) // Cleanup
defer os.RemoveAll(tempOutputDir) // Cleanup

executor := templit.NewExecutor()
executor := templit.NewExecutor(nil)
err = executor.WalkAndProcessDir(tt.inputDir, tempOutputDir, tt.data)
if err != nil {
if tt.expectedError == nil || err.Error() != tt.expectedError.Error() {
Expand Down

0 comments on commit 2845773

Please sign in to comment.