diff --git a/commands/benchmark.go b/commands/benchmark.go new file mode 100644 index 0000000..f196ca2 --- /dev/null +++ b/commands/benchmark.go @@ -0,0 +1,37 @@ +package commands + +import ( + "fmt" + "time" + + "github.com/montanaflynn/stats" +) + +var benchmark = app.Command("benchmark", "Repeat build for ten seconds. Implies --profile.") + +// benchmarkCommand builds the site repeatedly until at least 10 seconds has elapsed, +// and reports the trial times. Empirically, it the same mean but low variance as using +// a separate benchmark runner that invokes a new gojekyll process each time. +func benchmarkCommand() (err error) { + startTime := time.Now() + samples := []float64{} + for i := 0; time.Since(startTime) < 10*time.Second; i++ { + sampleStart := time.Now() + site, err := loadSite(*source, options) + if err != nil { + return err + } + _, err = site.Build() + if err != nil { + return err + } + dur := time.Since(sampleStart).Seconds() + samples = append(samples, dur) + quiet = true + fmt.Printf("Run #%d; %.1fs elapsed\n", i+1, time.Since(commandStartTime).Seconds()) + } + median, _ := stats.Median(samples) + stddev, _ := stats.StandardDeviationSample(samples) + fmt.Printf("%d samples @ %.2fs ± %.2fs\n", len(samples), median, stddev) + return nil +} diff --git a/commands/build.go b/commands/build.go new file mode 100644 index 0000000..4ac4176 --- /dev/null +++ b/commands/build.go @@ -0,0 +1,52 @@ +package commands + +import ( + "fmt" + "os" + "time" + + "github.com/osteele/gojekyll/site" +) + +// main sets this +var commandStartTime = time.Now() + +var build = app.Command("build", "Build your site").Alias("b") + +func init() { + build.Flag("dry-run", "Dry run").Short('n').BoolVar(&options.DryRun) +} + +func buildCommand(site *site.Site) error { + watch := site.Config().Watch + + logger.path("Destination:", site.DestDir()) + logger.label("Generating...", "") + count, err := site.Build() + switch { + case err == nil: + elapsed := time.Since(commandStartTime) + logger.label("", "wrote %d files in %.2fs.", count, elapsed.Seconds()) + case watch: + fmt.Fprintln(os.Stderr, err) + default: + return err + } + + // FIXME the watch will miss files that changed during the first build + + // server watch is implemented inside Server.Run, in contrast to this command + if watch { + events, err := site.WatchRebuild() + if err != nil { + return err + } + logger.label("Auto-regeneration:", "enabled for %q", site.SourceDir()) + for event := range events { + fmt.Print(event) + } + } else { + logger.label("Auto-regeneration:", "disabled. Use --watch to enable.") + } + return nil +} diff --git a/commands/clean.go b/commands/clean.go new file mode 100644 index 0000000..08473f3 --- /dev/null +++ b/commands/clean.go @@ -0,0 +1,10 @@ +package commands + +import "github.com/osteele/gojekyll/site" + +var clean = app.Command("clean", "Clean the site (removes site output) without building.") + +func cleanCommand(site *site.Site) error { + logger.label("Cleaner:", "Removing %s...", site.DestDir()) + return site.Clean() +} diff --git a/commands/commands.go b/commands/commands.go index ad3693e..c81598c 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -1,119 +1,13 @@ package commands import ( - "fmt" - "os" - "path/filepath" - "sort" "strings" - "time" - yaml "gopkg.in/yaml.v1" - - "github.com/montanaflynn/stats" "github.com/osteele/gojekyll/pages" - "github.com/osteele/gojekyll/server" "github.com/osteele/gojekyll/site" "github.com/osteele/gojekyll/utils" - "github.com/osteele/liquid" ) -// main sets this -var commandStartTime = time.Now() - -func buildCommand(site *site.Site) error { - watch := site.Config().Watch - - logger.path("Destination:", site.DestDir()) - logger.label("Generating...", "") - count, err := site.Build() - switch { - case err == nil: - elapsed := time.Since(commandStartTime) - logger.label("", "wrote %d files in %.2fs.", count, elapsed.Seconds()) - case watch: - fmt.Fprintln(os.Stderr, err) - default: - return err - } - - // FIXME the watch will miss files that changed during the first build - - // server watch is implemented inside Server.Run, in contrast to this command - if watch { - events, err := site.WatchRebuild() - if err != nil { - return err - } - logger.label("Auto-regeneration:", "enabled for %q", site.SourceDir()) - for event := range events { - fmt.Print(event) - } - } else { - logger.label("Auto-regeneration:", "disabled. Use --watch to enable.") - } - return nil -} - -func cleanCommand(site *site.Site) error { - logger.label("Cleaner:", "Removing %s...", site.DestDir()) - return site.Clean() -} - -// benchmarkCommand builds the site repeatedly until at least 10 seconds has elapsed, -// and reports the trial times. Empirically, it the same mean but low variance as using -// a separate benchmark runner that invokes a new gojekyll process each time. -func benchmarkCommand() (err error) { - startTime := time.Now() - samples := []float64{} - for i := 0; time.Since(startTime) < 10*time.Second; i++ { - sampleStart := time.Now() - site, err := loadSite(*source, options) - if err != nil { - return err - } - _, err = site.Build() - if err != nil { - return err - } - dur := time.Since(sampleStart).Seconds() - samples = append(samples, dur) - quiet = true - fmt.Printf("Run #%d; %.1fs elapsed\n", i+1, time.Since(commandStartTime).Seconds()) - } - median, _ := stats.Median(samples) - stddev, _ := stats.StandardDeviationSample(samples) - fmt.Printf("%d samples @ %.2fs ± %.2fs\n", len(samples), median, stddev) - return nil -} - -func routesCommand(site *site.Site) error { - logger.label("Routes:", "") - urls := []string{} - for u, p := range site.Routes { - if !(*dynamicRoutes && p.Static()) { - urls = append(urls, u) - } - } - sort.Strings(urls) - for _, u := range urls { - filename := site.Routes[u].SourcePath() - fmt.Printf(" %s -> %s\n", u, filename) - } - return nil -} - -func renderCommand(site *site.Site) error { - p, err := pageFromPathOrRoute(site, *renderPath) - if err != nil { - return err - } - logger.path("Render:", filepath.Join(site.SourceDir(), p.SourcePath())) - logger.label("URL:", p.Permalink()) - logger.label("Content:", "") - return site.WriteDocument(os.Stdout, p) -} - // If path starts with /, it's a URL path. Else it's a file path relative // to the site source directory. func pageFromPathOrRoute(s *site.Site, path string) (pages.Document, error) { @@ -135,52 +29,3 @@ func pageFromPathOrRoute(s *site.Site, path string) (pages.Document, error) { return page, nil } } - -func serveCommand(site *site.Site) error { - server := server.Server{Site: site} - return server.Run(*open, func(label, value string) { - logger.label(label, value) - }) -} - -func variablesCommand(site *site.Site) (err error) { - var data interface{} - switch { - case strings.HasPrefix(*variablePath, "site"): - data, err = utils.FollowDots(site, strings.Split(*variablePath, ".")[1:]) - if err != nil { - return - } - case *variablePath != "": - data, err = pageFromPathOrRoute(site, *variablePath) - if err != nil { - return - } - default: - data = site - } - data = liquid.FromDrop(data) - if m, ok := data.(map[string]interface{}); ok { - for k, v := range m { - if b, ok := v.([]byte); ok { - m[k] = string(b) - } - } - } - b, err := yaml.Marshal(data) - if err != nil { - return err - } - logger.label("Variables:", "") - fmt.Println(string(b)) - return nil -} - -func versionCommand() error { - var d string - if !BuildTime.IsZero() { - d = BuildTime.Format(" (Build time: 2006-01-02T15:04)") - } - fmt.Printf("gojekyll version %s%s\n", Version, d) - return nil -} diff --git a/commands/flags.go b/commands/flags.go index 78019e2..90a9479 100644 --- a/commands/flags.go +++ b/commands/flags.go @@ -20,27 +20,6 @@ var ( _ = app.Flag("future", "Publishes posts with a future date").Action(boolVar("future", &options.Future)).Bool() _ = app.Flag("unpublished", "Render posts that were marked as unpublished").Action(boolVar("unpublished", &options.Unpublished)).Bool() versionFlag = app.Flag("version", "Print the name and version").Short('v').Bool() - - build = app.Command("build", "Build your site").Alias("b") - clean = app.Command("clean", "Clean the site (removes site output) without building.") - - benchmark = app.Command("benchmark", "Repeat build for ten seconds. Implies --profile.") - - render = app.Command("render", "Render a file or URL path to standard output") - renderPath = render.Arg("PATH", "Path or URL").String() - - routes = app.Command("routes", "Display site permalinks and associated files") - dynamicRoutes = routes.Flag("dynamic", "Only show routes to non-static files").Bool() - - serve = app.Command("serve", "Serve your site locally").Alias("server").Alias("s") - open = serve.Flag("open-url", "Launch your site in a browser").Short('o').Bool() - _ = serve.Flag("host", "Host to bind to").Short('H').Action(stringVar("host", &options.Host)).String() - _ = serve.Flag("port", "Port to listen on").Short('P').Action(intVar("port", &options.Port)).Int() - - variables = app.Command("variables", "Display a file or URL path's variables").Alias("v").Alias("var").Alias("vars") - variablePath = variables.Arg("PATH", "Path, URL, site, or site...").String() - - versionCmd = app.Command("version", "Print the name and version") ) func init() { @@ -48,7 +27,6 @@ func init() { app.Flag("profile", "Create a Go pprof CPU profile").BoolVar(&profile) app.Flag("quiet", "Silence (some) output.").Short('q').BoolVar(&quiet) _ = app.Flag("verbose", "Print verbose output.").Short('V').Action(boolVar("verbose", &options.Verbose)).Bool() - build.Flag("dry-run", "Dry run").Short('n').BoolVar(&options.DryRun) // these flags are just present on build and serve, but I don't see a DRY way to say this app.Flag("incremental", "Enable incremental rebuild.").Short('I').Action(boolVar("incremental", &options.Incremental)).Bool() diff --git a/commands/render.go b/commands/render.go new file mode 100644 index 0000000..248d3f0 --- /dev/null +++ b/commands/render.go @@ -0,0 +1,22 @@ +package commands + +import ( + "os" + "path/filepath" + + "github.com/osteele/gojekyll/site" +) + +var render = app.Command("render", "Render a file or URL path to standard output") +var renderPath = render.Arg("PATH", "Path or URL").String() + +func renderCommand(site *site.Site) error { + p, err := pageFromPathOrRoute(site, *renderPath) + if err != nil { + return err + } + logger.path("Render:", filepath.Join(site.SourceDir(), p.SourcePath())) + logger.label("URL:", p.Permalink()) + logger.label("Content:", "") + return site.WriteDocument(os.Stdout, p) +} diff --git a/commands/routes.go b/commands/routes.go new file mode 100644 index 0000000..84735a0 --- /dev/null +++ b/commands/routes.go @@ -0,0 +1,27 @@ +package commands + +import ( + "fmt" + "sort" + + "github.com/osteele/gojekyll/site" +) + +var routes = app.Command("routes", "Display site permalinks and associated files") +var dynamicRoutes = routes.Flag("dynamic", "Only show routes to non-static files").Bool() + +func routesCommand(site *site.Site) error { + logger.label("Routes:", "") + urls := []string{} + for u, p := range site.Routes { + if !(*dynamicRoutes && p.Static()) { + urls = append(urls, u) + } + } + sort.Strings(urls) + for _, u := range urls { + filename := site.Routes[u].SourcePath() + fmt.Printf(" %s -> %s\n", u, filename) + } + return nil +} diff --git a/commands/serve.go b/commands/serve.go new file mode 100644 index 0000000..3e44864 --- /dev/null +++ b/commands/serve.go @@ -0,0 +1,20 @@ +package commands + +import ( + "github.com/osteele/gojekyll/server" + "github.com/osteele/gojekyll/site" +) + +var ( + serve = app.Command("serve", "Serve your site locally").Alias("server").Alias("s") + open = serve.Flag("open-url", "Launch your site in a browser").Short('o').Bool() + _ = serve.Flag("host", "Host to bind to").Short('H').Action(stringVar("host", &options.Host)).String() + _ = serve.Flag("port", "Port to listen on").Short('P').Action(intVar("port", &options.Port)).Int() +) + +func serveCommand(site *site.Site) error { + server := server.Server{Site: site} + return server.Run(*open, func(label, value string) { + logger.label(label, value) + }) +} diff --git a/commands/variables.go b/commands/variables.go new file mode 100644 index 0000000..cf3d3e5 --- /dev/null +++ b/commands/variables.go @@ -0,0 +1,47 @@ +package commands + +import ( + "fmt" + "strings" + + "github.com/osteele/gojekyll/site" + "github.com/osteele/gojekyll/utils" + "github.com/osteele/liquid" + yaml "gopkg.in/yaml.v1" +) + +var variables = app.Command("variables", "Display a file or URL path's variables").Alias("v").Alias("var").Alias("vars") +var variablePath = variables.Arg("PATH", "Path, URL, site, or site...").String() + +func variablesCommand(site *site.Site) (err error) { + var data interface{} + switch { + case strings.HasPrefix(*variablePath, "site"): + data, err = utils.FollowDots(site, strings.Split(*variablePath, ".")[1:]) + if err != nil { + return + } + case *variablePath != "": + data, err = pageFromPathOrRoute(site, *variablePath) + if err != nil { + return + } + default: + data = site + } + data = liquid.FromDrop(data) + if m, ok := data.(map[string]interface{}); ok { + for k, v := range m { + if b, ok := v.([]byte); ok { + m[k] = string(b) + } + } + } + b, err := yaml.Marshal(data) + if err != nil { + return err + } + logger.label("Variables:", "") + fmt.Println(string(b)) + return nil +} diff --git a/commands/version.go b/commands/version.go index d957e97..9b5ffeb 100644 --- a/commands/version.go +++ b/commands/version.go @@ -26,3 +26,14 @@ func init() { } } } + +var versionCmd = app.Command("version", "Print the name and version") + +func versionCommand() error { + var d string + if !BuildTime.IsZero() { + d = BuildTime.Format(" (Build time: 2006-01-02T15:04)") + } + fmt.Printf("gojekyll version %s%s\n", Version, d) + return nil +}