mirror of
https://github.com/facundoolano/jorge.git
synced 2024-12-25 21:58:28 +01:00
Add post command (#9)
* rename command to post * initial jorge post implementation * fixes * update comment * move funs around * move default front matter to constants * more docs * tweak defaults line breaks
This commit is contained in:
parent
2d52e13932
commit
20150cf7a3
6 changed files with 131 additions and 52 deletions
|
@ -7,42 +7,60 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"embed"
|
"embed"
|
||||||
|
|
||||||
"github.com/facundoolano/jorge/config"
|
"github.com/facundoolano/jorge/config"
|
||||||
"github.com/facundoolano/jorge/site"
|
"github.com/facundoolano/jorge/site"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed all:initfiles
|
//go:embed all:initfiles
|
||||||
var initfiles embed.FS
|
var initfiles embed.FS
|
||||||
var initConfig string = `name: "%s"
|
|
||||||
|
var INIT_CONFIG string = `name: "%s"
|
||||||
author: "%s"
|
author: "%s"
|
||||||
url: "%s"
|
url: "%s"
|
||||||
`
|
`
|
||||||
var initReadme string = `
|
var INIT_README string = `
|
||||||
# %s
|
# %s
|
||||||
|
|
||||||
A jorge blog by %s.
|
A jorge blog by %s.
|
||||||
`
|
`
|
||||||
|
var DEFAULT_FRONTMATTER string = `---
|
||||||
|
title: %s
|
||||||
|
date: %s
|
||||||
|
layout: post
|
||||||
|
lang: %s
|
||||||
|
tags: []
|
||||||
|
---
|
||||||
|
`
|
||||||
|
|
||||||
|
var DEFAULT_ORG_DIRECTIVES string = `#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: %s
|
||||||
|
`
|
||||||
|
|
||||||
const FILE_RW_MODE = 0777
|
const FILE_RW_MODE = 0777
|
||||||
|
|
||||||
|
// Initialize a new jorge project in the given directory,
|
||||||
|
// prompting for basic site config and creating default files.
|
||||||
func Init(projectDir string) error {
|
func Init(projectDir string) error {
|
||||||
if err := ensureEmptyProjectDir(projectDir); err != nil {
|
if err := ensureEmptyProjectDir(projectDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
siteName := prompt("site name")
|
siteName := Prompt("site name")
|
||||||
siteUrl := prompt("site url")
|
siteUrl := Prompt("site url")
|
||||||
siteAuthor := prompt("author")
|
siteAuthor := Prompt("author")
|
||||||
|
|
||||||
// creating config and readme files manually, since I want to use the supplied config values in their
|
// creating config and readme files manually, since I want to use the supplied config values in their
|
||||||
// contents. (I don't want to render liquid templates in the WalkDir below since some of the initfiles
|
// contents. (I don't want to render liquid templates in the WalkDir below since some of the initfiles
|
||||||
// are actual templates that should be left as is).
|
// are actual templates that should be left as is).
|
||||||
configFile := fmt.Sprintf(initConfig, siteName, siteAuthor, siteUrl)
|
configFile := fmt.Sprintf(INIT_CONFIG, siteName, siteAuthor, siteUrl)
|
||||||
readmeFile := fmt.Sprintf(initReadme, siteName, siteAuthor)
|
readmeFile := fmt.Sprintf(INIT_README, siteName, siteAuthor)
|
||||||
os.WriteFile(filepath.Join(projectDir, "config.yml"), []byte(configFile), site.FILE_RW_MODE)
|
os.WriteFile(filepath.Join(projectDir, "config.yml"), []byte(configFile), site.FILE_RW_MODE)
|
||||||
os.WriteFile(filepath.Join(projectDir, "README.md"), []byte(readmeFile), site.FILE_RW_MODE)
|
os.WriteFile(filepath.Join(projectDir, "README.md"), []byte(readmeFile), site.FILE_RW_MODE)
|
||||||
|
|
||||||
|
@ -80,10 +98,84 @@ func Init(projectDir string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fmt.Println("added", path)
|
||||||
return targetFile.Sync()
|
return targetFile.Sync()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new post template in the given site, with the given title,
|
||||||
|
// with pre-filled front matter.
|
||||||
|
func Post(root string, title string) error {
|
||||||
|
config, err := config.Load(root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
slug := slugify(title)
|
||||||
|
filename := strings.ReplaceAll(config.PostFormat, ":title", slug)
|
||||||
|
filename = strings.ReplaceAll(filename, ":year", fmt.Sprint(now.Year()))
|
||||||
|
filename = strings.ReplaceAll(filename, ":month", fmt.Sprint(int(now.Month())))
|
||||||
|
filename = strings.ReplaceAll(filename, ":day", fmt.Sprint(now.Day()))
|
||||||
|
path := filepath.Join(config.SrcDir, filename)
|
||||||
|
|
||||||
|
// ensure the dir already exists
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), FILE_RW_MODE); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if file already exists, prompt user for a different one
|
||||||
|
if _, err := os.Stat(path); os.IsExist(err) {
|
||||||
|
fmt.Printf("%s already exists\n", path)
|
||||||
|
filename = Prompt("filename")
|
||||||
|
path = filepath.Join(config.SrcDir, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the post front matter
|
||||||
|
content := fmt.Sprintf(DEFAULT_FRONTMATTER, title, now.Format(time.DateTime), config.Lang)
|
||||||
|
|
||||||
|
// org files need some extra boilerplate
|
||||||
|
if filepath.Ext(path) == ".org" {
|
||||||
|
content += fmt.Sprintf(DEFAULT_ORG_DIRECTIVES, config.Lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(path, []byte(content), FILE_RW_MODE); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("added", path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the files in src/ render them and copy the result to target/
|
||||||
|
func Build(root string) error {
|
||||||
|
config, err := config.Load(root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
site, err := site.Load(*config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.Build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt the user for a string value
|
||||||
|
func Prompt(label string) string {
|
||||||
|
// https://dev.to/tidalcloud/interactive-cli-prompts-in-go-3bj9
|
||||||
|
var s string
|
||||||
|
r := bufio.NewReader(os.Stdin)
|
||||||
|
for {
|
||||||
|
fmt.Fprint(os.Stderr, label+": ")
|
||||||
|
s, _ = r.ReadString('\n')
|
||||||
|
if s != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
|
|
||||||
func ensureEmptyProjectDir(projectDir string) error {
|
func ensureEmptyProjectDir(projectDir string) error {
|
||||||
if err := os.Mkdir(projectDir, 0777); err != nil {
|
if err := os.Mkdir(projectDir, 0777); err != nil {
|
||||||
// if it fails with dir already exist, check if it's empty
|
// if it fails with dir already exist, check if it's empty
|
||||||
|
@ -107,42 +199,15 @@ func ensureEmptyProjectDir(projectDir string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt the user for a string value
|
var nonWordRegex = regexp.MustCompile(`[^\w-]`)
|
||||||
func prompt(label string) string {
|
var whitespaceRegex = regexp.MustCompile(`\s+`)
|
||||||
// https://dev.to/tidalcloud/interactive-cli-prompts-in-go-3bj9
|
|
||||||
var s string
|
func slugify(title string) string {
|
||||||
r := bufio.NewReader(os.Stdin)
|
slug := strings.ToLower(title)
|
||||||
for {
|
slug = strings.TrimSpace(slug)
|
||||||
fmt.Fprint(os.Stderr, label+": ")
|
slug = norm.NFD.String(slug)
|
||||||
s, _ = r.ReadString('\n')
|
slug = whitespaceRegex.ReplaceAllString(slug, "-")
|
||||||
if s != "" {
|
slug = nonWordRegex.ReplaceAllString(slug, "")
|
||||||
break
|
|
||||||
}
|
return slug
|
||||||
}
|
|
||||||
return strings.TrimSpace(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() error {
|
|
||||||
// prompt for title
|
|
||||||
// slugify
|
|
||||||
// fail if file already exist
|
|
||||||
// create a new .org file with the slug
|
|
||||||
// add front matter and org options
|
|
||||||
fmt.Println("not implemented yet")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the files in src/ render them and copy the result to target/
|
|
||||||
func Build(root string) error {
|
|
||||||
config, err := config.Load(root)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
site, err := site.Load(*config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return site.Build()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ type Config struct {
|
||||||
DataDir string
|
DataDir string
|
||||||
|
|
||||||
SiteUrl string
|
SiteUrl string
|
||||||
SlugFormat string
|
PostFormat string
|
||||||
|
Lang string
|
||||||
|
|
||||||
Minify bool
|
Minify bool
|
||||||
LiveReload bool
|
LiveReload bool
|
||||||
|
@ -53,7 +54,8 @@ func Load(rootDir string) (*Config, error) {
|
||||||
LayoutsDir: filepath.Join(rootDir, "layouts"),
|
LayoutsDir: filepath.Join(rootDir, "layouts"),
|
||||||
IncludesDir: filepath.Join(rootDir, "includes"),
|
IncludesDir: filepath.Join(rootDir, "includes"),
|
||||||
DataDir: filepath.Join(rootDir, "data"),
|
DataDir: filepath.Join(rootDir, "data"),
|
||||||
SlugFormat: ":title",
|
PostFormat: "blog/:title.org",
|
||||||
|
Lang: "en",
|
||||||
Minify: true,
|
Minify: true,
|
||||||
LiveReload: false,
|
LiveReload: false,
|
||||||
LinkStatic: false,
|
LinkStatic: false,
|
||||||
|
@ -80,8 +82,11 @@ func Load(rootDir string) (*Config, error) {
|
||||||
if url, found := config.overrides["url"]; found {
|
if url, found := config.overrides["url"]; found {
|
||||||
config.SiteUrl = url.(string)
|
config.SiteUrl = url.(string)
|
||||||
}
|
}
|
||||||
if slug, found := config.overrides["url"]; found {
|
if format, found := config.overrides["post_format"]; found {
|
||||||
config.SlugFormat = slug.(string)
|
config.PostFormat = format.(string)
|
||||||
|
}
|
||||||
|
if format, found := config.overrides["lang"]; found {
|
||||||
|
config.Lang = format.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -10,6 +10,7 @@ require (
|
||||||
github.com/tdewolff/minify/v2 v2.20.16
|
github.com/tdewolff/minify/v2 v2.20.16
|
||||||
github.com/yuin/goldmark v1.7.0
|
github.com/yuin/goldmark v1.7.0
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||||
|
golang.org/x/text v0.3.3
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
)
|
)
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -29,6 +29,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|
11
main.go
11
main.go
|
@ -39,8 +39,15 @@ func run(args []string) error {
|
||||||
rootDir = os.Args[2]
|
rootDir = os.Args[2]
|
||||||
}
|
}
|
||||||
return commands.Build(rootDir)
|
return commands.Build(rootDir)
|
||||||
case "new":
|
case "post":
|
||||||
return commands.New()
|
var title string
|
||||||
|
if len(os.Args) >= 3 {
|
||||||
|
title = os.Args[2]
|
||||||
|
} else {
|
||||||
|
title = commands.Prompt("title")
|
||||||
|
}
|
||||||
|
rootDir := "."
|
||||||
|
return commands.Post(rootDir, title)
|
||||||
case "serve":
|
case "serve":
|
||||||
rootDir := "."
|
rootDir := "."
|
||||||
if len(os.Args) > 2 {
|
if len(os.Args) > 2 {
|
||||||
|
|
|
@ -338,7 +338,7 @@ func writeToFile(targetPath string, source io.Reader) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("added", targetPath)
|
fmt.Println("wrote", targetPath)
|
||||||
return targetFile.Sync()
|
return targetFile.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue