2015-12-10 22:19:38 +00:00
|
|
|
|
// Copyright 2015 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.
|
|
|
|
|
|
2014-09-10 17:30:03 +00:00
|
|
|
|
package helpers
|
|
|
|
|
|
|
|
|
|
import (
|
2017-12-27 18:31:42 +00:00
|
|
|
|
"fmt"
|
2014-12-26 20:18:26 +00:00
|
|
|
|
"reflect"
|
2015-01-07 20:40:35 +00:00
|
|
|
|
"strings"
|
2014-09-10 17:30:03 +00:00
|
|
|
|
"testing"
|
2016-05-07 21:34:53 +00:00
|
|
|
|
|
2017-12-27 18:31:42 +00:00
|
|
|
|
"github.com/spf13/afero"
|
2016-05-07 21:34:53 +00:00
|
|
|
|
"github.com/stretchr/testify/assert"
|
2017-07-30 15:46:04 +00:00
|
|
|
|
"github.com/stretchr/testify/require"
|
2014-09-10 17:30:03 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-01-20 16:44:35 +00:00
|
|
|
|
func TestGuessType(t *testing.T) {
|
|
|
|
|
for i, this := range []struct {
|
|
|
|
|
in string
|
|
|
|
|
expect string
|
|
|
|
|
}{
|
|
|
|
|
{"md", "markdown"},
|
|
|
|
|
{"markdown", "markdown"},
|
|
|
|
|
{"mdown", "markdown"},
|
Experimental AsciiDoc support with external helpers
See #470
* Based on existing support for reStructuredText files
* Handles content files with extensions `.asciidoc` and `.ad`
* Pipes content through `asciidoctor --safe -`.
If `asciidoctor` is not installed, then `asciidoc --safe -`.
* To make sure `asciidoctor` or `asciidoc` is found, after adding
a piece of AsciiDoc content, run `hugo` with the `-v` flag
and look for this message:
INFO: 2015/01/23 Rendering with /usr/bin/asciidoctor ...
Caveats:
* The final "Last updated" timestamp is currently not stripped.
* When `hugo` is run with `-v`, you may see a lot of these messages
INFO: 2015/01/23 Rendering with /usr/bin/asciidoctor ...
if you have lots of `*.ad`, `*.adoc` or `*.asciidoc` files.
* Some versions of `asciidoc` may have trouble with its safe mode.
To test if you are affected, try this:
$ echo "Hello" | asciidoc --safe -
asciidoc: ERROR: unsafe: ifeval invalid
asciidoc: FAILED: ifeval invalid safe document
If so, I recommend that you install `asciidoctor` instead.
Feedback and patches welcome!
Ideally, we should be using https://github.com/VonC/asciidocgo,
@VonC's wonderful Go implementation of Asciidoctor. However,
there is still a bit of work needed for asciidocgo to expose
its API so that Hugo can actually use it.
Until then, hope this "experimental AsciiDoc support through external
helpers" can serve as a stopgap solution for our community. :-)
2015-01-30: Updated for the replaceShortcodeTokens() syntax change
2015-02-21: Add `.adoc` extension as suggested by @Fale
Conflicts:
helpers/content.go
2015-01-23 18:59:14 +00:00
|
|
|
|
{"asciidoc", "asciidoc"},
|
|
|
|
|
{"adoc", "asciidoc"},
|
|
|
|
|
{"ad", "asciidoc"},
|
2015-01-20 16:44:35 +00:00
|
|
|
|
{"rst", "rst"},
|
2017-11-30 11:15:52 +00:00
|
|
|
|
{"pandoc", "pandoc"},
|
|
|
|
|
{"pdc", "pandoc"},
|
2015-01-30 14:17:50 +00:00
|
|
|
|
{"mmark", "mmark"},
|
2015-01-20 16:44:35 +00:00
|
|
|
|
{"html", "html"},
|
|
|
|
|
{"htm", "html"},
|
2017-02-21 07:46:03 +00:00
|
|
|
|
{"org", "org"},
|
2015-01-20 16:44:35 +00:00
|
|
|
|
{"excel", "unknown"},
|
|
|
|
|
} {
|
|
|
|
|
result := GuessType(this.in)
|
|
|
|
|
if result != this.expect {
|
2015-04-05 19:03:16 +00:00
|
|
|
|
t.Errorf("[%d] got %s but expected %s", i, result, this.expect)
|
2015-01-20 16:44:35 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-28 21:05:13 +00:00
|
|
|
|
func TestFirstUpper(t *testing.T) {
|
|
|
|
|
for i, this := range []struct {
|
|
|
|
|
in string
|
|
|
|
|
expect string
|
|
|
|
|
}{
|
|
|
|
|
{"foo", "Foo"},
|
|
|
|
|
{"foo bar", "Foo bar"},
|
|
|
|
|
{"Foo Bar", "Foo Bar"},
|
|
|
|
|
{"", ""},
|
|
|
|
|
{"å", "Å"},
|
|
|
|
|
} {
|
|
|
|
|
result := FirstUpper(this.in)
|
|
|
|
|
if result != this.expect {
|
|
|
|
|
t.Errorf("[%d] got %s but expected %s", i, result, this.expect)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-02 18:14:06 +00:00
|
|
|
|
func TestHasStringsPrefix(t *testing.T) {
|
|
|
|
|
for i, this := range []struct {
|
|
|
|
|
s []string
|
|
|
|
|
prefix []string
|
|
|
|
|
expect bool
|
|
|
|
|
}{
|
|
|
|
|
{[]string{"a"}, []string{"a"}, true},
|
|
|
|
|
{[]string{}, []string{}, true},
|
|
|
|
|
{[]string{"a", "b", "c"}, []string{"a", "b"}, true},
|
|
|
|
|
{[]string{"d", "a", "b", "c"}, []string{"a", "b"}, false},
|
|
|
|
|
{[]string{"abra", "ca", "dabra"}, []string{"abra", "ca"}, true},
|
|
|
|
|
{[]string{"abra", "ca"}, []string{"abra", "ca", "dabra"}, false},
|
|
|
|
|
} {
|
|
|
|
|
result := HasStringsPrefix(this.s, this.prefix)
|
|
|
|
|
if result != this.expect {
|
|
|
|
|
t.Fatalf("[%d] got %t but expected %t", i, result, this.expect)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHasStringsSuffix(t *testing.T) {
|
|
|
|
|
for i, this := range []struct {
|
|
|
|
|
s []string
|
|
|
|
|
suffix []string
|
|
|
|
|
expect bool
|
|
|
|
|
}{
|
|
|
|
|
{[]string{"a"}, []string{"a"}, true},
|
|
|
|
|
{[]string{}, []string{}, true},
|
|
|
|
|
{[]string{"a", "b", "c"}, []string{"b", "c"}, true},
|
|
|
|
|
{[]string{"abra", "ca", "dabra"}, []string{"abra", "ca"}, false},
|
|
|
|
|
{[]string{"abra", "ca", "dabra"}, []string{"ca", "dabra"}, true},
|
|
|
|
|
} {
|
|
|
|
|
result := HasStringsSuffix(this.s, this.suffix)
|
|
|
|
|
if result != this.expect {
|
|
|
|
|
t.Fatalf("[%d] got %t but expected %t", i, result, this.expect)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-29 19:12:13 +00:00
|
|
|
|
var containsTestText = (`На берегу пустынных волн
|
|
|
|
|
Стоял он, дум великих полн,
|
|
|
|
|
И вдаль глядел. Пред ним широко
|
|
|
|
|
Река неслася; бедный чёлн
|
|
|
|
|
По ней стремился одиноко.
|
|
|
|
|
По мшистым, топким берегам
|
|
|
|
|
Чернели избы здесь и там,
|
|
|
|
|
Приют убогого чухонца;
|
|
|
|
|
И лес, неведомый лучам
|
|
|
|
|
В тумане спрятанного солнца,
|
|
|
|
|
Кругом шумел.
|
|
|
|
|
|
|
|
|
|
Τη γλώσσα μου έδωσαν ελληνική
|
|
|
|
|
το σπίτι φτωχικό στις αμμουδιές του Ομήρου.
|
|
|
|
|
Μονάχη έγνοια η γλώσσα μου στις αμμουδιές του Ομήρου.
|
|
|
|
|
|
|
|
|
|
από το Άξιον Εστί
|
|
|
|
|
του Οδυσσέα Ελύτη
|
|
|
|
|
|
|
|
|
|
Sîne klâwen durh die wolken sint geslagen,
|
|
|
|
|
er stîget ûf mit grôzer kraft,
|
|
|
|
|
ich sih in grâwen tägelîch als er wil tagen,
|
|
|
|
|
den tac, der im geselleschaft
|
|
|
|
|
erwenden wil, dem werden man,
|
|
|
|
|
den ich mit sorgen în verliez.
|
|
|
|
|
ich bringe in hinnen, ob ich kan.
|
|
|
|
|
sîn vil manegiu tugent michz leisten hiez.
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
var containsBenchTestData = []struct {
|
|
|
|
|
v1 string
|
|
|
|
|
v2 []byte
|
|
|
|
|
expect bool
|
|
|
|
|
}{
|
|
|
|
|
{"abc", []byte("a"), true},
|
|
|
|
|
{"abc", []byte("b"), true},
|
|
|
|
|
{"abcdefg", []byte("efg"), true},
|
|
|
|
|
{"abc", []byte("d"), false},
|
|
|
|
|
{containsTestText, []byte("стремился"), true},
|
|
|
|
|
{containsTestText, []byte(containsTestText[10:80]), true},
|
2015-03-29 23:22:08 +00:00
|
|
|
|
{containsTestText, []byte(containsTestText[100:111]), true},
|
2015-03-29 19:12:13 +00:00
|
|
|
|
{containsTestText, []byte(containsTestText[len(containsTestText)-100 : len(containsTestText)-10]), true},
|
|
|
|
|
{containsTestText, []byte(containsTestText[len(containsTestText)-20:]), true},
|
|
|
|
|
{containsTestText, []byte("notfound"), false},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// some corner cases
|
|
|
|
|
var containsAdditionalTestData = []struct {
|
|
|
|
|
v1 string
|
|
|
|
|
v2 []byte
|
|
|
|
|
expect bool
|
|
|
|
|
}{
|
2015-03-29 23:22:08 +00:00
|
|
|
|
{"", nil, false},
|
2015-03-29 19:12:13 +00:00
|
|
|
|
{"", []byte("a"), false},
|
|
|
|
|
{"a", []byte(""), false},
|
|
|
|
|
{"", []byte(""), false},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestReaderContains(t *testing.T) {
|
|
|
|
|
for i, this := range append(containsBenchTestData, containsAdditionalTestData...) {
|
2016-05-07 21:34:53 +00:00
|
|
|
|
result := ReaderContains(strings.NewReader(this.v1), this.v2)
|
2015-03-29 19:12:13 +00:00
|
|
|
|
if result != this.expect {
|
2015-04-05 19:03:16 +00:00
|
|
|
|
t.Errorf("[%d] got %t but expected %t", i, result, this.expect)
|
2015-03-29 19:12:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-29 23:22:08 +00:00
|
|
|
|
|
|
|
|
|
assert.False(t, ReaderContains(nil, []byte("a")))
|
|
|
|
|
assert.False(t, ReaderContains(nil, nil))
|
2015-03-29 19:12:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-30 15:46:04 +00:00
|
|
|
|
func TestGetTitleFunc(t *testing.T) {
|
|
|
|
|
title := "somewhere over the rainbow"
|
|
|
|
|
assert := require.New(t)
|
|
|
|
|
|
|
|
|
|
assert.Equal("Somewhere Over The Rainbow", GetTitleFunc("go")(title))
|
|
|
|
|
assert.Equal("Somewhere over the Rainbow", GetTitleFunc("chicago")(title), "Chicago style")
|
|
|
|
|
assert.Equal("Somewhere over the Rainbow", GetTitleFunc("Chicago")(title), "Chicago style")
|
|
|
|
|
assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("ap")(title), "AP style")
|
|
|
|
|
assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("ap")(title), "AP style")
|
|
|
|
|
assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("")(title), "AP style")
|
|
|
|
|
assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("unknown")(title), "AP style")
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-29 19:12:13 +00:00
|
|
|
|
func BenchmarkReaderContains(b *testing.B) {
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
for i, this := range containsBenchTestData {
|
2016-05-07 21:34:53 +00:00
|
|
|
|
result := ReaderContains(strings.NewReader(this.v1), this.v2)
|
2015-03-29 19:12:13 +00:00
|
|
|
|
if result != this.expect {
|
2015-04-05 19:03:16 +00:00
|
|
|
|
b.Errorf("[%d] got %t but expected %t", i, result, this.expect)
|
2015-03-29 19:12:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 15:32:06 +00:00
|
|
|
|
func TestUniqueStrings(t *testing.T) {
|
|
|
|
|
in := []string{"a", "b", "a", "b", "c", "", "a", "", "d"}
|
|
|
|
|
output := UniqueStrings(in)
|
|
|
|
|
expected := []string{"a", "b", "c", "", "d"}
|
|
|
|
|
if !reflect.DeepEqual(output, expected) {
|
|
|
|
|
t.Errorf("Expected %#v, got %#v\n", expected, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-20 16:44:35 +00:00
|
|
|
|
func TestFindAvailablePort(t *testing.T) {
|
|
|
|
|
addr, err := FindAvailablePort()
|
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
assert.NotNil(t, addr)
|
|
|
|
|
assert.True(t, addr.Port > 0)
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 17:28:21 +00:00
|
|
|
|
func TestToLowerMap(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
input map[string]interface{}
|
|
|
|
|
expected map[string]interface{}
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
map[string]interface{}{
|
|
|
|
|
"abC": 32,
|
|
|
|
|
},
|
|
|
|
|
map[string]interface{}{
|
|
|
|
|
"abc": 32,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
map[string]interface{}{
|
|
|
|
|
"abC": 32,
|
|
|
|
|
"deF": map[interface{}]interface{}{
|
|
|
|
|
23: "A value",
|
|
|
|
|
24: map[string]interface{}{
|
|
|
|
|
"AbCDe": "A value",
|
|
|
|
|
"eFgHi": "Another value",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"gHi": map[string]interface{}{
|
|
|
|
|
"J": 25,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
map[string]interface{}{
|
|
|
|
|
"abc": 32,
|
|
|
|
|
"def": map[string]interface{}{
|
|
|
|
|
"23": "A value",
|
|
|
|
|
"24": map[string]interface{}{
|
|
|
|
|
"abcde": "A value",
|
|
|
|
|
"efghi": "Another value",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"ghi": map[string]interface{}{
|
|
|
|
|
"j": 25,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, test := range tests {
|
|
|
|
|
// ToLowerMap modifies input.
|
|
|
|
|
ToLowerMap(test.input)
|
|
|
|
|
if !reflect.DeepEqual(test.expected, test.input) {
|
|
|
|
|
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-27 18:31:42 +00:00
|
|
|
|
|
|
|
|
|
func TestFastMD5FromFile(t *testing.T) {
|
|
|
|
|
fs := afero.NewMemMapFs()
|
|
|
|
|
|
|
|
|
|
if err := afero.WriteFile(fs, "small.txt", []byte("abc"), 0777); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := afero.WriteFile(fs, "small2.txt", []byte("abd"), 0777); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := afero.WriteFile(fs, "bigger.txt", []byte(strings.Repeat("a bc d e", 100)), 0777); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := afero.WriteFile(fs, "bigger2.txt", []byte(strings.Repeat("c d e f g", 100)), 0777); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req := require.New(t)
|
|
|
|
|
|
|
|
|
|
sf1, err := fs.Open("small.txt")
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
sf2, err := fs.Open("small2.txt")
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
|
|
|
|
|
bf1, err := fs.Open("bigger.txt")
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
bf2, err := fs.Open("bigger2.txt")
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
|
|
|
|
|
defer sf1.Close()
|
|
|
|
|
defer sf2.Close()
|
|
|
|
|
defer bf1.Close()
|
|
|
|
|
defer bf2.Close()
|
|
|
|
|
|
|
|
|
|
m1, err := MD5FromFileFast(sf1)
|
|
|
|
|
req.NoError(err)
|
2017-12-28 21:52:27 +00:00
|
|
|
|
req.Equal("e9c8989b64b71a88b4efb66ad05eea96", m1)
|
2017-12-27 18:31:42 +00:00
|
|
|
|
|
|
|
|
|
m2, err := MD5FromFileFast(sf2)
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
req.NotEqual(m1, m2)
|
|
|
|
|
|
|
|
|
|
m3, err := MD5FromFileFast(bf1)
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
req.NotEqual(m2, m3)
|
|
|
|
|
|
|
|
|
|
m4, err := MD5FromFileFast(bf2)
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
req.NotEqual(m3, m4)
|
|
|
|
|
|
|
|
|
|
m5, err := MD5FromFile(bf2)
|
|
|
|
|
req.NoError(err)
|
|
|
|
|
req.NotEqual(m4, m5)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkMD5FromFileFast(b *testing.B) {
|
|
|
|
|
fs := afero.NewMemMapFs()
|
|
|
|
|
|
|
|
|
|
for _, full := range []bool{false, true} {
|
|
|
|
|
b.Run(fmt.Sprintf("full=%t", full), func(b *testing.B) {
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
b.StopTimer()
|
|
|
|
|
if err := afero.WriteFile(fs, "file.txt", []byte(strings.Repeat("1234567890", 2000)), 0777); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
f, err := fs.Open("file.txt")
|
|
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
b.StartTimer()
|
|
|
|
|
if full {
|
|
|
|
|
if _, err := MD5FromFile(f); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if _, err := MD5FromFileFast(f); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
f.Close()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|