2014-02-27 18:32:09 -05:00
// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
2015-11-23 22:16:36 -05:00
// Licensed under the Apache License, Version 2.0 (the "License");
2014-02-27 18:32:09 -05:00
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2015-11-23 22:16:36 -05:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-02-27 18:32:09 -05:00
//
// 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 (
"bytes"
2014-10-16 20:20:09 -04:00
"crypto/md5"
"encoding/hex"
2014-12-26 15:18:26 -05:00
"errors"
2014-05-15 15:07:46 -04:00
"fmt"
2014-10-16 20:20:09 -04:00
"io"
2014-05-15 15:07:46 -04:00
"net"
2014-12-07 13:48:00 -05:00
"path/filepath"
2014-12-26 15:18:26 -05:00
"reflect"
2014-02-27 18:32:09 -05:00
"strings"
2015-03-12 11:10:14 -04:00
"sync"
2015-05-28 17:05:13 -04:00
"unicode"
"unicode/utf8"
2015-01-30 09:17:50 -05:00
"github.com/spf13/cast"
bp "github.com/spf13/hugo/bufferpool"
jww "github.com/spf13/jwalterweatherman"
2015-09-09 01:05:11 -04:00
"github.com/spf13/pflag"
2015-01-30 09:17:50 -05:00
"github.com/spf13/viper"
2014-02-27 18:32:09 -05:00
)
2014-12-26 10:07:03 -05:00
// Filepath separator defined by os.Separator.
2014-12-07 13:48:00 -05:00
const FilePathSeparator = string ( filepath . Separator )
2015-03-06 18:02:06 -05:00
// FindAvailablePort returns an available and valid TCP port.
2014-05-15 15:07:46 -04:00
func FindAvailablePort ( ) ( * net . TCPAddr , error ) {
l , err := net . Listen ( "tcp" , ":0" )
if err == nil {
defer l . Close ( )
addr := l . Addr ( )
if a , ok := addr . ( * net . TCPAddr ) ; ok {
return a , nil
}
return nil , fmt . Errorf ( "Unable to obtain a valid tcp port. %v" , addr )
}
return nil , err
}
2014-10-16 20:20:09 -04:00
2014-12-26 10:07:03 -05:00
// InStringArray checks if a string is an element of a slice of strings
// and returns a boolean value.
2014-10-20 20:15:33 -04:00
func InStringArray ( arr [ ] string , el string ) bool {
for _ , v := range arr {
if v == el {
return true
}
}
return false
}
2014-12-11 15:57:25 -05:00
// GuessType attempts to guess the type of file from a given string.
2014-10-16 20:20:09 -04:00
func GuessType ( in string ) string {
switch strings . ToLower ( in ) {
case "md" , "markdown" , "mdown" :
return "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 13:59:14 -05:00
case "asciidoc" , "adoc" , "ad" :
return "asciidoc"
2015-01-30 09:17:50 -05:00
case "mmark" :
return "mmark"
2014-10-16 20:20:09 -04:00
case "rst" :
return "rst"
case "html" , "htm" :
return "html"
}
return "unknown"
}
2015-05-28 17:05:13 -04:00
// FirstUpper returns a string with the first character as upper case.
func FirstUpper ( s string ) string {
if s == "" {
return ""
}
r , n := utf8 . DecodeRuneInString ( s )
return string ( unicode . ToUpper ( r ) ) + s [ n : ]
}
2015-11-23 10:32:06 -05:00
// UniqueStrings returns a new slice with any duplicates removed.
func UniqueStrings ( s [ ] string ) [ ] string {
unique := make ( [ ] string , 0 )
set := map [ string ] interface { } { }
for _ , val := range s {
if _ , ok := set [ val ] ; ! ok {
unique = append ( unique , val )
set [ val ] = val
}
}
return unique
}
2014-12-26 10:07:03 -05:00
// ReaderToBytes takes an io.Reader argument, reads from it
// and returns bytes.
2014-10-16 20:20:09 -04:00
func ReaderToBytes ( lines io . Reader ) [ ] byte {
2015-03-12 15:50:44 -04:00
if lines == nil {
return [ ] byte { }
}
2015-01-30 14:12:07 -05:00
b := bp . GetBuffer ( )
defer bp . PutBuffer ( b )
2014-10-16 20:20:09 -04:00
b . ReadFrom ( lines )
2015-01-30 14:12:07 -05:00
bc := make ( [ ] byte , b . Len ( ) , b . Len ( ) )
copy ( bc , b . Bytes ( ) )
return bc
2014-10-16 20:20:09 -04:00
}
2014-12-26 10:07:03 -05:00
// ReaderToString is the same as ReaderToBytes, but returns a string.
2014-11-04 00:26:56 -05:00
func ReaderToString ( lines io . Reader ) string {
2015-03-12 15:50:44 -04:00
if lines == nil {
return ""
}
2015-01-30 14:12:07 -05:00
b := bp . GetBuffer ( )
defer bp . PutBuffer ( b )
2014-11-04 00:26:56 -05:00
b . ReadFrom ( lines )
return b . String ( )
}
2014-12-26 10:07:03 -05:00
// StringToReader does the opposite of ReaderToString.
2014-11-04 00:26:56 -05:00
func StringToReader ( in string ) io . Reader {
return strings . NewReader ( in )
}
2014-12-26 10:07:03 -05:00
// BytesToReader does the opposite of ReaderToBytes.
2014-11-04 00:26:56 -05:00
func BytesToReader ( in [ ] byte ) io . Reader {
return bytes . NewReader ( in )
}
2015-03-29 15:12:13 -04:00
// ReaderContains reports whether subslice is within r.
func ReaderContains ( r io . Reader , subslice [ ] byte ) bool {
2015-03-29 19:22:08 -04:00
if r == nil || len ( subslice ) == 0 {
2015-03-29 15:12:13 -04:00
return false
}
bufflen := len ( subslice ) * 4
halflen := bufflen / 2
buff := make ( [ ] byte , bufflen )
var err error
var n , i int
for {
i ++
if i == 1 {
n , err = io . ReadAtLeast ( r , buff [ : halflen ] , halflen )
} else {
if i != 2 {
// shift left to catch overlapping matches
copy ( buff [ : ] , buff [ halflen : ] )
}
n , err = io . ReadAtLeast ( r , buff [ halflen : ] , halflen )
}
if n > 0 && bytes . Contains ( buff , subslice ) {
return true
}
if err != nil {
break
}
}
return false
}
2015-03-06 18:02:06 -05:00
// ThemeSet checks whether a theme is in use or not.
2015-02-11 14:24:56 -05:00
func ThemeSet ( ) bool {
return viper . GetString ( "theme" ) != ""
}
2015-03-31 16:33:24 -04:00
// DistinctErrorLogger ignores duplicate log statements.
type DistinctErrorLogger struct {
2015-03-12 11:10:14 -04:00
sync . RWMutex
m map [ string ] bool
2015-03-31 16:33:24 -04:00
}
2015-03-12 11:10:14 -04:00
2015-04-03 16:29:57 -04:00
// Printf will ERROR log the string returned from fmt.Sprintf given the arguments,
// but not if it has been logged before.
2015-03-31 16:33:24 -04:00
func ( l * DistinctErrorLogger ) Printf ( format string , v ... interface { } ) {
logStatement := fmt . Sprintf ( format , v ... )
l . RLock ( )
2015-04-03 15:16:36 -04:00
if l . m [ logStatement ] {
l . RUnlock ( )
2015-03-12 11:10:14 -04:00
return
}
2015-04-03 15:16:36 -04:00
l . RUnlock ( )
2015-03-31 16:33:24 -04:00
l . Lock ( )
if ! l . m [ logStatement ] {
jww . ERROR . Print ( logStatement )
l . m [ logStatement ] = true
2015-03-12 13:51:31 -04:00
}
2015-03-31 16:33:24 -04:00
l . Unlock ( )
}
2015-04-03 16:29:57 -04:00
// NewDistinctErrorLogger creates a new DistinctErrorLogger
2015-03-31 16:33:24 -04:00
func NewDistinctErrorLogger ( ) * DistinctErrorLogger {
return & DistinctErrorLogger { m : make ( map [ string ] bool ) }
}
// Avoid spamming the logs with errors
2015-06-03 12:54:30 -04:00
var DistinctErrorLog = NewDistinctErrorLogger ( )
2015-03-31 16:33:24 -04:00
2015-04-03 16:29:57 -04:00
// Deprecated logs ERROR logs about a deprecation, but only once for a given set of arguments' values.
2015-03-31 16:33:24 -04:00
func Deprecated ( object , item , alternative string ) {
2015-05-26 17:08:32 -04:00
// deprecatedLogger.Printf("%s's %s is deprecated and will be removed in Hugo %s. Use %s instead.", object, item, NextHugoReleaseVersion(), alternative)
2015-06-03 12:54:30 -04:00
DistinctErrorLog . Printf ( "%s's %s is deprecated and will be removed VERY SOON. Use %s instead." , object , item , alternative )
2015-05-26 17:08:32 -04:00
2015-03-12 11:10:14 -04:00
}
2014-12-26 10:07:03 -05:00
// SliceToLower goes through the source slice and lowers all values.
2014-10-16 20:20:09 -04:00
func SliceToLower ( s [ ] string ) [ ] string {
if s == nil {
return nil
}
l := make ( [ ] string , len ( s ) )
for i , v := range s {
l [ i ] = strings . ToLower ( v )
}
return l
}
2014-12-26 10:07:03 -05:00
// Md5String takes a string and returns its MD5 hash.
2014-10-16 20:20:09 -04:00
func Md5String ( f string ) string {
h := md5 . New ( )
h . Write ( [ ] byte ( f ) )
return hex . EncodeToString ( h . Sum ( [ ] byte { } ) )
}
2014-12-26 15:18:26 -05:00
2015-07-12 05:05:37 -04:00
// IsWhitespace determines if the given rune is whitespace.
func IsWhitespace ( r rune ) bool {
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}
2015-02-24 04:56:16 -05:00
// Seq creates a sequence of integers.
// It's named and used as GNU's seq.
// Examples:
// 3 => 1, 2, 3
// 1 2 4 => 1, 3
// -3 => -1, -2, -3
// 1 4 => 1, 2, 3, 4
// 1 -2 => 1, 0, -1, -2
func Seq ( args ... interface { } ) ( [ ] int , error ) {
if len ( args ) < 1 || len ( args ) > 3 {
return nil , errors . New ( "Seq, invalid number of args: 'first' 'increment' (optional) 'last' (optional)" )
}
intArgs := cast . ToIntSlice ( args )
2015-03-18 06:10:04 -04:00
if len ( intArgs ) < 1 || len ( intArgs ) > 3 {
return nil , errors . New ( "Invalid argument(s) to Seq" )
}
2015-04-03 16:29:57 -04:00
var inc = 1
2015-02-24 04:56:16 -05:00
var last int
var first = intArgs [ 0 ]
if len ( intArgs ) == 1 {
last = first
if last == 0 {
return [ ] int { } , nil
} else if last > 0 {
first = 1
} else {
first = - 1
inc = - 1
}
} else if len ( intArgs ) == 2 {
last = intArgs [ 1 ]
if last < first {
inc = - 1
}
} else {
inc = intArgs [ 1 ]
last = intArgs [ 2 ]
if inc == 0 {
return nil , errors . New ( "'increment' must not be 0" )
}
if first < last && inc < 0 {
return nil , errors . New ( "'increment' must be > 0" )
}
if first > last && inc > 0 {
return nil , errors . New ( "'increment' must be < 0" )
}
}
2015-04-30 07:25:45 -04:00
// sanity check
if last < - 100000 {
return nil , errors . New ( "size of result exeeds limit" )
}
2015-02-24 04:56:16 -05:00
size := int ( ( ( last - first ) / inc ) + 1 )
// sanity check
2015-04-30 07:25:45 -04:00
if size <= 0 || size > 2000 {
2015-02-24 04:56:16 -05:00
return nil , errors . New ( "size of result exeeds limit" )
}
seq := make ( [ ] int , size )
val := first
for i := 0 ; ; i ++ {
seq [ i ] = val
val += inc
if ( inc < 0 && val < last ) || ( inc > 0 && val > last ) {
break
}
}
return seq , nil
}
2014-12-26 15:18:26 -05:00
// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
// determine the type of the two terms.
func DoArithmetic ( a , b interface { } , op rune ) ( interface { } , error ) {
av := reflect . ValueOf ( a )
bv := reflect . ValueOf ( b )
var ai , bi int64
var af , bf float64
var au , bu uint64
switch av . Kind ( ) {
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
ai = av . Int ( )
switch bv . Kind ( ) {
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
bi = bv . Int ( )
case reflect . Float32 , reflect . Float64 :
af = float64 ( ai ) // may overflow
ai = 0
bf = bv . Float ( )
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
bu = bv . Uint ( )
if ai >= 0 {
au = uint64 ( ai )
ai = 0
} else {
bi = int64 ( bu ) // may overflow
bu = 0
}
default :
return nil , errors . New ( "Can't apply the operator to the values" )
}
case reflect . Float32 , reflect . Float64 :
af = av . Float ( )
switch bv . Kind ( ) {
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
bf = float64 ( bv . Int ( ) ) // may overflow
case reflect . Float32 , reflect . Float64 :
bf = bv . Float ( )
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
bf = float64 ( bv . Uint ( ) ) // may overflow
default :
return nil , errors . New ( "Can't apply the operator to the values" )
}
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
au = av . Uint ( )
switch bv . Kind ( ) {
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
bi = bv . Int ( )
if bi >= 0 {
bu = uint64 ( bi )
bi = 0
} else {
ai = int64 ( au ) // may overflow
au = 0
}
case reflect . Float32 , reflect . Float64 :
af = float64 ( au ) // may overflow
au = 0
bf = bv . Float ( )
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
bu = bv . Uint ( )
default :
return nil , errors . New ( "Can't apply the operator to the values" )
}
case reflect . String :
as := av . String ( )
if bv . Kind ( ) == reflect . String && op == '+' {
bs := bv . String ( )
return as + bs , nil
}
2015-03-06 18:02:06 -05:00
return nil , errors . New ( "Can't apply the operator to the values" )
2014-12-26 15:18:26 -05:00
default :
return nil , errors . New ( "Can't apply the operator to the values" )
}
switch op {
case '+' :
if ai != 0 || bi != 0 {
return ai + bi , nil
} else if af != 0 || bf != 0 {
return af + bf , nil
} else if au != 0 || bu != 0 {
return au + bu , nil
} else {
return 0 , nil
}
case '-' :
if ai != 0 || bi != 0 {
return ai - bi , nil
} else if af != 0 || bf != 0 {
return af - bf , nil
} else if au != 0 || bu != 0 {
return au - bu , nil
} else {
return 0 , nil
}
case '*' :
if ai != 0 || bi != 0 {
return ai * bi , nil
} else if af != 0 || bf != 0 {
return af * bf , nil
} else if au != 0 || bu != 0 {
return au * bu , nil
} else {
return 0 , nil
}
case '/' :
if bi != 0 {
return ai / bi , nil
} else if bf != 0 {
return af / bf , nil
} else if bu != 0 {
return au / bu , nil
} else {
return nil , errors . New ( "Can't divide the value by 0" )
}
default :
return nil , errors . New ( "There is no such an operation" )
}
}
2015-09-09 01:05:11 -04:00
2015-10-06 18:58:58 -04:00
// NormalizeHugoFlags facilitates transitions of Hugo command-line flags,
2015-09-09 01:05:11 -04:00
// e.g. --baseUrl to --baseURL, --uglyUrls to --uglyURLs
2015-10-02 00:47:34 -04:00
func NormalizeHugoFlags ( f * pflag . FlagSet , name string ) pflag . NormalizedName {
2015-09-09 01:05:11 -04:00
switch name {
case "baseUrl" :
name = "baseURL"
break
case "uglyUrls" :
name = "uglyURLs"
break
}
return pflag . NormalizedName ( name )
}