From dfb4defeb9de55455a14bcf127610118db2f56c2 Mon Sep 17 00:00:00 2001 From: xuthus5 Date: Sat, 7 Jan 2023 12:26:54 +0800 Subject: [PATCH] feat: generate command --- cmd.go | 4 +- config.go | 13 +++ deploy.go | 251 +++++++--------------------------------------------- generate.go | 233 ++++++++++++++++++++++++++++++++++++++++++++++++ serve.go | 4 +- utils.go | 26 ++++++ 6 files changed, 307 insertions(+), 224 deletions(-) create mode 100644 generate.go diff --git a/cmd.go b/cmd.go index b595bde..2117fc6 100644 --- a/cmd.go +++ b/cmd.go @@ -25,13 +25,15 @@ func init() { // create a new site folder rootCmd.AddCommand(initCmd()) // generate static website - rootCmd.AddCommand(deployCmd()) + rootCmd.AddCommand(generateCmd()) // new post or page rootCmd.AddCommand(newCmd()) // run serve locally rootCmd.AddCommand(serveCmd()) // auto update rootCmd.AddCommand(updateCmd()) + // deploy project + rootCmd.AddCommand(deployCmd()) } func main() { diff --git a/config.go b/config.go index 03c7756..0fff58e 100644 --- a/config.go +++ b/config.go @@ -10,6 +10,7 @@ type Config struct { Theme string `yaml:"theme"` // 主题 Site Site `yaml:"site"` // 站点配置信息 Now time.Time // 当前时间 + Deploy Deploy `yaml:"deploy"` } type Site struct { @@ -39,6 +40,18 @@ type SEO struct { Keywords string `yaml:"keywords"` // 关键字 } +type DeployType string + +const ( + GitDeploy DeployType = "git" + UpyunDeploy DeployType = "upyun" +) + +type Deploy struct { + Type DeployType `yaml:"type"` + UpyunAuth string `yaml:"upyun_auth"` +} + // Post 文章属性 type Post struct { Title string // 文章标题 diff --git a/deploy.go b/deploy.go index 18658a3..b977cf6 100644 --- a/deploy.go +++ b/deploy.go @@ -1,232 +1,41 @@ package main -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "sort" - "strings" - "time" - - toc "github.com/abhinav/goldmark-toc" - "github.com/spf13/cobra" - meta "github.com/yuin/goldmark-meta" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/text" -) +import "github.com/spf13/cobra" func deployCmd() *cobra.Command { var outter Outter - cmd := &cobra.Command{ - Use: "deploy", - Short: "create a new mder folder", - Run: func(cmd *cobra.Command, args []string) { - startAt := time.Now() - // 读取配置文件 + Use: "deploy", + Aliases: []string{"d"}, + Short: "deploy project to upyun", + PreRun: func(cmd *cobra.Command, args []string) { outter.Config = readConfigFile() - // 读取资源文件 - outter.MetaData = readDataSource() - // 读取文章列表 - outter.Posts = readAllPosts() - // 读取页面列表 - outter.Pages = readAllPages() - // 读取主题模板文件 - outter.Theme = readTheme(outter.Config.Theme) - // 数据写入模板文件 - outter.generate() - endAt := time.Since(startAt) - fmt.Printf("generate used: %s\n", endAt.String()) + config := outter.Config.Deploy + switch config.Type { + case UpyunDeploy: + if !isCommandExist("upx") { + if err := goInstall("github.com/upyun/upx"); err != nil { + sfault("install upx failed: %+v", err) + } + } + case GitDeploy: + } + }, + Run: func(cmd *cobra.Command, args []string) { + config := outter.Config.Deploy + switch config.Type { + case UpyunDeploy: + if config.UpyunAuth == "" { + serr("please config upyun auth string: upx auth [bucket] [operator] [password]") + return + } + if err := uploadToUpyun(config.UpyunAuth); err != nil { + serr("%v", err) + } + sout("deploy to upyun success") + } }, } + return cmd } - -// readDataSource 读取资源文件 -func readDataSource() map[string]interface{} { - var source = make(map[string]interface{}) - const dataDir = "./data" - dirs, err := os.ReadDir(dataDir) - if err != nil { - return nil - } - for _, dir := range dirs { - if dir.IsDir() { - continue - } - // 不是json文件 - if !strings.HasSuffix(dir.Name(), ".json") { - continue - } - body := readFile(fmt.Sprintf("%s/%s", dataDir, dir.Name())) - var obj interface{} - if err := json.Unmarshal(body, &obj); err != nil { - sfault("unmarshal file failed: %v", err) - } - source[strings.ReplaceAll(dir.Name(), ".json", "")] = obj - } - return source -} - -// readPosts 读取文章 -func readAllPosts() []Post { - const postDir = "./posts" - dirs, err := os.ReadDir(postDir) - if err != nil { - sfault("read directory failed: %v", err) - } - var posts []Post - for _, info := range dirs { - // 获取文件信息 - fsInfo, err := info.Info() - if err != nil { - sfault("read file info failed: %v", err) - } - // 不是目录 没有分类 - if !info.IsDir() { - if !strings.HasSuffix(info.Name(), ".md") { - continue - } - post := readPost(fmt.Sprintf("%s/%s", postDir, info.Name())) - post.UpdatedAt = fsInfo.ModTime() - post.UpdatedAtFormat = post.UpdatedAt.Format("2006-01-02") - post.FileBasename = strings.ReplaceAll(fsInfo.Name(), ".md", "") - post.Link = fmt.Sprintf("/default/%s.html", post.FileBasename) - post.Category = "default" - posts = append(posts, post) - continue - } - // 是目录 - posts = append(posts, readPosts(postDir, info.Name())...) - } - sort.Slice(posts, func(i, j int) bool { - return posts[i].CreatedAt.Unix() > posts[j].CreatedAt.Unix() - }) - - return posts -} - -func readPost(fp string) Post { - var post = Post{} - content := readFile(fp) - var buf bytes.Buffer - - var parserContext = parser.NewContext() - doc := markdown.Parser().Parse(text.NewReader(content), parser.WithContext(parserContext)) - - tocTree, err := toc.Inspect(doc, content) - if err != nil { - sfault("read content toc tree failed: %v", err) - } - - list := toc.RenderList(tocTree) - - if list != nil { - if err := markdown.Renderer().Render(&buf, content, list); err != nil { - sfault("render toc content failed: %v", err) - } - post.TOC = buf.String() - buf.Reset() - } - - if err := markdown.Renderer().Render(&buf, content, doc); err != nil { - sfault("render content failed: %v", err) - } - - metaData := meta.Get(parserContext) - post.CreatedAt, err = datetimeStringToTime(mustString(metaData["date"])) - if err != nil { - serr("get post %s created_at time failed, please check file content", post.FileBasename) - } - post.CreatedAtFormat = post.CreatedAt.Format("2006-01-02") - post.Title = mustString(metaData["title"]) - post.Tags = mustStringSlice(metaData["tags"]) - post.Category = mustString(metaData["category"]) - post.MD = buf.String() - return post -} - -func readPage(fp string) Page { - var page Page - content := readFile(fp) - var buf bytes.Buffer - context := parser.NewContext() - if err := markdown.Convert(content, &buf, parser.WithContext(context)); err != nil { - sfault("render content failed: %v", err) - } - metaData := meta.Get(context) - page.Title = mustString(metaData["title"]) - page.MD = buf.String() - return page -} - -func readPosts(base, category string) []Post { - var dir = fmt.Sprintf("%s/%s", base, category) - dirs, err := os.ReadDir(dir) - if err != nil { - sfault("read directory failed: %v", err) - } - var posts []Post - for _, info := range dirs { - if info.IsDir() { - continue - } - if !strings.HasSuffix(info.Name(), ".md") { - continue - } - // 获取文件信息 - fsInfo, err := info.Info() - if err != nil { - sfault("read file info failed: %v", err) - } - post := readPost(fmt.Sprintf("%s/%s", dir, info.Name())) - post.UpdatedAt = fsInfo.ModTime() - post.UpdatedAtFormat = post.UpdatedAt.Format("2006-01-02") - post.FileBasename = strings.ReplaceAll(fsInfo.Name(), ".md", "") - post.Link = fmt.Sprintf("/%s/%s.html", strings.ToLower(category), post.FileBasename) - post.Category = category - posts = append(posts, post) - } - return posts -} - -func readAllPages() []Page { - var dir = "./pages" - dirs, err := os.ReadDir(dir) - if err != nil { - sfault("read directory failed: %v", err) - } - - var pages []Page - for _, info := range dirs { - if info.IsDir() { - continue - } - if !strings.HasSuffix(info.Name(), ".md") { - continue - } - page := readPage(fmt.Sprintf("%s/%s", dir, info.Name())) - page.Link = strings.ReplaceAll(info.Name(), ".md", "") - pages = append(pages, page) - } - return pages -} - -func readTheme(themeName string) Theme { - var theme Theme - var dir = fmt.Sprintf("./themes/%s", themeName) - theme.BaseLayout = readFile(fmt.Sprintf("%s/base.html", dir)) - theme.IndexLayout = readFile(fmt.Sprintf("%s/index.html", dir)) - theme.PageLayout = readFile(fmt.Sprintf("%s/page.html", dir)) - theme.PostLayout = readFile(fmt.Sprintf("%s/post.html", dir)) - theme.ArchiveLayout = readFile(fmt.Sprintf("%s/archive.html", dir)) - theme.TagLayout = readFile(fmt.Sprintf("%s/tag.html", dir)) - - theme.IndexLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.IndexLayout) - theme.PageLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.PageLayout) - theme.PostLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.PostLayout) - theme.ArchiveLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.ArchiveLayout) - theme.TagLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.TagLayout) - return theme -} diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..80a13fa --- /dev/null +++ b/generate.go @@ -0,0 +1,233 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "sort" + "strings" + "time" + + toc "github.com/abhinav/goldmark-toc" + "github.com/spf13/cobra" + meta "github.com/yuin/goldmark-meta" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +func generateCmd() *cobra.Command { + var outter Outter + + cmd := &cobra.Command{ + Use: "generate", + Aliases: []string{"g"}, + Short: "generate project to dist folder", + Run: func(cmd *cobra.Command, args []string) { + startAt := time.Now() + // 读取配置文件 + outter.Config = readConfigFile() + // 读取资源文件 + outter.MetaData = readDataSource() + // 读取文章列表 + outter.Posts = readAllPosts() + // 读取页面列表 + outter.Pages = readAllPages() + // 读取主题模板文件 + outter.Theme = readTheme(outter.Config.Theme) + // 数据写入模板文件 + outter.generate() + endAt := time.Since(startAt) + fmt.Printf("generate used: %s\n", endAt.String()) + }, + } + return cmd +} + +// readDataSource 读取资源文件 +func readDataSource() map[string]interface{} { + var source = make(map[string]interface{}) + const dataDir = "./data" + dirs, err := os.ReadDir(dataDir) + if err != nil { + return nil + } + for _, dir := range dirs { + if dir.IsDir() { + continue + } + // 不是json文件 + if !strings.HasSuffix(dir.Name(), ".json") { + continue + } + body := readFile(fmt.Sprintf("%s/%s", dataDir, dir.Name())) + var obj interface{} + if err := json.Unmarshal(body, &obj); err != nil { + sfault("unmarshal file failed: %v", err) + } + source[strings.ReplaceAll(dir.Name(), ".json", "")] = obj + } + return source +} + +// readPosts 读取文章 +func readAllPosts() []Post { + const postDir = "./posts" + dirs, err := os.ReadDir(postDir) + if err != nil { + sfault("read directory failed: %v", err) + } + var posts []Post + for _, info := range dirs { + // 获取文件信息 + fsInfo, err := info.Info() + if err != nil { + sfault("read file info failed: %v", err) + } + // 不是目录 没有分类 + if !info.IsDir() { + if !strings.HasSuffix(info.Name(), ".md") { + continue + } + post := readPost(fmt.Sprintf("%s/%s", postDir, info.Name())) + post.UpdatedAt = fsInfo.ModTime() + post.UpdatedAtFormat = post.UpdatedAt.Format("2006-01-02") + post.FileBasename = strings.ReplaceAll(fsInfo.Name(), ".md", "") + post.Link = fmt.Sprintf("/default/%s.html", post.FileBasename) + post.Category = "default" + posts = append(posts, post) + continue + } + // 是目录 + posts = append(posts, readPosts(postDir, info.Name())...) + } + sort.Slice(posts, func(i, j int) bool { + return posts[i].CreatedAt.Unix() > posts[j].CreatedAt.Unix() + }) + + return posts +} + +func readPost(fp string) Post { + var post = Post{} + content := readFile(fp) + var buf bytes.Buffer + + var parserContext = parser.NewContext() + doc := markdown.Parser().Parse(text.NewReader(content), parser.WithContext(parserContext)) + + tocTree, err := toc.Inspect(doc, content) + if err != nil { + sfault("read content toc tree failed: %v", err) + } + + list := toc.RenderList(tocTree) + + if list != nil { + if err := markdown.Renderer().Render(&buf, content, list); err != nil { + sfault("render toc content failed: %v", err) + } + post.TOC = buf.String() + buf.Reset() + } + + if err := markdown.Renderer().Render(&buf, content, doc); err != nil { + sfault("render content failed: %v", err) + } + + metaData := meta.Get(parserContext) + post.CreatedAt, err = datetimeStringToTime(mustString(metaData["date"])) + if err != nil { + serr("get post %s created_at time failed, please check file content", post.FileBasename) + } + post.CreatedAtFormat = post.CreatedAt.Format("2006-01-02") + post.Title = mustString(metaData["title"]) + post.Tags = mustStringSlice(metaData["tags"]) + post.Category = mustString(metaData["category"]) + post.MD = buf.String() + return post +} + +func readPage(fp string) Page { + var page Page + content := readFile(fp) + var buf bytes.Buffer + context := parser.NewContext() + if err := markdown.Convert(content, &buf, parser.WithContext(context)); err != nil { + sfault("render content failed: %v", err) + } + metaData := meta.Get(context) + page.Title = mustString(metaData["title"]) + page.MD = buf.String() + return page +} + +func readPosts(base, category string) []Post { + var dir = fmt.Sprintf("%s/%s", base, category) + dirs, err := os.ReadDir(dir) + if err != nil { + sfault("read directory failed: %v", err) + } + var posts []Post + for _, info := range dirs { + if info.IsDir() { + continue + } + if !strings.HasSuffix(info.Name(), ".md") { + continue + } + // 获取文件信息 + fsInfo, err := info.Info() + if err != nil { + sfault("read file info failed: %v", err) + } + post := readPost(fmt.Sprintf("%s/%s", dir, info.Name())) + post.UpdatedAt = fsInfo.ModTime() + post.UpdatedAtFormat = post.UpdatedAt.Format("2006-01-02") + post.FileBasename = strings.ReplaceAll(fsInfo.Name(), ".md", "") + post.Link = fmt.Sprintf("/%s/%s.html", strings.ToLower(category), post.FileBasename) + post.Category = category + posts = append(posts, post) + } + return posts +} + +func readAllPages() []Page { + var dir = "./pages" + dirs, err := os.ReadDir(dir) + if err != nil { + sfault("read directory failed: %v", err) + } + + var pages []Page + for _, info := range dirs { + if info.IsDir() { + continue + } + if !strings.HasSuffix(info.Name(), ".md") { + continue + } + page := readPage(fmt.Sprintf("%s/%s", dir, info.Name())) + page.Link = strings.ReplaceAll(info.Name(), ".md", "") + pages = append(pages, page) + } + return pages +} + +func readTheme(themeName string) Theme { + var theme Theme + var dir = fmt.Sprintf("./themes/%s", themeName) + theme.BaseLayout = readFile(fmt.Sprintf("%s/base.html", dir)) + theme.IndexLayout = readFile(fmt.Sprintf("%s/index.html", dir)) + theme.PageLayout = readFile(fmt.Sprintf("%s/page.html", dir)) + theme.PostLayout = readFile(fmt.Sprintf("%s/post.html", dir)) + theme.ArchiveLayout = readFile(fmt.Sprintf("%s/archive.html", dir)) + theme.TagLayout = readFile(fmt.Sprintf("%s/tag.html", dir)) + + theme.IndexLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.IndexLayout) + theme.PageLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.PageLayout) + theme.PostLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.PostLayout) + theme.ArchiveLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.ArchiveLayout) + theme.TagLayout = bytes.ReplaceAll(theme.BaseLayout, []byte("{{layout_placeholder}}"), theme.TagLayout) + return theme +} diff --git a/serve.go b/serve.go index a808e77..05819a6 100644 --- a/serve.go +++ b/serve.go @@ -51,7 +51,7 @@ func serveCmd() *cobra.Command { select { case event := <-w.Event: // 文件变更 更新文件 - if err := deployCmd().Execute(); err != nil { + if err := generateCmd().Execute(); err != nil { sfault("re generate website failed: %v", err) } sout("file change: %v", event.String()) @@ -63,7 +63,7 @@ func serveCmd() *cobra.Command { } }() // 先生成一次 - if err := deployCmd().Execute(); err != nil { + if err := generateCmd().Execute(); err != nil { sfault("generate website failed: %v", err) } // 五秒一次 diff --git a/utils.go b/utils.go index a15effc..c6f9d9f 100644 --- a/utils.go +++ b/utils.go @@ -136,3 +136,29 @@ func slash(str string) string { } return str } + +func isCommandExist(name string) bool { + _, err := exec.LookPath(name) + if err != nil { + return false + } + return true +} + +func goInstall(pkg string) error { + url := fmt.Sprintf("%s@latest", pkg) + _, err := exec.Command("go", "install", url).Output() + return err +} + +func uploadToUpyun(auth string) error { + _, err := exec.Command("upx", "--auth", auth, "rm", "/").Output() + if err != nil { + return fmt.Errorf("remove old data failed: %v", err) + } + _, err = exec.Command("upx", "--auth", auth, "put", "./dist").Output() + if err != nil { + return fmt.Errorf("deploy data failed: %v", err) + } + return nil +}