// Command rhiza-go is a starter application demonstrating the rhiza-go template structure.
//
// This is example code — replace it with your own application logic.
// It shows how to use the standard Go project layout:
// - cmd/ for application entry points
// - pkg/ for public library packages
// - internal/ for private internal packages
package main
import (
"fmt"
"io"
"os"
"github.com/jebel-quant/rhiza-go/pkg/config"
)
func main() {
if err := run(os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
// run executes the application logic, writing output to w.
func run(w io.Writer) error {
if _, err := fmt.Fprintln(w, "Rhiza-Go - Template System for Go Projects"); err != nil {
return err
}
if _, err := fmt.Fprintln(w, "============================================="); err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("loading configuration: %w", err)
}
if _, err := fmt.Fprintf(w, "Version: %s\n", cfg.Version); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "Go Version: %s\n", cfg.GoVersion); err != nil {
return err
}
return nil
}
// Package utils provides utility functions for the rhiza-go application.
//
// This is starter code — replace or extend it with your own utility functions.
package utils
import (
"path/filepath"
)
// SanitizePath normalizes a file path using filepath.Clean.
//
// Note: This does NOT prevent directory traversal attacks. The function preserves
// relative path components (like ../../../etc/passwd). Callers must validate the
// result is within allowed directories if security is a concern.
//
// For secure path handling, use filepath.Join with a base directory and verify
// the result with filepath.Rel or strings.HasPrefix to ensure it stays within bounds.
func SanitizePath(path string) string {
// Clean the path to normalize it
return filepath.Clean(path)
}
// Contains checks if a string slice contains a specific string
func Contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// Package config handles loading and managing configuration for the rhiza-go application.
//
// This is starter code — replace or extend it with your own configuration logic.
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
// Config represents the rhiza-go configuration
type Config struct {
Version string `yaml:"version"`
GoVersion string `yaml:"go_version"`
}
// Load returns a Config with default values and, if present, overrides GoVersion
// by reading it from a .go-version file in the current directory.
func Load() (*Config, error) {
cfg := &Config{
Version: "0.1.0",
GoVersion: "1.23",
}
// Try to read .go-version file
goVersionPath := filepath.Join(".", ".go-version")
// #nosec G304 -- Reading version file from project root is safe
if data, err := os.ReadFile(goVersionPath); err == nil {
cfg.GoVersion = strings.TrimSpace(string(data))
}
return cfg, nil
}
// Template represents a rhiza template configuration
type Template struct {
Repository string `yaml:"repository"`
Ref string `yaml:"ref"`
Include []string `yaml:"include"`
Exclude []string `yaml:"exclude"`
}
// LoadTemplate reads template configuration from .rhiza/template.yml
func LoadTemplate(path string) (*Template, error) {
// Validate and sanitize the path to prevent path traversal attacks
cleanPath := filepath.Clean(path)
// For relative paths, ensure they don't escape the current directory
if !filepath.IsAbs(cleanPath) && !filepath.IsLocal(cleanPath) {
return nil, fmt.Errorf("invalid path: path escapes local directory")
}
data, err := os.ReadFile(cleanPath)
if err != nil {
return nil, fmt.Errorf("failed to read template file: %w", err)
}
var tmpl Template
if err := yaml.Unmarshal(data, &tmpl); err != nil {
return nil, fmt.Errorf("failed to parse template YAML: %w", err)
}
return &tmpl, nil
}