1
0
mirror of https://github.com/danog/gojekyll.git synced 2024-11-30 07:58:59 +01:00

Implement --force_polling

This commit is contained in:
Oliver Steele 2017-07-23 11:25:17 -04:00
parent b563633b62
commit 49a8520b76
8 changed files with 98 additions and 40 deletions

View File

@ -146,13 +146,13 @@ Muzukashii:
- [x] Directory watch - [x] Directory watch
- [ ] Commands - [ ] Commands
- [x] `build` - [x] `build`
- [x] `--source`, `--destination`, `--drafts`, `--future`, `--unpublished`, `--watch` - [x] `--source`, `--destination`, `--drafts`, `--future`, `--unpublished`, `--watch`, `--force_polling`
- [ ] `--baseurl`, `--config`, `--incremental`, `--lsi` - [ ] `--baseurl`, `--config`, `--incremental`, `--lsi`
- [ ] `--force_polling`, `--limit-posts`, `JEKYLL_ENV=production` not planned - [ ] `--limit-posts`, `JEKYLL_ENV=production` not planned
- [x] `clean` - [x] `clean`
- [x] `help` - [x] `help`
- [x] `serve` - [x] `serve`
- [x] `--open-uri`, `--host`, `--port`, `watch` (enabled by default) - [x] `--open-uri`, `--host`, `--port`, `watch`, `--force_polling`
- [ ] `--baseurl`, `--config`, `--incremental` - [ ] `--baseurl`, `--config`, `--incremental`
- [ ] `--detach`, `--ssl`-* not planned - [ ] `--detach`, `--ssl`-* not planned
- [ ] `doctor`, `import`, `new`, `new-theme` not planned - [ ] `doctor`, `import`, `new`, `new-theme` not planned

View File

@ -47,7 +47,8 @@ func init() {
app.HelpFlag.Short('h') app.HelpFlag.Short('h')
app.Flag("profile", "Create a Go pprof CPU profile").BoolVar(&profile) app.Flag("profile", "Create a Go pprof CPU profile").BoolVar(&profile)
app.Flag("quiet", "Silence (some) output.").Short('q').BoolVar(&quiet) app.Flag("quiet", "Silence (some) output.").Short('q').BoolVar(&quiet)
app.Flag("verbose", "Print verbose output.").Short('V').BoolVar(&options.Verbose) _ = app.Flag("verbose", "Print verbose output.").Short('V').Action(boolVar("verbose", &options.Verbose)).Bool()
app.Flag("force_polling", "Force watch to use polling").BoolVar(&options.ForcePolling)
build.Flag("dry-run", "Dry run").Short('n').BoolVar(&options.DryRun) build.Flag("dry-run", "Dry run").Short('n').BoolVar(&options.DryRun)
// --watch has different defaults for build and serve // --watch has different defaults for build and serve

View File

@ -23,7 +23,8 @@ func ParseAndRun(args []string) error {
options.Destination = &dest options.Destination = &dest
} }
if options.DryRun { if options.DryRun {
options.Verbose = true verbose := true
options.Verbose = &verbose
} }
return run(cmd) return run(cmd)
} }

View File

