Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
shakahl committed May 26, 2019
1 parent 00f3a73 commit 3004c1b
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 1 deletion.
23 changes: 23 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# EditorConfig: https://EditorConfig.org

root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_size = 2
indent_style = space

[*.go]
indent_style = tab
indent_size = 8

[Makefile]
indent_style = tab

[*.{json, yml, yaml}]
indent_style = space
indent_size = 2

88 changes: 88 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Created by .ignore support plugin (hsz.mobi)
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

# Generated files
.idea/**/contentModel.xml

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# Gradle
.idea/**/gradle.xml
.idea/**/libraries

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr

# CMake
cmake-build-*/

# Mongo Explorer plugin
.idea/**/mongoSettings.xml

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Editor-based Rest Client
.idea/httpRequests

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

8 changes: 8 additions & 0 deletions .idea/golang-vfstemplate.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
# golang-httpfs-template
golang-vfstemplate
=======================

## Installation

```
go get github.com/shakahl/golang-vfstemplate
```

## Usage

Parsing templates by glob pattern:

```
template.Must(vfstemplate.ParseGlob(myhttpfs, nil, "/views/*.html")
```

Parsing templates by specifying a list of file paths:

```
vfstemplate.ParseFiles(myhttpfs, nil, "/views/first.html", "/views/second.html")
```

3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/shakahl/golang-vfstemplate

go 1.12
101 changes: 101 additions & 0 deletions match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package vfstemplate

import (
"net/http"
"os"
"path"
"sort"
"strings"
)

const separator = "/"

// Glob returns the names of all files matching pattern or nil
// if there is no matching file. The syntax of patterns is the same
// as in path.Match. The pattern may describe hierarchical names such as
// /usr/*/bin/ed.
//
// Glob ignores file system errors such as I/O errors reading directories.
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
func Glob(fs http.FileSystem, pattern string) (matches []string, err error) {
if !hasMeta(pattern) {
if _, err = Stat(fs, pattern); err != nil {
return nil, nil
}
return []string{pattern}, nil
}

dir, file := path.Split(pattern)
switch dir {
case "":
dir = "."
case string(separator):
// nothing
default:
dir = dir[0 : len(dir)-1] // chop off trailing separator
}

if !hasMeta(dir) {
return glob(fs, dir, file, nil)
}

var m []string
m, err = Glob(fs, dir)
if err != nil {
return
}
for _, d := range m {
matches, err = glob(fs, d, file, matches)
if err != nil {
return
}
}
return
}

// glob searches for files matching pattern in the directory dir
// and appends them to matches. If the directory cannot be
// opened, it returns the existing matches. New matches are
// added in lexicographical order.
func glob(fs http.FileSystem, dir, pattern string, matches []string) (m []string, e error) {
m = matches
fi, err := Stat(fs, dir)
if err != nil {
return
}
if !fi.IsDir() {
return
}
fis, err := ReadDir(fs, dir)
if err != nil {
return
}

sort.Sort(byName(fis))

for _, fi := range fis {
n := fi.Name()
matched, err := path.Match(path.Clean(pattern), n)
if err != nil {
return m, err
}
if matched {
m = append(m, path.Join(dir, n))
}
}
return
}

// hasMeta reports whether path contains any of the magic characters
// recognized by Match.
func hasMeta(path string) bool {
return strings.ContainsAny(path, "*?[")
}

// byName implements sort.Interface.
type byName []os.FileInfo

func (f byName) Len() int { return len(f) }
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
69 changes: 69 additions & 0 deletions template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package vfstemplate

import (
"fmt"
"html/template"
"net/http"
"path"
)

// ParseFiles creates a new Template if t is nil and parses the template definitions from
// the named files. The returned template's name will have the (base) name and
// (parsed) contents of the first file. There must be at least one file.
// If an error occurs, parsing stops and the returned *Template is nil.
func ParseFiles(fs http.FileSystem, t *template.Template, filenames ...string) (*template.Template, error) {
return parseFiles(fs, t, filenames...)
}

// ParseGlob parses the template definitions in the files identified by the
// pattern and associates the resulting templates with t. The pattern is
// processed by vfspath.Glob and must match at least one file. ParseGlob is
// equivalent to calling t.ParseFiles with the list of files matched by the
// pattern.
func ParseGlob(fs http.FileSystem, t *template.Template, pattern string) (*template.Template, error) {
filenames, err := Glob(fs, pattern)
if err != nil {
return nil, err
}
if len(filenames) == 0 {
return nil, fmt.Errorf("vfstemplate: pattern matches no files: %#q", pattern)
}
return parseFiles(fs, t, filenames...)
}

// parseFiles is the helper for the method and function. If the argument
// template is nil, it is created from the first file.
func parseFiles(fs http.FileSystem, t *template.Template, filenames ...string) (*template.Template, error) {
if len(filenames) == 0 {
// Not really a problem, but be consistent.
return nil, fmt.Errorf("vfstemplate: no files named in call to ParseFiles")
}
for _, filename := range filenames {
b, err := ReadFile(fs, filename)
if err != nil {
return nil, err
}
s := string(b)
name := path.Base(filename)
// First template becomes return value if not already defined,
// and we use that one for subsequent New calls to associate
// all the templates together. Also, if this file has the same name
// as t, this file becomes the contents of t, so
// t, err := New(name).Funcs(xxx).ParseFiles(name)
// works. Otherwise we create a new template associated with t.
var tmpl *template.Template
if t == nil {
t = template.New(name)
}
if name == t.Name() {
tmpl = t
} else {
tmpl = t.New(name)
}
_, err = tmpl.Parse(s)
if err != nil {
return nil, err
}
}
return t, nil
}
47 changes: 47 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package vfstemplate

import (
"io/ioutil"
"net/http"
"os"
)

// ReadDir reads the contents of the directory associated with file and
// returns a slice of FileInfo values in directory order.
func ReadDir(fs http.FileSystem, name string) ([]os.FileInfo, error) {
f, err := fs.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return f.Readdir(-1)
}

// Stat returns the FileInfo structure describing file.
func Stat(fs http.FileSystem, name string) (os.FileInfo, error) {
f, err := fs.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return f.Stat()
}

// ReadFile reads the file named by path from fs and returns the contents.
func ReadFile(fs http.FileSystem, path string) ([]byte, error) {
rc, err := fs.Open(path)
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}

// ReadFileString reads the file named by path from fs and returns the contents.
func ReadFileString(fs http.FileSystem, path string) (string, error) {
buf, err := ReadFile(fs, path)
if err != nil {
return "", err
}
return string(buf), nil
}

0 comments on commit 3004c1b

Please sign in to comment.