Skip to content

Commit

Permalink
Add support for URLs relative to context root
Browse files Browse the repository at this point in the history
Setting `RelativeURLs` to `true` will make all relative URLs in the site *really* relative.

And will do so with speed.

So:

In `/post/myblogpost.html`:

`/mycss.css` becomes `../mycss.css`

The same in /index.html` will become:

`./mycss.css` etc.

Note that absolute URLs will not be touched (either external resources, or URLs constructed with `BaseURL`).

THIS WORKS, BUT NEEDS SOME POLISH. WORK IN PROGRESS.

Fixes gohugoio#1104
Fixes gohugoio#622
  • Loading branch information
bep committed May 13, 2015
1 parent be1287f commit f1e05c5
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 7 deletions.
1 change: 1 addition & 0 deletions commands/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func InitializeConfig() {
viper.SetDefault("Verbose", false)
viper.SetDefault("IgnoreCache", false)
viper.SetDefault("CanonifyURLs", false)
viper.SetDefault("RelativeURLs", false)
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
viper.SetDefault("Permalinks", make(hugolib.PermalinkOverrides, 0))
viper.SetDefault("Sitemap", hugolib.Sitemap{Priority: -1})
Expand Down
34 changes: 34 additions & 0 deletions helpers/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,40 @@ func MakePathRelative(inPath string, possibleDirectories ...string) (string, err
return inPath, errors.New("Can't extract relative path, unknown prefix")
}

// TODO(bep)
var isFileRe = regexp.MustCompile(".*\\.(html|xml|txt)")

// Expects a relative path starting after the content directory.
func GetDottedRelativePath(inPath string) string {
isFile := isFileRe.MatchString(inPath)
if !isFile {
if !strings.HasSuffix(inPath, FilePathSeparator) {
inPath += FilePathSeparator
}
if !strings.HasPrefix(inPath, FilePathSeparator) {
inPath = FilePathSeparator + inPath
}
}
dir, _ := filepath.Split(inPath)

sectionCount := strings.Count(dir, FilePathSeparator)

if sectionCount == 0 || dir == FilePathSeparator {
return "./"
}

var dottedPath string

for i := 1; i < sectionCount; i++ {
dottedPath += ".."
if i < sectionCount {
dottedPath += "/"
}
}

return dottedPath
}

// Filename takes a path, strips out the extension,
// and returns the name of the file.
func Filename(in string) (name string) {
Expand Down
23 changes: 23 additions & 0 deletions helpers/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,29 @@ func TestMakePathRelative(t *testing.T) {
}
}

func TestGetDottedRelativePath(t *testing.T) {
type test struct {
input, expected string
}
data := []test{
{"", "./"},
{filepath.FromSlash("/"), "./"},
{filepath.FromSlash("post"), "../"},
{filepath.FromSlash("/post"), "../"},
{filepath.FromSlash("/post/"), "../"},
{filepath.FromSlash("/foo/bar/index.html"), "../../"},
{filepath.FromSlash("/foo/bar/foo/"), "../../../"},
{"404.html", "./"},
{"404.xml", "./"},
}
for i, d := range data {
output := GetDottedRelativePath(d.input)
if d.expected != output {
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
}
}
}

func TestMakeTitle(t *testing.T) {
type test struct {
input, expected string
Expand Down
14 changes: 12 additions & 2 deletions hugolib/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -1419,7 +1419,13 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou

transformLinks := transform.NewEmptyTransforms()

if viper.GetBool("CanonifyURLs") {
var dottedPath string

if viper.GetBool("RelativeURLs") {
dottedPath = helpers.GetDottedRelativePath(dest)
}

if viper.GetBool("RelativeURLs") || viper.GetBool("CanonifyURLs") {
absURL, err := transform.AbsURL()
if err != nil {
return err
Expand All @@ -1432,7 +1438,11 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou
}

transformer := transform.NewChain(transformLinks...)
transformer.Apply(outBuffer, renderBuffer)
if dottedPath != "" {
transformer.ApplyWithPath(outBuffer, renderBuffer, dottedPath)
} else {
transformer.Apply(outBuffer, renderBuffer)
}

if err == nil {
if err = s.WriteDestPage(dest, outBuffer); err != nil {
Expand Down
21 changes: 19 additions & 2 deletions transform/absurlreplacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type absurllexer struct {
// the target for the new absurlified content
w io.Writer

// path may be set to a "." relative path
path []byte

pos int // input position
start int // item start position
width int // width of last element
Expand Down Expand Up @@ -147,7 +150,11 @@ func checkCandidateBase(l *absurllexer) {
}
l.pos += len(m.match)
l.w.Write(m.quote)
l.w.Write(m.replacementURL)
if len(l.path) > 0 {
l.w.Write(l.path)
} else {
l.w.Write(m.replacementURL)
}
l.start = l.pos
}
}
Expand Down Expand Up @@ -188,7 +195,11 @@ func checkCandidateSrcset(l *absurllexer) {
l.w.Write([]byte(m.quote))
for i, f := range fields {
if f[0] == '/' {
l.w.Write(m.replacementURL)
if len(l.path) > 0 {
l.w.Write(l.path)
} else {
l.w.Write(m.replacementURL)
}
l.w.Write(f[1:])

} else {
Expand Down Expand Up @@ -252,9 +263,15 @@ func (l *absurllexer) replace() {
}

func doReplace(ct contentTransformer, matchers []absURLMatcher) {
var path string
if p, found := ct.(dotPathedTransformer); found {
path = p.Path()
}

lexer := &absurllexer{
content: ct.Content(),
w: ct,
path: []byte(path),
matchers: matchers}

lexer.replace()
Expand Down
25 changes: 24 additions & 1 deletion transform/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ type contentTransformer interface {
io.Writer
}

// dotPathedTransformer is a contentTransformer with a knowledge of URL path.
type dotPathedTransformer interface {
Path() string
}

// Implements contentTransformer
// Content is read from the from-buffer and rewritten to to the to-buffer.
type fromToBuffer struct {
Expand All @@ -42,7 +47,20 @@ func (ft fromToBuffer) Content() []byte {
return ft.from.Bytes()
}

type fromToBufferWithPath struct {
path string
*fromToBuffer
}

func (ft fromToBufferWithPath) Path() string {
return ft.path
}

func (c *chain) Apply(w io.Writer, r io.Reader) error {
return c.ApplyWithPath(w, r, "")
}

func (c *chain) ApplyWithPath(w io.Writer, r io.Reader, p string) error {

b1 := bp.GetBuffer()
defer bp.PutBuffer(b1)
Expand Down Expand Up @@ -72,7 +90,12 @@ func (c *chain) Apply(w io.Writer, r io.Reader) error {
}
}

tr(fb)
// TODO(bep)
if p != "" {
tr(&fromToBufferWithPath{p, fb})
} else {
tr(fb)
}
}

fb.to.WriteTo(w)
Expand Down
28 changes: 26 additions & 2 deletions transform/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package transform
import (
"bytes"
"github.com/spf13/hugo/helpers"
"path/filepath"
"strings"
"testing"
)
Expand Down Expand Up @@ -62,6 +63,9 @@ schemaless: &lt;img srcset=&#39;//img.jpg&#39; src=&#39;//basic.jpg&#39;&gt;
schemaless2: &lt;img srcset=&quot;//img.jpg&quot; src=&quot;//basic.jpg2&gt; POST
`

