diff options
| author | Mitja Felicijan <m@mitjafelicijan.com> | 2023-07-08 19:24:16 +0200 |
|---|---|---|
| committer | Mitja Felicijan <m@mitjafelicijan.com> | 2023-07-08 19:24:16 +0200 |
| commit | 8e83285cc5b36c516880c9e321b86cb78db0d27f (patch) | |
| tree | af17d0182e4ba9f306c74ac3e29fee7630214f5a | |
| download | jbmafp-8e83285cc5b36c516880c9e321b86cb78db0d27f.tar.gz | |
Enageg!
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | LICENSE | 24 | ||||
| -rw-r--r-- | README.md | 11 | ||||
| -rw-r--r-- | go.mod | 27 | ||||
| -rw-r--r-- | go.sum | 65 | ||||
| -rw-r--r-- | main.go | 326 |
6 files changed, 455 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1be5c1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +public/ +example/ @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2022-2023, Mitja Felicijan <m@mitjafelicijan.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..395da96 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Just Build Me A Fucking Page + +I am just so sick of all these complicated static site generators forcing you to +care about taxonomies and shit like this. All I want is to have a bunch of +markdown files and let them use specific templates. That is about it. Nothing +fancy! + +This generator is not for people who need something more complicated. Use Hugo +instead. But if you need a simple blog page that needs to spit out an RSS feed +or two and have the option to define different templates for different posts, +well then this might be useful to you. @@ -0,0 +1,27 @@ +module mitjafelicijan.com/jbmafp + +go 1.20 + +require ( + github.com/DavidBelicza/TextRank/v2 v2.1.3 + github.com/alexflint/go-arg v1.4.3 + github.com/microcosm-cc/bluemonday v1.0.24 + github.com/otiai10/copy v1.12.0 + github.com/tdewolff/minify/v2 v2.12.7 + github.com/yuin/goldmark v1.5.4 + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 + github.com/yuin/goldmark-meta v1.1.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/alecthomas/chroma/v2 v2.2.0 // indirect + github.com/alexflint/go-scalar v1.1.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/tdewolff/parse/v2 v2.6.6 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect +) @@ -0,0 +1,65 @@ +github.com/DavidBelicza/TextRank/v2 v2.1.3 h1:6gnDe761kdIdrCgTSzCf8fPu7bga/+jSiWqbPIhzlBw= +github.com/DavidBelicza/TextRank/v2 v2.1.3/go.mod h1:JWemq/WyDpOm6yxMhEOjnXCUXds0wQ6NT4TP4Af6byU= +github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= +github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= +github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= +github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= +github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw= +github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8= +github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= +github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +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/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA= +github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws= +github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo= +github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= +github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0= +github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 h1:Py16JEzkSdKAtEFJjiaYLYBOWGXc1r/xHj/Q/5lA37k= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -0,0 +1,326 @@ +package main + +import ( + "bytes" + "fmt" + "html/template" + "log" + "os" + "path" + "path/filepath" + "sort" + "strings" + "time" + + yaml "gopkg.in/yaml.v3" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark-meta" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" + + "github.com/DavidBelicza/TextRank/v2" + "github.com/alexflint/go-arg" + "github.com/microcosm-cc/bluemonday" + + "github.com/tdewolff/minify/v2" + mcss "github.com/tdewolff/minify/v2/css" + mhtml "github.com/tdewolff/minify/v2/html" + mjs "github.com/tdewolff/minify/v2/js" + + highlighting "github.com/yuin/goldmark-highlighting/v2" + + cp "github.com/otiai10/copy" +) + +type ConfigExtrasItem struct { + Type string `yaml:"type"` + Template string `yaml:"template"` + URL string `yaml:"url"` +} + +type Config struct { + Title string `yaml:"title"` + Description string `yaml:"description"` + BaseURL string `yaml:"baseurl"` + Language string `yaml:"language"` + Highlighting string `yaml:"highlighting"` + Minify bool `yaml:"minify"` + Extras []ConfigExtrasItem `yaml:"extras"` +} + +type Page struct { + Filepath string + Raw string + HTML template.HTML + Text string + Summary string + Meta map[string]interface{} + Title string + Type string + RelPermalink string + Created time.Time + Draft bool +} + +// Function to clean HTML tags using bluemonday. +func cleanHTMLTags(htmlString string) string { + p := bluemonday.StrictPolicy() + cleanString := p.Sanitize(htmlString) + return cleanString +} + +func initializeProject(projectRoot string) { + fmt.Println("Initializing new project") +} + +func main() { + projectRoot := os.Getenv("PROJECT_ROOT") + if projectRoot == "" { + projectRoot = "./" + } + + fmt.Println("Come back later! Still WIP!") + os.Exit(0) + + // Parsing arguments. + var args struct { + Init bool `arg:"-i,--init" help:"initialize new project"` + Build bool `arg:"-b,--build" help:"build the website"` + } + + arg.MustParse(&args) + + if !args.Init && !args.Build { + fmt.Println("No arguments provided. Try using `jbmafp --help`") + os.Exit(0) + } + + if args.Init { + initializeProject(projectRoot) + os.Exit(0) + } + + os.Exit(0) + + // Read config file. + configFilepath := path.Join(projectRoot, "config.yaml") + configFile, err := os.ReadFile(configFilepath) + if err != nil { + panic(err) + } + config := Config{} + err = yaml.Unmarshal(configFile, &config) + if err != nil { + panic(err) + } + + // Gets the list of all markdown files. + files, err := filepath.Glob(path.Join(projectRoot, "content/*.md")) + if err != nil { + panic(err) + } + + md := goldmark.New( + goldmark.WithExtensions( + extension.GFM, + meta.Meta, + highlighting.NewHighlighting( + highlighting.WithStyle(config.Highlighting), + ), + ), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + parser.WithBlockParsers(), + parser.WithInlineParsers(), + parser.WithParagraphTransformers(), + parser.WithAttribute(), + ), + goldmark.WithRendererOptions( + html.WithXHTML(), + html.WithUnsafe(), + ), + ) + + // Parse all markdown files in content folder. + pages := []Page{} + for _, file := range files { + source, err := os.ReadFile(file) + if err != nil { + panic(err) + } + + var buf bytes.Buffer + ctx := parser.NewContext() + if err := md.Convert(source, &buf, parser.WithContext(ctx)); err != nil { + panic(err) + } + + // Rank and summarize. + tr := textrank.NewTextRank() + rule := textrank.NewDefaultRule() + language := textrank.NewDefaultLanguage() + algorithmDef := textrank.NewDefaultAlgorithm() + tr.Populate(cleanHTMLTags(buf.String()), language, rule) + tr.Ranking(algorithmDef) + + sentences := textrank.FindSentencesByRelationWeight(tr, 50) + sentences = textrank.FindSentencesFrom(tr, 0, 1) + + summary := "" + for _, s := range sentences { + summary = strings.ReplaceAll(s.Value, "\n", "") + } + + metaData := meta.Get(ctx) + t, _ := time.Parse("2006-01-02T15:04:05-07:00", metaData["date"].(string)) + pages = append(pages, Page{ + Filepath: file, + Meta: metaData, + Raw: buf.String(), + HTML: template.HTML(buf.String()), + Text: cleanHTMLTags(buf.String()), + Summary: summary, + Title: metaData["title"].(string), + Type: metaData["type"].(string), + RelPermalink: metaData["url"].(string), + Created: t, + Draft: metaData["draft"].(bool), + }) + } + + // Sorting pages in descending created order. + sort.Slice(pages, func(i, j int) bool { + return pages[i].Created.After(pages[j].Created) + }) + + // Generate HTML files for all pages. + for _, page := range pages { + outFilepath := path.Join(projectRoot, "public", page.Meta["url"].(string)) + if !page.Draft { + pageTemplateFilename := fmt.Sprintf("%s.html", page.Meta["type"].(string)) + templatePathname := path.Join(projectRoot, "templates", pageTemplateFilename) + baseTemplatePathname := path.Join(projectRoot, "templates/base.html") + t, err := template.ParseFiles(baseTemplatePathname, templatePathname) + if err != nil { + panic(err) + } + + type Payload struct { + Config Config + Page Page + } + + var buf bytes.Buffer + err = t.Execute(&buf, Payload{ + Config: config, + Page: page, + }) + if err != nil { + panic(err) + } + + outHTML := buf.String() + if config.Minify { + m := minify.New() + m.AddFunc("text/html", mhtml.Minify) + m.AddFunc("text/css", mcss.Minify) + m.AddFunc("application/js", mjs.Minify) + outHTML, err = m.String("text/html", outHTML) + if err != nil { + panic(err) + } + } + + os.WriteFile(outFilepath, []byte(outHTML), 0755) + log.Println("Wrote", outFilepath) + } else { + log.Println("Skipped", outFilepath) + + } + + } + + // Generates index page. + { + log.Println("Writing index...") + templatePathname := path.Join(projectRoot, "templates/index.html") + baseTemplatePathname := path.Join(projectRoot, "templates/base.html") + t, err := template.ParseFiles(baseTemplatePathname, templatePathname) + if err != nil { + panic(err) + } + + type Payload struct { + Config Config + Pages []Page + } + + var buf bytes.Buffer + err = t.Execute(&buf, Payload{ + Config: config, + Pages: pages, + }) + if err != nil { + panic(err) + } + + outHTML := buf.String() + if config.Minify { + m := minify.New() + m.AddFunc("text/html", mhtml.Minify) + m.AddFunc("text/css", mcss.Minify) + m.AddFunc("application/js", mjs.Minify) + outHTML, err = m.String("text/html", outHTML) + if err != nil { + panic(err) + } + } + + outFilepath := path.Join(projectRoot, "public", "index.html") + os.WriteFile(outFilepath, []byte(outHTML), 0755) + } + + // Copy static files. + { + log.Println("Copying static files...") + err := cp.Copy(path.Join(projectRoot, "static"), path.Join(projectRoot, "public")) + if err != nil { + panic(err) + } + } + + // Generates extras. + { + for _, extra := range config.Extras { + log.Printf("Writing extras %s\n", extra.URL) + templatePathname := path.Join(projectRoot, "templates", extra.Template) + t, err := template.ParseFiles(templatePathname) + if err != nil { + panic(err) + } + + type Payload struct { + Config Config + Pages []Page + } + + var buf bytes.Buffer + err = t.Execute(&buf, Payload{ + Config: config, + Pages: pages, + }) + if err != nil { + panic(err) + } + + outFilepath := path.Join(projectRoot, "public", extra.URL) + os.WriteFile(outFilepath, []byte(buf.String()), 0755) + + } + } + + // Guess we are done! + log.Println("Done & done...") +} |
