Proof of concept for graph JSON output

pull/105/head
Mickaël Menu 3 years ago
parent 835e0dc142
commit 4e2e005884

@ -902,3 +902,69 @@ func buildMentionQuery(title, metadataJSON string) string {
type RowScanner interface {
Scan(dest ...interface{}) error
}
func (d *NoteDAO) FindLinksBetweenNotes(ids []core.NoteID) ([]core.ResolvedLink, error) {
links := make([]core.ResolvedLink, 0)
idsString := d.joinIds(ids, ",")
rows, err := d.tx.Query(fmt.Sprintf(`
SELECT id, source_id, source_path, target_id, target_path, title, href, external, rels, snippet, snippet_start, snippet_end
FROM links_with_metadata
WHERE source_id IN (%s) AND target_id IN (%s)
`, idsString, idsString))
if err != nil {
return links, err
}
defer rows.Close()
for rows.Next() {
link, err := d.scanLink(rows)
if err != nil {
d.logger.Err(err)
continue
}
if link != nil {
links = append(links, *link)
}
}
return links, nil
}
func (d *NoteDAO) scanLink(row RowScanner) (*core.ResolvedLink, error) {
var (
id, sourceID, targetID, snippetStart, snippetEnd int
sourcePath, targetPath, title, href, snippet string
external bool
rels sql.NullString
)
err := row.Scan(
&id, &sourceID, &sourcePath, &targetID, &targetPath, &title, &href,
&external, &rels, &snippet, &snippetStart, &snippetEnd,
)
switch {
case err == sql.ErrNoRows:
return nil, nil
case err != nil:
return nil, err
default:
return &core.ResolvedLink{
SourceID: core.NoteID(sourceID),
SourcePath: sourcePath,
TargetID: core.NoteID(targetID),
TargetPath: targetPath,
Link: core.Link{
Title: title,
Href: href,
IsExternal: external,
Rels: []core.LinkRelation{},
// FIXME
// Rels: parseListFromNullString(rels),
Snippet: snippet,
SnippetStart: snippetStart,
SnippetEnd: snippetEnd,
},
}, nil
}
}

@ -46,6 +46,15 @@ func (ni *NoteIndex) FindMinimal(opts core.NoteFindOpts) (notes []core.MinimalNo
return
}
// FindLinksBetweenNotes implements core.NoteIndex.
func (ni *NoteIndex) FindLinksBetweenNotes(ids []core.NoteID) (links []core.ResolvedLink, err error) {
err = ni.commit(func(dao *dao) error {
links, err = dao.notes.FindLinksBetweenNotes(ids)
return err
})
return
}
// FindCollections implements core.NoteIndex.
func (ni *NoteIndex) FindCollections(kind core.CollectionKind) (collections []core.Collection, err error) {
err = ni.commit(func(dao *dao) error {

@ -1,18 +1,20 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"github.com/mickael-menu/zk/internal/adapter/fzf"
"github.com/mickael-menu/zk/internal/cli"
"github.com/mickael-menu/zk/internal/core"
"github.com/mickael-menu/zk/internal/util/errors"
"github.com/mickael-menu/zk/internal/util/strings"
)
// Graph produces a directed graph of the notes matching a set of criteria.
type Graph struct {
Format string `group:format short:f help:"Format of the graph among: json." enum:"json" default:"json"`
Format string `group:format short:f help:"Format of the graph among: json." enum:"json" required`
Quiet bool `group:format short:q help:"Do not print the total number of notes found."`
cli.Filtering
}
@ -37,6 +39,14 @@ func (cmd *Graph) Run(container *cli.Container) error {
if err != nil {
return err
}
noteIDs := []core.NoteID{}
for _, note := range notes {
noteIDs = append(noteIDs, note.ID)
}
links, err := notebook.FindLinksBetweenNotes(noteIDs)
if err != nil {
return err
}
filter := container.NewNoteFilter(fzf.NoteFilterOpts{
Interactive: cmd.Interactive,
@ -52,22 +62,34 @@ func (cmd *Graph) Run(container *cli.Container) error {
return err
}
count := len(notes)
if count > 0 {
for i, note := range notes {
if i > 0 {
fmt.Println()
}
fmt.Print("{\n \"notes\": [\n")
for i, note := range notes {
if i > 0 {
fmt.Print(",\n")
}
ft, err := format(note)
if err != nil {
return err
}
fmt.Printf(" %s", ft)
}
ft, err := format(note)
if err != nil {
return err
}
fmt.Print(ft)
fmt.Print("\n ],\n \"links\": [\n")
for i, link := range links {
if i > 0 {
fmt.Print(",\n")
}
ft, err := json.Marshal(link)
if err != nil {
return err
}
fmt.Printf(" %s", string(ft))
}
fmt.Print("\n ]\n}\n")
if err == nil && !cmd.Quiet {
count := len(notes)
fmt.Fprintf(os.Stderr, "\n\nFound %d %s\n", count, strings.Pluralize("note", count))
}

@ -3,19 +3,27 @@ package core
// Link represents a link in a note to another note or an external resource.
type Link struct {
// Label of the link.
Title string
Title string `json:"title"`
// Destination URI of the link.
Href string
Href string `json:"href"`
// Indicates whether the target is a remote (e.g. HTTP) resource.
IsExternal bool
IsExternal bool `json:"isExternal"`
// Relationships between the note and the linked target.
Rels []LinkRelation
Rels []LinkRelation `json:"rels"`
// Excerpt of the paragraph containing the note.
Snippet string
Snippet string `json:"snippet"`
// Start byte offset of the snippet in the note content.
SnippetStart int
SnippetStart int `json:"snippetStart"`
// End byte offset of the snippet in the note content.
SnippetEnd int
SnippetEnd int `json:"snippetEnd"`
}
type ResolvedLink struct {
Link
SourceID NoteID `json:"sourceId"`
SourcePath string `json:"sourcePath"`
TargetID NoteID `json:"targetId"`
TargetPath string `json:"targetPath"`
}
// LinkRelation defines the relationship between a link's source and target.

@ -25,6 +25,9 @@ type NoteIndex interface {
// given filtering and sorting criteria.
FindMinimal(opts NoteFindOpts) ([]MinimalNote, error)
// FindLinksBetweenNotes retrieves the links between the given notes.
FindLinksBetweenNotes(ids []NoteID) ([]ResolvedLink, error)
// FindCollections retrieves all the collections of the given kind.
FindCollections(kind CollectionKind) ([]Collection, error)

@ -197,6 +197,11 @@ func (n *Notebook) FindByHref(href string) (*MinimalNote, error) {
}
}
// FindLinksBetweenNotes retrieves the links between the given notes.
func (n *Notebook) FindLinksBetweenNotes(ids []NoteID) ([]ResolvedLink, error) {
return n.index.FindLinksBetweenNotes(ids)
}
// FindCollections retrieves all the collections of the given kind.
func (n *Notebook) FindCollections(kind CollectionKind) ([]Collection, error) {
return n.index.FindCollections(kind)

Loading…
Cancel
Save