Skip to content

Commit 0f3f041

Browse files
add simple cli interface
1 parent 204da08 commit 0f3f041

File tree

4 files changed

+102
-57
lines changed

4 files changed

+102
-57
lines changed

README.md

+16-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,22 @@ config.xml # Configuration file
2424

2525
## Commands
2626

27-
- `gozer` Builds the site into `build/`
28-
- `gozer serve` Builds the site into `build/` and starts an HTTP server on `localhost:8080` serving the `build/` directory.
27+
Run `gozer` without any arguments to view the help text.
28+
29+
```
30+
Gozer - a fast & simple static site generator
31+
32+
Usage: gozer [OPTIONS] <COMMAND>
33+
34+
Commands:
35+
build Deletes the output directory if there is one and builds the site
36+
serve Builds the site and starts an HTTP server on http://localhost:8080
37+
38+
Options:
39+
-r, --root <ROOT> Directory to use as root of project (default: .)
40+
-c, --config <CONFIG> Path to confiruation file (default: config.xml)
41+
```
42+
2943

3044
## Content files
3145

log.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ func (l *logger) Info(format string, value ...any) {
2121
}
2222

2323
func (l *logger) Fatal(format string, value ...any) {
24-
stdlog.Printf("\u001B[0;31m[FATAL]\u001B[0;39m "+format, value)
24+
stdlog.Fatalf("\u001B[0;31m[FATAL]\u001B[0;39m "+format, value)
2525
}

main.go

+84-53
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
_ "embed"
77
"encoding/xml"
88
"errors"
9+
"flag"
910
"fmt"
1011
"github.com/yuin/goldmark"
1112
"github.com/yuin/goldmark/renderer/html"
@@ -46,34 +47,9 @@ type Page struct {
4647
Filepath string
4748
}
4849