@ -36,6 +36,9 @@ type Config struct {
// Plugins // Plugins
ExcerptSeparator string `yaml:"excerpt_separator"` ExcerptSeparator string `yaml:"excerpt_separator"`
// Conversion
Incremental bool
// Serving // Serving
Host string Host string
Port int Port int
@ -44,12 +47,14 @@ type Config struct {
// Outputting // Outputting
Permalink string Permalink string
Timezone string Timezone string
Verbose bool
// CLI-only // CLI-only
DryRun bool `yaml:"-"` DryRun bool `yaml:"-"`
Verbose bool `yaml:"-"` ForcePolling bool `yaml:"-"`
Watch bool `yaml:"-"` Watch bool `yaml:"-"`
Defaults []struct { Defaults []struct {
Scope struct { Scope struct {

View File

@ -46,6 +46,7 @@ plugins: []
# Conversion # Conversion
excerpt_separator: "\n\n" excerpt_separator: "\n\n"
incremental: false
# Serving # Serving
detach: false detach: false
@ -57,4 +58,6 @@ baseurl: "" # does not include hostname
permalink: date permalink: date
paginate_path: /page:num paginate_path: /page:num
timezone: null timezone: null
verbose: false
` `

View File

@ -9,11 +9,11 @@ import (
type Flags struct { type Flags struct {
// these are pointers so we can tell whether they've been set, and override // these are pointers so we can tell whether they've been set, and override
// the config file only if // the config file only if
Destination, Host *string Destination, Host *string
Drafts, Future, Unpublished *bool Drafts, Future, Unpublished, Verbose *bool
Port *int Port *int
// these aren't in the config file, so they can be treated more conventionally // these aren't in the config file, so they can be treated more conventionally
DryRun, Verbose, Watch bool DryRun, ForcePolling, Watch bool
} }
// ApplyFlags overwrites the configuration with values from flags. // ApplyFlags overwrites the configuration with values from flags.

View File

@ -1,7 +1,6 @@
package site package site
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -54,7 +53,6 @@ func (s *Site) Read() error {
// Reloaded returns a new site read the same source directory, configuration file, and load flags. // Reloaded returns a new site read the same source directory, configuration file, and load flags.
func (s *Site) Reloaded(paths []string) (*Site, error) { func (s *Site) Reloaded(paths []string) (*Site, error) {
if s.requiresFullReload(paths) { if s.requiresFullReload(paths) {
fmt.Println("reload everything")
copy, err := FromDirectory(s.SourceDir(), s.flags) copy, err := FromDirectory(s.SourceDir(), s.flags)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -2,12 +2,15 @@ package site
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath"
"sync" "sync"
"time" "time"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/osteele/gojekyll/utils" "github.com/osteele/gojekyll/utils"
"github.com/radovskyb/watcher"
) )
// FilesEvent is a list of changed or added site source files, with a single // FilesEvent is a list of changed or added site source files, with a single
@ -23,31 +26,9 @@ func (e FilesEvent) String() string {
return fmt.Sprintf("%d file%s changed at %s", count, inflect, e.Time.Format("3:04:05PM")) return fmt.Sprintf("%d file%s changed at %s", count, inflect, e.Time.Format("3:04:05PM"))
} }
func (s *Site) makeEventWatcher() (<-chan string, error) {
var (
sourceDir = s.SourceDir()
filenames = make(chan string, 100)
w, err = fsnotify.NewWatcher()
)
if err != nil {
return nil, err
}
go func() {
for {
select {
case event := <-w.Events:
filenames <- utils.MustRel(sourceDir, event.Name)
case err := <-w.Errors:
fmt.Fprintln(os.Stderr, "error:", err)
}
}
}()
return filenames, w.Add(sourceDir)
}
// WatchFiles sends FilesEvent on changes within the site directory. // WatchFiles sends FilesEvent on changes within the site directory.
func (s *Site) WatchFiles() (<-chan FilesEvent, error) { func (s *Site) WatchFiles() (<-chan FilesEvent, error) {
filenames, err := s.makeEventWatcher() filenames, err := s.makeFileWatcher()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -59,7 +40,7 @@ func (s *Site) WatchFiles() (<-chan FilesEvent, error) {
for { for {
paths := s.sitePaths(<-debounced) paths := s.sitePaths(<-debounced)
if len(paths) > 0 { if len(paths) > 0 {
// Make up a new timestamp. Except under pathological // Create a new timestamp. Except under pathological
// circumstances, it will be close enough. // circumstances, it will be close enough.
filesets <- FilesEvent{time.Now(), paths} filesets <- FilesEvent{time.Now(), paths}
} }
@ -105,6 +86,72 @@ func (s *Site) WatchRebuild() (<-chan interface{}, error) {
return events, nil return events, nil
} }
func (s *Site) makePollingWatcher() (<-chan string, error) {
var (
sourceDir = utils.MustAbs(s.SourceDir())
filenames = make(chan string, 100)
w = watcher.New()
)
if err := w.AddRecursive(sourceDir); err != nil {
return nil, err
}
for _, path := range s.config.Exclude {
if err := w.Ignore(filepath.Join(sourceDir, path)); err != nil {
return nil, err
}
}
if err := w.Ignore(s.DestDir()); err != nil {
return nil, err
}
go func() {
for {
select {
case event := <-w.Event:
filenames <- utils.MustRel(sourceDir, event.Path)
case err := <-w.Error:
fmt.Fprintln(os.Stderr, "error:", err)
case <-w.Closed:
return
}
}
}()
go func() {
if err := w.Start(time.Millisecond * 250); err != nil {
log.Fatal(err)
}
}()
return filenames, nil
}
func (s *Site) makeEventWatcher() (<-chan string, error) {
var (
sourceDir = s.SourceDir()
filenames = make(chan string, 100)
w, err = fsnotify.NewWatcher()
)
if err != nil {
return nil, err
}
go func() {
for {
select {
case event := <-w.Events:
filenames <- utils.MustRel(sourceDir, event.Name)
case err := <-w.Errors:
fmt.Fprintln(os.Stderr, "error:", err)
}
}
}()
return filenames, w.Add(sourceDir)
}
func (s *Site) makeFileWatcher() (<-chan string, error) {
if s.config.ForcePolling {
return s.makePollingWatcher()
}
return s.makeEventWatcher()
}
// reloads and rebuilds the site; returns a copy and count // reloads and rebuilds the site; returns a copy and count
func (s *Site) rebuild(paths []string) (r *Site, n int, err error) { func (s *Site) rebuild(paths []string) (r *Site, n int, err error) {
r, err = s.Reloaded(paths) r, err = s.Reloaded(paths)
@ -136,15 +183,18 @@ func (s *Site) sitePaths(filenames []string) []string {
// faster than interval // faster than interval
// TODO consider https://github.com/ReactiveX/RxGo // TODO consider https://github.com/ReactiveX/RxGo
func debounce(interval time.Duration, input <-chan string) <-chan []string { func debounce(interval time.Duration, input <-chan string) <-chan []string {
output := make(chan []string)
var ( var (
pending = []string{} pending = []string{}
output = make(chan []string)
ticker <-chan time.Time ticker <-chan time.Time
) )
go func() { go func() {
for { for {
select { select {
case value := <-input: case value := <-input:
if value == "." {
continue
}
pending = append(pending, value) pending = append(pending, value)
ticker = time.After(interval) // replaces the previous ticker ticker = time.After(interval) // replaces the previous ticker
case <-ticker: case <-ticker: