mirror of
https://github.com/nkanaev/yarr.git
synced 2025-09-14 02:10:04 +00:00
reorganizing server-related packages
This commit is contained in:
70
src/server/opml/opml.go
Normal file
70
src/server/opml/opml.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package opml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Folder struct {
|
||||
Title string
|
||||
Folders []Folder
|
||||
Feeds []Feed
|
||||
}
|
||||
|
||||
type Feed struct {
|
||||
Title string
|
||||
FeedUrl string
|
||||
SiteUrl string
|
||||
}
|
||||
|
||||
func (f Folder) AllFeeds() []Feed {
|
||||
feeds := make([]Feed, 0)
|
||||
feeds = append(feeds, f.Feeds...)
|
||||
for _, subfolder := range f.Folders {
|
||||
feeds = append(feeds, subfolder.AllFeeds()...)
|
||||
}
|
||||
return feeds
|
||||
}
|
||||
|
||||
var e = html.EscapeString
|
||||
var indent = " "
|
||||
var nl = "\n"
|
||||
|
||||
func (f Folder) outline(level int) string {
|
||||
builder := strings.Builder{}
|
||||
prefix := strings.Repeat(indent, level)
|
||||
|
||||
if level > 0 {
|
||||
builder.WriteString(prefix + fmt.Sprintf(`<outline text="%s">`+nl, e(f.Title)))
|
||||
}
|
||||
for _, folder := range f.Folders {
|
||||
builder.WriteString(folder.outline(level + 1))
|
||||
}
|
||||
for _, feed := range f.Feeds {
|
||||
builder.WriteString(feed.outline(level + 1))
|
||||
}
|
||||
if level > 0 {
|
||||
builder.WriteString(prefix + `</outline>` + nl)
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (f Feed) outline(level int) string {
|
||||
return strings.Repeat(indent, level) + fmt.Sprintf(
|
||||
`<outline type="rss" text="%s" xmlUrl="%s" htmlUrl="%s"/>`+nl,
|
||||
e(f.Title), e(f.FeedUrl), e(f.SiteUrl),
|
||||
)
|
||||
}
|
||||
|
||||
func (f Folder) OPML() string {
|
||||
builder := strings.Builder{}
|
||||
builder.WriteString(`<?xml version="1.0" encoding="UTF-8"?>` + nl)
|
||||
builder.WriteString(`<opml version="1.1">` + nl)
|
||||
builder.WriteString(`<head><title>subscriptions</title></head>` + nl)
|
||||
builder.WriteString(`<body>` + nl)
|
||||
builder.WriteString(f.outline(0))
|
||||
builder.WriteString(`</body>` + nl)
|
||||
builder.WriteString(`</opml>` + nl)
|
||||
return builder.String()
|
||||
}
|
54
src/server/opml/opml_test.go
Normal file
54
src/server/opml/opml_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package opml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOPML(t *testing.T) {
|
||||
have := (Folder{
|
||||
Title: "",
|
||||
Feeds: []Feed{
|
||||
{
|
||||
Title: "title1",
|
||||
FeedUrl: "https://baz.com/feed.xml",
|
||||
SiteUrl: "https://baz.com/",
|
||||
},
|
||||
},
|
||||
Folders: []Folder{
|
||||
{
|
||||
Title: "sub",
|
||||
Feeds: []Feed{
|
||||
{
|
||||
Title: "subtitle1",
|
||||
FeedUrl: "https://foo.com/feed.xml",
|
||||
SiteUrl: "https://foo.com/",
|
||||
},
|
||||
{
|
||||
Title: "&>",
|
||||
FeedUrl: "https://bar.com/feed.xml",
|
||||
SiteUrl: "https://bar.com/",
|
||||
},
|
||||
},
|
||||
Folders: []Folder{},
|
||||
},
|
||||
},
|
||||
}).OPML()
|
||||
want := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<opml version="1.1">
|
||||
<head><title>subscriptions</title></head>
|
||||
<body>
|
||||
<outline text="sub">
|
||||
<outline type="rss" text="subtitle1" xmlUrl="https://foo.com/feed.xml" htmlUrl="https://foo.com/"/>
|
||||
<outline type="rss" text="&>" xmlUrl="https://bar.com/feed.xml" htmlUrl="https://bar.com/"/>
|
||||
</outline>
|
||||
<outline type="rss" text="title1" xmlUrl="https://baz.com/feed.xml" htmlUrl="https://baz.com/"/>
|
||||
</body>
|
||||
</opml>
|
||||
`
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Logf("want: %s", want)
|
||||
t.Logf("have: %s", have)
|
||||
t.Fatal("invalid opml")
|
||||
}
|
||||
}
|
49
src/server/opml/read.go
Normal file
49
src/server/opml/read.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package opml
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
)
|
||||
|
||||
type opml struct {
|
||||
XMLName xml.Name `xml:"opml"`
|
||||
Outlines []outline `xml:"body>outline"`
|
||||
}
|
||||
|
||||
type outline struct {
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Title string `xml:"text,attr"`
|
||||
FeedUrl string `xml:"xmlUrl,attr,omitempty"`
|
||||
SiteUrl string `xml:"htmlUrl,attr,omitempty"`
|
||||
Outlines []outline `xml:"outline,omitempty"`
|
||||
}
|
||||
|
||||
func buildFolder(title string, outlines []outline) Folder {
|
||||
folder := Folder{Title: title}
|
||||
for _, outline := range outlines {
|
||||
if outline.Type == "rss" {
|
||||
folder.Feeds = append(folder.Feeds, Feed{
|
||||
Title: outline.Title,
|
||||
FeedUrl: outline.FeedUrl,
|
||||
SiteUrl: outline.SiteUrl,
|
||||
})
|
||||
} else {
|
||||
subfolder := buildFolder(outline.Title, outline.Outlines)
|
||||
folder.Folders = append(folder.Folders, subfolder)
|
||||
}
|
||||
}
|
||||
return folder
|
||||
}
|
||||
|
||||
func Parse(r io.Reader) (Folder, error) {
|
||||
val := new(opml)
|
||||
decoder := xml.NewDecoder(r)
|
||||
decoder.Entity = xml.HTMLEntity
|
||||
decoder.Strict = false
|
||||
|
||||
err := decoder.Decode(&val)
|
||||
if err != nil {
|
||||
return Folder{}, err
|
||||
}
|
||||
return buildFolder("", val.Outlines), nil
|
||||
}
|
58
src/server/opml/read_test.go
Normal file
58
src/server/opml/read_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package opml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
have, _ := Parse(strings.NewReader(`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<opml version="1.1">
|
||||
<head><title>Subscriptions</title></head>
|
||||
<body>
|
||||
<outline text="sub">
|
||||
<outline type="rss" text="subtitle1" description="sub1"
|
||||
xmlUrl="https://foo.com/feed.xml" htmlUrl="https://foo.com/"/>
|
||||
<outline type="rss" text="&>" description="<>"
|
||||
xmlUrl="https://bar.com/feed.xml" htmlUrl="https://bar.com/"/>
|
||||
</outline>
|
||||
<outline type="rss" text="title1" description="desc1"
|
||||
xmlUrl="https://baz.com/feed.xml" htmlUrl="https://baz.com/"/>
|
||||
</body>
|
||||
</opml>
|
||||
`))
|
||||
want := Folder{
|
||||
Title: "",
|
||||
Feeds: []Feed{
|
||||
{
|
||||
Title: "title1",
|
||||
FeedUrl: "https://baz.com/feed.xml",
|
||||
SiteUrl: "https://baz.com/",
|
||||
},
|
||||
},
|
||||
Folders: []Folder{
|
||||
{
|
||||
Title: "sub",
|
||||
Feeds: []Feed{
|
||||
{
|
||||
Title: "subtitle1",
|
||||
FeedUrl: "https://foo.com/feed.xml",
|
||||
SiteUrl: "https://foo.com/",
|
||||
},
|
||||
{
|
||||
Title: "&>",
|
||||
FeedUrl: "https://bar.com/feed.xml",
|
||||
SiteUrl: "https://bar.com/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Logf("want: %#v", want)
|
||||
t.Logf("have: %#v", have)
|
||||
t.Fatal("invalid opml")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user