Merge branch 'master' into dev
This commit is contained in:
commit
87e5073c63
122
README.md
122
README.md
@ -1,14 +1,124 @@
|
||||
# Goldsmith #
|
||||
|
||||
Goldsmith is a static website generator developed in Go with flexibility, extensibility, and performance as primary
|
||||
Goldsmith is a static website generator developed in Golang with flexibility, extensibility, and performance as primary
|
||||
design considerations. With Goldsmith you can easily build and deploy any type of site, whether it is a personal blog,
|
||||
image gallery, or a corporate homepage; the tool no assumptions are made about your layout or file structure. Goldsmith
|
||||
is trivially extensible via a plugin architecture which makes it simple to perform complex data transformations
|
||||
concurrently.
|
||||
concurrently. A growing set of core plugins, [Goldsmith-Plugins](../goldsmith-plugins/), is provided to make it easier
|
||||
to get started with this tool to generate static websites.
|
||||
|
||||
## Motivation ##
|
||||
|
||||
Why in the world did I make yet another static site generator? At first, I didn't think I needed to; after all, there is
|
||||
a wide variety of open source tools freely available for use. Surely one of these applications would allow me to build
|
||||
my portfolio page exactly the way I want right? After trying several static generators, namely Pelican, Hexo, Hugo, and
|
||||
Metalsmith, I found that although sometimes coming close, no tool gave me exactly what I wanted.
|
||||
Why in the world would one create yet another static site generator? At first, I didn't think I needed to; after all,
|
||||
there is a wide variety of open source tools freely available for use. Surely one of these applications would allow me
|
||||
to build my portfolio page exactly the way I want right?
|
||||
|
||||
After trying several static generators, namely [Pelican](http://blog.getpelican.com/), [Hexo](https://hexo.io/), and
|
||||
[Hugo](https://gohugo.io/), I found that although sometimes coming close, no tool gave me exactly what I needed.
|
||||
Although I hold the authors of these applications in high regard and sincerely appreciate their contribution to the open
|
||||
source community, everyone seemed overly eager to make assumptions about content organization and presentation.
|
||||
|
||||
Many of the static generators I've used feature extensive configuration files to support customization. Nevertheless, I
|
||||
was disappointed to discovered that even though I could approach my planned design, I could never realize it. There was
|
||||
always some architectural limitation preventing me from doing something with my site which seemed like basic
|
||||
functionality.
|
||||
|
||||
* Blog posts can be tagged, but static pages cannot.
|
||||
* Image files cannot be stored next to content files.
|
||||
* Navbar item activated when viewing blog, but not static pages.
|
||||
* Auto-generated pages behave differently from normal ones.
|
||||
|
||||
Upon asking on community forms, I learned that most users were content to live with such design decisions, with some
|
||||
offering workarounds that would get me halfway to where I wanted to go. As I am not one to make compromises, I kept
|
||||
hopping from one static site generator to another, until I discovered [Metalsmith](http://www.metalsmith.io/). Finally,
|
||||
it seemed like I found a tool that gets out of my way, and lets me build my website the way I want to. After using this
|
||||
tool for almost a year, I began to see its limits.
|
||||
|
||||
* The extension system is complicated; it's difficult to write and debug plugins.
|
||||
* Quality of existing plugins varies greatly; I found many subtle issues.
|
||||
* No support for parallel processing (this is a big one if you process images).
|
||||
* A full [Node.js](https://nodejs.org/) is stack (including dependencies) is required to build sites.
|
||||
|
||||
Rather than making do with what I had indefinitely, I decided to use the knowledge I've obtained from using various
|
||||
static site generators to build my own. The *Goldsmith* name is a reference to both the *Go* programming language I've
|
||||
selected for this project, as well as to *Metalsmith*, my inspiration for what an static site generator could be.
|
||||
|
||||
The motivation behind Goldsmith can be described by the following principles:
|
||||
|
||||
* Keep the core small and simple.
|
||||
* Enable efficient, multi-core processing.
|
||||
* Add new features via user plugins.
|
||||
* Customize behavior through user code.
|
||||
|
||||
I originally built this tool to generate my personal homepage, but I believe it can be of use to anyone who wants to
|
||||
enjoy the freedom of building a static site from ground up, especially users of Metalsmith. Why craft metal when you can
|
||||
be crafting gold?
|
||||
|
||||
## Usage ##
|
||||
|
||||
Goldsmith is at it's core, a pipeline-based file processor. Files are loaded from the source directory, processed by any
|
||||
number of plugins, and are finally output to the destination directory. Rather than explaining the process in detail
|
||||
conceptually, I will show some code samples which show how this tool can be used in practice.
|
||||
|
||||
* Start by copying files from a source directory to a destination directory (simplest possible use case):
|
||||
```
|
||||
goldsmith.Begin(srcDir).
|
||||
End(dstDir)
|
||||
```
|
||||
|
||||
* Now let's also convert our [Markdown](https://daringfireball.net/projects/markdown/) files to HTML using the
|
||||
[markdown plugin](../goldsmith-plugins/markdown):
|
||||
```
|
||||
goldsmith.Begin(srcDir).
|
||||
Chain(markdown.NewCommon()).
|
||||
End(dstDir)
|
||||
```
|
||||
|
||||
* If we are using *front matter* in our Markdown files, we can easily extract it by using the
|
||||
[frontmatter plugin](../goldsmith-plugins/frontmatter):
|
||||
```
|
||||
goldsmith.Begin(srcDir).
|
||||
Chain(frontmatter.New()).
|
||||
Chain(markdown.NewCommon()).
|
||||
End(dstDir)
|
||||
```
|
||||
|
||||
* Next we want to run our generated HTML through a template to add a header, footer, and a menu; for this we
|
||||
can use the [layout plugin](../goldsmith-plugins/layout):
|
||||
```
|
||||
goldsmith.Begin(srcDir).
|
||||
Chain(frontmatter.New()).
|
||||
Chain(markdown.NewCommon()).
|
||||
Chain(layout.New(
|
||||
layoutFiles, // array of paths for files containing template definitions
|
||||
templateNameVar, // metadata variable that contains the name of the template to use
|
||||
contentStoreVar, // metadata variable configured in template to insert content
|
||||
defTemplateName, // name of a default template to use if one is not specified
|
||||
userFuncs, // mapping of functions which can be executed from templates
|
||||
)).
|
||||
End(dstDir)
|
||||
```
|
||||
|
||||
* Finally, let's [minify](https://en.wikipedia.org/wiki/Minification_(programming)) our files to reduce data transfer
|
||||
and load times for our site's visitors using the [minify plugin](../goldsmith-plugins/minify).
|
||||
```
|
||||
goldsmith.Begin(srcDir).
|
||||
Chain(frontmatter.New()).
|
||||
Chain(markdown.NewCommon()).
|
||||
Chain(layout.New(layoutFiles, templateNameVar, contentStoreVar, defTemplateName, userFuncs)).
|
||||
Chain(minify.New()).
|
||||
End(dstDir)
|
||||
```
|
||||
|
||||
I hope this simple example effectively illustrates the conceptual simplicity of the Goldsmith pipeline-based processing
|
||||
method. Files are injected into the stream at Goldsmith initialization, processed in parallel through a series of
|
||||
plugins, and are finally written out to disk upon completion.
|
||||
|
||||
Files are guaranteed to flow through Goldsmith plugins in the same order, but not necessarily in the same sequence
|
||||
relative to each other. Timing differences can cause certain files to finish ahead of others; fortunately this, along
|
||||
with other threading characteristics of the tool is abstracted from the user. The execution, while appearing to be a
|
||||
mere series chained methods, will process files using all of your system's cores.
|
||||
|
||||
## License ##
|
||||
|
||||
MIT
|
||||
|
61
context.go
61
context.go
@ -30,26 +30,17 @@ import (
|
||||
|
||||
type context struct {
|
||||
gs *goldsmith
|
||||
plug Plugin
|
||||
input, output chan *file
|
||||
}
|
||||
|
||||
func newContext(gs *goldsmith) *context {
|
||||
ctx := &context{gs: gs, output: make(chan *file)}
|
||||
if len(gs.contexts) > 0 {
|
||||
ctx.input = gs.contexts[len(gs.contexts)-1].output
|
||||
}
|
||||
|
||||
gs.contexts = append(gs.contexts, ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *context) chain(p Plugin) {
|
||||
func (ctx *context) chain() {
|
||||
defer close(ctx.output)
|
||||
|
||||
init, _ := p.(Initializer)
|
||||
accept, _ := p.(Accepter)
|
||||
proc, _ := p.(Processor)
|
||||
fin, _ := p.(Finalizer)
|
||||
init, _ := ctx.plug.(Initializer)
|
||||
accept, _ := ctx.plug.(Accepter)
|
||||
proc, _ := ctx.plug.(Processor)
|
||||
fin, _ := ctx.plug.(Finalizer)
|
||||
|
||||
if init != nil {
|
||||
if err := init.Initialize(ctx); err != nil {
|
||||
@ -58,26 +49,28 @@ func (ctx *context) chain(p Plugin) {
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for f := range ctx.input {
|
||||
if proc == nil || accept != nil && !accept.Accept(ctx, f) {
|
||||
ctx.output <- f
|
||||
} else {
|
||||
if _, err := f.Seek(0, os.SEEK_SET); err != nil {
|
||||
ctx.gs.fault(f, err)
|
||||
}
|
||||
if err := proc.Process(ctx, f); err != nil {
|
||||
ctx.gs.fault(f, err)
|
||||
if ctx.input != nil {
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for f := range ctx.input {
|
||||
if proc == nil || accept != nil && !accept.Accept(ctx, f) {
|
||||
ctx.output <- f
|
||||
} else {
|
||||
if _, err := f.Seek(0, os.SEEK_SET); err != nil {
|
||||
ctx.gs.fault(f, err)
|
||||
}
|
||||
if err := proc.Process(ctx, f); err != nil {
|
||||
ctx.gs.fault(f, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if fin != nil {
|
||||
if err := fin.Finalize(ctx); err != nil {
|
||||
@ -94,10 +87,6 @@ func (ctx *context) DispatchFile(f File) {
|
||||
ctx.output <- f.(*file)
|
||||
}
|
||||
|
||||
func (ctx *context) ReferenceFile(path string) {
|
||||
ctx.gs.referenceFile(path)
|
||||
}
|
||||
|
||||
func (ctx *context) SrcDir() string {
|
||||
return ctx.gs.srcDir
|
||||
}
|
||||
|
143
core.go
Normal file
143
core.go
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package goldsmith
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type goldsmith struct {
|
||||
srcDir, dstDir string
|
||||
contexts []*context
|
||||
refs map[string]bool
|
||||
|
||||
errors []error
|
||||
errorMtx sync.Mutex
|
||||
}
|
||||
|
||||
func (gs *goldsmith) pushContext(plug Plugin) *context {
|
||||
ctx := &context{
|
||||
gs: gs,
|
||||
plug: plug,
|
||||
output: make(chan *file),
|
||||
}
|
||||
|
||||
if len(gs.contexts) > 0 {
|
||||
ctx.input = gs.contexts[len(gs.contexts)-1].output
|
||||
}
|
||||
|
||||
gs.contexts = append(gs.contexts, ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (gs *goldsmith) cleanupFiles() {
|
||||
files := make(chan string)
|
||||
dirs := make(chan string)
|
||||
go scanDir(gs.dstDir, files, dirs)
|
||||
|
||||
for files != nil || dirs != nil {
|
||||
var (
|
||||
path string
|
||||
ok bool
|
||||
)
|
||||
|
||||
select {
|
||||
case path, ok = <-files:
|
||||
if !ok {
|
||||
files = nil
|
||||
continue
|
||||
}
|
||||
case path, ok = <-dirs:
|
||||
if !ok {
|
||||
dirs = nil
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
relPath, _ := filepath.Rel(gs.dstDir, path)
|
||||
if contained, _ := gs.refs[relPath]; contained {
|
||||
continue
|
||||
}
|
||||
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *goldsmith) exportFile(f *file) error {
|
||||
if err := f.export(gs.dstDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pathSeg := cleanPath(f.path)
|
||||
for {
|
||||
gs.refs[pathSeg] = true
|
||||
if pathSeg == "." {
|
||||
break
|
||||
}
|
||||
|
||||
pathSeg = filepath.Dir(pathSeg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gs *goldsmith) fault(f *file, err error) {
|
||||
gs.errorMtx.Lock()
|
||||
defer gs.errorMtx.Unlock()
|
||||
|
||||
ferr := &Error{Err: err}
|
||||
if f != nil {
|
||||
ferr.Path = f.path
|
||||
}
|
||||
|
||||
gs.errors = append(gs.errors, ferr)
|
||||
}
|
||||
|
||||
//
|
||||
// Goldsmith Implementation
|
||||
//
|
||||
|
||||
func (gs *goldsmith) Chain(p Plugin) Goldsmith {
|
||||
gs.pushContext(p)
|
||||
return gs
|
||||
}
|
||||
|
||||
func (gs *goldsmith) End(dstDir string) []error {
|
||||
gs.dstDir = dstDir
|
||||
|
||||
for _, ctx := range gs.contexts {
|
||||
go ctx.chain()
|
||||
}
|
||||
|
||||
ctx := gs.contexts[len(gs.contexts)-1]
|
||||
for f := range ctx.output {
|
||||
gs.exportFile(f)
|
||||
}
|
||||
|
||||
gs.cleanupFiles()
|
||||
return gs.errors
|
||||
}
|
30
file.go
30
file.go
@ -28,17 +28,23 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type file struct {
|
||||
path string
|
||||
meta map[string]interface{}
|
||||
Meta map[string]interface{}
|
||||
|
||||
reader *bytes.Reader
|
||||
asset string
|
||||
}
|
||||
|
||||
func (f *file) export(dstPath string) error {
|
||||
func (f *file) export(dstDir string) error {
|
||||
dstPath := filepath.Join(dstDir, f.path)
|
||||
if len(f.asset) > 0 && fileCached(f.asset, dstPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(dstPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -93,13 +99,23 @@ func (f *file) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f *file) Meta() map[string]interface{} {
|
||||
return f.meta
|
||||
func (f *file) Dir() string {
|
||||
return path.Dir(f.path)
|
||||
}
|
||||
|
||||
func (f *file) Apply(m map[string]interface{}) {
|
||||
for key, value := range m {
|
||||
f.meta[key] = value
|
||||
func (f *file) Value(key string) (interface{}, bool) {
|
||||
value, ok := f.Meta[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (f *file) SetValue(key string, value interface{}) {
|
||||
f.Meta[key] = value
|
||||
}
|
||||
|
||||
func (f *file) CopyValues(src File) {
|
||||
rf := src.(*file)
|
||||
for name, value := range rf.Meta {
|
||||
f.SetValue(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
185
goldsmith.go
185
goldsmith.go
@ -23,129 +23,80 @@
|
||||
package goldsmith
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type goldsmith struct {
|
||||
srcDir, dstDir string
|
||||
contexts []*context
|
||||
|
||||
refs map[string]bool
|
||||
refMtx sync.Mutex
|
||||
|
||||
errors []error
|
||||
errorMtx sync.Mutex
|
||||
type Goldsmith interface {
|
||||
Chain(p Plugin) Goldsmith
|
||||
End(dstDir string) []error
|
||||
}
|
||||
|
||||
func (gs *goldsmith) queueFiles() {
|
||||
files := make(chan string)
|
||||
go scanDir(gs.srcDir, files, nil)
|
||||
|
||||
ctx := newContext(gs)
|
||||
|
||||
go func() {
|
||||
defer close(ctx.output)
|
||||
for path := range files {
|
||||
relPath, err := filepath.Rel(gs.srcDir, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f := NewFileFromAsset(relPath, path)
|
||||
ctx.DispatchFile(f)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (gs *goldsmith) cleanupFiles() {
|
||||
files := make(chan string)
|
||||
dirs := make(chan string)
|
||||
go scanDir(gs.dstDir, files, dirs)
|
||||
|
||||
for files != nil || dirs != nil {
|
||||
var (
|
||||
path string
|
||||
ok bool
|
||||
)
|
||||
|
||||
select {
|
||||
case path, ok = <-files:
|
||||
if !ok {
|
||||
files = nil
|
||||
continue
|
||||
}
|
||||
case path, ok = <-dirs:
|
||||
if !ok {
|
||||
dirs = nil
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(gs.dstDir, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if contained, _ := gs.refs[relPath]; contained {
|
||||
continue
|
||||
}
|
||||
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *goldsmith) exportFile(f *file) error {
|
||||
absPath := filepath.Join(gs.dstDir, f.path)
|
||||
if err := f.export(absPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gs.referenceFile(f.path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gs *goldsmith) referenceFile(path string) {
|
||||
gs.refMtx.Lock()
|
||||
defer gs.refMtx.Unlock()
|
||||
|
||||
path = cleanPath(path)
|
||||
|
||||
for {
|
||||
gs.refs[path] = true
|
||||
if path == "." {
|
||||
break
|
||||
}
|
||||
|
||||
path = filepath.Dir(path)
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *goldsmith) fault(f *file, err error) {
|
||||
gs.errorMtx.Lock()
|
||||
gs.errors = append(gs.errors, &Error{f, err})
|
||||
gs.errorMtx.Unlock()
|
||||
}
|
||||
|
||||
//
|
||||
// Goldsmith Implementation
|
||||
//
|
||||
|
||||
func (gs *goldsmith) Chain(p Plugin) Goldsmith {
|
||||
ctx := newContext(gs)
|
||||
go ctx.chain(p)
|
||||
func Begin(srcDir string) Goldsmith {
|
||||
gs := &goldsmith{srcDir: srcDir, refs: make(map[string]bool)}
|
||||
gs.Chain(new(loader))
|
||||
return gs
|
||||
}
|
||||
|
||||
func (gs *goldsmith) Complete() []error {
|
||||
ctx := gs.contexts[len(gs.contexts)-1]
|
||||
for f := range ctx.output {
|
||||
gs.exportFile(f)
|
||||
}
|
||||
type File interface {
|
||||
Path() string
|
||||
Dir() string
|
||||
|
||||
gs.cleanupFiles()
|
||||
return gs.errors
|
||||
Value(key string) (interface{}, bool)
|
||||
SetValue(key string, value interface{})
|
||||
CopyValues(src File)
|
||||
|
||||
Read(p []byte) (int, error)
|
||||
WriteTo(w io.Writer) (int64, error)
|
||||
Seek(offset int64, whence int) (int64, error)
|
||||
}
|
||||
|
||||
func NewFileFromData(path string, data []byte) File {
|
||||
return &file{
|
||||
path: path,
|
||||
Meta: make(map[string]interface{}),
|
||||
reader: bytes.NewReader(data),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFileFromAsset(path, asset string) File {
|
||||
return &file{
|
||||
path: path,
|
||||
Meta: make(map[string]interface{}),
|
||||
asset: asset,
|
||||
}
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
DispatchFile(f File)
|
||||
|
||||
SrcDir() string
|
||||
DstDir() string
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Err error
|
||||
Path string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
type Initializer interface {
|
||||
Initialize(ctx Context) error
|
||||
}
|
||||
|
||||
type Accepter interface {
|
||||
Accept(ctx Context, f File) bool
|
||||
}
|
||||
|
||||
type Processor interface {
|
||||
Process(ctx Context, f File) error
|
||||
}
|
||||
|
||||
type Finalizer interface {
|
||||
Finalize(ctx Context) error
|
||||
}
|
||||
|
||||
type Plugin interface{}
|
||||
|
40
loader.go
Normal file
40
loader.go
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package goldsmith
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
type loader struct{}
|
||||
|
||||
func (*loader) Initialize(ctx Context) error {
|
||||
files := make(chan string)
|
||||
go scanDir(ctx.SrcDir(), files, nil)
|
||||
|
||||
for path := range files {
|
||||
relPath, _ := filepath.Rel(ctx.SrcDir(), path)
|
||||
f := NewFileFromAsset(relPath, path)
|
||||
ctx.DispatchFile(f)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
106
types.go
106
types.go
@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package goldsmith
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Goldsmith interface {
|
||||
Chain(p Plugin) Goldsmith
|
||||
Complete() []error
|
||||
}
|
||||
|
||||
func New(srcDir, dstDir string) Goldsmith {
|
||||
gs := &goldsmith{
|
||||
srcDir: srcDir,
|
||||
dstDir: dstDir,
|
||||
refs: make(map[string]bool),
|
||||
}
|
||||
|
||||
gs.queueFiles()
|
||||
return gs
|
||||
}
|
||||
|
||||
type File interface {
|
||||
Path() string
|
||||
|
||||
Meta() map[string]interface{}
|
||||
Apply(m map[string]interface{})
|
||||
|
||||
Read(p []byte) (int, error)
|
||||
WriteTo(w io.Writer) (int64, error)
|
||||
Seek(offset int64, whence int) (int64, error)
|
||||
}
|
||||
|
||||
func NewFileFromData(path string, data []byte) File {
|
||||
return &file{
|
||||
path: path,
|
||||
meta: make(map[string]interface{}),
|
||||
reader: bytes.NewReader(data),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFileFromAsset(path, asset string) File {
|
||||
return &file{
|
||||
path: path,
|
||||
meta: make(map[string]interface{}),
|
||||
asset: asset,
|
||||
}
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
DispatchFile(f File)
|
||||
ReferenceFile(path string)
|
||||
|
||||
SrcDir() string
|
||||
DstDir() string
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
file File
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
type Initializer interface {
|
||||
Initialize(ctx Context) error
|
||||
}
|
||||
|
||||
type Accepter interface {
|
||||
Accept(ctx Context, file File) bool
|
||||
}
|
||||
|
||||
type Finalizer interface {
|
||||
Finalize(ctx Context) error
|
||||
}
|
||||
|
||||
type Processor interface {
|
||||
Process(ctx Context, f File) error
|
||||
}
|
||||
|
||||
type Plugin interface{}
|
14
util.go
14
util.go
@ -66,3 +66,17 @@ func scanDir(root string, files, dirs chan string) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func fileCached(srcPath, dstPath string) bool {
|
||||
srcStat, err := os.Stat(srcPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
dstStat, err := os.Stat(dstPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return dstStat.ModTime().Unix() >= srcStat.ModTime().Unix()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user