1
0
mirror of https://github.com/danog/gojekyll.git synced 2024-11-30 07:18:59 +01:00

Implement smartify filter

This commit is contained in:
Oliver Steele 2017-07-09 13:00:25 -04:00
parent ad59c0239d
commit b96f177f2a
3 changed files with 94 additions and 11 deletions

View File

@ -48,18 +48,12 @@ func AddJekyllFilters(e liquid.Engine, c config.Config) {
e.RegisterFilter("push", func(array []interface{}, item interface{}) interface{} {
return append(array, evaluator.MustConvertItem(item, array))
})
e.RegisterFilter("pop", func(array []interface{}) interface{} {
if len(array) == 0 {
return nil
}
e.RegisterFilter("pop", requireNonEmptyArray(func(array []interface{}) interface{} {
return array[0]
})
e.RegisterFilter("shift", func(array []interface{}) interface{} {
if len(array) == 0 {
return nil
}
}))
e.RegisterFilter("shift", requireNonEmptyArray(func(array []interface{}) interface{} {
return array[len(array)-1]
})
}))
e.RegisterFilter("unshift", func(array []interface{}, item interface{}) interface{} {
return append([]interface{}{evaluator.MustConvertItem(item, array)}, array...)
})
@ -132,7 +126,7 @@ func AddJekyllFilters(e liquid.Engine, c config.Config) {
e.RegisterFilter("cgi_escape", unimplementedFilter("cgi_escape"))
e.RegisterFilter("uri_escape", unimplementedFilter("uri_escape"))
e.RegisterFilter("scssify", unimplementedFilter("scssify"))
e.RegisterFilter("smartify", unimplementedFilter("smartify"))
e.RegisterFilter("smartify", smartifyFilter)
e.RegisterFilter("xml_escape", func(s string) string {
// TODO can't handle maps
// eval https://github.com/clbanning/mxj
@ -145,6 +139,17 @@ func AddJekyllFilters(e liquid.Engine, c config.Config) {
})
}
// helpers
func requireNonEmptyArray(fn func([]interface{}) interface{}) func([]interface{}) interface{} {
return func(array []interface{}) interface{} {
if len(array) == 0 {
return nil
}
return fn(array)
}
}
func unimplementedFilter(name string) func(value interface{}) interface{} {
warned := false
return func(value interface{}) interface{} {
@ -156,6 +161,8 @@ func unimplementedFilter(name string) func(value interface{}) interface{} {
}
}
// array filters
func arrayToSentenceStringFilter(array []string, conjunction func(string) string) string {
conj := conjunction("and ")
switch len(array) {
@ -254,3 +261,4 @@ func whereFilter(array []map[string]interface{}, key string, value interface{})
}
return out
}

View File

@ -61,6 +61,12 @@ var filterTests = []struct{ in, expected string }{
{`{{ "The _config.yml file" | slugify: 'default' }}`, "the-config-yml-file"},
{`{{ "The _config.yml file" | slugify: 'pretty' }}`, "the-_config.yml-file"},
{`{{ "smartify single 'quotes' here" | smartify }}`, "smartify single quotes here"},
{`{{ 'smartify double "quotes" here' | smartify }}`, "smartify double “quotes” here"},
{"{{ \"smartify ``backticks''\" | smartify }}", "smartify “backticks”"},
{`{{ "smartify it's they're" | smartify }}`, "smartify its theyre"},
{`{{ "smartify ... (c) (r) (tm) -- ---" | smartify }}`, "smartify … © ® ™ —"},
{`{{ "1 < 2 & 3" | xml_escape }}`, "1 &lt; 2 &amp; 3"},
// {`{{ "http://foo.com/?q=foo, \bar?" | uri_escape }}`, "http://foo.com/?q=foo,%20%5Cbar?"},
}

69
filters/smartify.go Normal file
View File

@ -0,0 +1,69 @@
package filters
// This file contains a very inefficient implementation, that scans the string multiple times.
// Replace it by a transducer if it shows up in hot spots.
import (
"fmt"
"regexp"
"strings"
)
var smartifyTransforms = []struct {
match *regexp.Regexp
repl string
}{
{regexp.MustCompile("(^|[^[:alnum:]])``(.+?)''"), "$1“$2”"},
{regexp.MustCompile(`(^|[^[:alnum:]])'`), "$1"},
{regexp.MustCompile(`'`), ""},
{regexp.MustCompile(`(^|[^[:alnum:]])"`), "$1“"},
{regexp.MustCompile(`"($|[^[:alnum:]])`), "”$1"},
{regexp.MustCompile(`(^|\s)--($|\s)`), "$1$2"},
{regexp.MustCompile(`(^|\s)---($|\s)`), "$1—$2"},
}
// replace these wherever they appear
var smartifyReplaceSpans = map[string]string{
"...": "…",
"(c)": "©",
"(r)": "®",
"(tm)": "™",
}
// replace these only if bounded by space or word boundaries
var smartifyReplaceWords = map[string]string{
// "---": "",
// "--": "—",
}
var smartifyReplacements map[string]string
var smartifyReplacementPattern *regexp.Regexp
func init() {
smartifyReplacements = map[string]string{}
disjuncts := []string{}
regexQuoter := regexp.MustCompile(`[\(\)\.]`)
escape := func(s string) string {
return regexQuoter.ReplaceAllString(s, `\$0`)
}
for k, v := range smartifyReplaceSpans {
disjuncts = append(disjuncts, escape(k))
smartifyReplacements[k] = v
}
for k, v := range smartifyReplaceWords {
disjuncts = append(disjuncts, fmt.Sprintf(`(\b|\s|^)%s(\b|\s|$)`, escape(k)))
smartifyReplacements[k] = fmt.Sprintf("$1%s$2", v)
}
p := fmt.Sprintf(`(%s)`, strings.Join(disjuncts, `|`))
smartifyReplacementPattern = regexp.MustCompile(p)
}
func smartifyFilter(s string) string {
for _, rule := range smartifyTransforms {
s = rule.match.ReplaceAllString(s, rule.repl)
}
s = smartifyReplacementPattern.ReplaceAllStringFunc(s, func(w string) string {
return smartifyReplacements[w]
})
return s
}