From 07a60c0b59ec459e98913bc2d319e944653af27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20P=C3=A9rez?= Date: Thu, 17 Oct 2024 20:56:10 +0200 Subject: [PATCH] file organization --- LICENSE | 21 ------ README.md | 107 ------------------------------ form.go | 95 --------------------------- functions.go | 71 -------------------- go.mod | 19 ------ go.sum | 30 --------- pages.go | 153 ------------------------------------------- render.go | 182 --------------------------------------------------- 8 files changed, 678 deletions(-) delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 form.go delete mode 100644 functions.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 pages.go delete mode 100644 render.go diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8ce4906..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Pedro Pérez - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 43017bb..0000000 --- a/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# gorender - -Simple y minimalista librería para procesar plantillas utilizando la librería -estándar de Go `html/template`. - -## Características - -- Procesamiento de plantillas utilizando `html/template`. -- Soporte para caché de plantillas. -- Soporte para paginación de elementos como tablas o múltiples blogs. -- Posibilidad de añadir funciones personalizadas a las plantillas. -- Configuración sencilla con opciones por defecto que se pueden sobreescribir. -Inspirado en `Gin`. - -## Instalación - -```bash -go get github.com/zepyrshut/gorender -``` - -## Uso mínimo - -Las plantillas deben tener la siguiente estructura, observa que las páginas a -procesar están dentro de `pages`. Los demás componentes como bases y fragmentos -pueden estar en el directorio raíz o dentro de un directorio. - -Puedes cambiar el nombre del directorio `template` y `pages`. Ejemplo en la -siguiente sección. - -``` -template/ -├── pages/ -│ └── page.html -├── base.html -└── fragment.html -``` - -```go -import ( - "github.com/zepyrshut/gorender" -) - -func main() { - ren := gorender.New() - - // ... - - td := &gorender.TemplateData{} - ren.Template(w, r, "index.html", td) - - // ... -} -``` - -## Personalización - -> Recuerda que si habilitas el caché, no podrás ver los cambios que realices -> durante el desarrollo. - -```go -func dummyFunc() string { - return "dummy" -} - -func main() { - - customFuncs := template.FuncMap{ - "dummyFunc": dummyFunc, - } - - renderOpts := &gorender.Render{ - EnableCache: true, - TemplatesPath: "template/path", - PageTemplatesPath: "template/path/pages", - Functions: customFuncs, - } - - ren := gorender.New(gorender.WithRenderOptions(renderOpts)) - - // ... - - td := &gorender.TemplateData{} - ren.Template(w, r, "index.html", td) - - // ... -} -``` -## Agradecimientos - -- [Protección CSRF justinas/nosurf](https://github.com/justinas/nosurf) -- [Valicación go-playground/validator](https://github.com/go-playground/validator) - -## Descargo de responsabilidad - -Esta librería fue creada para usar las plantillas en mis proyectos privados, es -posible que también solucione tu problema. Sin embargo, no ofrezco ninguna -garantía de que funcione para todos los casos de uso, tenga el máximo -rendimiento o esté libre de errores. - -Si decides integrarla en tu proyecto, te recomiendo que la pruebes para -asegurarte de que cumple con tus expectativas y requisitos. - -Si encuentras problemas o tienes sugerencias de mejora, puedes colocar tus -aportaciones a través de _issues_ o _pull requests_ en el repositorio. Estaré -encantado de ayudarte. - - diff --git a/form.go b/form.go deleted file mode 100644 index 0a2f46e..0000000 --- a/form.go +++ /dev/null @@ -1,95 +0,0 @@ -package gorender - -import ( - "strings" - - spanish "github.com/go-playground/locales/es" - ut "github.com/go-playground/universal-translator" - "github.com/go-playground/validator/v10" - esTranslations "github.com/go-playground/validator/v10/translations/es" -) - -type FormData struct { - HasErrors bool - Errors map[string]string - Values map[string]string -} - -func NewForm() FormData { - return FormData{ - HasErrors: false, - Errors: map[string]string{}, - Values: map[string]string{}, - } -} - -// AddError añade errores a la estructura FormData, es un mapa cuya clave es una -// cadena de carecteres. Hay que tener en cuenta que cuando se hace una -// validación, se llama a esta función cuya clave es el nombre del campo con lo -// cual si hay más de un error de validación se sobreescriben el anterior y sólo -// se muestra el último error. -func (fd *FormData) AddError(field, message string) { - fd.HasErrors = true - fd.Errors[field] = message -} - -func (fd *FormData) AddValue(field, value string) { - fd.Values[field] = value -} - -type ValidationError struct { - Field string - Reason string -} - -func (fd *FormData) ValidateStruct(s interface{}) (map[string]string, error) { - spanishTranslator := spanish.New() - uni := ut.New(spanishTranslator, spanishTranslator) - trans, _ := uni.GetTranslator("es") - validate := validator.New() - _ = esTranslations.RegisterDefaultTranslations(validate, trans) - errors := make(map[string]string) - var validationErrors []ValidationError - - err := validate.Struct(s) - if err != nil { - if _, ok := err.(*validator.InvalidValidationError); ok { - fd.AddError("form-error", "Error de validación de datos.") - return errors, err - } - - for _, err := range err.(validator.ValidationErrors) { - fieldName, _ := trans.T(err.Field()) - message := strings.Replace(err.Translate(trans), err.Field(), fieldName, -1) - - validationErrors = append(validationErrors, ValidationError{ - Field: strings.ToLower(err.Field()), - Reason: correctMessage(message), - }) - } - - for _, err := range validationErrors { - errors[err.Field] = err.Reason - } - - if len(errors) > 0 { - fd.Errors = errors - fd.HasErrors = true - } - - return errors, err - } - - return errors, nil -} - -func correctMessage(s string) string { - s = strings.TrimSpace(s) - runes := []rune(s) - runes[0] = []rune(strings.ToUpper(string(runes[0])))[0] - if runes[len(runes)-1] != '.' { - runes = append(runes, '.') - } - - return string(runes) -} diff --git a/functions.go b/functions.go deleted file mode 100644 index d00def8..0000000 --- a/functions.go +++ /dev/null @@ -1,71 +0,0 @@ -package gorender - -import ( - "bufio" - "fmt" - "os" - "strings" -) - -func or(a, b string) bool { - if a == "" && b == "" { - return false - } - return true -} - -// containsErrors hace una función similar a "{{ with index ... }}" con el -// añadido de que puede pasarle más de un argumento y comprobar si alguno de -// ellos está en el mapa de errores. -// -// Ejemplo: -// -// {{ if containsErrors .FormData.Errors "name" "email" }} -// {{index .FormData.Errors "name" }} -// {{index .FormData.Errors "email" }} -// {{ end }} -func containsErrors(errors map[string]string, names ...string) bool { - for _, name := range names { - if _, ok := errors[name]; ok { - return true - } - } - return false -} - -func loadTranslations(language string) map[string]string { - translations := make(map[string]string) - filePath := fmt.Sprintf("%s.translate", language) - file, err := os.Open(filePath) - if err != nil { - fmt.Println("Error opening translation file:", err) - return translations - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - parts := strings.Split(line, "=") - if len(parts) == 2 { - key := strings.TrimSpace(parts[0]) - value := strings.TrimSpace(parts[1]) - translations[key] = value - } - } - - if err := scanner.Err(); err != nil { - fmt.Println("Error reading translation file:", err) - } - - return translations -} - -func translateKey(key string) string { - translations := loadTranslations("es_ES") - translated := translations[key] - if translated != "" { - return translated - } - return key -} diff --git a/go.mod b/go.mod deleted file mode 100644 index ccdaa6b..0000000 --- a/go.mod +++ /dev/null @@ -1,19 +0,0 @@ -module github.com/zepyrshut/gorender - -go 1.23.0 - -require ( - github.com/go-playground/locales v0.14.1 - github.com/go-playground/universal-translator v0.18.1 - github.com/go-playground/validator/v10 v10.22.0 - github.com/justinas/nosurf v1.1.1 -) - -require ( - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index d6f9aad..0000000 --- a/go.sum +++ /dev/null @@ -1,30 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= -github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pages.go b/pages.go deleted file mode 100644 index 555a5c6..0000000 --- a/pages.go +++ /dev/null @@ -1,153 +0,0 @@ -package gorender - -import ( - "net/http" - "strconv" -) - -// Pages contiene la información de paginación. -type Pages struct { - // totalElements son la cantidad de elementos totales a paginar. Pueden ser - // total de filas o total de páginas de blog. - totalElements int - // showElements muestra la cantidad máxima de elementos a mostrar en una - // página. - showElements int - // currentPage es la página actual, utilizado como ayuda para mostrar la - // página activa. - currentPage int -} - -// Page contiene la información de una página. -type Page struct { - // number es el número de página. - number int - // active es un dato lógico que indica si la página es la actual. - active bool -} - -// NewPages crea un nuevo objeto para paginación. -func NewPages(totalElements, showElements, currentPage int) Pages { - if showElements <= 0 { - showElements = 1 - } - if currentPage <= 0 { - currentPage = 1 - } - p := Pages{totalElements, showElements, currentPage} - if p.currentPage > p.TotalPages() { - p.currentPage = p.TotalPages() - } - - return p -} - -// Limit devuelve la cantidad de elementos máximos a mostrar por página. -func (p *Pages) Limit() int { - return p.showElements -} - -// TotalPages devuelve la cantidad total de páginas. -func (p *Pages) TotalPages() int { - return (p.totalElements + p.showElements - 1) / p.showElements -} - -// IsFirst indica si la página actual es la primera. -func (p *Pages) IsFirst() bool { - return p.currentPage == 1 -} - -// IsLast indica si la página actual es la última. -func (p *Pages) IsLast() bool { - return p.currentPage == p.TotalPages() -} - -// HasPrevious indica si hay una página anterior. -func (p *Pages) HasPrevious() bool { - return p.currentPage > 1 -} - -// HasNext indica si hay una página siguiente. -func (p *Pages) HasNext() bool { - return p.currentPage < p.TotalPages() -} - -// Previous devuelve el número de la página anterior. -func (p *Pages) Previous() int { - return p.currentPage - 1 -} - -// Next devuelve el número de la página siguiente. -func (p *Pages) Next() int { - return p.currentPage + 1 -} - -func (p *Page) NumberOfPage() int { - return p.number -} - -// IsActive indica si la página es la actual. -func (p *Page) IsActive() bool { - return p.active -} - -// Pages devuelve un arreglo de páginas para mostrar en la paginación. El -// parametro pagesShow indica la cantidad de páginas a mostrar, asignable desde -// la plantilla. -func (p *Pages) Pages(pagesShow int) []*Page { - var pages []*Page - startPage := p.currentPage - (pagesShow / 2) - endPage := p.currentPage + (pagesShow/2 - 1) - - if startPage < 1 { - startPage = 1 - endPage = pagesShow - } - - if endPage > p.TotalPages() { - endPage = p.TotalPages() - startPage = p.TotalPages() - pagesShow + 1 - if startPage < 1 { - startPage = 1 - } - } - - for i := startPage; i <= endPage; i++ { - pages = append(pages, &Page{i, i == p.currentPage}) - } - return pages -} - -func PaginateArray[T any](items []T, currentPage, itemsPerPage int) []T { - totalItems := len(items) - - startIndex := (currentPage - 1) * itemsPerPage - endIndex := startIndex + itemsPerPage - - if startIndex > totalItems { - startIndex = totalItems - } - if endIndex > totalItems { - endIndex = totalItems - } - - return items[startIndex:endIndex] -} - -func PaginationParams(r *http.Request) (int, int, int) { - limit := r.FormValue("limit") - if limit == "" { - limit = "50" - } - page := r.FormValue("page") - if page == "" || page == "0" { - page = "1" - } - - limitInt, _ := strconv.Atoi(limit) - pageInt, _ := strconv.Atoi(page) - offset := (pageInt - 1) * limitInt - actualPage := offset/limitInt + 1 - - return limitInt, offset, actualPage -} diff --git a/render.go b/render.go deleted file mode 100644 index 9fb9416..0000000 --- a/render.go +++ /dev/null @@ -1,182 +0,0 @@ -package gorender - -import ( - "bytes" - "errors" - "html/template" - "io/fs" - "log/slog" - "net/http" - "path/filepath" - - "github.com/justinas/nosurf" -) - -type TemplateCache map[string]*template.Template - -type Render struct { - EnableCache bool - // TemplatesPath es la ruta donde se encuentran las plantillas de la - // aplicación, pueden ser bases, fragmentos o ambos. Lo que quieras. - TemplatesPath string - // PageTemplatesPath es la ruta donde se encuentran las plantillas de las - // páginas de la aplicación. Estas son las que van a ser llamadas para - // mostrar en pantalla. - PageTemplatesPath string - TemplateCache TemplateCache - Functions template.FuncMap -} - -type OptionFunc func(*Render) - -type TemplateData struct { - Data map[string]interface{} - // SessionData contiene los datos de la sesión del usuario. - SessionData interface{} - // FeedbackData tiene como función mostrar los mensajes habituales de - // información, advertencia, éxito y error. No va implícitamente relacionado - // con los errores de validación de formularios pero pueden ser usados para - // ello. - FeedbackData map[string]string - // FormData es una estructura que contiene los errores de validación de los - // formularios además de los valores que se han introducido en los campos. - FormData FormData - CSRFToken string - Page Pages -} - -func WithRenderOptions(opts *Render) OptionFunc { - return func(re *Render) { - re.TemplatesPath = opts.TemplatesPath - re.PageTemplatesPath = opts.PageTemplatesPath - - if opts.Functions != nil { - for k, v := range opts.Functions { - re.Functions[k] = v - } - } - - if opts.EnableCache { - re.EnableCache = opts.EnableCache - re.TemplateCache, _ = re.createTemplateCache() - } - } -} - -func New(opts ...OptionFunc) *Render { - functions := template.FuncMap{ - "translateKey": translateKey, - "or": or, - "containsErrors": containsErrors, - } - - config := &Render{ - EnableCache: false, - TemplatesPath: "templates", - PageTemplatesPath: "templates/pages", - TemplateCache: TemplateCache{}, - Functions: functions, - } - - return config.apply(opts...) -} - -func (re *Render) apply(opts ...OptionFunc) *Render { - for _, opt := range opts { - opt(re) - } - - return re -} - -func addDefaultData(td *TemplateData, r *http.Request) *TemplateData { - td.CSRFToken = nosurf.Token(r) - return td -} - -func (re *Render) Template(w http.ResponseWriter, r *http.Request, tmpl string, td *TemplateData) error { - var tc TemplateCache - var err error - - if re.EnableCache { - tc = re.TemplateCache - } else { - tc, err = re.createTemplateCache() - if err != nil { - slog.Error("error creating template cache:", "error", err) - return err - } - } - - t, ok := tc[tmpl] - if !ok { - return errors.New("can't get template from cache") - } - - buf := new(bytes.Buffer) - td = addDefaultData(td, r) - err = t.Execute(buf, td) - if err != nil { - slog.Error("error executing template:", "error", err) - return err - } - - _, err = buf.WriteTo(w) - if err != nil { - slog.Error("error writing template to browser:", "error", err) - } - - return nil -} - -func findHTMLFiles(root string) ([]string, error) { - var files []string - - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if !d.IsDir() && filepath.Ext(path) == ".html" { - files = append(files, path) - } - - return nil - }) - - if err != nil { - return nil, err - } - - return files, nil -} - -func (re *Render) createTemplateCache() (TemplateCache, error) { - myCache := TemplateCache{} - - pagesTemplates, err := findHTMLFiles(re.PageTemplatesPath) - if err != nil { - return myCache, err - } - - files, err := findHTMLFiles(re.TemplatesPath) - if err != nil { - return myCache, err - } - - for function := range re.Functions { - slog.Info("function found", "function", function) - } - - for _, file := range pagesTemplates { - name := filepath.Base(file) - ts, err := template.New(name).Funcs(re.Functions).ParseFiles(append(files, file)...) - if err != nil { - return myCache, err - } - - myCache[name] = ts - } - - return myCache, nil -}