49-
func NewPageFromFile(file string, baseUrl string) (*Page, error) {
50-
info, err := os.Stat(file)
51-
if err != nil {
52-
return nil, err
53-
}
54-
55-
urlPath, datePublished := parseFilename(file)
56-
57-
p := Page{
58-
Filepath: file,
59-
UrlPath: urlPath,
60-
DatePublished: datePublished,
61-
DateModified: info.ModTime(),
62-
Template: "default.html",
63-
}
64-
65-
p.Permalink = baseUrl + p.UrlPath
66-
67-
if err := parseFrontMatter(&p); err != nil {
68-
return nil, err
69-
}
70-
71-
return &p, nil
72-
}
73-
7450
// parseFilename parses the URL path and optional date component from the given file path
75-
func parseFilename(path string) (string, time.Time) {
76-
path = strings.TrimPrefix(path, "content/")
51+
func parseFilename(path string, rootDir string) (string, time.Time) {
52+
path = strings.TrimPrefix(path, rootDir+"content/")
7753
path = strings.TrimSuffix(path, ".md")
7854
path = strings.TrimSuffix(path, "index")
7955

@@ -172,7 +148,7 @@ func (s *Site) buildPage(p *Page) error {
172148
}
173149

174150
dest := p.UrlPath + "index.html"
175-
if err := os.MkdirAll("build/"+filepath.Dir(dest), 0755); err != nil {
151+
if err := os.MkdirAll("build/"+filepath.Dir(dest), 0655); err != nil {
176152
return err
177153
}
178154

@@ -191,35 +167,55 @@ func (s *Site) buildPage(p *Page) error {
191167
"Page": p,
192168
"Posts": s.posts,
193169
"Pages": s.pages,
194-
"SiteUrl": s.config.SiteUrl,
170+
"SiteUrl": s.SiteUrl,
195171
"Title": p.Title,
196172
"Content": template.HTML(content),
197173
})
198174
}
199175

200176
type Site struct {
201-
pages []Page
202-
posts []Page
203-
config *Config
177+
pages []Page
178+
posts []Page
179+
180+
SiteUrl string
181+
RootDir string
204182
}
205183

206184
func (s *Site) AddPageFromFile(file string) error {
207-
p, err := NewPageFromFile(file, s.config.SiteUrl)
185+
info, err := os.Stat(file)
208186
if err != nil {
209187
return err
210188
}
211189

212-
s.pages = append(s.pages, *p)
190+
urlPath, datePublished := parseFilename(file, s.RootDir)
191+
192+
p := Page{
193+
Filepath: file,
194+
UrlPath: urlPath,
195+
DatePublished: datePublished,
196+
DateModified: info.ModTime(),
197+
Template: "default.html",
198+
}
199+
200+
p.Permalink = s.SiteUrl + p.UrlPath
201+
202+
if err := parseFrontMatter(&p); err != nil {
203+
return err
204+
}
205+
206+
s.pages = append(s.pages, p)
207+
208+
// every page with a date is assumed to be a blog post
213209
if !p.DatePublished.IsZero() {
214-
s.posts = append(s.posts, *p)
210+
s.posts = append(s.posts, p)
215211
}
216212

217213
return nil
218214
}
219215

220-
func (s *Site) readContent() error {
216+
func (s *Site) readContent(dir string) error {
221217
// walk over files in "content" directory
222-
err := filepath.WalkDir("content", func(file string, d fs.DirEntry, err error) error {
218+
err := filepath.WalkDir(dir, func(file string, d fs.DirEntry, err error) error {
223219
if d.IsDir() {
224220
return nil
225221
}
@@ -253,7 +249,7 @@ func (s *Site) createSitemap() error {
253249
urls := make([]Url, 0, len(s.pages))
254250
for _, p := range s.pages {
255251
urls = append(urls, Url{
256-
Loc: s.config.SiteUrl + p.UrlPath,
252+
Loc: s.SiteUrl + p.UrlPath,
257253
LastMod: p.DateModified.Format(time.RFC1123Z),
258254
})
259255
}
@@ -325,10 +321,10 @@ func (s *Site) createRSSFeed() error {
325321

326322
items = append(items, Item{
327323
Title: p.Title,
328-
Link: s.config.SiteUrl + p.UrlPath,
324+
Link: s.SiteUrl + p.UrlPath,
329325
Description: pageContent,
330326
PubDate: p.DatePublished.Format(time.RFC1123Z),
331-
GUID: s.config.SiteUrl + p.UrlPath,
327+
GUID: s.SiteUrl + p.UrlPath,
332328
})
333329
}
334330

@@ -337,7 +333,7 @@ func (s *Site) createRSSFeed() error {
337333
Atom: "http://www.w3.org/2005/Atom",
338334
Channel: Channel{
339335
Title: "Site title",
340-
Link: s.config.SiteUrl,
336+
Link: s.SiteUrl,
341337
Generator: "Gosite",
342338
LastBuildDate: time.Now().Format(time.RFC1123Z),
343339
Items: items,
@@ -361,8 +357,8 @@ func (s *Site) createRSSFeed() error {
361357
return nil
362358
}
363359

364-
func parseConfig() (*Config, error) {
365-
wr, err := os.Open("config.xml")
360+
func parseConfig(file string) (*Config, error) {
361+
wr, err := os.Open(file)
366362
if err != nil {
367363
return nil, err
368364
}
@@ -378,24 +374,59 @@ func parseConfig() (*Config, error) {
378374
}
379375

380376
func main() {
377+
configFile := "config.xml"
378+
rootPath := ""
379+
380+
// parse flags
381+
flag.StringVar(&configFile, "config", configFile, "")
382+
flag.StringVar(&configFile, "c", configFile, "")
383+
flag.StringVar(&rootPath, "root", rootPath, "")
384+
flag.StringVar(&rootPath, "r", rootPath, "")
385+
flag.Parse()
386+
387+
command := os.Args[len(os.Args)-1]
388+
if command != "build" && command != "serve" {
389+
fmt.Printf(`Gozer - a fast & simple static site generator
390+
391+
Usage: gozer [OPTIONS] <COMMAND>
392+
393+
Commands:
394+
build Deletes the output directory if there is one and builds the site
395+
serve Builds the site and starts an HTTP server on http://localhost:8080
396+
397+
Options:
398+
-r, --root <ROOT> Directory to use as root of project (default: .)
399+
-c, --config <CONFIG> Path to confiruation file (default: config.xml)
400+
`)
401+
return
402+
}
403+
404+
if rootPath != "" {
405+
rootPath = strings.TrimSuffix(rootPath, "/") + "/"
406+
}
407+
381408
var err error
382409
timeStart := time.Now()
383410

384-
templates, err = template.ParseFS(os.DirFS("templates/"), "*.html")
411+
templates, err = template.ParseFS(os.DirFS(rootPath+"templates/"), "*.html")
385412
if err != nil {
386413
log.Fatal("Error reading templates/ directory: %s", err)
387414
}
388415

389-
site := Site{}
390-
391416
// read config.xml
392-
site.config, err = parseConfig()
417+
var config *Config
418+
config, err = parseConfig(rootPath + configFile)
393419
if err != nil {
394-
log.Fatal("Error reading config.xml: %w\n", err)
420+
log.Fatal("Error reading configuration file at %s: %w\n", rootPath+configFile, err)
421+
}
422+
423+
site := Site{
424+
SiteUrl: config.SiteUrl,
425+
RootDir: rootPath,
395426
}
396427

397428
// read content
398-
if err := site.readContent(); err != nil {
429+
if err := site.readContent(rootPath + "content/"); err != nil {
399430
log.Fatal("Error reading content/: %s", err)
400431
}
401432

@@ -406,24 +437,24 @@ func main() {
406437
}
407438
}
408439

409-
// create sitemap
440+
// create XML sitemap
410441
if err := site.createSitemap(); err != nil {
411442
log.Warn("Error creating sitemap: %s\n", err)
412443
}
413444

414-
// create sitemap
445+
// create RSS feed
415446
if err := site.createRSSFeed(); err != nil {
416447
log.Warn("Error creating RSS feed: %s\n", err)
417448
}
418449

419450
// static files
420-
if err := copyDirRecursively("public", "build"); err != nil {
451+
if err := copyDirRecursively(rootPath+"public/", "build/"); err != nil {
421452
log.Fatal("Error copying public/ directory: %s", err)
422453
}
423454

424455
log.Info("Built site containing %d pages in %d ms\n", len(site.pages), time.Since(timeStart).Milliseconds())
425456

426-
if len(os.Args) > 1 && os.Args[1] == "serve" {
457+
if command == "serve" {
427458
log.Info("Listening on http://localhost:8080\n")
428459
log.Fatal("Hello", http.ListenAndServe("localhost:8080", http.FileServer(http.Dir("build/"))))
429460
}

main_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestFilepathToUrlpath(t *testing.T) {
5050
}
5151

5252
for _, tc := range tests {
53-
urlPath, datePublished := parseFilename(tc.input)
53+
urlPath, datePublished := parseFilename(tc.input, "")
5454
if urlPath != tc.expectedUrlPath {
5555
t.Errorf("expected %v, got %v", tc.expectedUrlPath, urlPath)
5656
}

0 commit comments

Comments
 (0)