Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions cmd/templit/templit.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//nolint:gochecknoglobals,gochecknoinits
package main

import (
"encoding/json"
"errors"
"fmt"
"html/template"
"maps"
Expand All @@ -11,6 +13,8 @@ import (
"github.com/spf13/cobra"
)

var ErrMissingToken = errors.New("embed and import functions requires a GitHub token")

// flagValues stores the values of command-line flags
var flagValues = struct {
token string
Expand All @@ -23,7 +27,8 @@ var templitCmd = &cobra.Command{
Use: "templit <command>",
Short: "A CLI tool for rendering templates from remote repositories",
Long: `templit is a CLI tool for rendering templates from remote repositories.`,
Run: func(cmd *cobra.Command, args []string) {
Args: cobra.MinimumNArgs(3), //nolint:mnd
Run: func(cmd *cobra.Command, _ []string) {
if err := cmd.Help(); err != nil {
fmt.Fprintln(os.Stderr, err)
}
Expand All @@ -35,15 +40,7 @@ var renderCmd = &cobra.Command{
Use: "render <inputPath> <outputPath> <jsonData>",
Short: "A CLI tool for rendering templates from remote repositories",
Long: `generate is for rendering templates.`,
Run: func(cmd *cobra.Command, args []string) {
// Check for the correct number of command-line arguments
if len(args) < 3 {
if err := cmd.Help(); err != nil {
fmt.Fprintln(os.Stderr, err)
}
return
}

Run: func(_ *cobra.Command, args []string) {
if flagValues.token == "" {
flagValues.token = os.Getenv("GIT_TOKEN")
}
Expand All @@ -57,20 +54,21 @@ var renderCmd = &cobra.Command{
// Parse the JSON data from the command-line argument
if err := json.Unmarshal([]byte(inputData), &values); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing JSON data: %s\n", err)

return
}

// executor is the template executor
executor := templit.NewExecutor(templit.NewDefaultGitClient(flagValues.branch, flagValues.token))

// funcMap defines the custom functions that can be used in templates
var funcMap = template.FuncMap{
funcMap := template.FuncMap{
// return an error for the embed and import functions if no GitHub token is provided
"embed": func(repoAndPath string, ctx interface{}) (string, error) {
return "", fmt.Errorf("embed function requires a GitHub token")
"embed": func(string, interface{}) (string, error) {
return "", ErrMissingToken
},
"import": func(repoAndPath string, destPath string, ctx interface{}) (string, error) {
return "", fmt.Errorf("import function requires a GitHub token")
"import": func(string, string, interface{}) (string, error) {
return "", ErrMissingToken
},
}

Expand All @@ -80,14 +78,15 @@ var renderCmd = &cobra.Command{
}

// Copy the default function map from the templit package
maps.Copy(funcMap, templit.DefaultFuncMap)
executor.Funcs(templit.DefaultFuncMap())
executor.Funcs(funcMap)

// If a remote repository is specified, process the template and write it to the output directory
if flagValues.remote != "" {
importParts, err := templit.ParseDepURL(flagValues.remote)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing remote: %s\n", err)

return
}

Expand All @@ -96,11 +95,12 @@ var renderCmd = &cobra.Command{
if _, err := executor.ImportFunc(outputPath)(importParts.String(), "./", values); err != nil {
fmt.Fprintf(os.Stderr, "Error processing template: %s\n", err)
}

return
}

// Copy the default function map from the templit package
maps.Copy(funcMap, templit.DefaultFuncMap)
maps.Copy(funcMap, templit.DefaultFuncMap())
executor.Funcs(funcMap)

// Process the templates in the input directory and write them to the output directory
Expand Down
28 changes: 18 additions & 10 deletions dep.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package templit

import (
"errors"
"fmt"
"net/url"
"strings"
Expand All @@ -20,6 +21,9 @@ type DepInfo struct {
Tag string
}

// ErrInvalidPath is returned when the path is invalid.
var ErrInvalidPath = errors.New("invalid path")

// String returns the string representation of a DepInfo.
func (d DepInfo) String() string {
var builder strings.Builder
Expand Down Expand Up @@ -56,7 +60,7 @@ func ParseDepURL(rawURL string) (*DepInfo, error) {

u, err := url.Parse(rawURL)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse URL: %w", err)
}

// Extract the tag if it exists before splitting the path
Expand All @@ -68,14 +72,15 @@ func ParseDepURL(rawURL string) (*DepInfo, error) {

// Split path into components
pathParts := strings.Split(fullPath, "/")
if len(pathParts) < 2 {
return nil, fmt.Errorf("invalid path format in embed URL")
minParts := 2
if len(pathParts) < minParts {
return nil, ErrInvalidPath
}

owner := pathParts[0]
repo := pathParts[1]
path := ""
if len(pathParts) > 2 {
if len(pathParts) > minParts {
path = strings.Join(pathParts[2:], "/")
}

Expand All @@ -101,6 +106,7 @@ func splitAtSign(s string) (string, string) {
if len(parts) > 1 {
return parts[0], parts[1]
}

return parts[0], ""
}

Expand All @@ -109,9 +115,12 @@ func extractBlockAndTag(fragment string) (string, string) {
parts := strings.Split(fragment, "@")
if len(parts) > 1 {
return parts[0], parts[1]
} else if len(parts) == 1 {
}

if len(parts) == 1 {
return parts[0], ""
}

return "", ""
}

Expand Down Expand Up @@ -147,9 +156,9 @@ func (d *DefaultGitClient) DefaultBranch() string {

// 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)
repoURL := host + "/" + owner + "/" + repo + ".git"
if !strings.HasPrefix(repoURL, "https://") {
repoURL = fmt.Sprintf("https://%s", repoURL)
repoURL = "https://" + repoURL
}

var auth *http.BasicAuth
Expand All @@ -164,7 +173,6 @@ func (d *DefaultGitClient) Clone(host, owner, repo, dest string) error {
URL: repoURL,
Auth: auth,
})

if err != nil {
return fmt.Errorf("failed to clone repo %s: %w", repoURL, err)
}
Expand All @@ -176,12 +184,12 @@ func (d *DefaultGitClient) Clone(host, owner, repo, dest string) error {
func (d *DefaultGitClient) Checkout(path, ref string) error {
r, err := git.PlainOpen(path)
if err != nil {
return err
return fmt.Errorf("failed to open repository: %w", err)
}

w, err := r.Worktree()
if err != nil {
return err
return fmt.Errorf("failed to get worktree: %w", err)
}

// Try to checkout branch
Expand Down
3 changes: 2 additions & 1 deletion docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,14 @@ func (app *App) renderTemplate(this js.Value, args []js.Value) interface{} {
jsonStr := args[1].String()

executor := templit.NewExecutor(templit.NewDefaultGitClient("main", ""))
funcs := templit.DefaultFuncMap
funcs := templit.DefaultFuncMap()
funcs["embed"] = func(name string) string {
return fmt.Sprintf("Embed (not enabled): %s", name)
}
funcs["import"] = func(name string) string {
return fmt.Sprintf("Import (not enabled): %s", name)
}

executor.Template.Funcs(funcs)

var data interface{}
Expand Down
Binary file modified docs/main.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion embed_func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestEmbedFunc(t *testing.T) {
repoAndPath: "invalidpath",
ctx: nil,
expectedText: "",
expectedError: fmt.Errorf("invalid path format in embed URL"),
expectedError: fmt.Errorf("invalid path"),
},
{
name: "Invalid repo",
Expand Down
59 changes: 31 additions & 28 deletions funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,43 @@ import (
)

// DefaultFuncMap is the default function map for templates.
var DefaultFuncMap = template.FuncMap{
"lower": strings.ToLower,
"upper": strings.ToUpper,
"trim": strings.TrimSpace,
"split": strings.Split,
"join": strings.Join,
"replace": strings.ReplaceAll,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"trimPrefix": strings.TrimPrefix,
"trimSuffix": strings.TrimSuffix,
"trimSpace": strings.TrimSpace,
"trimLeft": strings.TrimLeft,
"trimRight": strings.TrimRight,
"count": strings.Count,
"repeat": strings.Repeat,
"equalFold": strings.EqualFold,
"splitN": strings.SplitN,
"splitAfter": strings.SplitAfter,
"splitAfterN": strings.SplitAfterN,
"fields": strings.Fields,
"toTitle": strings.ToTitle,
"toSnakeCase": ToSnakeCase,
"toCamelCase": ToCamelCase,
"toKebabCase": ToKebabCase,
"toPascalCase": ToPascalCase,
"default": defaultVal,
func DefaultFuncMap() template.FuncMap {
return template.FuncMap{
"lower": strings.ToLower,
"upper": strings.ToUpper,
"trim": strings.TrimSpace,
"split": strings.Split,
"join": strings.Join,
"replace": strings.ReplaceAll,
"contains": strings.Contains,
"has_prefix": strings.HasPrefix,
"has_suffix": strings.HasSuffix,
"trim_prefix": strings.TrimPrefix,
"trim_suffix": strings.TrimSuffix,
"trim_space": strings.TrimSpace,
"trim_left": strings.TrimLeft,
"trim_right": strings.TrimRight,
"count": strings.Count,
"repeat": strings.Repeat,
"equal_fold": strings.EqualFold,
"split_n": strings.SplitN,
"split_after": strings.SplitAfter,
"split_after_n": strings.SplitAfterN,
"fields": strings.Fields,
"title_case": strings.ToTitle,
"snake_case": ToSnakeCase,
"camel_case": ToCamelCase,
"kebab_case": ToKebabCase,
"pascal_case": ToPascalCase,
"default": defaultVal,
}
}

// defaultVal returns defaultValue if value is nil, otherwise value.
func defaultVal(value, defaultValue interface{}) interface{} {
if value == nil {
return defaultValue
}

return value
}
9 changes: 1 addition & 8 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
Expand All @@ -25,13 +20,11 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
Expand Down
8 changes: 6 additions & 2 deletions import_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,23 @@ func (e *Executor) ImportFunc(outputDir string) func(repoAndTag, destPath string

// check if path is a file
if info, err := os.Stat(sourcePath); err == nil && !info.IsDir() {
if os.IsNotExist(err) {
return "", fmt.Errorf("file does not exist %s, %w", sourcePath, os.ErrNotExist)
}
// parse the file
if err := e.ParsePath(filepath.Dir(sourcePath)); err != nil {
return "", fmt.Errorf("failed to create executor: %w", err)
}

// render the file
string, err := e.Render(sourcePath, data)
output, err := e.Render(sourcePath, data)
if err != nil {
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 {
const perms = os.FileMode(0o644)
if err := os.WriteFile(filepath.Join(outputPath, filepath.Base(depInfo.Path)), []byte(output), perms); err != nil {
return "", fmt.Errorf("failed to write file: %w", err)
}

Expand Down
4 changes: 2 additions & 2 deletions import_func_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package templit_test

import (
"errors"
"fmt"
"os"
"strings"
Expand All @@ -11,7 +12,6 @@ import (

// TestImportFunc tests the ImportFunc function.
func TestImportFunc(t *testing.T) {

tests := []struct {
name string
repoAndTag string
Expand Down Expand Up @@ -46,7 +46,7 @@ func TestImportFunc(t *testing.T) {
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() {
if tt.expectedError == nil || errors.Is(err, tt.expectedError) {
t.Fatalf("expected error %v, got %v", tt.expectedError, err)
}
return
Expand Down
Loading
Loading