Skip to content

Commit

Permalink
Merge branch 'master' into add-rss-support
Browse files Browse the repository at this point in the history
  • Loading branch information
roblillack committed Jul 12, 2022
2 parents 4a9f517 + 04d8980 commit 10e0ad0
Show file tree
Hide file tree
Showing 11 changed files with 685 additions and 384 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A static site generator for the long run.
[![Go Report Card](https://goreportcard.com/badge/github.com/roblillack/tack)](https://goreportcard.com/report/github.com/roblillack/tack)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/roblillack/tack?label=latest%20version)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6239/badge)](https://bestpractices.coreinfrastructure.org/projects/6239)

The project's goal is to create and maintain a sustainable tool that does the
(arguably pretty easy) job of filling HTML templates with content well enough
Expand Down
2 changes: 2 additions & 0 deletions commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,7 @@ func newTackerWithArgs(args ...string) (*core.Tacker, error) {
t.DebugLogger = nil
}

t.Strict = StrictMode

return t, nil
}
2 changes: 2 additions & 0 deletions commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package commands
import "flag"

var DebugMode bool
var StrictMode bool

func init() {
flag.BoolVar(&DebugMode, "d", false, "Print debugging information during site builds")
flag.BoolVar(&StrictMode, "s", false, "Enable strict mode (fails when trying to render undefined variables)")
}
14 changes: 14 additions & 0 deletions core/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ type fileInfo struct {
ModTime time.Time
}

// Checkpoint is a full listing of all files and their respective modification
// timestamps of a specific directory.
type Checkpoint struct {
files []fileInfo
}

// Equals compares this checkpoint to another one. If an empty Checkpoint to
// compare with is given, or both structures do not share the exact same files,
// or any of the files has a different modification timestamp, they will not
// be regarded as “equal.”
func (c *Checkpoint) Equals(o *Checkpoint) bool {
if len(c.files) != len(o.files) {
return false
Expand All @@ -29,6 +35,9 @@ func (c *Checkpoint) Equals(o *Checkpoint) bool {
return true
}

// Checkpoint stats all the files within the source directory and stores the
// names and modification timestamps so we're able to compare this list with a
// future checkpoint without the need to set up file watchers.
func (t *Tacker) Checkpoint() (*Checkpoint, error) {
outputDir := filepath.Join(t.BaseDir, TargetDir)
checkpoint := &Checkpoint{}
Expand All @@ -54,6 +63,11 @@ func (t *Tacker) Checkpoint() (*Checkpoint, error) {
return checkpoint, nil
}

// HasChanges will create a fresh Checkpoint for the current Tacker and
// compare it to the previous one. The functions returns if there are
// changes between the checkpoints (or none was provided in the first)
// place, as well the new Checkpoint, and any error which might have
// occurred while creating it.
func (t *Tacker) HasChanges(prev *Checkpoint) (bool, *Checkpoint, error) {
if prev == nil {
c, err := t.Checkpoint()
Expand Down
29 changes: 28 additions & 1 deletion core/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
var enumerationRegex = regexp.MustCompile(`^[0-9]+\.\s*`)
var dateRegex = regexp.MustCompile(`^([0-9]{4}-[0-9]{2}-[0-9]{2})[\.\-]\s*`)

// Page is the main structure holding page content. Some of the fields are
// only available after the page has been initialized using Init().
type Page struct {
// available directly after construction.
Slug string
Expand All @@ -41,6 +43,9 @@ type Page struct {
addTagPages bool
}

// NewPage creates a new page structure for the specified Tacker
// based on the given path of the page's directory. No file i/o
// will take place here just yet.
func NewPage(tacker *Tacker, realPath string) *Page {
fn := filepath.Base(realPath)
if realPath == filepath.Join(tacker.BaseDir, ContentDir) {
Expand Down Expand Up @@ -70,10 +75,16 @@ func NewPage(tacker *Tacker, realPath string) *Page {
return page
}

// Root determines if the current page is the root page of the website being
// tacked. The root page might be stored in the top-level content directory
// or a directory with the slug "index" just below the top level.
func (p *Page) Root() bool {
return p.DiskPath == filepath.Join(p.Tacker.BaseDir, ContentDir) || p.Slug == "index" && filepath.Dir(p.DiskPath) == filepath.Join(p.Tacker.BaseDir, ContentDir)
return p.DiskPath == filepath.Join(p.Tacker.BaseDir, ContentDir) ||
p.Slug == "index" && filepath.Dir(p.DiskPath) == filepath.Join(p.Tacker.BaseDir, ContentDir)
}

// Permalink return an absolute path to the current page based on its and it's
// ancestor pages' slugs. The Page must be Init()ed prior to calling this.
func (p *Page) Permalink() string {
if p.Parent == nil {
if p.Root() {
Expand All @@ -85,6 +96,9 @@ func (p *Page) Permalink() string {
return path.Join(p.Parent.Permalink(), p.Slug)
}

// TargetDir returns the (absolute) path to the directory which will contain
// this page's HTML and further assets. The Page must be Init()ed prior to
// calling this.
func (p *Page) TargetDir() []string {
if p.Parent == nil {
if p.Root() {
Expand All @@ -96,6 +110,9 @@ func (p *Page) TargetDir() []string {
return append(p.Parent.TargetDir(), TagSlug(p.Slug))
}

// Ancestors returns a slice of all of this page's ancestors, starting with
// the immediate parent page and ending with the root page. The Page must be
// Init()ed prior to calling this.
func (p *Page) Ancestors() []*Page {
r := []*Page{}

Expand All @@ -106,6 +123,8 @@ func (p *Page) Ancestors() []*Page {
return r
}

// Siblings returns a slice of all sibling pages of the current one. The Page
// must be Init()ed prior to calling this.
func (p *Page) Siblings() []*Page {
r := []*Page{}

Expand All @@ -118,10 +137,15 @@ func (p *Page) Siblings() []*Page {
return r
}

// Post returns `true` if the current page has a post date defined as
// part of the content directory name.
func (p *Page) Post() bool {
return !p.Date.IsZero()
}

// Init initializes the page content, by reading the content and metadata from
// the disk, resolving the used template and creating the necessary structures
// to reference other pages from this one.
func (p *Page) Init() error {
parent := filepath.Dir(p.DiskPath)
siblingsAndMe := []*Page{}
Expand Down Expand Up @@ -240,6 +264,9 @@ func (p *Page) addVariables(md map[string]interface{}) error {
return nil
}

// Generate renders the current page given all the content and metadata read
// from disk and the configured template. If not done already, calling this
// function will initialize the page using Init().
func (p *Page) Generate() error {
if !p.inited {
if err := p.Init(); err != nil {
Expand Down
25 changes: 21 additions & 4 deletions core/tacker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"time"

"github.com/cbroglie/mustache"
"gopkg.in/yaml.v2"
yaml "gopkg.in/yaml.v2"
)

const ContentDir = "content"
Expand All @@ -25,6 +25,9 @@ var TemplateExtensions = []string{"mustache", "mu", "stache"}
var MetadataExtensions = []string{"yaml", "yml"}
var MarkupExtensions = []string{"md", "mkd"}

// Tacker is the main configuration structure of tack. A Tacker is used by
// the command-line interface, or programmatically when tacking a website from
// within third-party code.
type Tacker struct {
BaseDir string
Metadata map[string]interface{}
Expand All @@ -36,12 +39,16 @@ type Tacker struct {
TagIndex *Page
Logger *log.Logger
DebugLogger *log.Logger
<<<<<<< HEAD
BuildTime time.Time
=======
Strict bool
>>>>>>> master
}

// NewTacker creates a new tack configuration structure based on the files
// found in the directory provided.
func NewTacker(dir string) (*Tacker, error) {
mustache.AllowMissingVariables = true

if !DirExists(dir) {
return nil, fmt.Errorf("directory does not exist: %s", dir)
}
Expand All @@ -65,6 +72,7 @@ func NewTacker(dir string) (*Tacker, error) {
return t, nil
}

// Reload re-reads all site content and re-builds the page structure.
func (t *Tacker) Reload() error {
t.TagIndex = nil
t.Tags = nil
Expand Down Expand Up @@ -163,9 +171,18 @@ func (t *Tacker) Debug(format string, args ...interface{}) {
t.DebugLogger.Printf(format+"\n", args...)
}

// Tack is the main “tacking” functionality: All pages are rendered into the
// output directory by filling the respective templates with the page content.
func (t *Tacker) Tack() error {
t.BuildTime = time.Now()
t.Log("Tacking up %s (%d pages)", t.BaseDir, len(t.Pages))

mustache.AllowMissingVariables = !t.Strict
strictModeOn := ""
if t.Strict {
strictModeOn = " in strict mode"
}

t.Log("Tacking up %s (%d pages)%s", t.BaseDir, len(t.Pages), strictModeOn)

if _, err := os.Stat(filepath.Join(t.BaseDir, TargetDir)); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
Expand Down
38 changes: 34 additions & 4 deletions core/tacker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,41 @@ func TestTacker(t *testing.T) {
if strings.HasPrefix(filepath.Base(site), ".") {
continue
}
tacker, err := NewTacker(site)
assert.NoError(t, err)
if filepath.Base(site) != "minimal-blog-with-tags-below-index" {
continue
}
passStrict := map[string]struct{}{
"helloworld": {},
"helloworld-index-not-in-root": {},
"minimal": {},
"minimal-with-nav": {},
"test-copying-assets": {},
"test-different-file-extensions": {},
"test-page-variable-overrides-site-metadata": {},
"test-page-variable-overrides-template": {},
}
for _, strictMode := range []bool{false, true} {
tacker, err := NewTacker(site)
tacker.Strict = strictMode
assert.NoError(t, err)

err = tacker.Tack()
if !strictMode && err != nil {
t.Fatalf("Unable to tack site %s: %s", filepath.Base(site), err)
}

_, shouldPass := passStrict[filepath.Base(site)]
if strictMode && shouldPass && err != nil {
t.Fatalf("Site %s should be tackable in strict mode but is not: %s", filepath.Base(site), err)
} else if strictMode && err == nil && !shouldPass {
t.Fatalf("Site %s should not be tackable in strict mode but is!", filepath.Base(site))
} else if strictMode && err != nil && !shouldPass {
t.Logf("Got error tacking %s in strict mode as expected", filepath.Base(site))
continue
}

assert.NoError(t, tacker.Tack())
AssertDirEquals(t, filepath.Join(site, "output.expected"), filepath.Join(site, "output"))
AssertDirEquals(t, filepath.Join(site, "output.expected"), filepath.Join(site, "output"))
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ module github.com/roblillack/tack
go 1.16

require (
github.com/cbroglie/mustache v1.2.2
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/cbroglie/mustache v1.3.1
github.com/stretchr/testify v1.7.0
github.com/yuin/goldmark v1.3.7
github.com/yuin/goldmark-meta v1.0.0
Expand Down
Loading

0 comments on commit 10e0ad0

Please sign in to comment.