mirror of
https://github.com/danog/gojekyll.git
synced 2024-11-30 05:58:59 +01:00
226 lines
5.2 KiB
Go
226 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/urfave/cli"
|
|
|
|
_ "gopkg.in/urfave/cli.v1"
|
|
yaml "gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// Command-line options
|
|
var options struct {
|
|
useHardLinks bool
|
|
dryRun bool
|
|
}
|
|
|
|
// This is the longest label. Pull it out here so we can both use it, and measure it for alignment.
|
|
const configurationFileLabel = "Configuration file:"
|
|
|
|
func printSetting(label string, value string) {
|
|
fmt.Printf("%s %s\n", LeftPad(label, len(configurationFileLabel)), value)
|
|
}
|
|
|
|
func printPathSetting(label string, path string) {
|
|
path, err := filepath.Abs(path)
|
|
if err != nil {
|
|
panic("Couldn't convert to absolute path")
|
|
}
|
|
printSetting(label, path)
|
|
}
|
|
|
|
var commandStartTime = time.Now()
|
|
|
|
func main() {
|
|
var source, destination string
|
|
|
|
app := cli.NewApp()
|
|
app.Name = "gojekyll"
|
|
app.Usage = "a (maybe someday) Jekyll-compatible blog generator in Go"
|
|
|
|
app.Flags = []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "source",
|
|
Value: ".",
|
|
Usage: "Source directory",
|
|
Destination: &source,
|
|
},
|
|
cli.StringFlag{
|
|
Name: "destination",
|
|
Value: "",
|
|
Usage: "Destination directory",
|
|
Destination: &destination,
|
|
},
|
|
}
|
|
|
|
// Load the site specified at destination into the site global, and print the common banner settings.
|
|
loadSite := func() error {
|
|
if err := site.ReadConfiguration(source, destination); err != nil {
|
|
return err
|
|
}
|
|
if site.ConfigFile != nil {
|
|
printPathSetting(configurationFileLabel, *site.ConfigFile)
|
|
} else {
|
|
printSetting(configurationFileLabel, "none")
|
|
|
|
}
|
|
printPathSetting("Source:", site.Source)
|
|
return site.ReadFiles()
|
|
}
|
|
|
|
// Given a subcommand function, load the site and then call the subcommand.
|
|
withSite := func(cmd func(c *cli.Context) error) func(c *cli.Context) error {
|
|
return func(c *cli.Context) error {
|
|
if err := loadSite(); err != nil {
|
|
return cli.NewExitError(err, 1)
|
|
}
|
|
if err := cmd(c); err != nil {
|
|
return cli.NewExitError(err, 1)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
app.Commands = []cli.Command{
|
|
{
|
|
Name: "server",
|
|
Aliases: []string{"s", "serve"},
|
|
Usage: "Serve your site locally",
|
|
Action: withSite(serveCommand),
|
|
},
|
|
{
|
|
Name: "build",
|
|
Aliases: []string{"b"},
|
|
Usage: "Build your site",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "dry-run, n",
|
|
Usage: "Dry run",
|
|
Destination: &options.dryRun,
|
|
},
|
|
},
|
|
Action: withSite(buildCommand),
|
|
},
|
|
{
|
|
Name: "data",
|
|
Aliases: []string{"b"},
|
|
Action: withSite(dataCommand),
|
|
},
|
|
{
|
|
Name: "routes",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "dynamic",
|
|
Usage: "Only show routes to non-static files",
|
|
},
|
|
},
|
|
Action: withSite(routesCommand),
|
|
},
|
|
{
|
|
Name: "render",
|
|
Action: withSite(renderCommand),
|
|
},
|
|
}
|
|
|
|
_ = app.Run(os.Args)
|
|
}
|
|
|
|
func buildCommand(c *cli.Context) error {
|
|
printPathSetting("Destination:", site.Destination)
|
|
printSetting("Generating...", "")
|
|
count, err := site.Build()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
elapsed := time.Since(commandStartTime)
|
|
printSetting("", fmt.Sprintf("created %d files in %.2fs.", count, elapsed.Seconds()))
|
|
return nil
|
|
}
|
|
|
|
func serveCommand(c *cli.Context) error {
|
|
return server()
|
|
}
|
|
|
|
func dataCommand(c *cli.Context) error {
|
|
p, err := cliPage(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printSetting("Variables:", "")
|
|
// The YAML representation including collections is impractically large for debugging.
|
|
// (Actually it's circular, which the yaml package can't handle.)
|
|
// Neuter it. This destroys it as Liquid data, but that's okay in this context.
|
|
siteData := site.Variables
|
|
for _, c := range site.Collections {
|
|
siteData[c.Name] = fmt.Sprintf("<elided page data for %d items>", len(siteData[c.Name].([]VariableMap)))
|
|
}
|
|
b, _ := yaml.Marshal(p.DebugVariables())
|
|
fmt.Println(string(b))
|
|
return nil
|
|
}
|
|
|
|
func routesCommand(c *cli.Context) error {
|
|
printSetting("Routes:", "")
|
|
urls := []string{}
|
|
for u, p := range site.Paths {
|
|
if !(c.Bool("dynamic") && p.Static()) {
|
|
urls = append(urls, u)
|
|
}
|
|
}
|
|
sort.Strings(urls)
|
|
for _, u := range urls {
|
|
fmt.Printf(" %s -> %s\n", u, site.Paths[u].Path())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func renderCommand(c *cli.Context) error {
|
|
page, err := cliPage(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printPathSetting("Render:", filepath.Join(site.Source, page.Path()))
|
|
printSetting("URL:", page.Permalink())
|
|
printSetting("Content:", "")
|
|
return page.Write(os.Stdout)
|
|
}
|
|
|
|
// If path starts with /, it's a URL path. Else it's a file path relative
|
|
// to the site source directory.
|
|
func cliPage(c *cli.Context) (page Page, err error) {
|
|
path := "/"
|
|
if c.NArg() > 0 {
|
|
path = c.Args().Get(0)
|
|
}
|
|
if strings.HasPrefix(path, "/") {
|
|
page = site.Paths[path]
|
|
if page == nil {
|
|
err = &os.PathError{Op: "render", Path: path, Err: errors.New("the site does not include a file with this URL path")}
|
|
}
|
|
} else {
|
|
page = site.FindPageByFilePath(path)
|
|
if page == nil {
|
|
err = &os.PathError{Op: "render", Path: path, Err: errors.New("no such file")}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// FindPageByFilePath returns a Page or nil, referenced by relative path.
|
|
func (s *Site) FindPageByFilePath(path string) Page {
|
|
for _, p := range s.Paths {
|
|
if p.Path() == path {
|
|
return p
|
|
}
|
|
}
|
|
return nil
|
|
}
|