Compare commits
No commits in common. "3923758807e75cd4a4e17c419dc686538d63931b" and "ef646bf98ebcd42f760b6f6e2b75ff5aea5784d5" have entirely different histories.
3923758807
...
ef646bf98e
@ -1,39 +0,0 @@
|
|||||||
package blogposts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var markdownSuffix = map[string]struct{}{
|
|
||||||
"md": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrUnknownFileType = errors.New("unknown file type, must be markdown")
|
|
||||||
|
|
||||||
func NewPostsFromFS(fileSystem fs.FS) ([]Post, error) {
|
|
||||||
dir, err := fs.ReadDir(fileSystem, ".")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var posts []Post
|
|
||||||
for _, f := range dir {
|
|
||||||
fileName := f.Name()
|
|
||||||
if !isMarkdownFile(fileName) {
|
|
||||||
return nil, ErrUnknownFileType
|
|
||||||
}
|
|
||||||
post, err := getPost(fileSystem, fileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
posts = append(posts, post)
|
|
||||||
}
|
|
||||||
return posts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isMarkdownFile(fileName string) bool {
|
|
||||||
splitted := strings.Split(fileName, ".")
|
|
||||||
_, ok := markdownSuffix[splitted[len(splitted)-1:][0]]
|
|
||||||
return ok
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
package blogposts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"testing/fstest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE: This should be a black box test outside blogposts package.
|
|
||||||
|
|
||||||
// NOTE: If we want to test the return error
|
|
||||||
//
|
|
||||||
// type StubFailingFS struct{}
|
|
||||||
//
|
|
||||||
// func (s StubFailingFS) Open(name string) (fs.File, error) {
|
|
||||||
// return nil, errors.New("fail")
|
|
||||||
// }
|
|
||||||
|
|
||||||
func TestNewBlogPosts(t *testing.T) {
|
|
||||||
const (
|
|
||||||
firstBody = `Title: Post 1
|
|
||||||
Description: Description 1
|
|
||||||
Tags: tdd, go
|
|
||||||
---
|
|
||||||
Hello
|
|
||||||
World`
|
|
||||||
secondBody = `Title: Post 2
|
|
||||||
Description: Description 2
|
|
||||||
Tags: rust, borrow-checker
|
|
||||||
---
|
|
||||||
B
|
|
||||||
L
|
|
||||||
M`
|
|
||||||
)
|
|
||||||
|
|
||||||
fs := fstest.MapFS{
|
|
||||||
"hello world.md": {Data: []byte(firstBody)},
|
|
||||||
"hello-world2.md": {Data: []byte(secondBody)},
|
|
||||||
}
|
|
||||||
|
|
||||||
posts, err := NewPostsFromFS(fs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(posts) != len(fs) {
|
|
||||||
t.Errorf("got %d posts, wanted %d posts", len(posts), len(fs))
|
|
||||||
}
|
|
||||||
|
|
||||||
assertPost(t, posts[0], Post{
|
|
||||||
Title: "Post 1",
|
|
||||||
Description: "Description 1",
|
|
||||||
Tags: []string{"tdd", "go"},
|
|
||||||
Body: `Hello
|
|
||||||
World`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrongFile(t *testing.T) {
|
|
||||||
fs := fstest.MapFS{
|
|
||||||
"hello world.txt": {Data: []byte("Yolo")},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := NewPostsFromFS(fs)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("should be an error but not")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertPost(t testing.TB, got Post, want Post) {
|
|
||||||
t.Helper()
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
|
||||||
t.Errorf("got %+v, want %+v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
Title: Hello, TDD world!
|
|
||||||
Description: First post on our wonderful blog
|
|
||||||
Tags: tdd, go
|
|
||||||
---
|
|
||||||
Hello world!
|
|
||||||
|
|
||||||
The body of posts starts after the `---`
|
|
@ -1,73 +0,0 @@
|
|||||||
package blogposts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Post struct {
|
|
||||||
Title, Description, Body string
|
|
||||||
Tags []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Split a metadata line into 2 parts, one is the key, another is the
|
|
||||||
// value, check if the key exists in the key map (ex. Title, Description etc.)
|
|
||||||
// If not exist, then ignore this metadata line.
|
|
||||||
//
|
|
||||||
// WARN: What if a file doesn't have metadata, just pure Markdown ??
|
|
||||||
// We can control if we at least found title line. If at the end, it is not
|
|
||||||
// present, we consider that the post has an error.
|
|
||||||
const (
|
|
||||||
titleLine = "Title: "
|
|
||||||
descriptionLine = "Description: "
|
|
||||||
tagsLine = "Tags: "
|
|
||||||
tagSeparator = ", "
|
|
||||||
bodySeparator = "---"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getPost(fileSystem fs.FS, fileName string) (Post, error) {
|
|
||||||
postFile, err := fileSystem.Open(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return Post{}, err
|
|
||||||
}
|
|
||||||
defer postFile.Close()
|
|
||||||
|
|
||||||
return newPost(postFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Does newPost have to be coupled to an fs.File ?
|
|
||||||
// Do we use all the methods and data from this type? What do we really need?
|
|
||||||
func newPost(postFile io.Reader) (Post, error) {
|
|
||||||
scanner := bufio.NewScanner(postFile)
|
|
||||||
|
|
||||||
readMetaLine := func(tagName string) string {
|
|
||||||
scanner.Scan()
|
|
||||||
return strings.TrimPrefix(scanner.Text(), tagName)
|
|
||||||
}
|
|
||||||
|
|
||||||
post := Post{
|
|
||||||
Title: readMetaLine(titleLine),
|
|
||||||
Description: readMetaLine(descriptionLine),
|
|
||||||
Tags: strings.Split(readMetaLine(tagsLine), tagSeparator),
|
|
||||||
Body: readBody(scanner),
|
|
||||||
}
|
|
||||||
return post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readBody(scanner *bufio.Scanner) string {
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.TrimSpace(scanner.Text()) == bodySeparator {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The rest is the body
|
|
||||||
var buf strings.Builder
|
|
||||||
for scanner.Scan() {
|
|
||||||
buf.Write(scanner.Bytes())
|
|
||||||
buf.Write([]byte{'\n'})
|
|
||||||
}
|
|
||||||
return strings.TrimSuffix(buf.String(), "\n")
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user