Source file based relative linking
authorSven Dowideit <SvenDowideit@home.org.au>
Wed, 9 Sep 2015 00:03:38 +0000 (10:03 +1000)
committerSteve Francia <steve.francia@gmail.com>
Fri, 1 Jan 2016 20:23:11 +0000 (15:23 -0500)
ala GitHub repository markdown for both md files and non-md files

Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
helpers/content.go
helpers/content_renderer.go
hugolib/page.go
hugolib/site.go
hugolib/site_test.go

index a5ad647baa47798ebd113f6ec78f9d723a281bdb..ccbdb09c5ce6fe4cf9529294d96a3fb5a7731f6b 100644 (file)
@@ -43,27 +43,29 @@ var SummaryDivider = []byte("<!--more-->")
 
 // Blackfriday holds configuration values for Blackfriday rendering.
 type Blackfriday struct {
-       Smartypants     bool
-       AngledQuotes    bool
-       Fractions       bool
-       HrefTargetBlank bool
-       SmartDashes     bool
-       LatexDashes     bool
-       PlainIDAnchors  bool
-       Extensions      []string
-       ExtensionsMask  []string
+       Smartypants             bool
+       AngledQuotes            bool
+       Fractions               bool
+       HrefTargetBlank         bool
+       SmartDashes             bool
+       LatexDashes             bool
+       PlainIDAnchors          bool
+       SourceRelativeLinksEval bool
+       Extensions              []string
+       ExtensionsMask          []string
 }
 
 // NewBlackfriday creates a new Blackfriday filled with site config or some sane defaults
 func NewBlackfriday() *Blackfriday {
        combinedParam := map[string]interface{}{
-               "smartypants":     true,
-               "angledQuotes":    false,
-               "fractions":       true,
-               "hrefTargetBlank": false,
-               "smartDashes":     true,
-               "latexDashes":     true,
-               "plainIDAnchors":  false,
+               "smartypants":         true,
+               "angledQuotes":        false,
+               "fractions":           true,
+               "hrefTargetBlank":     false,
+               "smartDashes":         true,
+               "latexDashes":         true,
+               "plainIDAnchors":      false,
+               "sourceRelativeLinks": false,
        }
 
        siteParam := viper.GetStringMap("blackfriday")
@@ -198,7 +200,9 @@ func GetHTMLRenderer(defaultFlags int, ctx *RenderingContext) blackfriday.Render
        }
 
        return &HugoHtmlRenderer{
-               blackfriday.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters),
+               FileResolver: ctx.FileResolver,
+               LinkResolver: ctx.LinkResolver,
+               Renderer:     blackfriday.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters),
        }
 }
 
