mirror of
https://github.com/kataras/iris.git
synced 2026-01-02 17:57:11 +00:00
Version 3.0.0-beta cleaned
This commit is contained in:
76
render/template/engine/amber/amber.go
Normal file
76
render/template/engine/amber/amber.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package amber
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/eknkc/amber"
|
||||
"github.com/kataras/iris/config"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
Config *config.Template
|
||||
templateCache map[string]*template.Template
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func New(cfg config.Template) *Engine {
|
||||
return &Engine{Config: &cfg}
|
||||
}
|
||||
|
||||
func (e *Engine) BuildTemplates() error {
|
||||
opt := amber.DirOptions{}
|
||||
opt.Recursive = true
|
||||
if e.Config.Extensions == nil || len(e.Config.Extensions) == 0 {
|
||||
e.Config.Extensions = []string{".html"}
|
||||
}
|
||||
|
||||
// prepare the global amber funcs
|
||||
funcs := template.FuncMap{}
|
||||
for k, v := range amber.FuncMap { // add the amber's default funcs
|
||||
funcs[k] = v
|
||||
}
|
||||
if e.Config.Amber.Funcs != nil { // add the config's funcs
|
||||
for k, v := range e.Config.Amber.Funcs {
|
||||
funcs[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
amber.FuncMap = funcs //set the funcs
|
||||
|
||||
opt.Ext = e.Config.Extensions[0]
|
||||
templates, err := amber.CompileDir(e.Config.Directory, opt, amber.DefaultOptions) // this returns the map with stripped extension, we want extension so we copy the map
|
||||
if err == nil {
|
||||
e.templateCache = make(map[string]*template.Template)
|
||||
for k, v := range templates {
|
||||
name := filepath.ToSlash(k + opt.Ext)
|
||||
e.templateCache[name] = v
|
||||
delete(templates, k)
|
||||
}
|
||||
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
func (e *Engine) fromCache(relativeName string) *template.Template {
|
||||
e.mu.Lock()
|
||||
tmpl, ok := e.templateCache[relativeName]
|
||||
if ok {
|
||||
e.mu.Unlock()
|
||||
return tmpl
|
||||
}
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
|
||||
if tmpl := e.fromCache(name); tmpl != nil {
|
||||
return tmpl.ExecuteTemplate(out, name, binding)
|
||||
}
|
||||
|
||||
return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, e.Config.Directory)
|
||||
}
|
||||
227
render/template/engine/html/html.go
Normal file
227
render/template/engine/html/html.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/config"
|
||||
)
|
||||
|
||||
type (
|
||||
Engine struct {
|
||||
Config *config.Template
|
||||
Templates *template.Template
|
||||
// Middleware
|
||||
// Note:
|
||||
// I see that many template engines returns html/template as result
|
||||
// so I decided that the HTMLTemplate should accept a middleware for the final string content will be parsed to the main *html/template.Template
|
||||
// for example user of this property is Jade, currently
|
||||
Middleware func(string, string) (string, error)
|
||||
}
|
||||
)
|
||||
|
||||
var emptyFuncs = template.FuncMap{
|
||||
"yield": func() (string, error) {
|
||||
return "", fmt.Errorf("yield was called, yet no layout defined")
|
||||
},
|
||||
"partial": func() (string, error) {
|
||||
return "", fmt.Errorf("block was called, yet no layout defined")
|
||||
},
|
||||
"current": func() (string, error) {
|
||||
return "", nil
|
||||
}, "render": func() (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
// just for test with jade
|
||||
/*"bold": func() (string, error) {
|
||||
return "", nil
|
||||
},*/
|
||||
}
|
||||
|
||||
// New creates and returns the HTMLTemplate template engine
|
||||
func New(c config.Template) *Engine {
|
||||
return &Engine{Config: &c}
|
||||
}
|
||||
|
||||
func (s *Engine) BuildTemplates() error {
|
||||
|
||||
if s.Config.Asset == nil || s.Config.AssetNames == nil {
|
||||
return s.buildFromDir()
|
||||
|
||||
}
|
||||
return s.buildFromAsset()
|
||||
|
||||
}
|
||||
|
||||
func (s *Engine) buildFromDir() error {
|
||||
if s.Config.Directory == "" {
|
||||
return nil //we don't return fill error here(yet)
|
||||
}
|
||||
|
||||
var templateErr error
|
||||
/*var minifier *minify.M
|
||||
if s.Config.Minify {
|
||||
minifier = minify.New()
|
||||
minifier.AddFunc("text/html", htmlMinifier.Minify)
|
||||
} // Note: minifier has bugs, I complety remove this from Iris.
|
||||
*/
|
||||
dir := s.Config.Directory
|
||||
s.Templates = template.New(dir)
|
||||
s.Templates.Delims(s.Config.HTMLTemplate.Left, s.Config.HTMLTemplate.Right)
|
||||
hasMiddleware := s.Middleware != nil
|
||||
// Walk the supplied directory and compile any files that match our extension list.
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(dir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := ""
|
||||
if strings.Index(rel, ".") != -1 {
|
||||
ext = filepath.Ext(rel)
|
||||
}
|
||||
|
||||
for _, extension := range s.Config.Extensions {
|
||||
if ext == extension {
|
||||
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
contents := string(buf)
|
||||
/*if s.Config.Minify {
|
||||
buf, err = minifier.Bytes("text/html", buf)
|
||||
}*/
|
||||
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
|
||||
name := filepath.ToSlash(rel)
|
||||
tmpl := s.Templates.New(name)
|
||||
|
||||
if hasMiddleware {
|
||||
contents, err = s.Middleware(name, contents)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
|
||||
// Add our funcmaps.
|
||||
if s.Config.HTMLTemplate.Funcs != nil {
|
||||
tmpl.Funcs(s.Config.HTMLTemplate.Funcs)
|
||||
}
|
||||
|
||||
tmpl.Funcs(emptyFuncs).Parse(contents)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return templateErr
|
||||
}
|
||||
|
||||
func (s *Engine) buildFromAsset() error {
|
||||
var templateErr error
|
||||
dir := s.Config.Directory
|
||||
s.Templates = template.New(dir)
|
||||
s.Templates.Delims(s.Config.HTMLTemplate.Left, s.Config.HTMLTemplate.Right)
|
||||
|
||||
for _, path := range s.Config.AssetNames() {
|
||||
if !strings.HasPrefix(path, dir) {
|
||||
continue
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(dir, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ext := ""
|
||||
if strings.Index(rel, ".") != -1 {
|
||||
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
|
||||
}
|
||||
|
||||
for _, extension := range s.Config.Extensions {
|
||||
if ext == extension {
|
||||
|
||||
buf, err := s.Config.Asset(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := filepath.ToSlash(rel)
|
||||
tmpl := s.Templates.New(name)
|
||||
|
||||
// Add our funcmaps.
|
||||
//for _, funcs := range s.Config.HTMLTemplate.Funcs {
|
||||
if s.Config.HTMLTemplate.Funcs != nil {
|
||||
tmpl.Funcs(s.Config.HTMLTemplate.Funcs)
|
||||
}
|
||||
|
||||
tmpl.Funcs(emptyFuncs).Parse(string(buf))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return templateErr
|
||||
}
|
||||
|
||||
func (s *Engine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := s.Templates.ExecuteTemplate(buf, name, binding)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func (s *Engine) layoutFuncsFor(name string, binding interface{}) {
|
||||
funcs := template.FuncMap{
|
||||
"yield": func() (template.HTML, error) {
|
||||
buf, err := s.executeTemplateBuf(name, binding)
|
||||
// Return safe HTML here since we are rendering our own template.
|
||||
return template.HTML(buf.String()), err
|
||||
},
|
||||
"current": func() (string, error) {
|
||||
return name, nil
|
||||
},
|
||||
"partial": func(partialName string) (template.HTML, error) {
|
||||
fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
|
||||
if s.Config.HTMLTemplate.RequirePartials || s.Templates.Lookup(fullPartialName) != nil {
|
||||
buf, err := s.executeTemplateBuf(fullPartialName, binding)
|
||||
// Return safe HTML here since we are rendering our own template.
|
||||
return template.HTML(buf.String()), err
|
||||
}
|
||||
return "", nil
|
||||
},
|
||||
"render": func(fullPartialName string) (template.HTML, error) {
|
||||
buf, err := s.executeTemplateBuf(fullPartialName, binding)
|
||||
// Return safe HTML here since we are rendering our own template.
|
||||
return template.HTML(buf.String()), err
|
||||
|
||||
},
|
||||
// just for test with jade
|
||||
/*"bold": func(content string) (template.HTML, error) {
|
||||
return template.HTML("<b>" + content + "</b>"), nil
|
||||
},*/
|
||||
}
|
||||
if tpl := s.Templates.Lookup(name); tpl != nil {
|
||||
tpl.Funcs(funcs)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
|
||||
if layout != "" && layout != config.NoLayout {
|
||||
s.layoutFuncsFor(name, binding)
|
||||
name = layout
|
||||
}
|
||||
return s.Templates.ExecuteTemplate(out, name, binding)
|
||||
}
|
||||
20
render/template/engine/jade/jade.go
Normal file
20
render/template/engine/jade/jade.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package jade
|
||||
|
||||
import (
|
||||
"github.com/Joker/jade"
|
||||
"github.com/kataras/iris/config"
|
||||
"github.com/kataras/iris/render/template/engine/html"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
*html.Engine
|
||||
}
|
||||
|
||||
func New(cfg config.Template) *Engine {
|
||||
|
||||
underline := &Engine{Engine: html.New(cfg)}
|
||||
underline.Middleware = func(relativeName string, fileContents string) (string, error) {
|
||||
return jade.Parse(relativeName, fileContents)
|
||||
}
|
||||
return underline
|
||||
}
|
||||
151
render/template/engine/markdown/markdown.go
Normal file
151
render/template/engine/markdown/markdown.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/kataras/iris/config"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
// Supports RAW markdown only, no context binding or layout, to use dynamic markdown with other template engine use the context.Markdown/MarkdownString
|
||||
type (
|
||||
Engine struct {
|
||||
Config *config.Template
|
||||
templateCache map[string][]byte
|
||||
mu sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
// New creates and returns a Pongo template engine
|
||||
func New(c config.Template) *Engine {
|
||||
return &Engine{Config: &c, templateCache: make(map[string][]byte)}
|
||||
}
|
||||
|
||||
func (e *Engine) BuildTemplates() error {
|
||||
if e.Config.Asset == nil || e.Config.AssetNames == nil {
|
||||
return e.buildFromDir()
|
||||
}
|
||||
return e.buildFromAsset()
|
||||
|
||||
}
|
||||
|
||||
func (e *Engine) buildFromDir() (templateErr error) {
|
||||
if e.Config.Directory == "" {
|
||||
return nil //we don't return fill error here(yet)
|
||||
}
|
||||
dir := e.Config.Directory
|
||||
|
||||
// Walk the supplied directory and compile any files that match our extension list.
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
if info == nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(dir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := ""
|
||||
if strings.Index(rel, ".") != -1 {
|
||||
ext = filepath.Ext(rel)
|
||||
}
|
||||
|
||||
for _, extension := range e.Config.Extensions {
|
||||
if ext == extension {
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
|
||||
buf = blackfriday.MarkdownCommon(buf)
|
||||
if e.Config.Markdown.Sanitize {
|
||||
buf = bluemonday.UGCPolicy().SanitizeBytes(buf)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
name := filepath.ToSlash(rel)
|
||||
e.templateCache[name] = buf
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) buildFromAsset() error {
|
||||
var templateErr error
|
||||
dir := e.Config.Directory
|
||||
for _, path := range e.Config.AssetNames() {
|
||||
if !strings.HasPrefix(path, dir) {
|
||||
continue
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(dir, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ext := ""
|
||||
if strings.Index(rel, ".") != -1 {
|
||||
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
|
||||
}
|
||||
|
||||
for _, extension := range e.Config.Extensions {
|
||||
if ext == extension {
|
||||
|
||||
buf, err := e.Config.Asset(path)
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
b := blackfriday.MarkdownCommon(buf)
|
||||
if e.Config.Markdown.Sanitize {
|
||||
b = bluemonday.UGCPolicy().SanitizeBytes(b)
|
||||
}
|
||||
name := filepath.ToSlash(rel)
|
||||
e.templateCache[name] = b
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return templateErr
|
||||
}
|
||||
|
||||
func (e *Engine) fromCache(relativeName string) []byte {
|
||||
e.mu.Lock()
|
||||
|
||||
tmpl, ok := e.templateCache[relativeName]
|
||||
|
||||
if ok {
|
||||
e.mu.Unlock() // defer is slow
|
||||
return tmpl
|
||||
}
|
||||
e.mu.Unlock() // defer is slow
|
||||
return nil
|
||||
}
|
||||
|
||||
// layout here is unnesecery
|
||||
func (e *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
|
||||
if tmpl := e.fromCache(name); tmpl != nil {
|
||||
_, err := out.Write(tmpl)
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, e.Config.Directory)
|
||||
}
|
||||
189
render/template/engine/pongo/pongo.go
Normal file
189
render/template/engine/pongo/pongo.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package pongo
|
||||
|
||||
/* TODO:
|
||||
1. Find if pongo2 supports layout, it should have extends or something like django but I don't know yet, if exists then do something with the layour parameter in Exeucte/Gzip.
|
||||
|
||||
*/
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/flosch/pongo2"
|
||||
"github.com/kataras/iris/config"
|
||||
)
|
||||
|
||||
type (
|
||||
Engine struct {
|
||||
Config *config.Template
|
||||
templateCache map[string]*pongo2.Template
|
||||
mu sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
// New creates and returns a Pongo template engine
|
||||
func New(c config.Template) *Engine {
|
||||
return &Engine{Config: &c, templateCache: make(map[string]*pongo2.Template)}
|
||||
}
|
||||
|
||||
func (p *Engine) BuildTemplates() error {
|
||||
// Add our filters. first
|
||||
for k, v := range p.Config.Pongo.Filters {
|
||||
pongo2.RegisterFilter(k, v)
|
||||
}
|
||||
if p.Config.Asset == nil || p.Config.AssetNames == nil {
|
||||
return p.buildFromDir()
|
||||
|
||||
}
|
||||
return p.buildFromAsset()
|
||||
|
||||
}
|
||||
|
||||
func (p *Engine) buildFromDir() (templateErr error) {
|
||||
if p.Config.Directory == "" {
|
||||
return nil //we don't return fill error here(yet)
|
||||
}
|
||||
dir := p.Config.Directory
|
||||
|
||||
fsLoader, err := pongo2.NewLocalFileSystemLoader(dir) // I see that this doesn't read the content if already parsed, so do it manually via filepath.Walk
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set := pongo2.NewSet("", fsLoader)
|
||||
set.Globals = getPongoContext(p.Config.Pongo.Globals)
|
||||
// Walk the supplied directory and compile any files that match our extension list.
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
// Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html".
|
||||
// These dirs should be excluded as they are not valid golang templates, but files under
|
||||
// them should be treat as normal.
|
||||
// If is a dir, return immediately (dir is not a valid golang template).
|
||||
if info == nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(dir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := ""
|
||||
if strings.Index(rel, ".") != -1 {
|
||||
ext = filepath.Ext(rel)
|
||||
}
|
||||
|
||||
for _, extension := range p.Config.Extensions {
|
||||
if ext == extension {
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
name := filepath.ToSlash(rel)
|
||||
p.templateCache[name], templateErr = set.FromString(string(buf))
|
||||
|
||||
//_, templateErr = p.Templates.FromCache(rel) // use Relative, no from path because it calculates the basedir of the fsLoader
|
||||
if templateErr != nil {
|
||||
return templateErr // break the file walk(;)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Engine) buildFromAsset() error {
|
||||
var templateErr error
|
||||
dir := p.Config.Directory
|
||||
fsLoader, err := pongo2.NewLocalFileSystemLoader(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set := pongo2.NewSet("", fsLoader)
|
||||
set.Globals = getPongoContext(p.Config.Pongo.Globals)
|
||||
for _, path := range p.Config.AssetNames() {
|
||||
if !strings.HasPrefix(path, dir) {
|
||||
continue
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(dir, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ext := ""
|
||||
if strings.Index(rel, ".") != -1 {
|
||||
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
|
||||
}
|
||||
|
||||
for _, extension := range p.Config.Extensions {
|
||||
if ext == extension {
|
||||
|
||||
buf, err := p.Config.Asset(path)
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
name := filepath.ToSlash(rel)
|
||||
p.templateCache[name], err = set.FromString(string(buf)) // I don't konw if that will work, yet
|
||||
if err != nil {
|
||||
templateErr = err
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return templateErr
|
||||
}
|
||||
|
||||
// getPongoContext returns the pongo2.Context from map[string]interface{} or from pongo2.Context, used internaly
|
||||
func getPongoContext(templateData interface{}) pongo2.Context {
|
||||
if templateData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, isMap := templateData.(map[string]interface{}); isMap {
|
||||
return v
|
||||
}
|
||||
|
||||
if contextData, isPongoContext := templateData.(pongo2.Context); isPongoContext {
|
||||
return contextData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Engine) fromCache(relativeName string) *pongo2.Template {
|
||||
p.mu.Lock()
|
||||
|
||||
tmpl, ok := p.templateCache[relativeName]
|
||||
|
||||
if ok {
|
||||
p.mu.Unlock() // defer is slow
|
||||
return tmpl
|
||||
}
|
||||
p.mu.Unlock() // defer is slow
|
||||
return nil
|
||||
}
|
||||
|
||||
// layout here is unnesecery
|
||||
func (p *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
|
||||
if tmpl := p.fromCache(name); tmpl != nil {
|
||||
return tmpl.ExecuteWriter(getPongoContext(binding), out)
|
||||
}
|
||||
|
||||
return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, p.Config.Directory)
|
||||
}
|
||||
Reference in New Issue
Block a user