mirror of https://github.com/jlelse/GoBlog
"GoBlog export path" for exporting markdown files of posts
This commit is contained in:
parent
da9ea7c0d7
commit
ef66cd7c80
|
@ -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
|
||||||
|
}
|
|
@ -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
2
go.mod
|
@ -54,7 +54,7 @@ require (
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
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
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -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-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-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-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-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-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 h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|
15
main.go
15
main.go
|
@ -100,6 +100,21 @@ func main() {
|
||||||
return
|
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
|
// Initialize components
|
||||||
app.initComponents()
|
app.initComponents()
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,13 @@ footer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details summary {
|
||||||
|
// Show first child of summary inline
|
||||||
|
> *:first-child {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.border-top {
|
.border-top {
|
||||||
@include color-border(border-top, 1px, solid, primary);
|
@include color-border(border-top, 1px, solid, primary);
|
||||||
}
|
}
|
||||||
|
@ -235,7 +242,7 @@ footer {
|
||||||
height: 400px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print */
|
// Print
|
||||||
@media print {
|
@media print {
|
||||||
html {
|
html {
|
||||||
@include lightmode;
|
@include lightmode;
|
||||||
|
|
|
@ -249,6 +249,7 @@ type postsRequestConfig struct {
|
||||||
priorityOrder bool
|
priorityOrder bool
|
||||||
withoutParameters bool
|
withoutParameters bool
|
||||||
withOnlyParameters []string
|
withOnlyParameters []string
|
||||||
|
withoutRenderedTitle bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPostsQuery(c *postsRequestConfig, selection string) (query string, args []interface{}) {
|
func buildPostsQuery(c *postsRequestConfig, selection string) (query string, args []interface{}) {
|
||||||
|
@ -429,11 +430,13 @@ func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Render post title
|
// Render post title
|
||||||
|
if !config.withoutRenderedTitle {
|
||||||
for _, p := range posts {
|
for _, p := range posts {
|
||||||
if t := p.Title(); t != "" {
|
if t := p.Title(); t != "" {
|
||||||
p.RenderedTitle = a.renderMdTitle(t)
|
p.RenderedTitle = a.renderMdTitle(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return posts, nil
|
return posts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,23 +133,6 @@ func (p *post) isPublishedSectionPost() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) postToMfItem(p *post) *microformatItem {
|
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
|
var mfStatus, mfVisibility string
|
||||||
switch p.Status {
|
switch p.Status {
|
||||||
case statusDraft:
|
case statusDraft:
|
||||||
|
@ -173,7 +156,7 @@ func (a *goBlog) postToMfItem(p *post) *microformatItem {
|
||||||
PostStatus: []string{mfStatus},
|
PostStatus: []string{mfStatus},
|
||||||
Visibility: []string{mfVisibility},
|
Visibility: []string{mfVisibility},
|
||||||
Category: p.Parameters[a.cfg.Micropub.CategoryParam],
|
Category: p.Parameters[a.cfg.Micropub.CategoryParam],
|
||||||
Content: []string{content},
|
Content: []string{p.contentWithParams()},
|
||||||
URL: []string{a.fullPostURL(p)},
|
URL: []string{a.fullPostURL(p)},
|
||||||
InReplyTo: p.Parameters[a.cfg.Micropub.ReplyParam],
|
InReplyTo: p.Parameters[a.cfg.Micropub.ReplyParam],
|
||||||
LikeOf: p.Parameters[a.cfg.Micropub.LikeParam],
|
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]
|
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
|
// Public because of rendering
|
||||||
|
|
||||||
func (p *post) Title() string {
|
func (p *post) Title() string {
|
||||||
|
|
|
@ -131,6 +131,10 @@ footer * {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details summary > *:first-child {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
.border-top, footer {
|
.border-top, footer {
|
||||||
border-top: 1px solid #000;
|
border-top: 1px solid #000;
|
||||||
border-top: 1px solid var(--primary, #000);
|
border-top: 1px solid var(--primary, #000);
|
||||||
|
@ -195,7 +199,6 @@ footer * {
|
||||||
height: 400px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print */
|
|
||||||
@media print {
|
@media print {
|
||||||
html {
|
html {
|
||||||
--background: #fff;
|
--background: #fff;
|
||||||
|
|
Loading…
Reference in New Issue