"GoBlog export path" for exporting markdown files of posts

This commit is contained in:
Jan-Lukas Else 2021-08-10 13:27:19 +02:00
parent da9ea7c0d7
commit ef66cd7c80
9 changed files with 138 additions and 26 deletions

29
export.go Normal file
View File

@ -0,0 +1,29 @@
package main
import (
"os"
"path/filepath"
)
func (a *goBlog) exportMarkdownFiles(dir string) error {
posts, err := a.getPosts(&postsRequestConfig{
withoutRenderedTitle: true,
})
if err != nil {
return err
}
dir = defaultIfEmpty(dir, "export")
for _, p := range posts {
filename := filepath.Join(dir, p.Path+".md")
filedir := filepath.Dir(filename)
err = os.MkdirAll(filedir, 0644)
if err != nil {
return err
}
err = os.WriteFile(filename, []byte(p.contentWithParams()), 0644)
if err != nil {
return err
}
}
return nil
}

52
export_test.go Normal file
View File

@ -0,0 +1,52 @@
package main
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_export(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
},
}
_ = app.initDatabase(false)
app.initMarkdown()
err := app.db.savePost(&post{
Path: "/test/abc",
Content: "ABC",
Blog: "en",
Section: "test",
Status: statusDraft,
Parameters: map[string][]string{
"title": {"Title"},
},
}, &postCreationOptions{new: true})
require.NoError(t, err)
exportPath := filepath.Join(t.TempDir(), "export")
err = app.exportMarkdownFiles(exportPath)
require.NoError(t, err)
exportFilePath := filepath.Join(exportPath, "/test/abc.md")
require.FileExists(t, exportFilePath)
fileContentBytes, err := os.ReadFile(exportFilePath)
require.NoError(t, err)
fileContent := string(fileContentBytes)
assert.Contains(t, fileContent, `path: /test/abc`)
assert.Contains(t, fileContent, `title: Title`)
assert.Contains(t, fileContent, `updated: ""`)
assert.Contains(t, fileContent, `published: ""`)
assert.Contains(t, fileContent, `ABC`)
}

2
go.mod
View File

@ -54,7 +54,7 @@ require (
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b

4
go.sum
View File

@ -530,8 +530,8 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

15
main.go
View File

@ -100,6 +100,21 @@ func main() {
return
}
// Markdown export
if len(os.Args) >= 2 && os.Args[1] == "export" {
var dir string
if len(os.Args) >= 3 {
dir = os.Args[2]
}
err = app.exportMarkdownFiles(dir)
if err != nil {
app.logErrAndQuit("Failed to export markdown files:", err.Error())
return
}
app.shutdown.ShutdownAndWait()
return
}
// Initialize components
app.initComponents()

View File

@ -173,6 +173,13 @@ footer {
}
}
details summary {
// Show first child of summary inline
> *:first-child {
display: inline;
}
}
.border-top {
@include color-border(border-top, 1px, solid, primary);
}
@ -235,7 +242,7 @@ footer {
height: 400px;
}
/* Print */
// Print
@media print {
html {
@include lightmode;

View File

@ -249,6 +249,7 @@ type postsRequestConfig struct {
priorityOrder bool
withoutParameters bool
withOnlyParameters []string
withoutRenderedTitle bool
}
func buildPostsQuery(c *postsRequestConfig, selection string) (query string, args []interface{}) {
@ -429,9 +430,11 @@ func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error)
}
}
// Render post title
for _, p := range posts {
if t := p.Title(); t != "" {
p.RenderedTitle = a.renderMdTitle(t)
if !config.withoutRenderedTitle {
for _, p := range posts {
if t := p.Title(); t != "" {
p.RenderedTitle = a.renderMdTitle(t)
}
}
}
return posts, nil

View File

@ -133,23 +133,6 @@ func (p *post) isPublishedSectionPost() bool {
}
func (a *goBlog) postToMfItem(p *post) *microformatItem {
params := map[string]interface{}{}
for k, v := range p.Parameters {
if l := len(v); l == 1 {
params[k] = v[0]
} else if l > 1 {
params[k] = v
}
}
params["path"] = p.Path
params["section"] = p.Section
params["blog"] = p.Blog
params["published"] = p.Published
params["updated"] = p.Updated
params["status"] = string(p.Status)
params["priority"] = p.Priority
pb, _ := yaml.Marshal(params)
content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content)
var mfStatus, mfVisibility string
switch p.Status {
case statusDraft:
@ -173,7 +156,7 @@ func (a *goBlog) postToMfItem(p *post) *microformatItem {
PostStatus: []string{mfStatus},
Visibility: []string{mfVisibility},
Category: p.Parameters[a.cfg.Micropub.CategoryParam],
Content: []string{content},
Content: []string{p.contentWithParams()},
URL: []string{a.fullPostURL(p)},
InReplyTo: p.Parameters[a.cfg.Micropub.ReplyParam],
LikeOf: p.Parameters[a.cfg.Micropub.LikeParam],
@ -222,6 +205,26 @@ func (a *goBlog) photoLinks(p *post) []string {
return p.Parameters[a.cfg.Micropub.PhotoParam]
}
func (p *post) contentWithParams() string {
params := map[string]interface{}{}
for k, v := range p.Parameters {
if l := len(v); l == 1 {
params[k] = v[0]
} else if l > 1 {
params[k] = v
}
}
params["path"] = p.Path
params["section"] = p.Section
params["blog"] = p.Blog
params["published"] = p.Published
params["updated"] = p.Updated
params["status"] = string(p.Status)
params["priority"] = p.Priority
pb, _ := yaml.Marshal(params)
return fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content)
}
// Public because of rendering
func (p *post) Title() string {

View File

@ -131,6 +131,10 @@ footer * {
margin-bottom: 0;
}
details summary > *:first-child {
display: inline;
}
.border-top, footer {
border-top: 1px solid #000;
border-top: 1px solid var(--primary, #000);
@ -195,7 +199,6 @@ footer * {
height: 400px;
}
/* Print */
@media print {
html {
--background: #fff;