@@ -329,11 +333,13 @@ func ExtractTOC(content []byte) (newcontent []byte, toc []byte) {
 // RenderingContext holds contextual information, like content and configuration,
 // for a given content renderin.g
 type RenderingContext struct {
-       Content    []byte
-       PageFmt    string
-       DocumentID string
-       Config     *Blackfriday
-       configInit sync.Once
+       Content      []byte
+       PageFmt      string
+       DocumentID   string
+       Config       *Blackfriday
+       FileResolver FileResolverFunc
+       LinkResolver LinkResolverFunc
+       configInit   sync.Once
 }
 
 func (c *RenderingContext) getConfig() *Blackfriday {
index 00997900d3c03be16d53a5dd1bc1a47746e2bd73..81386e03b790a6b219e6445e3f2477fcb158e266 100644 (file)
@@ -19,12 +19,18 @@ import (
 
        "github.com/miekg/mmark"
        "github.com/russross/blackfriday"
+       jww "github.com/spf13/jwalterweatherman"
        "github.com/spf13/viper"
 )
 
+type LinkResolverFunc func(ref string) (string, error)
+type FileResolverFunc func(ref string) (string, error)
+
 // Wraps a blackfriday.Renderer, typically a blackfriday.Html
 // Enabling Hugo to customise the rendering experience
 type HugoHtmlRenderer struct {
+       FileResolver FileResolverFunc
+       LinkResolver LinkResolverFunc
        blackfriday.Renderer
 }
 
@@ -38,6 +44,33 @@ func (renderer *HugoHtmlRenderer) BlockCode(out *bytes.Buffer, text []byte, lang
        }
 }
 
+func (renderer *HugoHtmlRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
+       if renderer.LinkResolver == nil || bytes.HasPrefix(link, []byte("{@{@HUGOSHORTCODE")) {
+               // Use the blackfriday built in Link handler
+               renderer.Renderer.Link(out, link, title, content)
+       } else {
+               newLink, err := renderer.LinkResolver(string(link))
+               if err != nil {
+                       newLink = string(link)
+                       jww.ERROR.Printf("LinkResolver: %s", err)
+               }
+               renderer.Renderer.Link(out, []byte(newLink), title, content)
+       }
+}
+func (renderer *HugoHtmlRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
+       if renderer.FileResolver == nil || bytes.HasPrefix(link, []byte("{@{@HUGOSHORTCODE")) {
+               // Use the blackfriday built in Image handler
+               renderer.Renderer.Image(out, link, title, alt)
+       } else {
+               newLink, err := renderer.FileResolver(string(link))
+               if err != nil {
+                       newLink = string(link)
+                       jww.ERROR.Printf("FileResolver: %s", err)
+               }
+               renderer.Renderer.Image(out, []byte(newLink), title, alt)
+       }
+}
+
 // Wraps a mmark.Renderer, typically a mmark.html
 // Enabling Hugo to customise the rendering experience
 type HugoMmarkHtmlRenderer struct {
index 0a2a4a5e9a76040dee788a3de6bc120be7e5c9c5..a37d21663fc37a3cf08292093cf32e5055f8a026 100644 (file)
@@ -234,14 +234,34 @@ func (p *Page) setSummary() {
 }
 
 func (p *Page) renderBytes(content []byte) []byte {
+       var fn helpers.LinkResolverFunc
+       var fileFn helpers.FileResolverFunc
+       if p.getRenderingConfig().SourceRelativeLinksEval {
+               fn = func(ref string) (string, error) {
+                       return p.Node.Site.GitHub(ref, p)
+               }
+               fileFn = func(ref string) (string, error) {
+                       return p.Node.Site.GitHubFileLink(ref, p)
+               }
+       }
        return helpers.RenderBytes(
                &helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(),
-                       DocumentID: p.UniqueID(), Config: p.getRenderingConfig()})
+                       DocumentID: p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
 }
 
 func (p *Page) renderContent(content []byte) []byte {
+       var fn helpers.LinkResolverFunc
+       var fileFn helpers.FileResolverFunc
+       if p.getRenderingConfig().SourceRelativeLinksEval {
+               fn = func(ref string) (string, error) {
+                       return p.Node.Site.GitHub(ref, p)
+               }
+               fileFn = func(ref string) (string, error) {
+                       return p.Node.Site.GitHubFileLink(ref, p)
+               }
+       }
        return helpers.RenderBytesWithTOC(&helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(),
-               DocumentID: p.UniqueID(), Config: p.getRenderingConfig()})
+               DocumentID: p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
 }
 
 func (p *Page) getRenderingConfig() *helpers.Blackfriday {
index 6a6760dbad66b89461d3f7fd9b6dc8f279f30f89..ec0eb886f8ee9c695418567216a341fbb7f99e1c 100644 (file)
@@ -100,7 +100,7 @@ type SiteInfo struct {
        Social                SiteSocial
        Sections              Taxonomy
        Pages                 *Pages
-       Files                 []*source.File
+       Files                 *[]*source.File
        Menus                 *Menus
        Hugo                  *HugoInfo
        Title                 string
@@ -217,6 +217,166 @@ func (s *SiteInfo) RelRef(ref string, page *Page) (string, error) {
        return s.refLink(ref, page, true)
 }
 
+func (s *SiteInfo) GitHub(ref string, page *Page) (string, error) {
+       return s.githubLink(ref, page, true)
+}
+
+func (s *SiteInfo) githubLink(ref string, currentPage *Page, relative bool) (string, error) {
+       var refURL *url.URL
+       var err error
+
+       // TODO can I make this a param to `hugo --use-github-links=/docs`?
+       // SVEN: add more tests - the prefix might be a real dir inside tho - add some pages that have it as a legitimate path
+       repositoryPathPrefix := "/docs"
+
+       refURL, err = url.Parse(strings.TrimPrefix(ref, repositoryPathPrefix))
+       if err != nil {
+               return "", err
+       }
+
+       if refURL.Scheme != "" {
+               // TODO: consider looking for http(s?)://github.com/user/project/prefix and replacing it - tho this may be intentional, so idk
+               //return "", fmt.Errorf("Not a plain filepath link (%s)", ref)
+               // Treat this as not an error, as the link is used as-is
+               return ref, nil
+       }
+
+       var target *Page
+       var link string
+
+       if refURL.Path != "" {
+               refPath := filepath.Clean(filepath.FromSlash(refURL.Path))
+
+               if strings.IndexRune(refPath, os.PathSeparator) == 0 { // filepath.IsAbs fails to me.
+                       refPath = refPath[1:]
+               } else {
+                       if currentPage != nil {
+                               refPath = filepath.Join(currentPage.Source.Dir(), refURL.Path)
+                       }
+               }
+
+               for _, page := range []*Page(*s.Pages) {
+                       if page.Source.Path() == refPath {
+                               target = page
+                               break
+                       }
+               }
+               // need to exhaust the test, then try with the others :/
+               // if the refPath doesn't end in a filename with extension `.md`, then try with `.md` , and then `/index.md`
+               mdPath := strings.TrimSuffix(refPath, string(os.PathSeparator)) + ".md"
+               for _, page := range []*Page(*s.Pages) {
+                       if page.Source.Path() == mdPath {
+                               target = page
+                               break
+                       }
+               }
+               indexPath := filepath.Join(refPath, "index.md")
+               for _, page := range []*Page(*s.Pages) {
+                       if page.Source.Path() == indexPath {
+                               target = page
+                               break
+                       }
+               }
+
+               if target == nil {
+                       return "", fmt.Errorf("No page found for \"%s\" on page \"%s\".\n", ref, currentPage.Source.Path())
+               }
+
+               // SVEN: look at filepath.Rel() it might help, got the rel/non-rel url's (dangerous tho)
+               if relative {
+                       link, err = target.RelPermalink()
+               } else {
+                       link, err = target.Permalink()
+               }
+
+               if err != nil {
+                       return "", err
+               }
+       }
+
+       // SVEN: add tests for github style relative fragments
+       if refURL.Fragment != "" {
+               link = link + "#" + refURL.Fragment
+
+               if refURL.Path != "" && target != nil && !target.getRenderingConfig().PlainIDAnchors {
+                       link = link + ":" + target.UniqueID()
+               } else if currentPage != nil && !currentPage.getRenderingConfig().PlainIDAnchors {
+                       link = link + ":" + currentPage.UniqueID()
+               }
+       }
+
+       return link, nil
+}
+
+func (s *SiteInfo) GitHubFileLink(ref string, page *Page) (string, error) {
+       return s.githubFileLink(ref, page, false)
+}
+
+// for non-pages in the site tree
+func (s *SiteInfo) githubFileLink(ref string, currentPage *Page, relative bool) (string, error) {
+       var refURL *url.URL
+       var err error
+
+       // TODO can I make this a param to `hugo --use-github-links=/docs`?
+       // SVEN: add more tests - the prefix might be a real dir inside tho - add some pages that have it as a legitimate path
+       repositoryPathPrefix := "/docs"
+
+       refURL, err = url.Parse(strings.TrimPrefix(ref, repositoryPathPrefix))
+       if err != nil {
+               return "", err
+       }
+
+       if refURL.Scheme != "" {
+               // TODO: consider looking for http(s?)://github.com/user/project/prefix and replacing it - tho this may be intentional, so idk
+               //return "", fmt.Errorf("Not a plain filepath link (%s)", ref)
+               // Treat this as not an error, as the link is used as-is
+               return ref, nil
+       }
+
+       var target *source.File
+       var link string
+
+       if refURL.Path != "" {
+               refPath := filepath.Clean(filepath.FromSlash(refURL.Path))
+
+               if strings.IndexRune(refPath, os.PathSeparator) == 0 { // filepath.IsAbs fails to me.
+                       refPath = refPath[1:]
+               } else {
+                       if currentPage != nil {
+                               refPath = filepath.Join(currentPage.Source.Dir(), refURL.Path)
+                       }
+               }
+
+               for _, file := range []*source.File(*s.Files) {
+                       if file.Path() == refPath {
+                               target = file
+                               break
+                       }
+               }
+
+               if target == nil {
+                       return "", fmt.Errorf("No file found for \"%s\" on page \"%s\".\n", ref, currentPage.Source.Path())
+               }
+
+               link = target.Path()
+               // SVEN: look at filepath.Rel() it might help, got the rel/non-rel url's (dangerous tho)
+               // SVEN: reconsider the fact I hardcoded the `relative` bool in both github resolvers
+               if relative {
+                       return "./" + filepath.ToSlash(link), nil
+               } else {
+                       return "/" + filepath.ToSlash(link), nil
+               }
+
+               if err != nil {
+                       return "", err
+               }
+
+               return link, nil
+       }
+
+       return "", fmt.Errorf("failed to find a file to match \"%s\" on page \"%s\"", ref, currentPage.Source.Path())
+}
+
 func (s *SiteInfo) addToPaginationPageCount(cnt uint64) {
        atomic.AddUint64(&s.paginationPageCount, cnt)
 }
@@ -479,6 +639,7 @@ func (s *Site) initializeSiteInfo() {
                canonifyURLs:          viper.GetBool("CanonifyURLs"),
                preserveTaxonomyNames: viper.GetBool("PreserveTaxonomyNames"),
                Pages:      &s.Pages,
+               Files:      &s.Files,
                Menus:      &s.Menus,
                Params:     params,
                Permalinks: permalinks,
@@ -1395,6 +1556,7 @@ func (s *Site) Stats() {
        jww.FEEDBACK.Println(s.draftStats())
        jww.FEEDBACK.Println(s.futureStats())
        jww.FEEDBACK.Printf("%d pages created\n", len(s.Pages))
+       jww.FEEDBACK.Printf("%d non-page files copied\n", len(s.Files))
        jww.FEEDBACK.Printf("%d paginator pages created\n", s.Info.paginationPageCount)
        taxonomies := viper.GetStringMapString("Taxonomies")
 
index d99fadf52c9808bf351cd472ff813b5731c031b2..15cdc02d80775100f661f8f016c77441afb4f752 100644 (file)
@@ -14,7 +14,6 @@
 package hugolib
 
 import (
-       "bitbucket.org/pkg/inflect"
        "bytes"
        "fmt"
        "html/template"
@@ -23,6 +22,8 @@ import (
        "strings"
        "testing"
 
+       "bitbucket.org/pkg/inflect"
+
        "github.com/spf13/afero"
        "github.com/spf13/hugo/helpers"
        "github.com/spf13/hugo/hugofs"
@@ -1026,26 +1027,30 @@ func findPage(site *Site, f string) *Page {
        return nil
 }
 
-func TestRefLinking(t *testing.T) {
-       viper.Reset()
-       defer viper.Reset()
-
+func setupLinkingMockSite(t *testing.T) *Site {
        hugofs.DestinationFS = new(afero.MemMapFs)
        sources := []source.ByteSource{
                {filepath.FromSlash("index.md"), []byte("")},
                {filepath.FromSlash("rootfile.md"), []byte("")},
+               {filepath.FromSlash("root-image.png"), []byte("")},
 
                {filepath.FromSlash("level2/2-root.md"), []byte("")},
                {filepath.FromSlash("level2/index.md"), []byte("")},
                {filepath.FromSlash("level2/common.md"), []byte("")},
 
-               {filepath.FromSlash("level2b/2b-root.md"), []byte("")},
-               {filepath.FromSlash("level2b/index.md"), []byte("")},
-               {filepath.FromSlash("level2b/common.md"), []byte("")},
+
+//             {filepath.FromSlash("level2b/2b-root.md"), []byte("")},
+//             {filepath.FromSlash("level2b/index.md"), []byte("")},
+//             {filepath.FromSlash("level2b/common.md"), []byte("")},
+
+               {filepath.FromSlash("level2/2-image.png"), []byte("")},
+               {filepath.FromSlash("level2/common.png"), []byte("")},
 
                {filepath.FromSlash("level2/level3/3-root.md"), []byte("")},
                {filepath.FromSlash("level2/level3/index.md"), []byte("")},
                {filepath.FromSlash("level2/level3/common.md"), []byte("")},
+               {filepath.FromSlash("level2/level3/3-image.png"), []byte("")},
+               {filepath.FromSlash("level2/level3/common.png"), []byte("")},
        }
 
        site := &Site{
@@ -1064,7 +1069,13 @@ func TestRefLinking(t *testing.T) {
        viper.Set("PluralizeListTitles", false)
        viper.Set("CanonifyURLs", false)
 
-       // END init mock site
+       return site
+}
+
+func TestRefLinking(t *testing.T) {
+       viper.Reset()
+       defer viper.Reset()
+       site := setupLinkingMockSite(t)
 
        currentPage := findPage(site, "level2/level3/index.md")
        if currentPage == nil {
@@ -1084,3 +1095,189 @@ func TestRefLinking(t *testing.T) {
        }
        // TODO: and then the failure cases.
 }
+
+func TestSourceRelativeLinksing(t *testing.T) {
+       viper.Reset()
+       defer viper.Reset()
+       site := setupLinkingMockSite(t)
+
+       type resultMap map[string]string
+
+       okresults := map[string]resultMap{
+               "index.md": map[string]string{
+                       "/docs/rootfile.md":             "/rootfile/",
+                       "/docs/index.md":                "/",
+                       "rootfile.md":                   "/rootfile/",
+                       "index.md":                      "/",
+                       "level2/2-root.md":              "/level2/2-root/",
+                       "level2/index.md":               "/level2/",
+                       "/docs/level2/2-root.md":        "/level2/2-root/",
+                       "/docs/level2/index.md":         "/level2/",
+                       "level2/level3/3-root.md":       "/level2/level3/3-root/",
+                       "level2/level3/index.md":        "/level2/level3/",
+                       "/docs/level2/level3/3-root.md": "/level2/level3/3-root/",
+                       "/docs/level2/level3/index.md":  "/level2/level3/",
+                       "/docs/level2/2-root/":          "/level2/2-root/",
+                       "/docs/level2/":                 "/level2/",
+                       "/docs/level2/2-root":           "/level2/2-root/",
+                       "/docs/level2":                  "/level2/",
+                       "/level2/2-root/":               "/level2/2-root/",
+                       "/level2/":                      "/level2/",
+                       "/level2/2-root":                "/level2/2-root/",
+                       "/level2":                       "/level2/",
+               }, "rootfile.md": map[string]string{
+                       "/docs/rootfile.md":             "/rootfile/",
+                       "/docs/index.md":                "/",
+                       "rootfile.md":                   "/rootfile/",
+                       "index.md":                      "/",
+                       "level2/2-root.md":              "/level2/2-root/",
+                       "level2/index.md":               "/level2/",
+                       "/docs/level2/2-root.md":        "/level2/2-root/",
+                       "/docs/level2/index.md":         "/level2/",
+                       "level2/level3/3-root.md":       "/level2/level3/3-root/",
+                       "level2/level3/index.md":        "/level2/level3/",
+                       "/docs/level2/level3/3-root.md": "/level2/level3/3-root/",
+                       "/docs/level2/level3/index.md":  "/level2/level3/",
+               }, "level2/2-root.md": map[string]string{
+                       "../rootfile.md":                "/rootfile/",
+                       "../index.md":                   "/",
+                       "/docs/rootfile.md":             "/rootfile/",
+                       "/docs/index.md":                "/",
+                       "2-root.md":                     "/level2/2-root/",
+                       "index.md":                      "/level2/",
+                       "../level2/2-root.md":           "/level2/2-root/",
+                       "../level2/index.md":            "/level2/",
+                       "./2-root.md":                   "/level2/2-root/",
+                       "./index.md":                    "/level2/",
+                       "/docs/level2/index.md":         "/level2/",
+                       "/docs/level2/2-root.md":        "/level2/2-root/",
+                       "level3/3-root.md":              "/level2/level3/3-root/",
+                       "level3/index.md":               "/level2/level3/",
+                       "../level2/level3/index.md":     "/level2/level3/",
+                       "../level2/level3/3-root.md":    "/level2/level3/3-root/",
+                       "/docs/level2/level3/index.md":  "/level2/level3/",
+                       "/docs/level2/level3/3-root.md": "/level2/level3/3-root/",
+               }, "level2/index.md": map[string]string{
+                       "../rootfile.md":                "/rootfile/",
+                       "../index.md":                   "/",
+                       "/docs/rootfile.md":             "/rootfile/",
+                       "/docs/index.md":                "/",
+                       "2-root.md":                     "/level2/2-root/",
+                       "index.md":                      "/level2/",
+                       "../level2/2-root.md":           "/level2/2-root/",
+                       "../level2/index.md":            "/level2/",
+                       "./2-root.md":                   "/level2/2-root/",
+                       "./index.md":                    "/level2/",
+                       "/docs/level2/index.md":         "/level2/",
+                       "/docs/level2/2-root.md":        "/level2/2-root/",
+                       "level3/3-root.md":              "/level2/level3/3-root/",
+                       "level3/index.md":               "/level2/level3/",
+                       "../level2/level3/index.md":     "/level2/level3/",
+                       "../level2/level3/3-root.md":    "/level2/level3/3-root/",
+                       "/docs/level2/level3/index.md":  "/level2/level3/",
+                       "/docs/level2/level3/3-root.md": "/level2/level3/3-root/",
+               }, "level2/level3/3-root.md": map[string]string{
+                       "../../rootfile.md":      "/rootfile/",
+                       "../../index.md":         "/",
+                       "/docs/rootfile.md":      "/rootfile/",
+                       "/docs/index.md":         "/",
+                       "../2-root.md":           "/level2/2-root/",
+                       "../index.md":            "/level2/",
+                       "/docs/level2/2-root.md": "/level2/2-root/",
+                       "/docs/level2/index.md":  "/level2/",
+                       "3-root.md":              "/level2/level3/3-root/",
+                       "index.md":               "/level2/level3/",
+                       "./3-root.md":            "/level2/level3/3-root/",
+                       "./index.md":             "/level2/level3/",
+                       //                      "../level2/level3/3-root.md":    "/level2/level3/3-root/",
+                       //                      "../level2/level3/index.md":     "/level2/level3/",
+                       "/docs/level2/level3/3-root.md": "/level2/level3/3-root/",
+                       "/docs/level2/level3/index.md":  "/level2/level3/",
+               }, "level2/level3/index.md": map[string]string{
+                       "../../rootfile.md":      "/rootfile/",
+                       "../../index.md":         "/",
+                       "/docs/rootfile.md":      "/rootfile/",
+                       "/docs/index.md":         "/",
+                       "../2-root.md":           "/level2/2-root/",
+                       "../index.md":            "/level2/",
+                       "/docs/level2/2-root.md": "/level2/2-root/",
+                       "/docs/level2/index.md":  "/level2/",
+                       "3-root.md":              "/level2/level3/3-root/",
+                       "index.md":               "/level2/level3/",
+                       "./3-root.md":            "/level2/level3/3-root/",
+                       "./index.md":             "/level2/level3/",
+                       //                      "../level2/level3/3-root.md":    "/level2/level3/3-root/",
+                       //                      "../level2/level3/index.md":     "/level2/level3/",
+                       "/docs/level2/level3/3-root.md": "/level2/level3/3-root/",
+                       "/docs/level2/level3/index.md":  "/level2/level3/",
+               },
+       }
+
+       for currentFile, results := range okresults {
+               currentPage := findPage(site, currentFile)
+               if currentPage == nil {
+                       t.Fatalf("failed to find current page in site")
+               }
+               for link, url := range results {
+                       if out, err := site.Info.githubLink(link, currentPage, true); err != nil || out != url {
+                               t.Errorf("Expected %s to resolve to (%s), got (%s) - error: %s", link, url, out, err)
+                       } else {
+                               //t.Logf("tested ok %s maps to %s", link, out)
+                       }
+               }
+       }
+       // TODO: and then the failure cases.
+       //                      "https://docker.com":           "",
+       // site_test.go:1094: Expected https://docker.com to resolve to (), got () - error: Not a plain filepath link (https://docker.com)
+
+}
+
+func TestGitHubFileLinking(t *testing.T) {
+       viper.Reset()
+       defer viper.Reset()
+       site := setupLinkingMockSite(t)
+
+       type resultMap map[string]string
+
+       okresults := map[string]resultMap{
+               "index.md": map[string]string{
+                       "/root-image.png": "/root-image.png",
+                       "root-image.png":  "/root-image.png",
+               }, "rootfile.md": map[string]string{
+                       "/root-image.png": "/root-image.png",
+               }, "level2/2-root.md": map[string]string{
+                       "/root-image.png": "/root-image.png",
+                       "common.png":      "/level2/common.png",
+               }, "level2/index.md": map[string]string{
+                       "/root-image.png": "/root-image.png",
+                       "common.png":      "/level2/common.png",
+                       "./common.png":    "/level2/common.png",
+               }, "level2/level3/3-root.md": map[string]string{
+                       "/root-image.png": "/root-image.png",
+                       "common.png":      "/level2/level3/common.png",
+                       "../common.png":   "/level2/common.png",
+               }, "level2/level3/index.md": map[string]string{
+                       "/root-image.png": "/root-image.png",
+                       "common.png":      "/level2/level3/common.png",
+                       "../common.png":   "/level2/common.png",
+               },
+       }
+
+       for currentFile, results := range okresults {
+               currentPage := findPage(site, currentFile)
+               if currentPage == nil {
+                       t.Fatalf("failed to find current page in site")
+               }
+               for link, url := range results {
+                       if out, err := site.Info.githubFileLink(link, currentPage, false); err != nil || out != url {
+                               t.Errorf("Expected %s to resolve to (%s), got (%s) - error: %s", link, url, out, err)
+                       } else {
+                               //t.Logf("tested ok %s maps to %s", link, out)
+                       }
+               }
+       }
+       // TODO: and then the failure cases.
+       //                      "https://docker.com":           "",
+       // site_test.go:1094: Expected https://docker.com to resolve to (), got () - error: Not a plain filepath link (https://docker.com)
+
+}