diff --git a/helpers/baseURL.go b/helpers/baseURL.go new file mode 100644 index 000000000..9a4b77edd --- /dev/null +++ b/helpers/baseURL.go @@ -0,0 +1,74 @@ +// Copyright 2017-present The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "fmt" + "net/url" + "strings" +) + +// A BaseURL in Hugo is normally on the form scheme://path, but the +// form scheme: is also valid (mailto:hugo@rules.com). +type BaseURL struct { + url *url.URL + urlStr string +} + +func (b BaseURL) String() string { + return b.urlStr +} + +// Protocol is normaly on the form "scheme://", i.e. "webcal://". +func (b BaseURL) WithProtocol(protocol string) (string, error) { + u := b.URL() + + scheme := protocol + isFullProtocol := strings.HasSuffix(scheme, "://") + isOpaqueProtocol := strings.HasSuffix(scheme, ":") + + if isFullProtocol { + scheme = strings.TrimSuffix(scheme, "://") + } else if isOpaqueProtocol { + scheme = strings.TrimSuffix(scheme, ":") + } + + u.Scheme = scheme + + if isFullProtocol && u.Opaque != "" { + u.Opaque = "//" + u.Opaque + } else if isOpaqueProtocol && u.Opaque == "" { + return "", fmt.Errorf("Cannot determine BaseURL for protocol %q", protocol) + } + + return u.String(), nil +} + +func (b BaseURL) URL() *url.URL { + // create a copy as it will be modified. + c := *b.url + return &c +} + +func newBaseURLFromString(b string) (BaseURL, error) { + var result BaseURL + + base, err := url.Parse(b) + if err != nil { + return result, err + } + + // TODO(bep) output consider saving original URL? + return BaseURL{url: base, urlStr: base.String()}, nil +} diff --git a/helpers/baseURL_test.go b/helpers/baseURL_test.go new file mode 100644 index 000000000..eaa27ddb9 --- /dev/null +++ b/helpers/baseURL_test.go @@ -0,0 +1,51 @@ +// Copyright 2017-present The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBaseURL(t *testing.T) { + b, err := newBaseURLFromString("http://example.com") + require.NoError(t, err) + require.Equal(t, "http://example.com", b.String()) + + p, err := b.WithProtocol("webcal://") + require.NoError(t, err) + require.Equal(t, "webcal://example.com", p) + + p, err = b.WithProtocol("webcal") + require.NoError(t, err) + require.Equal(t, "webcal://example.com", p) + + _, err = b.WithProtocol("mailto:") + require.Error(t, err) + + b, err = newBaseURLFromString("mailto:hugo@rules.com") + require.NoError(t, err) + require.Equal(t, "mailto:hugo@rules.com", b.String()) + + // These are pretty constructed + p, err = b.WithProtocol("webcal") + require.NoError(t, err) + require.Equal(t, "webcal:hugo@rules.com", p) + + p, err = b.WithProtocol("webcal://") + require.NoError(t, err) + require.Equal(t, "webcal://hugo@rules.com", p) + +} diff --git a/helpers/url.go b/helpers/url.go index a73e54999..defde6a17 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -23,33 +23,6 @@ import ( "github.com/PuerkitoBio/purell" ) -type BaseURL struct { - url *url.URL - urlStr string -} - -func (b BaseURL) String() string { - return b.urlStr -} - -func (b BaseURL) URL() *url.URL { - // create a copy as it will be modified. - c := *b.url - return &c -} - -func newBaseURLFromString(b string) (BaseURL, error) { - var result BaseURL - - base, err := url.Parse(b) - if err != nil { - return result, err - } - - // TODO(bep) output consider saving original URL? - return BaseURL{url: base, urlStr: base.String()}, nil -} - type pathBridge struct { } diff --git a/hugolib/page.go b/hugolib/page.go index 4e9ea2e78..dba3942de 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -862,7 +862,12 @@ func (p *Page) initURLs() error { p.outputFormats = p.s.outputFormats[p.Kind] } rel := p.createRelativePermalink() - p.permalink = p.s.permalink(rel) + + var err error + p.permalink, err = p.s.permalinkForOutputFormat(rel, p.outputFormats[0]) + if err != nil { + return err + } rel = p.s.PathSpec.PrependBasePath(rel) p.relPermalink = rel p.layoutDescriptor = p.createLayoutDescriptor() diff --git a/hugolib/page_output.go b/hugolib/page_output.go index 0038b7fdd..ec662bf23 100644 --- a/hugolib/page_output.go +++ b/hugolib/page_output.go @@ -197,7 +197,8 @@ func (o OutputFormats) Get(name string) *OutputFormat { // Permalink returns the absolute permalink to this output format. func (o *OutputFormat) Permalink() string { rel := o.p.createRelativePermalinkForOutputFormat(o.f) - return o.p.s.permalink(rel) + perm, _ := o.p.s.permalinkForOutputFormat(rel, o.f) + return perm } // Permalink returns the relative permalink to this output format. diff --git a/hugolib/site.go b/hugolib/site.go index 1cdb285ba..e3a896148 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -1766,9 +1766,29 @@ func (s *SiteInfo) GetPage(typ string, path ...string) *Page { return s.getPage(typ, path...) } -func (s *Site) permalink(link string) string { - baseURL := s.PathSpec.BaseURL.String() +func (s *Site) permalinkForOutputFormat(link string, f output.Format) (string, error) { + var ( + baseURL string + err error + ) + if f.Protocol != "" { + baseURL, err = s.PathSpec.BaseURL.WithProtocol(f.Protocol) + if err != nil { + return "", err + } + } else { + baseURL = s.PathSpec.BaseURL.String() + } + return s.permalinkForBaseURL(link, baseURL), nil +} + +func (s *Site) permalink(link string) string { + return s.permalinkForBaseURL(link, s.PathSpec.BaseURL.String()) + +} + +func (s *Site) permalinkForBaseURL(link, baseURL string) string { link = strings.TrimPrefix(link, "/") if !strings.HasSuffix(baseURL, "/") { baseURL += "/" diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go index 6694a9bfd..006f3bad0 100644 --- a/hugolib/site_output_test.go +++ b/hugolib/site_output_test.go @@ -52,7 +52,7 @@ func TestDefaultOutputFormats(t *testing.T) { } func TestSiteWithPageOutputs(t *testing.T) { - for _, outputs := range [][]string{{"html", "json"}, {"json"}} { + for _, outputs := range [][]string{{"html", "json", "calendar"}, {"json"}} { t.Run(fmt.Sprintf("%v", outputs), func(t *testing.T) { doTestSiteWithPageOutputs(t, outputs) }) @@ -146,4 +146,12 @@ Output/Rel: {{ .Name -}}/{{ .Rel }}| require.Equal(t, "/blog/index.json", json.RelPermalink()) require.Equal(t, "http://example.com/blog/index.json", json.Permalink()) + if helpers.InStringArray(outputs, "cal") { + // TODO(bep) output have do some protocil handling for the default too if set. + cal := of.Get("calendar") + require.NotNil(t, cal) + require.Equal(t, "/blog/index.ics", cal.RelPermalink()) + require.Equal(t, "webcal://example.com/blog/index.ics", cal.Permalink()) + } + } diff --git a/output/outputFormat.go b/output/outputFormat.go index efc0cd85f..c50d74f3f 100644 --- a/output/outputFormat.go +++ b/output/outputFormat.go @@ -72,11 +72,12 @@ var ( ) var builtInTypes = map[string]Format{ - strings.ToLower(AMPType.Name): AMPType, - strings.ToLower(CSSType.Name): CSSType, - strings.ToLower(HTMLType.Name): HTMLType, - strings.ToLower(JSONType.Name): JSONType, - strings.ToLower(RSSType.Name): RSSType, + strings.ToLower(AMPType.Name): AMPType, + strings.ToLower(CalendarType.Name): CalendarType, + strings.ToLower(CSSType.Name): CSSType, + strings.ToLower(HTMLType.Name): HTMLType, + strings.ToLower(JSONType.Name): JSONType, + strings.ToLower(RSSType.Name): RSSType, } type Formats []Format diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go index efc31a223..a76c50aee 100644 --- a/output/outputFormat_test.go +++ b/output/outputFormat_test.go @@ -30,6 +30,7 @@ func TestDefaultTypes(t *testing.T) { require.Equal(t, "HTML", HTMLType.Name) require.Equal(t, media.HTMLType, HTMLType.MediaType) require.Empty(t, HTMLType.Path) + require.Empty(t, HTMLType.Protocol) // Will inherit the BaseURL protocol. require.False(t, HTMLType.IsPlainText) require.Equal(t, "RSS", RSSType.Name)