├── .github └── workflows │ └── go.yml ├── LICENSE ├── README.md ├── doc ├── example.png ├── example_diary.png ├── example_diary_cluster.png └── example_ignore_alice_or_bob.png ├── example ├── alice.wiki ├── bar.wiki ├── baz.md ├── bob.wiki ├── diary │ ├── diary.wiki │ ├── today.wiki │ └── yesterday.wiki ├── foo.wiki ├── index.wiki └── markdown.md ├── go.mod ├── go.sum ├── main.go ├── vimwikigraph.go └── vimwikigraph_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Set up Go 1.x 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: ^1.14 19 | 20 | - name: Check out code into the Go module directory 21 | uses: actions/checkout@v2 22 | 23 | - name: Get dependencies 24 | run: | 25 | go get -v -t -d ./... 26 | if [ -f Gopkg.toml ]; then 27 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 28 | dep ensure 29 | fi 30 | 31 | - name: Build 32 | run: go build -v . 33 | 34 | - name: Test 35 | run: go test -v . 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 max 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vimwikigraph 2 | 3 | ![Go](https://github.com/MaxvdKolk/vimwikigraph/workflows/Go/badge.svg) 4 | 5 | `vimwikigraph` walks all files in a 6 | [vimwiki](https://github.com/vimwiki/vimwiki) directory and builds a 7 | graph between the encountered files and their internal references. The 8 | code supports vimwiki-style links `[[link]]`, `[[link|description]]` 9 | and markdown-style links `[description](link)`. The graph is 10 | converted to the DOT language 11 | using [`dot`](https://github.com/emicklei/dot). The results can then be 12 | visualised with [graphviz](https://www.graphviz.org/about/), e.g. using 13 | `dot`, `neato`, `fdp`, etc. 14 | 15 | The graph visualises your notes and their connections, possibly 16 | providing new insights. 17 | 18 | ## Usage 19 | 20 | ``` 21 | ./vimwikigraph $HOME/vimwiki | dot -Tpng > test.png && open test.png 22 | ``` 23 | 24 | `-diary`: collapse all diary entries under a single node `diary.wiki` 25 | 26 | `-cluster`: cluster subdirectories as subgraphs 27 | 28 | `-l`: only nodes with at least `l` edges are inserted. The inserted nodes are 29 | inserted with all their edges. Thus, nodes with less than `l` edges can appear 30 | when they are connected to other nodes that do satisfy the requirement. 31 | For `-l 0`, all nodes are inserted. 32 | 33 | `--ignore REGEX`: ignores any encountered path matching `REGEX` 34 | 35 | Note: any trailing argument are considered directories to be skipped. 36 | 37 | ## Examples 38 | 39 | To illustrate `/example/` contains some `.wiki` files and also a 40 | diary, `/example/diary/*.wiki`. Running `vimwikigraph` produces the 41 | following output. All `diary` files are collapse by default into a 42 | single node. Any connections to and from any diary files are simply 43 | an arrow point in to, or out of, the diary. 44 | 45 | ``` 46 | ./vimwikigraph example | dot -Tpng > example.png 47 | ``` 48 | 49 | ![](./doc/example.png) 50 | 51 | The `-diary` flag ensures all diary items are shown as nodes. 52 | 53 | ``` 54 | ./vimwikigraph example -diary | dot -Tpng > example.png 55 | ``` 56 | 57 | ![](./doc/example_diary.png) 58 | 59 | The `-cluster` flag adds all diary items to a subgraph. 60 | 61 | ``` 62 | ./vimwikigraph example -diary -cluster | dot -Tpng > example.png 63 | ``` 64 | 65 | ![](./doc/example_diary_cluster.png) 66 | 67 | The `--ignore` allows to filter files by the given regex, e.g. to ignore 68 | any matches with `alice` or `bob` 69 | 70 | ``` 71 | ./vimwikigraph example -diary -cluster --ignore "alice|bob" | dot -Tpng > example.png 72 | ``` 73 | 74 | ![](./doc/example_ignore_alice_or_bob.png) 75 | 76 | ## Installation 77 | 78 | ``` 79 | go get github.com/maxvdkolk/vimwikigraph 80 | ``` 81 | 82 | ## Change log 83 | 84 | - 2021/05/31: add `--ignore` flag to ignore any path that matches the provided 85 | regex. 86 | -------------------------------------------------------------------------------- /doc/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxvdKolk/vimwikigraph/48e78c2869f7eeb8faca9aca13017368cdd345a8/doc/example.png -------------------------------------------------------------------------------- /doc/example_diary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxvdKolk/vimwikigraph/48e78c2869f7eeb8faca9aca13017368cdd345a8/doc/example_diary.png -------------------------------------------------------------------------------- /doc/example_diary_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxvdKolk/vimwikigraph/48e78c2869f7eeb8faca9aca13017368cdd345a8/doc/example_diary_cluster.png -------------------------------------------------------------------------------- /doc/example_ignore_alice_or_bob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxvdKolk/vimwikigraph/48e78c2869f7eeb8faca9aca13017368cdd345a8/doc/example_ignore_alice_or_bob.png -------------------------------------------------------------------------------- /example/alice.wiki: -------------------------------------------------------------------------------- 1 | [[foo]] 2 | 3 | [[diary/today]] 4 | -------------------------------------------------------------------------------- /example/bar.wiki: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxvdKolk/vimwikigraph/48e78c2869f7eeb8faca9aca13017368cdd345a8/example/bar.wiki -------------------------------------------------------------------------------- /example/baz.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxvdKolk/vimwikigraph/48e78c2869f7eeb8faca9aca13017368cdd345a8/example/baz.md -------------------------------------------------------------------------------- /example/bob.wiki: -------------------------------------------------------------------------------- 1 | [[baz.md]] 2 | [link](markdown.md) 3 | -------------------------------------------------------------------------------- /example/diary/diary.wiki: -------------------------------------------------------------------------------- 1 | [[today]] 2 | [[yesterday]] 3 | -------------------------------------------------------------------------------- /example/diary/today.wiki: -------------------------------------------------------------------------------- 1 | Meet: 2 | 3 | [[../alice]] 4 | [[../bob]] 5 | 6 | -------------------------------------------------------------------------------- /example/diary/yesterday.wiki: -------------------------------------------------------------------------------- 1 | [[../foo]] 2 | [[tomorrow]] 3 | 4 | -------------------------------------------------------------------------------- /example/foo.wiki: -------------------------------------------------------------------------------- 1 | Link to [[bar]] 2 | -------------------------------------------------------------------------------- /example/index.wiki: -------------------------------------------------------------------------------- 1 | [[foo]] 2 | [[bar]] 3 | -------------------------------------------------------------------------------- /example/markdown.md: -------------------------------------------------------------------------------- 1 | [description](baz.md) 2 | [wikilink](foo.wiki) 3 | [[foo.wiki]] 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/maxvdkolk/vimwikigraph 2 | 3 | go 1.14 4 | 5 | require github.com/emicklei/dot v0.11.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/emicklei/dot v0.11.0 h1:Ase39UD9T9fRBOb5ptgpixrxfx8abVzNWZi2+lr53PI= 2 | github.com/emicklei/dot v0.11.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/emicklei/dot" 10 | ) 11 | 12 | // example: go run main.go example | dot -Tpng > test.png && open test.png 13 | func main() { 14 | 15 | // fall back to current directory if no directory given 16 | var dir string 17 | if len(os.Args) == 1 { 18 | dir, _ = os.Executable() 19 | fmt.Fprintf(os.Stderr, "warning: using current directory: '%s'\n", dir) 20 | } else { 21 | if os.Args[1] != "-h" { 22 | dir = os.Args[1] 23 | os.Args = os.Args[1:] 24 | } 25 | } 26 | 27 | cluster := flag.Bool("cluster", false, "cluster nodes in sub directories") 28 | diary := flag.Bool("diary", false, "collapse all diary entries under a single `diary.wiki` node") 29 | level := flag.Int("l", 1, "draw only edges from nodes with at least level number of edges") 30 | ignoreRegex := flag.String("ignore", "", "ignore any files that match the given regex") 31 | flag.Parse() 32 | 33 | // remap any path that contains `diary` into `diary.wiki` 34 | remap := make(map[string]string) 35 | if !*diary { 36 | remap["diary"] = "diary.wiki" 37 | } 38 | 39 | // setup vimwiki struct 40 | wiki, err := newWiki(dir, remap, *cluster, *ignoreRegex) 41 | if err != nil { 42 | log.Fatalf("Error in constructor: %v", err) 43 | } 44 | 45 | // any trailing arguments are considered directories to skip 46 | subDirToSkip := []string{".git"} 47 | for _, dir := range flag.Args() { 48 | subDirToSkip = append(subDirToSkip, dir) 49 | } 50 | 51 | // walk directories and build graph 52 | if err := wiki.Walk(subDirToSkip); err != nil { 53 | log.Fatalf("Error when walking directories: %v", err) 54 | } 55 | 56 | // convert to a dot-graph for visualisation 57 | g := wiki.Dot(*level, dot.Directed) 58 | g.Attr("rankdir", "LR") 59 | g.Write(os.Stdout) 60 | } 61 | -------------------------------------------------------------------------------- /vimwikigraph.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "strings" 11 | 12 | "github.com/emicklei/dot" 13 | ) 14 | 15 | const wiki_ext string = ".wiki" 16 | const wikiref string = `\[\[([^\[\]]*)\]\]` 17 | const markdownref string = `\[(.*)\]\((.*)\)` 18 | 19 | type Wiki struct { 20 | // Root directory of vimwiki structure 21 | root string 22 | // Connections from a file to its links 23 | graph map[string][]string 24 | // Directories to rename during processing 25 | remap map[string]string 26 | // Enable clustered plotting of files in sub directories 27 | cluster bool 28 | // When any path matches this string, it is ignored in the resulting 29 | // graphs. 30 | ignorePath string 31 | 32 | // Contains all regular expressions to match links 33 | wikilink *regexp.Regexp 34 | markdownlink *regexp.Regexp 35 | ignored *regexp.Regexp 36 | } 37 | 38 | func newWiki(dir string, remap map[string]string, cluster bool, ignore string) (*Wiki, error) { 39 | wiki := Wiki{ 40 | root: dir, 41 | remap: remap, 42 | graph: make(map[string][]string), 43 | ignorePath: ignore, 44 | cluster: cluster, 45 | } 46 | err := wiki.CompileExpressions() 47 | return &wiki, err 48 | } 49 | 50 | // Walk walks over all directories in wiki.root except for any directory 51 | // contained in subDirToSkip. 52 | func (wiki *Wiki) Walk(subDirToSkip []string) error { 53 | err := filepath.Walk(wiki.root, func(path string, info os.FileInfo, err error) error { 54 | if err != nil { 55 | log.Printf("err %v", err) 56 | return err 57 | } 58 | if info.IsDir() { 59 | for _, s := range subDirToSkip { 60 | if info.Name() == s { 61 | fmt.Fprintf(os.Stderr, "skipping: %v\n", info.Name()) 62 | return filepath.SkipDir 63 | } 64 | } 65 | return nil 66 | } 67 | if wiki.IgnorePath(path) { 68 | return nil 69 | } 70 | return wiki.Add(path) 71 | }) 72 | return err 73 | } 74 | 75 | func (wiki *Wiki) Insert(key, value string) { 76 | // prevent (possibly many) duplicates 77 | if unique(value, wiki.graph[key]) { 78 | wiki.graph[key] = append(wiki.graph[key], value) 79 | } 80 | } 81 | 82 | func (wiki *Wiki) Remap(dir, key, match string) (string, string) { 83 | 84 | // joins current directory with link 85 | match = filepath.Join(dir, match) 86 | 87 | // apply remap naming, diary/file.wiki -> diary.wiki 88 | for k, v := range wiki.remap { 89 | if k == dir { 90 | key = v 91 | } 92 | if strings.Contains(match, k) { 93 | match = v 94 | } 95 | } 96 | 97 | return key, match 98 | } 99 | 100 | // Compile compiles all regex to match links with 101 | func (wiki *Wiki) CompileExpressions() error { 102 | wikilink, err := regexp.Compile(wikiref) 103 | if err != nil { 104 | return err 105 | } 106 | wiki.wikilink = wikilink 107 | 108 | markdownlink, err := regexp.Compile(markdownref) 109 | if err != nil { 110 | return err 111 | } 112 | wiki.markdownlink = markdownlink 113 | 114 | if wiki.ignorePath != "" { 115 | ignored, err := regexp.Compile(wiki.ignorePath) 116 | if err != nil { 117 | return err 118 | } 119 | wiki.ignored = ignored 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // Links returns all links available in text. 126 | func (wiki *Wiki) Links(text string) []string { 127 | 128 | // wiki syntax 129 | wikilinks := wiki.WikiLinks(text) 130 | for i, m := range wikilinks { 131 | wikilinks[i] = wiki.ParseWikiLinks(m) 132 | } 133 | 134 | // markdown syntax 135 | markdownlinks := wiki.MarkdownLinks(text) 136 | for i, m := range markdownlinks { 137 | link := wiki.ParseMarkdownLinks(m) 138 | if link != "" { 139 | markdownlinks[i] = link 140 | } 141 | } 142 | return append(wikilinks, markdownlinks...) 143 | } 144 | 145 | // WikiLinks matches on all vimwiki syntax links in text. 146 | func (wiki *Wiki) WikiLinks(text string) []string { 147 | return wiki.wikilink.FindAllString(text, -1) 148 | } 149 | 150 | // MarkdownLinks matches on all markdown syntax links in text. 151 | func (wiki *Wiki) MarkdownLinks(text string) []string { 152 | return wiki.markdownlink.FindAllString(text, -1) 153 | } 154 | 155 | // ParseMarkdownLinks extracts the filename from markdown syntax links. 156 | func (wiki *Wiki) ParseMarkdownLinks(link string) string { 157 | idx := strings.Index(link, "(") 158 | link = link[idx:] 159 | link = strings.Trim(link, "()") 160 | 161 | ext := filepath.Ext(link) 162 | if ext == ".md" || ext == ".wiki" { 163 | return link 164 | } 165 | 166 | // assume it refers to a local markdown file 167 | if ext == "" { 168 | return link + ".md" 169 | } 170 | 171 | // if ext is anything else, we should probably skip the file 172 | return "" 173 | } 174 | 175 | // ParseWikiLinks extracts the filename from vimwiki syntax links. 176 | func (wiki *Wiki) ParseWikiLinks(link string) string { 177 | // [[file]] -> dir/file.wiki 178 | link = strings.Trim(link, "[]") 179 | 180 | // split of description [[link|description]] 181 | idx := strings.Index(link, "|") 182 | if idx > 0 { 183 | link = link[:idx] 184 | } 185 | 186 | ext := filepath.Ext(link) 187 | if ext != ".md" && ext != ".wiki" { 188 | link += ".wiki" 189 | } 190 | return link 191 | } 192 | 193 | func (wiki *Wiki) IgnorePath(path string) bool { 194 | // When no regexes are provided to be ignored, always accpet the files 195 | if wiki.ignored == nil { 196 | return false 197 | } 198 | 199 | // Otherwise, return true if any match with the given regex is observed, 200 | // in that case the link should not be added to the graph 201 | return wiki.ignored.Match([]byte(path)) 202 | } 203 | 204 | // Add adds path to the wiki.graph when it contains links to other files. 205 | // 206 | // Only the relative paths are considered between the passed path and wiki.root. 207 | func (wiki *Wiki) Add(path string) error { 208 | key, err := filepath.Rel(wiki.root, path) 209 | if err != nil { 210 | return err 211 | } 212 | dir := filepath.Dir(key) // current dir when in subdirectory 213 | 214 | // initialise a node 215 | if _, ok := wiki.graph[key]; !ok { 216 | wiki.graph[key] = make([]string, 0) 217 | } 218 | 219 | // open file to find links 220 | file, err := os.Open(path) 221 | if err != nil { 222 | return err 223 | } 224 | defer file.Close() 225 | 226 | scanner := bufio.NewScanner(file) 227 | 228 | for scanner.Scan() { 229 | for _, link := range wiki.Links(scanner.Text()) { 230 | // do not insert links to ignored paths 231 | if wiki.IgnorePath(link) { 232 | continue 233 | } 234 | 235 | // rename and/or collapse folders 236 | key, link = wiki.Remap(dir, key, link) 237 | 238 | // insert into the graph 239 | wiki.Insert(key, link) 240 | } 241 | } 242 | return scanner.Err() 243 | } 244 | 245 | // Dot converts wiki.graph into dot.Graph. 246 | // 247 | // Only nodes, and their connections, are drawn if their sum of edges 248 | // is greater than the provided level. For `level = 0` all nodes 249 | // are inserted. 250 | // 251 | // If wiki.cluster == true any nodes that correspond to a subdirectory are 252 | // inserted in the corresponding subgraph of that subdirectory. By default, the 253 | // visualisation will highlight these subgraphs. 254 | func (wiki *Wiki) Dot(level int, opts ...dot.GraphOption) *dot.Graph { 255 | graph := dot.NewGraph() 256 | for _, opt := range opts { 257 | opt.Apply(graph) 258 | } 259 | 260 | var a, b dot.Node 261 | 262 | for k, val := range wiki.graph { 263 | 264 | // skip nodes with less edges 265 | if len(val) < level { 266 | continue 267 | } 268 | 269 | // insert in subgraph if wiki and in subdirectory 270 | // FIXME move into func? 271 | dir, _ := filepath.Split(k) 272 | if wiki.cluster && dir != "" { 273 | subgraph := graph.Subgraph(dir, dot.ClusterOption{}) 274 | a = subgraph.Node(k) 275 | } else { 276 | a = graph.Node(k) 277 | } 278 | 279 | for _, v := range val { 280 | // insert in subgraph if wiki and in subdirectory 281 | dir, _ := filepath.Split(v) 282 | if wiki.cluster && dir != "" { 283 | subgraph := graph.Subgraph(dir, dot.ClusterOption{}) 284 | b = subgraph.Node(v) 285 | } else { 286 | b = graph.Node(v) 287 | } 288 | 289 | // only insert unique edges 290 | if len(graph.FindEdges(a, b)) == 0 { 291 | graph.Edge(a, b) 292 | } 293 | } 294 | } 295 | 296 | return graph 297 | } 298 | 299 | // unique returns true when s is not present in values 300 | func unique(s string, vals []string) bool { 301 | for _, v := range vals { 302 | if s == v { 303 | return false 304 | } 305 | } 306 | return true 307 | } 308 | -------------------------------------------------------------------------------- /vimwikigraph_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/emicklei/dot" 9 | ) 10 | 11 | type match struct { 12 | text string 13 | matches []string 14 | links []string 15 | dir []string 16 | ignore string 17 | } 18 | 19 | func TestMappingCollapse(t *testing.T) { 20 | cases := []match{ 21 | match{ 22 | text: "[[diary/link]]", 23 | links: []string{"diary"}, 24 | dir: []string{"."}, 25 | ignore: "", 26 | }, 27 | match{ 28 | text: "[[../link]]", 29 | links: []string{"link.wiki"}, 30 | dir: []string{"diary"}, 31 | ignore: "", 32 | }, 33 | match{ 34 | text: "[[link]]", 35 | links: []string{"diary"}, 36 | dir: []string{"diary"}, 37 | ignore: "", 38 | }, 39 | } 40 | 41 | wiki := Wiki{} 42 | if err := wiki.CompileExpressions(); err != nil { 43 | t.Error(err) 44 | } 45 | wiki.remap = make(map[string]string) 46 | wiki.remap["diary"] = "diary" 47 | 48 | for _, c := range cases { 49 | for i, m := range wiki.Links(c.text) { 50 | 51 | _, link := wiki.Remap(c.dir[i], ".", m) 52 | 53 | if link != c.links[i] { 54 | t.Errorf("Expected link: %v:, got: %v", c.links[i], link) 55 | } 56 | } 57 | } 58 | } 59 | 60 | func TestMappingNoCollapse(t *testing.T) { 61 | cases := []match{ 62 | match{ 63 | text: "[[diary/link]]", 64 | matches: []string{"[[diary/link]]"}, 65 | links: []string{"diary/link.wiki"}, 66 | dir: []string{"."}, 67 | ignore: "", 68 | }, 69 | match{ 70 | text: "[[../link]]", 71 | matches: []string{"[[../link]]"}, 72 | links: []string{"link.wiki"}, 73 | dir: []string{"diary.wiki"}, 74 | ignore: "", 75 | }, 76 | match{ 77 | text: "[[link]]", 78 | links: []string{"diary/link.wiki"}, 79 | dir: []string{"diary"}, 80 | ignore: "", 81 | }, 82 | } 83 | 84 | wiki := Wiki{} 85 | if err := wiki.CompileExpressions(); err != nil { 86 | t.Error(err) 87 | } 88 | 89 | for _, c := range cases { 90 | for i, m := range wiki.Links(c.text) { 91 | 92 | _, link := wiki.Remap(c.dir[i], ".", m) 93 | 94 | if link != c.links[i] { 95 | t.Errorf("Expected link: %v:, got: %v", c.links[i], link) 96 | } 97 | } 98 | } 99 | } 100 | 101 | func TestMatchParseMarkdownLinks(t *testing.T) { 102 | cases := []match{ 103 | match{ 104 | text: "[link](url)", 105 | matches: []string{"[link](url)"}, 106 | links: []string{"url.md"}, 107 | ignore: "", 108 | }, 109 | match{ 110 | text: "[link](url.md)", 111 | matches: []string{"[link](url.md)"}, 112 | links: []string{"url.md"}, 113 | ignore: "", 114 | }, 115 | match{ 116 | text: "[link](vimwiki.wiki)", 117 | matches: []string{"[link](vimwiki.wiki)"}, 118 | links: []string{"vimwiki.wiki"}, 119 | ignore: "", 120 | }, 121 | match{ 122 | text: "![figure](image.png)", 123 | matches: []string{"[figure](image.png)"}, 124 | links: []string{""}, 125 | ignore: "", 126 | }, 127 | } 128 | 129 | wiki := Wiki{} 130 | if err := wiki.CompileExpressions(); err != nil { 131 | t.Error(err) 132 | } 133 | 134 | for _, c := range cases { 135 | matches := wiki.MarkdownLinks(c.text) 136 | 137 | if len(matches) != len(c.matches) { 138 | t.Errorf("Expected %d matches, got %d matches", len(c.matches), len(matches)) 139 | } 140 | 141 | for i, m := range matches { 142 | if m != c.matches[i] { 143 | t.Errorf("Expected match %v, got %v", c.matches[i], m) 144 | } 145 | } 146 | 147 | for i, m := range matches { 148 | link := wiki.ParseMarkdownLinks(m) 149 | if link != c.links[i] { 150 | t.Errorf("Expected link: %v, got %v", c.links[i], link) 151 | } 152 | } 153 | } 154 | } 155 | 156 | func TestMatchParseWikiLinks(t *testing.T) { 157 | cases := []match{ 158 | match{ 159 | text: "[[link]]", 160 | matches: []string{"[[link]]"}, 161 | links: []string{"link.wiki"}, 162 | }, 163 | match{ 164 | text: "[[a]]\n[[b]]", 165 | matches: []string{"[[a]]", "[[b]]"}, 166 | links: []string{"a.wiki", "b.wiki"}, 167 | ignore: "", 168 | }, 169 | match{ 170 | text: "[[link|description]]", 171 | matches: []string{"[[link|description]]"}, 172 | links: []string{"link.wiki"}, 173 | ignore: "", 174 | }, 175 | match{ 176 | text: "[[link.wiki]]", 177 | matches: []string{"[[link.wiki]]"}, 178 | links: []string{"link.wiki"}, 179 | ignore: "", 180 | }, 181 | match{ 182 | text: "[[link.md]]", 183 | matches: []string{"[[link.md]]"}, 184 | links: []string{"link.md"}, 185 | ignore: "", 186 | }, 187 | } 188 | 189 | wiki := Wiki{} 190 | if err := wiki.CompileExpressions(); err != nil { 191 | t.Error(err) 192 | } 193 | 194 | for _, c := range cases { 195 | matches := wiki.WikiLinks(c.text) 196 | 197 | if len(matches) != len(c.matches) { 198 | t.Errorf("Expected %d matches, got %d matches", len(c.matches), len(matches)) 199 | } 200 | 201 | for i, m := range matches { 202 | if m != c.matches[i] { 203 | t.Errorf("Expected match %v, got %v", c.matches[i], m) 204 | } 205 | } 206 | 207 | for i, m := range matches { 208 | link := wiki.ParseWikiLinks(m) 209 | if link != c.links[i] { 210 | t.Errorf("Expected link: %v, got %v", c.links[i], link) 211 | } 212 | } 213 | } 214 | } 215 | 216 | func TestNodeConnectionLevel(t *testing.T) { 217 | os.Chdir(".") 218 | dir, _ := os.Executable() 219 | t.Log(dir) 220 | wiki, err := newWiki("example", make(map[string]string), false, "") 221 | 222 | if err != nil { 223 | t.Errorf("Expected no error in constructor") 224 | } 225 | 226 | // build 0 < i < 10 entries with each i connections, where the current 227 | // nodes are not considered as connections 228 | wiki.graph = make(map[string][]string) 229 | for i := 0; i < 10; i++ { 230 | k := fmt.Sprintf("%d", i) 231 | wiki.graph[k] = make([]string, 0, 0) 232 | for j := 0; j < i; j++ { 233 | wiki.graph[k] = append(wiki.graph[k], fmt.Sprintf("%d00", j)) 234 | } 235 | } 236 | 237 | // level zero: 9 entries + 10 connections: 19 nodes in total 238 | // each increment of the level should draw one node less 239 | for l := 0; l < 10; l++ { 240 | g := wiki.Dot(l, dot.Directed) 241 | nconn := len(g.FindNodes()) 242 | exp := 19 - l 243 | if nconn != exp { 244 | t.Errorf("For level %v: exp: %v, got %v", l, exp, nconn) 245 | } 246 | } 247 | 248 | // higher than maximum connectivity should result in zero nodes 249 | nconn := len(wiki.Dot(10, dot.Directed).FindNodes()) 250 | if nconn > 0 { 251 | t.Errorf("Expected 0 connections, got %v", nconn) 252 | } 253 | } 254 | 255 | func TestIgnorePaths(t *testing.T) { 256 | wiki, err := newWiki("example", make(map[string]string), false, "t*") 257 | if err != nil { 258 | t.Errorf("Expected no error in constructor") 259 | } 260 | if !wiki.IgnorePath("test") { 261 | t.Errorf("Path should be discarged given the regex") 262 | } 263 | } 264 | --------------------------------------------------------------------------------