initial commit
This commit is contained in:
commit
2037f55230
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func (app *application) home(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) generateRss(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type application struct {
|
||||||
|
errorLog *log.Logger
|
||||||
|
infoLog *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
addr := flag.String("addr", ":8000", "HTTP network address")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
|
||||||
|
errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
|
app := &application {
|
||||||
|
errorLog: errorLog,
|
||||||
|
infoLog: infoLog,
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := &http.Server {
|
||||||
|
Addr: *addr,
|
||||||
|
ErrorLog: errorLog,
|
||||||
|
Handler: app.routes(),
|
||||||
|
}
|
||||||
|
|
||||||
|
infoLog.Printf("Starting server on %s", *addr)
|
||||||
|
err := srv.ListenAndServe()
|
||||||
|
errorLog.Fatal(err)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
import "github.com/go-chi/chi"
|
||||||
|
|
||||||
|
func (app *application) routes() http.Handler {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
fileServer := http.FileServer(http.Dir("./ui/static"))
|
||||||
|
r.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
||||||
|
|
||||||
|
r.Get("/", app.home)
|
||||||
|
r.Get("/generate", app.generateRss)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
module git.32bit.cafe/yequari/rss-gen
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-chi/chi v1.5.5
|
||||||
|
golang.org/x/net v0.20.0
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
|
||||||
|
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
|
||||||
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
|
@ -0,0 +1,4 @@
|
||||||
|
package rss
|
||||||
|
|
||||||
|
var ParseArticle = parseArticle
|
||||||
|
var FetchPage = fetchPage
|
|
@ -0,0 +1,89 @@
|
||||||
|
package rss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fetchPage(url string) (string, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error sending Get request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTime(timestr string) (*time.Time, error) {
|
||||||
|
var formats = []string {
|
||||||
|
time.ANSIC,
|
||||||
|
time.UnixDate,
|
||||||
|
time.RubyDate,
|
||||||
|
time.RFC822,
|
||||||
|
time.RFC822Z,
|
||||||
|
time.RFC850,
|
||||||
|
time.RFC1123,
|
||||||
|
time.RFC1123Z,
|
||||||
|
time.RFC3339,
|
||||||
|
time.RFC3339Nano,
|
||||||
|
time.DateTime,
|
||||||
|
time.DateOnly,
|
||||||
|
}
|
||||||
|
for _, f := range formats {
|
||||||
|
pagetime, err := time.Parse(f, timestr)
|
||||||
|
if err == nil {
|
||||||
|
return &pagetime, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Error parsing time: invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseArticle returns an error if it could not parse the HTML or if it could not parse a time
|
||||||
|
// if a time could not be parsed, the parsed html article will still be returned
|
||||||
|
func parseArticle(content string) (string, *time.Time, error) {
|
||||||
|
doc, err := html.Parse(strings.NewReader(content))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("Error parsing HTML: %w", err)
|
||||||
|
}
|
||||||
|
var f func(*html.Node, string)
|
||||||
|
var element *html.Node
|
||||||
|
var pagetime *time.Time
|
||||||
|
f = func(n *html.Node, tag string) {
|
||||||
|
if n.Type == html.ElementNode && n.Data == tag {
|
||||||
|
element = n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
f(c, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f(doc, "article")
|
||||||
|
var builder strings.Builder
|
||||||
|
html.Render(&builder, element)
|
||||||
|
|
||||||
|
f(element, "time")
|
||||||
|
for _, d := range element.Attr {
|
||||||
|
if d.Key == "datetime" {
|
||||||
|
pagetime, err = parseTime(d.Val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String(), pagetime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRss(siteUrl, siteTitle string, pageUrls []string) string {
|
||||||
|
// get page
|
||||||
|
// parse article
|
||||||
|
// parse date
|
||||||
|
// create item element
|
||||||
|
// collect item elements into feed
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package rss_test
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
import "time"
|
||||||
|
import "git.32bit.cafe/yequari/rss-gen/rss"
|
||||||
|
|
||||||
|
func TestArticleParse(t *testing.T) {
|
||||||
|
testDate, err := time.Parse("2006-Jan-02", "2004-May-14")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("creating test date failed: %s", err)
|
||||||
|
}
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want_time *time.Time
|
||||||
|
want_article string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"article stripped out of basic HTML",
|
||||||
|
"<html><head></head><body><article>hello world</article></body></html>",
|
||||||
|
nil,
|
||||||
|
"<article>hello world</article>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"article and time stripped out of basic HTML",
|
||||||
|
"<html><head></head><body><article><time datetime=\"2004-05-14\">May 14 2004</time>hello world</article></body></html>",
|
||||||
|
&testDate,
|
||||||
|
"<article><time datetime=\"2004-05-14\">May 14 2004</time>hello world</article>",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func (t *testing.T) {
|
||||||
|
article, articleTime, err := rss.ParseArticle(tt.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error: %s", err)
|
||||||
|
}
|
||||||
|
if article != tt.want_article {
|
||||||
|
t.Errorf("got %s, want %s", article, tt.want_article)
|
||||||
|
}
|
||||||
|
if tt.want_time != nil && !articleTime.Equal(*tt.want_time) {
|
||||||
|
t.Errorf("got %s, want %s", articleTime, *tt.want_time)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue