func (wiki *Wiki) Dot(level int, opts ...dot.GraphOption) *dot.Graph {
	graph := dot.NewGraph()
	for _, opt := range opts {
		opt.Apply(graph)
	}
	var a, b dot.Node
	for k, val := range wiki.graph {
		if len(val) < level {
			continue
		}
		dir, _ := filepath.Split(k)
		if wiki.cluster && dir != "" {
			subgraph := graph.Subgraph(dir, dot.ClusterOption{})
			a = subgraph.Node(k)
		} else {
			a = graph.Node(k)
		}
		for _, v := range val {
			dir, _ := filepath.Split(v)
			if wiki.cluster && dir != "" {
				subgraph := graph.Subgraph(dir, dot.ClusterOption{})
				b = subgraph.Node(v)
			} else {
				b = graph.Node(v)
			}
			if len(graph.FindEdges(a, b)) == 0 {
				graph.Edge(a, b)
			}
		}
	}
	return graph
}
Dot converts wiki.graph into dot.Graph.

Only nodes, and their connections, are drawn if their sum of edges
is greater than the provided level. For `level = 0` all nodes
are inserted.

If wiki.cluster == true any nodes that correspond to a subdirectory are
inserted in the corresponding subgraph of that subdirectory. By default, the
visualisation will highlight these subgraphs.
package main
import (
	"bufio"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"github.com/emicklei/dot"
)
const wiki_ext string = ".wiki"
const wikiref string = `\[\[([^\[\]]*)\]\]`
const markdownref string = `\[(.*)\]\((.*)\)`
type Wiki struct {
	root string
	graph map[string][]string
	remap map[string]string
	cluster bool
	ignorePath string
	wikilink     *regexp.Regexp
	markdownlink *regexp.Regexp
	ignored      *regexp.Regexp
}
func newWiki(dir string, remap map[string]string, cluster bool, ignore string) (*Wiki, error) {
	wiki := Wiki{
		root:       dir,
		remap:      remap,
		graph:      make(map[string][]string),
		ignorePath: ignore,
		cluster:    cluster,
	}
	err := wiki.CompileExpressions()
	return &wiki, err
}
func (wiki *Wiki) Walk(subDirToSkip []string) error {
	err := filepath.Walk(wiki.root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Printf("err %v", err)
			return err
		}
		if info.IsDir() {
			for _, s := range subDirToSkip {
				if info.Name() == s {
					fmt.Fprintf(os.Stderr, "skipping: %v\n", info.Name())
					return filepath.SkipDir
				}
			}
			return nil
		}
		if wiki.IgnorePath(path) {
			return nil
		}
		return wiki.Add(path)
	})
	return err
}
func (wiki *Wiki) Insert(key, value string) {
	if unique(value, wiki.graph[key]) {
		wiki.graph[key] = append(wiki.graph[key], value)
	}
}
func (wiki *Wiki) Remap(dir, key, match string) (string, string) {
	match = filepath.Join(dir, match)
	for k, v := range wiki.remap {
		if k == dir {
			key = v
		}
		if strings.Contains(match, k) {
			match = v
		}
	}
	return key, match
}
func (wiki *Wiki) CompileExpressions() error {
	wikilink, err := regexp.Compile(wikiref)
	if err != nil {
		return err
	}
	wiki.wikilink = wikilink
	markdownlink, err := regexp.Compile(markdownref)
	if err != nil {
		return err
	}
	wiki.markdownlink = markdownlink
	if wiki.ignorePath != "" {
		ignored, err := regexp.Compile(wiki.ignorePath)
		if err != nil {
			return err
		}
		wiki.ignored = ignored
	}
	return nil
}
func (wiki *Wiki) Links(text string) []string {
	wikilinks := wiki.WikiLinks(text)
	for i, m := range wikilinks {
		wikilinks[i] = wiki.ParseWikiLinks(m)
	}
	markdownlinks := wiki.MarkdownLinks(text)
	for i, m := range markdownlinks {
		link := wiki.ParseMarkdownLinks(m)
		if link != "" {
			markdownlinks[i] = link
		}
	}
	return append(wikilinks, markdownlinks...)
}
func (wiki *Wiki) WikiLinks(text string) []string {
	return wiki.wikilink.FindAllString(text, -1)
}
func (wiki *Wiki) MarkdownLinks(text string) []string {
	return wiki.markdownlink.FindAllString(text, -1)
}
func (wiki *Wiki) ParseMarkdownLinks(link string) string {
	idx := strings.Index(link, "(")
	link = link[idx:]
	link = strings.Trim(link, "()")
	ext := filepath.Ext(link)
	if ext == ".md" || ext == ".wiki" {
		return link
	}
	if ext == "" {
		return link + ".md"
	}
	return ""
}
func (wiki *Wiki) ParseWikiLinks(link string) string {
	link = strings.Trim(link, "[]")
	idx := strings.Index(link, "|")
	if idx > 0 {
		link = link[:idx]
	}
	ext := filepath.Ext(link)
	if ext != ".md" && ext != ".wiki" {
		link += ".wiki"
	}
	return link
}
func (wiki *Wiki) IgnorePath(path string) bool {
	if wiki.ignored == nil {
		return false
	}
	return wiki.ignored.Match([]byte(path))
}
func (wiki *Wiki) Add(path string) error {
	key, err := filepath.Rel(wiki.root, path)
	if err != nil {
		return err
	}
	dir := filepath.Dir(key)  
	if _, ok := wiki.graph[key]; !ok {
		wiki.graph[key] = make([]string, 0)
	}
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		for _, link := range wiki.Links(scanner.Text()) {
			if wiki.IgnorePath(link) {
				continue
			}
			key, link = wiki.Remap(dir, key, link)
			wiki.Insert(key, link)
		}
	}
	return scanner.Err()
}