Skip to content

Commit

Permalink
Implement note guid
Browse files Browse the repository at this point in the history
  • Loading branch information
revelaction committed Oct 27, 2023
1 parent 7626f7d commit ee74631
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 22 deletions.
18 changes: 12 additions & 6 deletions export.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"
"path/filepath"
"strconv"
"strings"
"unicode"
)
Expand All @@ -30,7 +31,8 @@ const (
mankidownTag = "mankidown"

// Anki Import UI wants each note type field to be non empty. We allow
// empty fields and add emptyComment in order to fill with something
// empty fields in markdown and add emptyComment in order to fill the fields
// with something
emptyComment = "<!---->"
)

Expand Down Expand Up @@ -75,11 +77,11 @@ func (ex *Exporter) Export(notes *Notes) error {
ex.appendHeaderColumns(out, notes.FieldNames())
ex.appendHeaderTags(out, inFile)

for _, note := range notes.Notes {
for i, note := range notes.Notes {

// 1 field) id
fields := []string{}
fields = append(fields, ex.buildIdField(note, outFile))
fields = append(fields, ex.buildIdField(note, i, outFile))

// 2 field) tags
fields = append(fields, buildTagsField(note))
Expand Down Expand Up @@ -149,13 +151,17 @@ func (ex *Exporter) appendHeaderTags(out io.Writer, inFile string) {
fmt.Fprintf(out, HeaderTags, strings.Join(tags, " "))
}

func (ex *Exporter) buildIdField(n *Note, outFile string) string {
func (ex *Exporter) buildIdField(n *Note, i int, outFile string) string {

if n.hasGuid() {
return n.Guid()
}

if ex.config.GuidPrefix != "" {
return ex.config.GuidPrefix + n.Id()
return ex.config.GuidPrefix + strconv.Itoa(i)
}

return outFile + n.Id()
return outFile + strconv.Itoa(i)
}

// buildTagsField builds the Tags string for a note
Expand Down
78 changes: 62 additions & 16 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package mankidown
import (
"bytes"
"fmt"
"strconv"
"strings"

"github.com/yuin/goldmark"
Expand All @@ -12,19 +11,23 @@ import (
"github.com/yuin/goldmark/text"
)

// guidPrefix is the tag prefix that, if present in the H1 header, determines
// the guid of the note
const guidPrefix = "guid:"

type Parser struct {
mdp goldmark.Markdown
}

// A Field contains the parsed html content inside a H2 Heading element
// Field contains the parsed html content inside a H2 Heading element
type Field struct {
Html string
}

// A Note contains parsed mardown html that will be mapped to a anki note type
// field in the output file
type Note struct {
id string
guid string
tags []string
fields []Field
}
Expand All @@ -36,8 +39,12 @@ func newNote() *Note {
}
}

func (n *Note) Id() string {
return n.id
func (n *Note) Guid() string {
return n.guid
}

func (n *Note) hasGuid() bool {
return n.guid != ""
}

func (n *Note) Fields() []Field {
Expand All @@ -60,14 +67,18 @@ func (n *Note) addTags(tags string) {

words := strings.Fields(tags)

n.tags = append(n.tags, words...)
}
for _, w := range words {
// guid
if strings.HasPrefix(w, guidPrefix) {
n.guid = strings.TrimPrefix(w, guidPrefix)
continue
}

func (n *Note) addId(id string) {
n.id = id
n.tags = append(n.tags, w)
}
}

// A Notes contains the parsed notes elements
// Notes contains the parsed notes elements
type Notes struct {
Notes []*Note
fieldNames []string
Expand Down Expand Up @@ -103,8 +114,16 @@ func (n *Notes) validateNote(nt *Note) error {
return fmt.Errorf("invalid number of fields in note %d (want %d, have %d)", n.numNotes()+1, n.numFieds(), nt.numFieds())
}

if nt.Id() == "" {
return fmt.Errorf("no notes declared at the start by parsing note %d", n.numNotes()+1)
if n.numNotes() > 0 {
if n.hasGuids() != nt.hasGuid() {
return fmt.Errorf("guid mismatch for note %d", n.numNotes()+1)
}
}

if nt.hasGuid() {
if n.hasGuid(nt.guid) {
return fmt.Errorf("guid %q in note %d already exists", nt.Guid(), n.numNotes()+1)
}
}

return nil
Expand All @@ -118,6 +137,37 @@ func (n *Notes) numNotes() int {
return len(n.Notes)
}

// hasGuids returns true if the document notes have guids. As we do not allow
// notes in the markdown with and without guids at the same time, just checking
// one is enough
func (n *Notes) hasGuids() bool {
if n.numNotes() == 0 {
return false
}

if n.Notes[0].Guid() == "" {
return false
}

return true
}

// hasGuid return true if one of the Note of Notes has the same guid as guid.
func (n *Notes) hasGuid(guid string) bool {

if n.numNotes() == 0 {
return false
}

for _, note := range n.Notes {
if note.Guid() == guid {
return true
}
}

return false
}

func (ns *Notes) addFieldName(fieldName string) error {

// after first note:
Expand Down Expand Up @@ -189,10 +239,6 @@ func (p *Parser) Parse(markdown []byte) (*Notes, error) {
nt.addTags(tags)
}

// note guid suffix
noteNum := len(notes.Notes) + 1
nt.addId(strconv.Itoa(noteNum))

return ast.WalkSkipChildren, nil
}

Expand Down
58 changes: 58 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,61 @@ func TestParseNoFieldsAfterNote(t *testing.T) {
t.Errorf("\ngot %s\nwant Error", err)
}
}

func TestParseNotesWithAndWithoutGuids(t *testing.T) {

WithAndWithout := []byte(`# note1 tag1 guid:note1
## field1
First note has two fields, Field1 and Field2
## field2
text
# note2 tag2 noguid
##
text
##
Text`)

md := mankidown.NewParser()
_, err := md.Parse(WithAndWithout)

t.Logf("Error is %q", err)
if err == nil {
t.Errorf("\ngot %s\nwant Error", err)
}
}

func TestParseNotesWithDuplicateGuid(t *testing.T) {

WithAndWithout := []byte(`# note1 tag1 guid:note1
## field1
First note has two fields, Field1 and Field2
## field2
text
# note2 tag2 guid:note1
##
text
##
Text`)

md := mankidown.NewParser()
_, err := md.Parse(WithAndWithout)

t.Logf("Error is %q", err)
if err == nil {
t.Errorf("\ngot %s\nwant Error", err)
}
}

0 comments on commit ee74631

Please sign in to comment.