diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c716f0f..6c16051 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,16 +20,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Setup Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v6 with: - go-version: "stable" + go-version-file: go.mod + cache-dependency-path: go.sum - name: Build run: sh ./release.sh - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') name: Release Nightly - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v3 with: files: release/ink* prerelease: true @@ -39,7 +40,7 @@ jobs: fail_on_unmatched_files: true - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v3 with: files: release/ink* prerelease: false diff --git a/api.go b/api.go index c14eebe..50ed278 100644 --- a/api.go +++ b/api.go @@ -11,9 +11,6 @@ import ( "path/filepath" "strings" "time" - - "github.com/InkProject/ink.go" - "github.com/edwardrf/symwalk" ) type NewArticle struct { @@ -39,34 +36,40 @@ func hashPath(path string) string { return hex.EncodeToString(md5Hex[:]) } -func replyJSON(ctx *ink.Context, status int, data interface{}) { +func replyJSON(w http.ResponseWriter, status int, data interface{}) { jsonStr, err := json.Marshal(data) if err != nil { - http.Error(ctx.Res, err.Error(), http.StatusInternalServerError) - ctx.Stop() + http.Error(w, err.Error(), http.StatusInternalServerError) return } if status == http.StatusOK { - ctx.Header().Set("Content-Type", "application/json") - ctx.Header().Set("Access-Control-Allow-Origin", "*") - ctx.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - ctx.Res.Write(jsonStr) + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + if _, err := w.Write(jsonStr); err != nil { + Warn(err.Error()) + } } else { Warn(data) - http.Error(ctx.Res, data.(string), status) + http.Error(w, data.(string), status) } - ctx.Stop() } func UpdateArticleCache() { articleCache = make(map[string]CacheArticleInfo, 0) - symwalk.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { + if err := walkSymlinks(sourcePath, func(path string, _ os.FileInfo, err error) error { + if err != nil { + return err + } fileExt := strings.ToLower(filepath.Ext(path)) if fileExt == ".md" { fileName := strings.TrimPrefix(strings.TrimSuffix(strings.ToLower(path), ".md"), "template/source/") config, _ := ParseArticleConfig(path) + if config == nil { + return nil + } id := hashPath(path) - articleCache[string(id)] = CacheArticleInfo{ + articleCache[id] = CacheArticleInfo{ Name: fileName, Path: path, Date: ParseDate(config.Date), @@ -74,161 +77,163 @@ func UpdateArticleCache() { } } return nil - }) + }); err != nil { + Warn(err.Error()) + } } -func ApiListArticle(ctx *ink.Context) { +func ApiListArticle(w http.ResponseWriter, r *http.Request) { UpdateArticleCache() - replyJSON(ctx, http.StatusOK, articleCache) + replyJSON(w, http.StatusOK, articleCache) } -func ApiGetArticle(ctx *ink.Context) { +func ApiGetArticle(w http.ResponseWriter, r *http.Request) { UpdateArticleCache() - article, ok := articleCache[ctx.Param["id"]] + article, ok := articleCache[r.PathValue("id")] if !ok { - replyJSON(ctx, http.StatusNotFound, "Not Found") + replyJSON(w, http.StatusNotFound, "Not Found") return } filePath := article.Path data, err := os.ReadFile(filePath) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } - replyJSON(ctx, http.StatusOK, string(data)) + replyJSON(w, http.StatusOK, string(data)) } -func ApiRemoveArticle(ctx *ink.Context) { +func ApiRemoveArticle(w http.ResponseWriter, r *http.Request) { UpdateArticleCache() - article, ok := articleCache[ctx.Param["id"]] + article, ok := articleCache[r.PathValue("id")] if !ok { - replyJSON(ctx, http.StatusNotFound, "Not Found") + replyJSON(w, http.StatusNotFound, "Not Found") return } filePath := article.Path err := os.Remove(filePath) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } - replyJSON(ctx, http.StatusOK, nil) + replyJSON(w, http.StatusOK, nil) } -func ApiCreateArticle(ctx *ink.Context) { - decoder := json.NewDecoder(ctx.Req.Body) +func ApiCreateArticle(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) var article NewArticle err := decoder.Decode(&article) if err != nil { - replyJSON(ctx, http.StatusBadRequest, err.Error()) + replyJSON(w, http.StatusBadRequest, err.Error()) return } filePath := filepath.Join(sourcePath, article.Name+".md") err = os.WriteFile(filePath, []byte(article.Content), 0644) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } - replyJSON(ctx, http.StatusOK, map[string]string{ + replyJSON(w, http.StatusOK, map[string]string{ "id": hashPath(filePath), }) } -func ApiSaveArticle(ctx *ink.Context) { +func ApiSaveArticle(w http.ResponseWriter, r *http.Request) { UpdateArticleCache() - decoder := json.NewDecoder(ctx.Req.Body) + decoder := json.NewDecoder(r.Body) var article OldArticle err := decoder.Decode(&article) if err != nil { - replyJSON(ctx, http.StatusBadRequest, err.Error()) + replyJSON(w, http.StatusBadRequest, err.Error()) return } - cacheArticle, ok := articleCache[ctx.Param["id"]] + cacheArticle, ok := articleCache[r.PathValue("id")] if !ok { - replyJSON(ctx, http.StatusNotFound, "Not Found") + replyJSON(w, http.StatusNotFound, "Not Found") return } // Write path := cacheArticle.Path err = os.WriteFile(path, []byte(article.Content), 0644) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } - replyJSON(ctx, http.StatusOK, nil) + replyJSON(w, http.StatusOK, nil) } -func getFormFile(ctx *ink.Context, field string) (data []byte, handler *multipart.FileHeader, err error) { - file, handler, err := ctx.Req.FormFile(field) +func getFormFile(w http.ResponseWriter, r *http.Request, field string) (data []byte, handler *multipart.FileHeader, err error) { + file, handler, err := r.FormFile(field) if err != nil { - replyJSON(ctx, http.StatusBadRequest, err.Error()) + replyJSON(w, http.StatusBadRequest, err.Error()) return nil, handler, err } data, err = io.ReadAll(file) if err != nil { - replyJSON(ctx, http.StatusBadRequest, err.Error()) + replyJSON(w, http.StatusBadRequest, err.Error()) return data, handler, err } return data, handler, err } -func ApiUploadFile(ctx *ink.Context) { +func ApiUploadFile(w http.ResponseWriter, r *http.Request) { UpdateArticleCache() - fileData, handler, err := getFormFile(ctx, "file") + fileData, handler, err := getFormFile(w, r, "file") if err != nil { - replyJSON(ctx, http.StatusBadRequest, err.Error()) + replyJSON(w, http.StatusBadRequest, err.Error()) return } - articleId := ctx.Req.FormValue("article_id") - article, ok := articleCache[articleId] + articleID := r.FormValue("article_id") + article, ok := articleCache[articleID] if !ok { - replyJSON(ctx, http.StatusNotFound, "Not Found") + replyJSON(w, http.StatusNotFound, "Not Found") return } fileDirPath := filepath.Join(sourcePath, "images", article.Name) err = os.MkdirAll(fileDirPath, 0777) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } if err = os.WriteFile(filepath.Join(fileDirPath, handler.Filename), fileData, 0777); err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } - replyJSON(ctx, http.StatusOK, map[string]string{ + replyJSON(w, http.StatusOK, map[string]string{ "path": "-/" + filepath.Join("images", article.Name, handler.Filename), }) } -func ApiGetConfig(ctx *ink.Context) { +func ApiGetConfig(w http.ResponseWriter, r *http.Request) { filePath := filepath.Join(rootPath, "config.yml") data, err := os.ReadFile(filePath) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } - replyJSON(ctx, http.StatusOK, string(data)) + replyJSON(w, http.StatusOK, string(data)) } -func ApiSaveConfig(ctx *ink.Context) { - content, err := io.ReadAll(ctx.Req.Body) +func ApiSaveConfig(w http.ResponseWriter, r *http.Request) { + content, err := io.ReadAll(r.Body) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } filePath := filepath.Join(rootPath, "config.yml") - err = os.WriteFile(filePath, []byte(content), 0644) + err = os.WriteFile(filePath, content, 0644) if err != nil { - replyJSON(ctx, http.StatusInternalServerError, err.Error()) + replyJSON(w, http.StatusInternalServerError, err.Error()) return } - replyJSON(ctx, http.StatusOK, nil) + replyJSON(w, http.StatusOK, nil) } -// func ApiRenameArticle(ctx *ink.Context) { +// func ApiRenameArticle(w http.ResponseWriter, r *http.Request) { // // Rename -// cacheArticle, ok := articleCache[ctx.Param["id"]] +// cacheArticle, ok := articleCache[r.PathValue("id")] // if !ok { -// replyJSON(ctx, http.StatusNotFound, "Not Found") +// replyJSON(w, http.StatusNotFound, "Not Found") // return // } // oldPath := cacheArticle.(map[string]CacheArticleInfo)["path"].(string) diff --git a/build.go b/build.go index 4da1072..bf03099 100755 --- a/build.go +++ b/build.go @@ -9,8 +9,6 @@ import ( "strings" "sync" "time" - - "github.com/edwardrf/symwalk" ) // Parse config @@ -48,26 +46,26 @@ type Collections []interface{} func (v Collections) Len() int { return len(v) } func (v Collections) Swap(i, j int) { v[i], v[j] = v[j], v[i] } func (v Collections) Less(i, j int) bool { - switch v[i].(type) { + switch item := v[i].(type) { case ArticleInfo: - return v[i].(ArticleInfo).DetailDate > v[j].(ArticleInfo).DetailDate + return item.DetailDate > v[j].(ArticleInfo).DetailDate case Article: - article1 := v[i].(Article) + article1 := item article2 := v[j].(Article) if article1.Top && !article2.Top { return true - } else if !article1.Top && article2.Top { + } + if !article1.Top && article2.Top { return false - } else { - return article1.Date > article2.Date } + return article1.Date > article2.Date case Archive: - return v[i].(Archive).Year > v[j].(Archive).Year + return item.Year > v[j].(Archive).Year case Tag: - if v[i].(Tag).Count == v[j].(Tag).Count { - return v[i].(Tag).Name > v[j].(Tag).Name + if item.Count == v[j].(Tag).Count { + return item.Name > v[j].(Tag).Name } - return v[i].(Tag).Count > v[j].(Tag).Count + return item.Count > v[j].(Tag).Count } return false } @@ -85,7 +83,10 @@ func Build() { sourcePath = filepath.Join(rootPath, "source") // Append all partial html var partialTpl string - files, _ := filepath.Glob(filepath.Join(themePath, "*.html")) + files, err := filepath.Glob(filepath.Join(themePath, "*.html")) + if err != nil { + Fatal(err.Error()) + } for _, path := range files { fileExt := strings.ToLower(filepath.Ext(path)) baseName := strings.ToLower(filepath.Base(path)) @@ -115,13 +116,21 @@ func Build() { // Clean public folder cleanPatterns := []string{"post", "tag", "images", "js", "css", "*.html", "favicon.ico", "robots.txt"} for _, pattern := range cleanPatterns { - files, _ := filepath.Glob(filepath.Join(publicPath, pattern)) + files, err := filepath.Glob(filepath.Join(publicPath, pattern)) + if err != nil { + Fatal(err.Error()) + } for _, path := range files { - os.RemoveAll(path) + if err := os.RemoveAll(path); err != nil { + Fatal(err.Error()) + } } } // Find all .md to generate article - symwalk.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { + if err := walkSymlinks(sourcePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } fileExt := strings.ToLower(filepath.Ext(path)) if fileExt == ".md" { // Parse markdown data @@ -170,7 +179,9 @@ func Build() { archiveMap[dateYear] = append(archiveMap[dateYear], articleInfo) } return nil - }) + }); err != nil { + Fatal(err.Error()) + } if len(visibleArticles) == 0 { Fatal("Must be have at least one article") } @@ -245,7 +256,7 @@ func Build() { }) } // Sort by count - sort.Sort(Collections(tags)) + sort.Sort(tags) wg.Add(1) go RenderPage(tagTpl, map[string]interface{}{ "Total": len(visibleArticles), @@ -254,7 +265,10 @@ func Build() { "I18n": globalConfig.I18n, }, filepath.Join(publicPath, "tag.html")) // Generate other pages - files, _ = filepath.Glob(filepath.Join(sourcePath, "*.html")) + files, err = filepath.Glob(filepath.Join(sourcePath, "*.html")) + if err != nil { + Fatal(err.Error()) + } funcCxt = FuncContext{ rootPath: rootPath, themePath: themePath, @@ -267,7 +281,10 @@ func Build() { baseName := filepath.Base(path) if fileExt == ".html" && !strings.HasPrefix(baseName, "_") { htmlTpl := CompileTpl(path, partialTpl, baseName, funcCxt) - relPath, _ := filepath.Rel(sourcePath, path) + relPath, err := filepath.Rel(sourcePath, path) + if err != nil { + Fatal(err.Error()) + } wg.Add(1) go RenderPage(htmlTpl, globalConfig, filepath.Join(publicPath, relPath)) } diff --git a/funcs.go b/funcs.go index 4e23ef9..6ea6c18 100644 --- a/funcs.go +++ b/funcs.go @@ -26,6 +26,9 @@ func (ctx FuncContext) I18n(val string) string { } func (ctx FuncContext) ReadFile(path string) template.HTML { - bytes, _ := os.ReadFile(filepath.Join(ctx.currentCwd, path)) + bytes, err := os.ReadFile(filepath.Join(ctx.currentCwd, path)) + if err != nil { + Fatal(err.Error()) + } return template.HTML(bytes) } diff --git a/go.mod b/go.mod index f970ee3..9687e39 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,15 @@ module github.com/InkProject/ink -go 1.23.0 - -toolchain go1.24.3 +go 1.25.0 require ( - github.com/InkProject/ink.go v0.0.0-20160120061933-86de6d066e8d - github.com/edwardrf/symwalk v0.1.0 - github.com/fsnotify/fsnotify v1.9.0 - github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b + github.com/fsnotify/fsnotify v1.10.1 + github.com/gomarkdown/markdown v0.0.0-20260417124207-7d523f7318df github.com/gorilla/feeds v1.2.0 github.com/gorilla/websocket v1.5.3 - github.com/snabb/sitemap v1.0.4 - github.com/urfave/cli/v2 v2.27.7 - gopkg.in/yaml.v2 v2.4.0 + github.com/snabb/sitemap v1.0.5 + github.com/urfave/cli/v3 v3.9.0 + go.yaml.in/yaml/v4 v4.0.0-rc.4 ) -require ( - github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/snabb/diagio v1.0.4 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/sys v0.33.0 // indirect -) +require golang.org/x/sys v0.45.0 // indirect diff --git a/go.sum b/go.sum index 84662de..7b5789b 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,11 @@ -github.com/InkProject/ink.go v0.0.0-20160120061933-86de6d066e8d h1:cKKHWaSZqckOTguui9bShV83raKxQKpTB9X4M7WbeaY= -github.com/InkProject/ink.go v0.0.0-20160120061933-86de6d066e8d/go.mod h1:sGm8pED0mDi7pXIgjvCf7/m7LMmLSWpz3bhtB8KoKL8= -github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= -github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/edwardrf/symwalk v0.1.0 h1:NAakDFfWD1ef65UDiYREFvWiu1+RqRC2dJIDXJdpFjM= -github.com/edwardrf/symwalk v0.1.0/go.mod h1:qlC1zULcOcrPbB0Mog6yENGnszBOdCM59+rLC6g607Y= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= -github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/gomarkdown/markdown v0.0.0-20260417124207-7d523f7318df h1:Mwihr/o+v4L5h56rwHLOE20+hh7Okhwno5BHz3zDuao= +github.com/gomarkdown/markdown v0.0.0-20260417124207-7d523f7318df/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -18,21 +14,19 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/snabb/diagio v1.0.4 h1:XnlKoBarZWiAEnNBYE5t1nbvJhdaoTaW7IBzu0R4AqM= -github.com/snabb/diagio v1.0.4/go.mod h1:Y+Pja4UJrskCOKaLxOfa8b8wYSVb0JWpR4YFNHuzjDI= -github.com/snabb/sitemap v1.0.4 h1:BC6cPW5jXLsKWtlYQKD2s1W58CarvNzqOmdl680uQPw= -github.com/snabb/sitemap v1.0.4/go.mod h1:815/fxQQ8Tt7Eqwe8Lcat4ax73zuHyPxWBZySnbaxkc= -github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= -github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +github.com/snabb/sitemap v1.0.5 h1:D0RZpRjdC1WuTl4bdGmjJcbqpr+VLkrcPedvGeCM0CM= +github.com/snabb/sitemap v1.0.5/go.mod h1:cr/ilsACOZxG2JCZuinCxqmpztSq87QdFKJmZ0HscZI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c= +github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= +go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 0e7ba04..c595797 100755 --- a/main.go +++ b/main.go @@ -2,8 +2,10 @@ package main import ( "bufio" + "context" "fmt" "html/template" + "net/mail" "os" "os/exec" "path/filepath" @@ -11,9 +13,8 @@ import ( "strings" "time" - "github.com/edwardrf/symwalk" - "github.com/urfave/cli/v2" - "gopkg.in/yaml.v2" + "github.com/urfave/cli/v3" + "go.yaml.in/yaml/v4" ) const ( @@ -48,20 +49,20 @@ var rootPath string func main() { - app := cli.NewApp() + app := &cli.Command{} app.Name = "ink" app.Usage = "An elegant static blog generator" - app.Authors = []*cli.Author{ - {Name: "Harrison", Email: "harrison@lolwut.com"}, - {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, + app.Authors = []any{ + mail.Address{Name: "Harrison", Address: "harrison@lolwut.com"}, + mail.Address{Name: "Oliver Allen", Address: "oliver@toyshop.com"}, } - //app.Email = "imeoer@gmail.com" + // app.Email = "imeoer@gmail.com" app.Version = VERSION app.Commands = []*cli.Command{ { Name: "build", Usage: "Generate blog to public folder", - Action: func(c *cli.Context) error { + Action: func(ctx context.Context, c *cli.Command) error { ParseGlobalConfigByCli(c, false) Build() return nil @@ -70,7 +71,7 @@ func main() { { Name: "preview", Usage: "Run in server mode to preview blog", - Action: func(c *cli.Context) error { + Action: func(ctx context.Context, c *cli.Command) error { ParseGlobalConfigByCli(c, true) Build() Watch() @@ -81,7 +82,7 @@ func main() { { Name: "publish", Usage: "Generate blog to public folder and publish", - Action: func(c *cli.Context) error { + Action: func(ctx context.Context, c *cli.Command) error { ParseGlobalConfigByCli(c, false) Build() Publish() @@ -91,7 +92,7 @@ func main() { { Name: "serve", Usage: "Run in server mode to serve blog", - Action: func(c *cli.Context) error { + Action: func(ctx context.Context, c *cli.Command) error { ParseGlobalConfigByCli(c, true) Build() Serve() @@ -101,7 +102,7 @@ func main() { { Name: "convert", Usage: "Convert Jekyll/Hexo post format to Ink format (Beta)", - Action: func(c *cli.Context) error { + Action: func(ctx context.Context, c *cli.Command) error { Convert(c) return nil }, @@ -161,17 +162,19 @@ func main() { Usage: "Adds a tag to the article", }, }, - Action: func(c *cli.Context) error { + Action: func(ctx context.Context, c *cli.Command) error { New(c) return nil }, }, } - app.Run(os.Args) + if err := app.Run(context.Background(), os.Args); err != nil { + Fatal(err) + } os.Exit(exitCode) } -func ParseGlobalConfigByCli(c *cli.Context, develop bool) { +func ParseGlobalConfigByCli(c *cli.Command, develop bool) { if c.Args().Len() > 0 { rootPath = c.Args().Slice()[0] } else { @@ -194,10 +197,12 @@ func ParseGlobalConfigWrap(root string, develop bool) { } } -func New(c *cli.Context) { +func New(c *cli.Command) { // If source folder does not exist, create if _, err := os.Stat("source/"); os.IsNotExist(err) { - os.Mkdir("source", os.ModePerm) + if err := os.Mkdir("source", os.ModePerm); err != nil { + Fatal(err) + } } var author, blogTitle, fileName string @@ -339,8 +344,14 @@ func Publish() { cmd := exec.Command(shell, flag, command) cmd.Dir = filepath.Join(rootPath, globalConfig.Build.Output) // Start print stdout and stderr of process - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() + stdout, pipeErr := cmd.StdoutPipe() + if pipeErr != nil { + Fatal(pipeErr) + } + stderr, pipeErr := cmd.StderrPipe() + if pipeErr != nil { + Fatal(pipeErr) + } out := bufio.NewScanner(stdout) err := bufio.NewScanner(stderr) // Print stdout @@ -356,10 +367,12 @@ func Publish() { } }() // Exec command - cmd.Run() + if err := cmd.Run(); err != nil { + Fatal(err) + } } -func Convert(c *cli.Context) { +func Convert(c *cli.Command) { // Parse arguments var sourcePath, rootPath string args := c.Args() @@ -379,7 +392,10 @@ func Convert(c *cli.Context) { } // Parse Jekyll/Hexo post file count := 0 - symwalk.Walk(sourcePath, func(path string, f os.FileInfo, err error) error { + if err := walkSymlinks(sourcePath, func(path string, _ os.FileInfo, err error) error { + if err != nil { + return err + } fileExt := strings.ToLower(filepath.Ext(path)) if fileExt == ".md" || fileExt == ".html" { // Read data from file @@ -393,17 +409,17 @@ func Convert(c *cli.Context) { var configStr, contentStr string content := strings.TrimSpace(string(data)) parseAry := strings.SplitN(content, "---", 3) - parseLen := len(parseAry) - if parseLen == 3 { // Jekyll + switch len(parseAry) { + case 3: // Jekyll configStr = parseAry[1] contentStr = parseAry[2] - } else if parseLen == 2 { // Hexo + case 2: // Hexo configStr = parseAry[0] contentStr = parseAry[1] } // Parse config var article ArticleConfig - if err = yaml.Unmarshal([]byte(configStr), &article); err != nil { + if err = yaml.Load([]byte(configStr), &article); err != nil { Fatal(err.Error()) } tags := make(map[string]bool) @@ -424,7 +440,7 @@ func Convert(c *cli.Context) { article.Date = dateAry[0] } if len(article.Date) == 10 { - article.Date = article.Date + " 00:00:00" + article.Date += " 00:00:00" } if len(article.Date) == 0 { article.Date = "1970-01-01 00:00:00" @@ -432,19 +448,23 @@ func Convert(c *cli.Context) { article.Update = "" // Generate Config var inkConfig []byte - if inkConfig, err = yaml.Marshal(article); err != nil { + if inkConfig, err = yaml.Dump(article, yaml.V3); err != nil { Fatal(err.Error()) } inkConfigStr := string(inkConfig) markdownStr := inkConfigStr + "\n\n---\n\n" + contentStr + "\n" targetName := "source/" + fileName if fileExt != ".md" { - targetName = targetName + ".md" + targetName += ".md" + } + if err := os.WriteFile(filepath.Join(rootPath, targetName), []byte(markdownStr), 0644); err != nil { + return err } - os.WriteFile(filepath.Join(rootPath, targetName), []byte(markdownStr), 0644) count++ } return nil - }) + }); err != nil { + Fatal(err.Error()) + } fmt.Printf("\nConvert finish, total %v articles\n", count) } diff --git a/parse.go b/parse.go index d4a2fba..8e831e8 100755 --- a/parse.go +++ b/parse.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "gopkg.in/yaml.v2" + "go.yaml.in/yaml/v4" gomk "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/ast" @@ -124,8 +124,12 @@ func renderHookLazyLoadImage(w io.Writer, node ast.Node, entering bool) (ast.Wal img.Attrs["data-src"] = img.Destination img.Destination = []byte("data:image/gif;base64,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") } else { - w.Write([]byte("\" data-src=\"")) - w.Write(img.Attrs["data-src"]) + if _, err := w.Write([]byte("\" data-src=\"")); err != nil { + return ast.Terminate, true + } + if _, err := w.Write(img.Attrs["data-src"]); err != nil { + return ast.Terminate, true + } } return ast.GoToNext, false } @@ -145,7 +149,7 @@ func ParseMarkdown(markdown string, toc bool) template.HTML { } func ReplaceRootFlag(content string) string { - return strings.Replace(content, "-/", globalConfig.Site.Root+"/", -1) + return strings.ReplaceAll(content, "-/", globalConfig.Site.Root+"/") } func ParseGlobalConfig(configPath string, develop bool) (*GlobalConfig, *ThemeConfig) { @@ -155,7 +159,7 @@ func ParseGlobalConfig(configPath string, develop bool) (*GlobalConfig, *ThemeCo if err != nil { return nil, nil } - if err = yaml.Unmarshal(data, &config); err != nil { + if err = yaml.Load(data, &config); err != nil { Fatal(err.Error()) } if config.Site.Config == nil { @@ -165,7 +169,7 @@ func ParseGlobalConfig(configPath string, develop bool) (*GlobalConfig, *ThemeCo if develop { config.Site.Root = "" } - config.Site.Logo = strings.Replace(config.Site.Logo, "-/", config.Site.Root+"/", -1) + config.Site.Logo = strings.ReplaceAll(config.Site.Logo, "-/", config.Site.Root+"/") if config.Site.Url != "" && strings.HasSuffix(config.Site.Url, "/") { config.Site.Url = strings.TrimSuffix(config.Site.Url, "/") } @@ -192,7 +196,7 @@ func ParseThemeConfig(configPath string) *ThemeConfig { Fatal(err.Error()) } // Parse config content - if err := yaml.Unmarshal(data, &themeConfig); err != nil { + if err := yaml.Load(data, &themeConfig); err != nil { Fatal(err.Error()) } return themeConfig @@ -217,7 +221,7 @@ func ParseArticleConfig(markdownPath string) (config *ArticleConfig, content str content = markdownStr[1] } // Parse config content - if err := yaml.Unmarshal([]byte(configStr), &config); err != nil { + if err := yaml.Load([]byte(configStr), &config); err != nil { Error(err.Error()) return nil, "" } @@ -229,7 +233,7 @@ func ParseArticleConfig(markdownPath string) (config *ArticleConfig, content str } // Parse preview splited by MORE_SPLIT previewAry := strings.SplitN(content, MORE_SPLIT, 2) - if len(config.Preview) <= 0 && len(previewAry) > 1 { + if len(config.Preview) == 0 && len(previewAry) > 1 { config.Preview = ParseMarkdown(previewAry[0], false) content = strings.Replace(content, MORE_SPLIT, "", 1) } else { @@ -315,7 +319,7 @@ func ParseArticle(markdownPath string) *Article { } link = globalConfig.Site.Link for key, val := range linkMap { - link = strings.Replace(link, key, val, -1) + link = strings.ReplaceAll(link, key, val) } } } diff --git a/render.go b/render.go index 42ab08b..664d429 100755 --- a/render.go +++ b/render.go @@ -46,7 +46,9 @@ func RenderPage(tpl template.Template, tplData interface{}, outPath string) { Fatal(err.Error()) } defer func() { - outFile.Close() + if err := outFile.Close(); err != nil { + Fatal(err.Error()) + } }() defer wg.Done() // Template render @@ -164,7 +166,9 @@ func GenerateSitemap(articles Collections) { } var sitemap bytes.Buffer - sm.WriteTo(&sitemap) + if _, err := sm.WriteTo(&sitemap); err != nil { + Fatal(err.Error()) + } err := os.WriteFile(filepath.Join(publicPath, "sitemap.xml"), sitemap.Bytes(), 0644) if err != nil { Fatal(err.Error()) @@ -177,7 +181,9 @@ func RenderArticleList(rootPath string, articles Collections, tagName string) { defer wg.Done() // Create path pagePath := filepath.Join(publicPath, rootPath) - os.MkdirAll(pagePath, 0777) + if err := os.MkdirAll(pagePath, 0777); err != nil { + Fatal(err.Error()) + } // Split page limit := globalConfig.Site.Limit total := len(articles) @@ -241,6 +247,11 @@ func GenerateJSON(articles Collections) { } datas = append(datas, data) } - str, _ := json.Marshal(datas) - os.WriteFile(filepath.Join(publicPath, "index.json"), []byte(str), 0644) + str, err := json.Marshal(datas) + if err != nil { + Fatal(err.Error()) + } + if err := os.WriteFile(filepath.Join(publicPath, "index.json"), str, 0644); err != nil { + Fatal(err.Error()) + } } diff --git a/serve.go b/serve.go index abc91a3..f10f11e 100644 --- a/serve.go +++ b/serve.go @@ -1,12 +1,11 @@ package main import ( + "net/http" "os" "path/filepath" "reflect" - "github.com/InkProject/ink.go" - "github.com/edwardrf/symwalk" "github.com/fsnotify/fsnotify" "github.com/gorilla/websocket" ) @@ -20,7 +19,7 @@ func buildWatchList() (files []string, dirs []string) { } files = []string{ filepath.Join(rootPath, "config.yml"), - filepath.Join(themePath), + themePath, } // Add files and directories defined in theme's config.yml to watcher @@ -43,37 +42,48 @@ func buildWatchList() (files []string, dirs []string) { } // Add files and dirs to watcher -func configureWatcher(watcher *fsnotify.Watcher, files []string, dirs []string) error { +func configureWatcher(watcher *fsnotify.Watcher, files []string, dirs []string) { for _, source := range dirs { - symwalk.Walk(source, func(path string, f os.FileInfo, err error) error { + if err := walkSymlinks(source, func(path string, f os.FileInfo, err error) error { + if err != nil { + Warn(err.Error()) + return nil + } if f != nil && f.IsDir() { if err := watcher.Add(path); err != nil { Warn(err.Error()) } } return nil - }) + }); err != nil { + Warn(err.Error()) + } } for _, source := range files { if err := watcher.Add(source); err != nil { Warn(err.Error()) } } - return nil } func Watch() { // Listen watched file change event if watcher != nil { - watcher.Close() + if err := watcher.Close(); err != nil { + Warn(err.Error()) + } + } + newWatcher, err := fsnotify.NewWatcher() + if err != nil { + Fatal(err.Error()) } - watcher, _ = fsnotify.NewWatcher() + watcher = newWatcher files, dirs := buildWatchList() go func() { for { select { case event := <-watcher.Events: - if event.Op == fsnotify.Write { + if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) { // Handle when file change Log(event.Name) ParseGlobalConfigWrap(rootPath, true) @@ -101,41 +111,41 @@ func Watch() { configureWatcher(watcher, files, dirs) } -func Websocket(ctx *ink.Context) { +func Websocket(w http.ResponseWriter, r *http.Request) { var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } - if c, err := upgrader.Upgrade(ctx.Res, ctx.Req, nil); err != nil { + if c, err := upgrader.Upgrade(w, r, nil); err != nil { Warn(err) } else { conn = c } - ctx.Stop() } func Serve() { - // editorWeb := ink.New() + // editorWeb := http.NewServeMux() // - // editorWeb.Get("/articles", ApiListArticle) - // editorWeb.Get("/articles/:id", ApiGetArticle) - // editorWeb.Post("/articles", ApiCreateArticle) - // editorWeb.Put("/articles/:id", ApiSaveArticle) - // editorWeb.Delete("/articles/:id", ApiRemoveArticle) - // editorWeb.Get("/config", ApiGetConfig) - // editorWeb.Put("/config", ApiSaveConfig) - // editorWeb.Post("/upload", ApiUploadFile) - // editorWeb.Use(ink.Cors) - // editorWeb.Get("*", ink.Static(filepath.Join("editor/assets"))) + // editorWeb.HandleFunc("GET /articles", ApiListArticle) + // editorWeb.HandleFunc("GET /articles/{id}", ApiGetArticle) + // editorWeb.HandleFunc("POST /articles", ApiCreateArticle) + // editorWeb.HandleFunc("PUT /articles/{id}", ApiSaveArticle) + // editorWeb.HandleFunc("DELETE /articles/{id}", ApiRemoveArticle) + // editorWeb.HandleFunc("GET /config", ApiGetConfig) + // editorWeb.HandleFunc("PUT /config", ApiSaveConfig) + // editorWeb.HandleFunc("POST /upload", ApiUploadFile) + // editorWeb.Handle("/", http.FileServer(http.Dir(filepath.Join("editor/assets")))) // Log("Access http://localhost:" + globalConfig.Build.Port + "/ to open editor") - // go editorWeb.Listen(":2333") + // go http.ListenAndServe(":2333", editorWeb) - previewWeb := ink.New() - previewWeb.Get("/live", Websocket) - previewWeb.Get("*", ink.Static(filepath.Join(rootPath, globalConfig.Build.Output))) + previewWeb := http.NewServeMux() + previewWeb.HandleFunc("/live", Websocket) + previewWeb.Handle("/", http.FileServer(http.Dir(filepath.Join(rootPath, globalConfig.Build.Output)))) uri := "http://localhost:" + globalConfig.Build.Port + "/" Log("Access " + uri + " to open preview") - previewWeb.Listen(":" + globalConfig.Build.Port) + if err := http.ListenAndServe(":"+globalConfig.Build.Port, previewWeb); err != nil { + Warn(err) + } } diff --git a/util.go b/util.go index 9f24c50..9533b78 100755 --- a/util.go +++ b/util.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "path/filepath" "runtime" "time" ) @@ -86,6 +87,59 @@ func IsDir(path string) bool { return file.IsDir() } +func walkSymlinks(root string, fn filepath.WalkFunc) error { + root, err := filepath.EvalSymlinks(root) + if err != nil { + return err + } + visitedDirs := make([]os.FileInfo, 0) + + var walkFn filepath.WalkFunc + walkFn = func(path string, info os.FileInfo, err error) error { + if err != nil { + return fn(path, info, err) + } + + if info.IsDir() { + for _, visited := range visitedDirs { + if os.SameFile(info, visited) { + return filepath.SkipDir + } + } + visitedDirs = append(visitedDirs, info) + } + + if err := fn(path, info, nil); err != nil { + return err + } + + if info.Mode()&os.ModeSymlink == 0 { + return nil + } + + linkedInfo, err := os.Stat(path) + if err != nil { + return err + } + if !linkedInfo.IsDir() { + return nil + } + for _, visited := range visitedDirs { + if os.SameFile(linkedInfo, visited) { + return nil + } + } + + linkedPath, err := filepath.EvalSymlinks(path) + if err != nil { + return err + } + return filepath.Walk(linkedPath, walkFn) + } + + return filepath.Walk(root, walkFn) +} + // Copy folder and file // Refer to https://www.socketloop.com/tutorials/golang-copy-directory-including-sub-directories-files func CopyFile(source string, dest string) { @@ -97,7 +151,11 @@ func CopyFile(source string, dest string) { if err != nil { Fatal(err.Error()) } - defer destfile.Close() + defer func() { + if err := destfile.Close(); err != nil { + Fatal(err.Error()) + } + }() defer wg.Done() _, err = io.Copy(destfile, sourcefile) if err != nil { @@ -111,7 +169,9 @@ func CopyFile(source string, dest string) { if err != nil { Fatal(err.Error()) } - sourcefile.Close() + if err := sourcefile.Close(); err != nil { + Fatal(err.Error()) + } } func CopyDir(source string, dest string) { @@ -123,8 +183,15 @@ func CopyDir(source string, dest string) { if err != nil { Fatal(err.Error()) } - directory, _ := os.Open(source) - defer directory.Close() + directory, err := os.Open(source) + if err != nil { + Fatal(err.Error()) + } + defer func() { + if err := directory.Close(); err != nil { + Fatal(err.Error()) + } + }() defer wg.Done() objects, err := directory.Readdir(-1) if err != nil {