const REL_PATH_VARIATIONS = `PRE. a href="/img/small.jpg" POST.`
const REL_PATH_VARIATIONS_CORRECT = `PRE. a href="../../img/small.jpg" POST.`

var abs_url_bench_tests = []test{
{H5_JS_CONTENT_DOUBLE_QUOTE, CORRECT_OUTPUT_SRC_HREF_DQ},
{H5_JS_CONTENT_SINGLE_QUOTE, CORRECT_OUTPUT_SRC_HREF_SQ},
Expand All @@ -85,6 +89,8 @@ var srcset_xml_tests = []test{
{SRCSET_XML_SINGLE_QUOTE, SRCSET_XML_SINGLE_QUOTE_CORRECT},
{SRCSET_XML_VARIATIONS, SRCSET_XML_VARIATIONS_CORRECT}}

var relurl_tests = []test{{REL_PATH_VARIATIONS, REL_PATH_VARIATIONS_CORRECT}}

func TestChainZeroTransformers(t *testing.T) {
tr := NewChain()
in := new(bytes.Buffer)
Expand Down Expand Up @@ -161,6 +167,14 @@ func TestAbsURL(t *testing.T) {

}

func TestRelativeURL(t *testing.T) {
absURL, _ := absURLFromURL("http://base")
tr := NewChain(absURL...)

applyWithPath(t.Errorf, tr, relurl_tests, helpers.GetDottedRelativePath(filepath.FromSlash("/post/sub/")))

}

func TestAbsURLSrcSet(t *testing.T) {
absURL, _ := absURLFromURL("http://base")
tr := NewChain(absURL...)
Expand Down Expand Up @@ -194,10 +208,16 @@ func TestXMLAbsURL(t *testing.T) {

type errorf func(string, ...interface{})

func apply(ef errorf, tr chain, tests []test) {
func applyWithPath(ef errorf, tr chain, tests []test, path string) {
for _, test := range tests {
out := new(bytes.Buffer)
err := tr.Apply(out, strings.NewReader(test.content))
var err error
if path != "" {
err = tr.ApplyWithPath(out, strings.NewReader(test.content), path)
} else {
err = tr.Apply(out, strings.NewReader(test.content))
}

if err != nil {
ef("Unexpected error: %s", err)
}
Expand All @@ -207,6 +227,10 @@ func apply(ef errorf, tr chain, tests []test) {
}
}

func apply(ef errorf, tr chain, tests []test) {
applyWithPath(ef, tr, tests, "")
}

type test struct {
content string
expected string
Expand Down

0 comments on commit f1e05c5

Please sign in to comment.