1
0
Fork 0
mirror of https://github.com/Luzifer/repo-template.git synced 2024-11-14 02:12:44 +00:00

Update vendored dependencies

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-04-15 20:42:26 +02:00
parent 2ac6bd1366
commit afd68ccae4
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
1957 changed files with 102893 additions and 414754 deletions

129
Gopkg.lock generated
View file

@ -2,99 +2,125 @@
[[projects]] [[projects]]
digest = "1:ac6e59cf5217c887f6872dc24ebcc3dbbbca6828d7f30b2f24008ec88b5839f3"
name = "github.com/Luzifer/go_helpers" name = "github.com/Luzifer/go_helpers"
packages = ["str"] packages = ["str"]
revision = "8fdddb7041fe962e750caa553a0714f94e261c4a" pruneopts = "NUT"
version = "v2.3.1" revision = "dabc93c52b28192379134e29b8b8b35a9c0e9801"
version = "v2.9.1"
[[projects]] [[projects]]
digest = "1:f6cc072a289a686fda22819d871cd1b0407640141b2f6616dfbab957c96bf6c3"
name = "github.com/Luzifer/rconfig" name = "github.com/Luzifer/rconfig"
packages = ["."] packages = ["."]
revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e" pruneopts = "NUT"
version = "v1.2.0" revision = "d38bbb45e0ac240398f2af710a6b21833954f006"
version = "v2.2.1"
[[projects]] [[projects]]
digest = "1:97fe15521b94567623f129867afbcdbe4010c471ed295a5dd2d458656f18678a"
name = "github.com/flosch/pongo2" name = "github.com/flosch/pongo2"
packages = ["."] packages = ["."]
pruneopts = "NUT"
revision = "5e81b817a0c48c1c57cdf1a9056cf76bdee02ca9" revision = "5e81b817a0c48c1c57cdf1a9056cf76bdee02ca9"
version = "v3.0" version = "v3.0"
[[projects]] [[projects]]
digest = "1:318f1c959a8a740366fce4b1e1eb2fd914036b4af58fbd0a003349b305f118ad"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = ["proto"] packages = ["proto"]
revision = "925541529c1fa6821df4e44ce2723319eb2be768" pruneopts = "NUT"
revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
version = "v1.3.1"
[[projects]]
digest = "1:b4303d38d92c00af6603f3b865a9bd20e3c50cebe55489e5aeb721c26750e375"
name = "github.com/google/go-github"
packages = ["github"]
pruneopts = "NUT"
revision = "7462feb2032c2da9e3b85e9b04e6853a6e9e14ca"
version = "v24.0.1"
[[projects]]
digest = "1:a63cff6b5d8b95638bfe300385d93b2a6d9d687734b863da8e09dc834510a690"
name = "github.com/google/go-querystring"
packages = ["query"]
pruneopts = "NUT"
revision = "44c6ddd0a2342c386950e880b658017258da92fc"
version = "v1.0.0" version = "v1.0.0"
[[projects]] [[projects]]
name = "github.com/google/go-github" digest = "1:974a3957354622ffb7b0b345374322f782800a8e4c4517022fce77ef796249c5"
packages = ["github"]
revision = "e48060a28fac52d0f1cb758bc8b87c07bac4a87d"
version = "v15.0.0"
[[projects]]
branch = "master"
name = "github.com/google/go-querystring"
packages = ["query"]
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
[[projects]]
name = "github.com/gosimple/slug" name = "github.com/gosimple/slug"
packages = ["."] packages = ["."]
revision = "e9f42fa127660e552d0ad2b589868d403a9be7c6" pruneopts = "NUT"
version = "v1.1.1" revision = "e78d39397ba41d2c33d9615044ef93eca8a309d8"
version = "v1.5.0"
[[projects]]
digest = "1:58999a98719fddbac6303cb17e8d85b945f60b72f48e3a2df6b950b97fa926f1"
name = "github.com/konsorten/go-windows-terminal-sequences"
packages = ["."]
pruneopts = "NUT"
revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
version = "v1.0.2"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:16e2136a67ec44aa2d1d6b0fd65394b3c4a8b2a1b6730c77967f7b7b06b179b2"
name = "github.com/rainycape/unidecode" name = "github.com/rainycape/unidecode"
packages = ["."] packages = ["."]
pruneopts = "NUT"
revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c" revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c"
[[projects]] [[projects]]
digest = "1:998e4640c25e00ccb7b2569bf94562283937b8afb3485c0e1a0a05a15330240d"
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
packages = ["."] packages = ["."]
revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba" pruneopts = "NUT"
version = "v1.0.4" revision = "8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f"
version = "v1.4.1"
[[projects]] [[projects]]
digest = "1:9d8420bbf131d1618bde6530af37c3799340d3762cc47210c1d9532a4c3a2779"
name = "github.com/spf13/pflag" name = "github.com/spf13/pflag"
packages = ["."] packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" pruneopts = "NUT"
version = "v1.0.0" revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
version = "v1.0.3"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
revision = "d9133f5469342136e669e85192a26056b587f503"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:f8b491a7c25030a895a0e579742d07136e6958e77ef2d46e769db8eec4e58fcd"
name = "golang.org/x/net" name = "golang.org/x/net"
packages = [ packages = [
"context", "context",
"context/ctxhttp" "context/ctxhttp",
] ]
revision = "2fb46b16b8dda405028c50f7c7f0f9dd1fa6bfb1" pruneopts = "NUT"
revision = "4a65cf94b67935be46fbc55ddaea48ae3f7a1471"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:4d25d388c9ad5169b31c1a6f16cc7bcdb856489958e99f2123d8d8cdf000d7eb"
name = "golang.org/x/oauth2" name = "golang.org/x/oauth2"
packages = [ packages = [
".", ".",
"internal" "internal",
] ]
revision = "543e37812f10c46c622c9575afd7ad22f22a12ba" pruneopts = "NUT"
revision = "9f3314589c9a9136388751d9adae6b0ed400978a"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:6340f2bc34a845d3f5141cb87916197518eebc94490f46aefe430b666a322606"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = [ packages = ["unix"]
"unix", pruneopts = "NUT"
"windows" revision = "3fd5a3612ccd7907f26270fa92579a0f2f76f734"
]
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
[[projects]] [[projects]]
digest = "1:372cd8eba449f9b6db06677d0e73fa193ec5b19aaee148f355503ab6127045ca"
name = "google.golang.org/appengine" name = "google.golang.org/appengine"
packages = [ packages = [
"internal", "internal",
@ -103,26 +129,39 @@
"internal/log", "internal/log",
"internal/remote_api", "internal/remote_api",
"internal/urlfetch", "internal/urlfetch",
"urlfetch" "urlfetch",
] ]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" pruneopts = "NUT"
version = "v1.0.0" revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610"
version = "v1.5.0"
[[projects]] [[projects]]
branch = "v2" branch = "v2"
digest = "1:1ab6db2d2bd353449c5d1e976ba7a92a0ece6e83aaab3e6674f8f2f1faebb85a"
name = "gopkg.in/validator.v2" name = "gopkg.in/validator.v2"
packages = ["."] packages = ["."]
revision = "59c90c7046f643cbe0d4e7c8776c42a84ce75910" pruneopts = "NUT"
revision = "135c24b11c19e52befcae2ec3fca5d9b78c4e98e"
[[projects]] [[projects]]
branch = "v2" digest = "1:18108594151654e9e696b27b181b953f9a90b16bf14d253dd1b397b025a1487f"
name = "gopkg.in/yaml.v2" name = "gopkg.in/yaml.v2"
packages = ["."] packages = ["."]
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" pruneopts = "NUT"
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "d03c24ca54cfeb2db8d69ce43727eb0ea2a31fa382a9c15719855dcbf63cf509" input-imports = [
"github.com/Luzifer/go_helpers/str",
"github.com/Luzifer/rconfig",
"github.com/flosch/pongo2",
"github.com/google/go-github/github",
"github.com/gosimple/slug",
"github.com/sirupsen/logrus",
"golang.org/x/oauth2",
]
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -1,6 +1,6 @@
# Gopkg.toml example # Gopkg.toml example
# #
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation. # for detailed Gopkg.toml documentation.
# #
# required = ["github.com/user/thing/cmd/thing"] # required = ["github.com/user/thing/cmd/thing"]
@ -18,11 +18,20 @@
# [[override]] # [[override]]
# name = "github.com/x/y" # name = "github.com/x/y"
# version = "2.4.0" # version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/Luzifer/go_helpers"
version = "2.9.1"
[[constraint]] [[constraint]]
name = "github.com/Luzifer/rconfig" name = "github.com/Luzifer/rconfig"
version = "1.2.0" version = "2.2.1"
[[constraint]] [[constraint]]
name = "github.com/flosch/pongo2" name = "github.com/flosch/pongo2"
@ -30,16 +39,21 @@
[[constraint]] [[constraint]]
name = "github.com/google/go-github" name = "github.com/google/go-github"
version = "15.0.0" version = "24.0.1"
[[constraint]] [[constraint]]
name = "github.com/gosimple/slug" name = "github.com/gosimple/slug"
version = "1.1.1" version = "1.5.0"
[[constraint]] [[constraint]]
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
version = "1.0.4" version = "1.4.1"
[[constraint]] [[constraint]]
branch = "master" branch = "master"
name = "golang.org/x/oauth2" name = "golang.org/x/oauth2"
[prune]
non-go = true
go-tests = true
unused-packages = true

View file

@ -1,7 +0,0 @@
language: go
go:
- 1.7
- 1.8
- 1.9
- tip

View file

@ -1,43 +0,0 @@
# 2.3.1 / 2017-11-05
* Fix TIP version error: Sprintf format %s has arg of wrong type byte
* Travis: Test on Go 1.7, 1.8, 1.9, tip
# 2.3.0 / 2017-11-05
* Implement digest header generation
# 2.2.0 / 2017-04-13
* Add HTTPLogHandler
# 2.1.0 / 2016-12-23
* Add time.Duration formatter
# 2.0.0 / 2016-10-12
* Drop Go1.5 / Go1.6 support with using contexts
* Add github-binary update helper
# 1.4.0 / 2016-05-29
* Added environment helpers
# 1.3.0 / 2016-05-18
* Added AccessLogResponseWriter
# 1.2.0 / 2016-05-16
* Added helper to find binaries in path or directory
# 1.1.0 / 2016-05-06
* Added Haversine helper functions
1.0.0 / 2016-04-23
==================
* First versioned revision for use with gopkg.in

202
vendor/github.com/Luzifer/go_helpers/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016- Knut Ahlers <knut@ahlers.me>
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.

View file

@ -1,37 +0,0 @@
package accessLogger
import (
"fmt"
"net/http"
"strconv"
)
type AccessLogResponseWriter struct {
StatusCode int
Size int
http.ResponseWriter
}
func New(res http.ResponseWriter) *AccessLogResponseWriter {
return &AccessLogResponseWriter{
StatusCode: 200,
Size: 0,
ResponseWriter: res,
}
}
func (a *AccessLogResponseWriter) Write(out []byte) (int, error) {
s, err := a.ResponseWriter.Write(out)
a.Size += s
return s, err
}
func (a *AccessLogResponseWriter) WriteHeader(code int) {
a.StatusCode = code
a.ResponseWriter.WriteHeader(code)
}
func (a *AccessLogResponseWriter) HTTPResponseType() string {
return fmt.Sprintf("%cxx", strconv.FormatInt(int64(a.StatusCode), 10)[0])
}

View file

@ -1,61 +0,0 @@
package duration
import (
"bytes"
"math"
"strings"
"text/template"
"time"
"github.com/leekchan/gtf"
)
const defaultDurationFormat = `{{if gt .Years 0}}{{.Years}} year{{.Years|pluralize "s"}}, {{end}}` +
`{{if gt .Days 0}}{{.Days}} day{{.Days|pluralize "s"}}, {{end}}` +
`{{if gt .Hours 0}}{{.Hours}} hour{{.Hours|pluralize "s"}}, {{end}}` +
`{{if gt .Minutes 0}}{{.Minutes}} minute{{.Minutes|pluralize "s"}}, {{end}}` +
`{{if gt .Seconds 0}}{{.Seconds}} second{{.Seconds|pluralize "s"}}{{end}}`
func HumanizeDuration(in time.Duration) string {
f, err := CustomHumanizeDuration(in, defaultDurationFormat)
if err != nil {
panic(err)
}
return strings.Trim(f, " ,")
}
func CustomHumanizeDuration(in time.Duration, tpl string) (string, error) {
result := struct{ Years, Days, Hours, Minutes, Seconds int64 }{}
in = time.Duration(math.Abs(float64(in)))
for in > 0 {
switch {
case in > 365.25*24*time.Hour:
result.Years = int64(in / (365 * 24 * time.Hour))
in = in - time.Duration(result.Years)*365*24*time.Hour
case in > 24*time.Hour:
result.Days = int64(in / (24 * time.Hour))
in = in - time.Duration(result.Days)*24*time.Hour
case in > time.Hour:
result.Hours = int64(in / time.Hour)
in = in - time.Duration(result.Hours)*time.Hour
case in > time.Minute:
result.Minutes = int64(in / time.Minute)
in = in - time.Duration(result.Minutes)*time.Minute
default:
result.Seconds = int64(in / time.Second)
in = 0
}
}
tmpl, err := template.New("timeformat").Funcs(template.FuncMap(gtf.GtfFuncMap)).Parse(tpl)
if err != nil {
return "", err
}
buf := bytes.NewBuffer([]byte{})
tmpl.Execute(buf, result)
return buf.String(), nil
}

View file

@ -1,35 +0,0 @@
package duration
import (
"testing"
"time"
)
func TestCustomFormat(t *testing.T) {
d := 389*24*time.Hour +
12*time.Hour +
31*time.Minute +
54*time.Second +
346*time.Millisecond
f := `{{.Years}} - {{.Days}} - {{.Hours}} - {{.Minutes}} - {{.Seconds}}`
e := `1 - 24 - 12 - 31 - 54`
if s, _ := CustomHumanizeDuration(d, f); s != e {
t.Errorf("Got unexpected result: expected=%q result=%q", e, s)
}
}
func TestDefaultFormat(t *testing.T) {
d := 389*24*time.Hour +
12*time.Hour +
31*time.Minute +
54*time.Second +
346*time.Millisecond
e := `1 year, 24 days, 12 hours, 31 minutes, 54 seconds`
if s := HumanizeDuration(d); s != e {
t.Errorf("Got unexpected result: expected=%q result=%q", e, s)
}
}

View file

@ -1,26 +0,0 @@
package env
import "strings"
// ListToMap converts a list of strings in format KEY=VALUE into a map
func ListToMap(list []string) map[string]string {
out := map[string]string{}
for _, entry := range list {
if len(entry) == 0 || entry[0] == '#' {
continue
}
parts := strings.SplitN(entry, "=", 2)
out[parts[0]] = strings.Trim(parts[1], "\"")
}
return out
}
// MapToList converts a map into a list of strings in format KEY=VALUE
func MapToList(envMap map[string]string) []string {
out := []string{}
for k, v := range envMap {
out = append(out, k+"="+v)
}
return out
}

View file

@ -1,13 +0,0 @@
package env_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestEnv(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Env Suite")
}

View file

@ -1,55 +0,0 @@
package env_test
import (
"sort"
. "github.com/Luzifer/go_helpers/env"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Env", func() {
Context("ListToMap", func() {
var (
list = []string{
"FIRST_KEY=firstvalue",
"SECOND_KEY=secondvalue",
"WEIRD=",
"",
}
emap = map[string]string{
"FIRST_KEY": "firstvalue",
"SECOND_KEY": "secondvalue",
"WEIRD": "",
}
)
It("should convert the list in the expected way", func() {
Expect(ListToMap(list)).To(Equal(emap))
})
})
Context("MapToList", func() {
var (
list = []string{
"FIRST_KEY=firstvalue",
"SECOND_KEY=secondvalue",
"WEIRD=",
}
emap = map[string]string{
"FIRST_KEY": "firstvalue",
"SECOND_KEY": "secondvalue",
"WEIRD": "",
}
)
It("should convert the map in the expected way", func() {
l := MapToList(emap)
sort.Strings(l) // Workaround: The test needs the elements to be in same order
Expect(l).To(Equal(list))
})
})
})

View file

@ -1,13 +0,0 @@
package float_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestFloat(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Float Suite")
}

View file

@ -1,14 +0,0 @@
package float
import "math"
// Round returns a float rounded according to "Round to nearest, ties away from zero" IEEE floaing point rounding rule
func Round(x float64) float64 {
var absx, y float64
absx = math.Abs(x)
y = math.Floor(absx)
if absx-y >= 0.5 {
y += 1.0
}
return math.Copysign(y, x)
}

View file

@ -1,35 +0,0 @@
package float_test
import (
"math"
. "github.com/Luzifer/go_helpers/float"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Round", func() {
It("should match the example table of IEEE 754 rules", func() {
Expect(Round(11.5)).To(Equal(12.0))
Expect(Round(12.5)).To(Equal(13.0))
Expect(Round(-11.5)).To(Equal(-12.0))
Expect(Round(-12.5)).To(Equal(-13.0))
})
It("should have correct rounding for numbers near 0.5", func() {
Expect(Round(0.499999999997)).To(Equal(0.0))
Expect(Round(-0.499999999997)).To(Equal(0.0))
})
It("should be able to handle +/-Inf", func() {
Expect(Round(math.Inf(1))).To(Equal(math.Inf(1)))
Expect(Round(math.Inf(-1))).To(Equal(math.Inf(-1)))
})
It("should be able to handle NaN", func() {
Expect(math.IsNaN(Round(math.NaN()))).To(Equal(true))
})
})

View file

@ -1,213 +0,0 @@
package github
import (
"bufio"
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"runtime"
"strings"
"text/template"
"time"
update "github.com/inconshreveable/go-update"
)
const (
defaultTimeout = 60 * time.Second
defaultNamingScheme = `{{.ProductName}}_{{.GOOS}}_{{.GOARCH}}{{.EXT}}`
)
var (
errReleaseNotFound = errors.New("Release not found")
)
// Updater is the core struct of the update library holding all configurations
type Updater struct {
repo string
myVersion string
HTTPClient *http.Client
RequestTimeout time.Duration
Context context.Context
Filename string
releaseCache string
}
// NewUpdater initializes a new Updater and tries to guess the Filename
func NewUpdater(repo, myVersion string) (*Updater, error) {
var err error
u := &Updater{
repo: repo,
myVersion: myVersion,
HTTPClient: http.DefaultClient,
RequestTimeout: defaultTimeout,
Context: context.Background(),
}
u.Filename, err = u.compileFilename()
return u, err
}
// HasUpdate checks which tag was used in the latest version and compares it to the current version. If it differs the function will return true. No comparison is done to determine whether the found version is higher than the current one.
func (u *Updater) HasUpdate(forceRefresh bool) (bool, error) {
if forceRefresh {
u.releaseCache = ""
}
latest, err := u.getLatestRelease()
switch err {
case nil:
return u.myVersion != latest, nil
case errReleaseNotFound:
return false, nil
default:
return false, err
}
}
// Apply downloads the new binary from Github, fetches the SHA256 sum from the SHA256SUMS file and applies the update to the currently running binary
func (u *Updater) Apply() error {
updateAvailable, err := u.HasUpdate(false)
if err != nil {
return err
}
if !updateAvailable {
return nil
}
checksum, err := u.getSHA256(u.Filename)
if err != nil {
return err
}
newRelease, err := u.getFile(u.Filename)
if err != nil {
return err
}
defer newRelease.Close()
return update.Apply(newRelease, update.Options{
Checksum: checksum,
})
}
func (u Updater) getSHA256(filename string) ([]byte, error) {
shaFile, err := u.getFile("SHA256SUMS")
if err != nil {
return nil, err
}
defer shaFile.Close()
scanner := bufio.NewScanner(shaFile)
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, u.Filename) {
continue
}
return hex.DecodeString(line[0:64])
}
return nil, fmt.Errorf("No SHA256 found for file %q", u.Filename)
}
func (u Updater) getFile(filename string) (io.ReadCloser, error) {
release, err := u.getLatestRelease()
if err != nil {
return nil, err
}
requestURL := fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", u.repo, release, filename)
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
return nil, err
}
ctx, _ := context.WithTimeout(u.Context, u.RequestTimeout)
res, err := u.HTTPClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("File not found: %q", requestURL)
}
return res.Body, nil
}
func (u *Updater) getLatestRelease() (string, error) {
if u.releaseCache != "" {
return u.releaseCache, nil
}
result := struct {
TagName string `json:"tag_name"`
}{}
requestURL := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", u.repo)
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
return "", err
}
ctx, cancel := context.WithTimeout(u.Context, u.RequestTimeout)
defer cancel()
res, err := u.HTTPClient.Do(req.WithContext(ctx))
if err != nil {
return "", err
}
defer res.Body.Close()
if err = json.NewDecoder(res.Body).Decode(&result); err != nil {
return "", err
}
if res.StatusCode != 200 || result.TagName == "" {
return "", errReleaseNotFound
}
u.releaseCache = result.TagName
return result.TagName, nil
}
func (u Updater) compileFilename() (string, error) {
repoName := strings.Split(u.repo, "/")
if len(repoName) != 2 {
return "", errors.New("Repository name not in format <owner>/<repository>")
}
tpl, err := template.New("filename").Parse(defaultNamingScheme)
if err != nil {
return "", err
}
var ext string
if runtime.GOOS == "windows" {
ext = ".exe"
}
buf := bytes.NewBuffer([]byte{})
if err = tpl.Execute(buf, map[string]interface{}{
"GOOS": runtime.GOOS,
"GOARCH": runtime.GOARCH,
"EXT": ext,
"ProductName": repoName[1],
}); err != nil {
return "", err
}
return buf.String(), nil
}

View file

@ -1,55 +0,0 @@
package http
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http"
"strings"
"github.com/Luzifer/go_helpers/str"
)
func GetDigestAuth(resp *http.Response, method, requestPath, user, password string) string {
params := map[string]string{}
for _, part := range strings.Split(resp.Header.Get("Www-Authenticate"), " ") {
if !strings.Contains(part, `="`) {
continue
}
spl := strings.Split(strings.Trim(part, " ,"), "=")
if !str.StringInSlice(spl[0], []string{"nonce", "realm", "qop"}) {
continue
}
params[spl[0]] = strings.Trim(spl[1], `"`)
}
b := make([]byte, 8)
io.ReadFull(rand.Reader, b)
params["cnonce"] = fmt.Sprintf("%x", b)
params["nc"] = "1"
params["uri"] = requestPath
params["username"] = user
params["response"] = getMD5([]string{
getMD5([]string{params["username"], params["realm"], password}),
params["nonce"],
params["nc"],
params["cnonce"],
params["qop"],
getMD5([]string{method, requestPath}),
})
authParts := []string{}
for k, v := range params {
authParts = append(authParts, fmt.Sprintf("%s=%q", k, v))
}
return "Digest " + strings.Join(authParts, ", ")
}
func getMD5(in []string) string {
h := md5.New()
h.Write([]byte(strings.Join(in, ":")))
return hex.EncodeToString(h.Sum(nil))
}

View file

@ -1,35 +0,0 @@
package http
import (
"log"
"net/http"
"time"
"github.com/Luzifer/go_helpers/accessLogger"
)
type HTTPLogHandler struct {
Handler http.Handler
}
func NewHTTPLogHandler(h http.Handler) http.Handler {
return HTTPLogHandler{Handler: h}
}
func (l HTTPLogHandler) ServeHTTP(res http.ResponseWriter, r *http.Request) {
start := time.Now()
ares := accessLogger.New(res)
l.Handler.ServeHTTP(ares, r)
log.Printf("%s - \"%s %s\" %d %d \"%s\" \"%s\" %s",
r.RemoteAddr,
r.Method,
r.URL.Path,
ares.StatusCode,
ares.Size,
r.Header.Get("Referer"),
r.Header.Get("User-Agent"),
time.Since(start),
)
}

View file

@ -1,21 +0,0 @@
package position
import "math"
const (
earthRadius = float64(6371)
)
func Haversine(lonFrom float64, latFrom float64, lonTo float64, latTo float64) (distance float64) {
var deltaLat = (latTo - latFrom) * (math.Pi / 180)
var deltaLon = (lonTo - lonFrom) * (math.Pi / 180)
var a = math.Sin(deltaLat/2)*math.Sin(deltaLat/2) +
math.Cos(latFrom*(math.Pi/180))*math.Cos(latTo*(math.Pi/180))*
math.Sin(deltaLon/2)*math.Sin(deltaLon/2)
var c = 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
distance = earthRadius * c
return
}

View file

@ -1,34 +0,0 @@
package position_test
import (
. "github.com/Luzifer/go_helpers/position"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Haversine", func() {
var testCases = []struct {
SourceLat float64
SourceLon float64
DestLat float64
DestLon float64
Distance float64
}{
{50.066389, -5.714722, 58.643889, -3.070000, 968.8535441168448},
{50.063995, -5.609464, 53.553027, 9.993782, 1137.894906816002},
{53.553027, 9.993782, 53.554528, 9.991357, 0.23133816528015647},
{50, 9, 51, 9, 111.19492664455873},
{0, 9, 0, 10, 111.19492664455873},
{1, 0, -1, 0, 222.38985328911747},
}
It("should have the documented distance", func() {
for i := range testCases {
tc := testCases[i]
Expect(Haversine(tc.SourceLon, tc.SourceLat, tc.DestLon, tc.DestLat)).To(Equal(tc.Distance))
}
})
})

View file

@ -1,13 +0,0 @@
package position_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestPosition(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Position Suite")
}

View file

@ -1,52 +0,0 @@
package str_test
import (
. "github.com/Luzifer/go_helpers/str"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Slice", func() {
Context("AppendIfMissing", func() {
var sl = []string{
"test1",
"test2",
"test3",
}
It("should not append existing elements", func() {
Expect(len(AppendIfMissing(sl, "test1"))).To(Equal(3))
Expect(len(AppendIfMissing(sl, "test2"))).To(Equal(3))
Expect(len(AppendIfMissing(sl, "test3"))).To(Equal(3))
})
It("should append not existing elements", func() {
Expect(len(AppendIfMissing(sl, "test4"))).To(Equal(4))
Expect(len(AppendIfMissing(sl, "test5"))).To(Equal(4))
Expect(len(AppendIfMissing(sl, "test6"))).To(Equal(4))
})
})
Context("StringInSlice", func() {
var sl = []string{
"test1",
"test2",
"test3",
}
It("should find elements of slice", func() {
Expect(StringInSlice("test1", sl)).To(Equal(true))
Expect(StringInSlice("test2", sl)).To(Equal(true))
Expect(StringInSlice("test3", sl)).To(Equal(true))
})
It("should not find elements not in slice", func() {
Expect(StringInSlice("test4", sl)).To(Equal(false))
Expect(StringInSlice("test5", sl)).To(Equal(false))
Expect(StringInSlice("test6", sl)).To(Equal(false))
})
})
})

View file

@ -1,13 +0,0 @@
package str_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestStr(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Str Suite")
}

View file

@ -1,54 +0,0 @@
package which
import (
"errors"
"os"
"path"
"strings"
)
// Common named errors to match in programs using this library
var (
ErrBinaryNotFound = errors.New("Requested binary was not found")
ErrNoSearchSpecified = errors.New("You need to specify a binary to search")
)
// FindInPath searches the specified binary in directories listed in $PATH and returns first match
func FindInPath(binary string) (string, error) {
pathEnv := os.Getenv("PATH")
if len(pathEnv) == 0 {
return "", errors.New("Found empty $PATH, not able to search $PATH")
}
for _, part := range strings.Split(pathEnv, ":") {
if len(part) == 0 {
continue
}
if found, err := FindInDirectory(binary, part); err != nil {
return "", err
} else if found {
return path.Join(part, binary), nil
}
}
return "", ErrBinaryNotFound
}
// FindInDirectory checks whether the specified file is present in the directory
func FindInDirectory(binary, directory string) (bool, error) {
if len(binary) == 0 {
return false, ErrNoSearchSpecified
}
_, err := os.Stat(path.Join(directory, binary))
switch {
case err == nil:
return true, nil
case os.IsNotExist(err):
return false, nil
default:
return false, err
}
}

View file

@ -1,13 +0,0 @@
package which_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestWhich(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Which Suite")
}

View file

@ -1,63 +0,0 @@
package which_test
import (
. "github.com/Luzifer/go_helpers/which"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Which", func() {
var (
result string
err error
found bool
)
Context("With a file available on linux systems", func() {
BeforeEach(func() {
found, err = FindInDirectory("bash", "/bin")
})
It("should not have errored", func() {
Expect(err).NotTo(HaveOccurred())
})
It("should have found the binary at /bin/bash", func() {
Expect(found).To(BeTrue())
})
})
Context("Searching bash on the system", func() {
BeforeEach(func() {
result, err = FindInPath("bash")
})
It("should not have errored", func() {
Expect(err).NotTo(HaveOccurred())
})
It("should have a result", func() {
Expect(len(result)).NotTo(Equal(0))
})
})
Context("Searching a non existent file", func() {
BeforeEach(func() {
result, err = FindInPath("dfqoiwurgtqi3uegrds")
})
It("should have errored", func() {
Expect(err).To(Equal(ErrBinaryNotFound))
})
})
Context("Searching an empty file", func() {
BeforeEach(func() {
result, err = FindInPath("")
})
It("should have errored", func() {
Expect(err).To(Equal(ErrNoSearchSpecified))
})
})
})

View file

@ -1,8 +0,0 @@
language: go
go:
- 1.6
- 1.7
- tip
script: go test -v -race -cover ./...

View file

@ -1,9 +0,0 @@
# 1.2.0 / 2017-06-19
* Add ParseAndValidate method
# 1.1.0 / 2016-06-28
* Support time.Duration config parameters
* Added goreportcard badge
* Added testcase for using bool with ENV and default

View file

@ -1,13 +1,202 @@
Copyright 2015 Knut Ahlers <knut@ahlers.me> Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Licensed under the Apache License, Version 2.0 (the "License"); TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
you may not use this file except in compliance with the License.
You may obtain a copy of the License at 1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015- Knut Ahlers <knut@ahlers.me>
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 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.

View file

@ -1,87 +0,0 @@
[![Build Status](https://travis-ci.org/Luzifer/rconfig.svg?branch=master)](https://travis-ci.org/Luzifer/rconfig)
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig)
[![Go Report](http://goreportcard.com/badge/Luzifer/rconfig)](http://goreportcard.com/report/Luzifer/rconfig)
## Description
> Package rconfig implements a CLI configuration reader with struct-embedded defaults, environment variables and posix compatible flag parsing using the [pflag](https://github.com/spf13/pflag) library.
## Installation
Install by running:
```
go get -u github.com/Luzifer/rconfig
```
OR fetch a specific version:
```
go get -u gopkg.in/luzifer/rconfig.v1
```
Run tests by running:
```
go test -v -race -cover github.com/Luzifer/rconfig
```
## Usage
A very simple usecase is to just configure a struct inside the vars section of your `main.go` and to parse the commandline flags from the `main()` function:
```go
package main
import (
"fmt"
"github.com/Luzifer/rconfig"
)
var (
cfg = struct {
Username string `default:"unknown" flag:"user" description:"Your name"`
Details struct {
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
}
}{}
)
func main() {
rconfig.Parse(&cfg)
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
cfg.Username,
cfg.Details.Age)
}
```
### Provide variable defaults by using a file
Given you have a file `~/.myapp.yml` containing some secrets or usernames (for the example below username is assumed to be "luzifer") as a default configuration for your application you can use this source code to load the defaults from that file using the `vardefault` tag in your configuration struct.
The order of the directives (lower number = higher precedence):
1. Flags provided in command line
1. Environment variables
1. Variable defaults (`vardefault` tag in the struct)
1. `default` tag in the struct
```go
var cfg = struct {
Username string `vardefault:"username" flag:"username" description:"Your username"`
}
func main() {
rconfig.SetVariableDefaults(rconfig.VarDefaultsFromYAMLFile("~/.myapp.yml"))
rconfig.Parse(&cfg)
fmt.Printf("Username = %s", cfg.Username)
// Output: Username = luzifer
}
```
## More info
You can see the full reference documentation of the rconfig package [at godoc.org](https://godoc.org/github.com/Luzifer/rconfig), or through go's standard documentation system by running `godoc -http=:6060` and browsing to [http://localhost:6060/pkg/github.com/Luzifer/rconfig](http://localhost:6060/pkg/github.com/Luzifer/rconfig) after installation.

64
vendor/github.com/Luzifer/rconfig/autoenv.go generated vendored Normal file
View file

@ -0,0 +1,64 @@
package rconfig
import "strings"
type characterClass [2]rune
func (c characterClass) Contains(r rune) bool {
return c[0] <= r && c[1] >= r
}
type characterClasses []characterClass
func (c characterClasses) Contains(r rune) bool {
for _, cc := range c {
if cc.Contains(r) {
return true
}
}
return false
}
var (
charGroupUpperLetter = characterClass{'A', 'Z'}
charGroupLowerLetter = characterClass{'a', 'z'}
charGroupNumber = characterClass{'0', '9'}
charGroupLowerNumber = characterClasses{charGroupLowerLetter, charGroupNumber}
)
func deriveEnvVarName(s string) string {
var (
words []string
word []rune
)
for _, l := range s {
switch {
case charGroupUpperLetter.Contains(l):
if len(word) > 0 && charGroupLowerNumber.Contains(word[len(word)-1]) {
words = append(words, string(word))
word = []rune{}
}
word = append(word, l)
case charGroupLowerLetter.Contains(l):
if len(word) > 1 && charGroupUpperLetter.Contains(word[len(word)-1]) {
words = append(words, string(word[0:len(word)-1]))
word = word[len(word)-1:]
}
word = append(word, l)
case charGroupNumber.Contains(l):
word = append(word, l)
default:
if len(word) > 0 {
words = append(words, string(word))
}
word = []rune{}
}
}
words = append(words, string(word))
return strings.ToUpper(strings.Join(words, "_"))
}

View file

@ -1,70 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing bool parsing", func() {
type t struct {
Test1 bool `default:"true"`
Test2 bool `default:"false" flag:"test2"`
Test3 bool `default:"true" flag:"test3,t"`
Test4 bool `flag:"test4"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--test2",
"-t",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test1).To(Equal(true))
Expect(cfg.Test2).To(Equal(true))
Expect(cfg.Test3).To(Equal(true))
Expect(cfg.Test4).To(Equal(false))
})
})
var _ = Describe("Testing to set bool from ENV with default", func() {
type t struct {
Test1 bool `default:"true" env:"TEST1"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
os.Unsetenv("TEST1")
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test1).To(Equal(true))
})
})

View file

@ -16,9 +16,25 @@ import (
validator "gopkg.in/validator.v2" validator "gopkg.in/validator.v2"
) )
type afterFunc func() error
var ( var (
autoEnv bool
fs *pflag.FlagSet fs *pflag.FlagSet
variableDefaults map[string]string variableDefaults map[string]string
timeParserFormats = []string{
// Default constants
time.RFC3339Nano, time.RFC3339,
time.RFC1123Z, time.RFC1123,
time.RFC822Z, time.RFC822,
time.RFC850, time.RubyDate, time.UnixDate, time.ANSIC,
"2006-01-02 15:04:05.999999999 -0700 MST",
// More uncommon time formats
"2006-01-02 15:04:05", "2006-01-02 15:04:05Z07:00", // Simplified ISO time format
"01/02/2006 15:04:05", "01/02/2006 15:04:05Z07:00", // US time format
"02.01.2006 15:04:05", "02.01.2006 15:04:05Z07:00", // DE time format
}
) )
func init() { func init() {
@ -60,6 +76,18 @@ func Args() []string {
return fs.Args() return fs.Args()
} }
// AddTimeParserFormats adds custom formats to parse time.Time fields
func AddTimeParserFormats(f ...string) {
timeParserFormats = append(timeParserFormats, f...)
}
// AutoEnv enables or disables automated env variable guessing. If no `env` struct
// tag was set and AutoEnv is enabled the env variable name is derived from the
// name of the field: `MyFieldName` will get `MY_FIELD_NAME`
func AutoEnv(enable bool) {
autoEnv = enable
}
// Usage prints a basic usage with the corresponding defaults for the flags to // Usage prints a basic usage with the corresponding defaults for the flags to
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV. // os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
func Usage() { func Usage() {
@ -89,22 +117,37 @@ func parse(in interface{}, args []string) error {
} }
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
if err := execTags(in, fs); err != nil { afterFuncs, err := execTags(in, fs)
if err != nil {
return err return err
} }
return fs.Parse(args) if err := fs.Parse(args); err != nil {
return err
}
if afterFuncs != nil {
for _, f := range afterFuncs {
if err := f(); err != nil {
return err
}
}
}
return nil
} }
func execTags(in interface{}, fs *pflag.FlagSet) error { func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
if reflect.TypeOf(in).Kind() != reflect.Ptr { if reflect.TypeOf(in).Kind() != reflect.Ptr {
return errors.New("Calling parser with non-pointer") return nil, errors.New("Calling parser with non-pointer")
} }
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct { if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
return errors.New("Calling parser with pointer to non-struct") return nil, errors.New("Calling parser with pointer to non-struct")
} }
afterFuncs := []afterFunc{}
st := reflect.ValueOf(in).Elem() st := reflect.ValueOf(in).Elem()
for i := 0; i < st.NumField(); i++ { for i := 0; i < st.NumField(); i++ {
valField := st.Field(i) valField := st.Field(i)
@ -116,7 +159,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
} }
value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default")) value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default"))
value = envDefault(typeField.Tag.Get("env"), value) value = envDefault(typeField, value)
parts := strings.Split(typeField.Tag.Get("flag"), ",") parts := strings.Split(typeField.Tag.Get("flag"), ",")
switch typeField.Type { switch typeField.Type {
@ -126,7 +169,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
v = time.Duration(0) v = time.Duration(0)
} else { } else {
return err return nil, err
} }
} }
@ -140,6 +183,53 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
valField.Set(reflect.ValueOf(v)) valField.Set(reflect.ValueOf(v))
} }
continue continue
case reflect.TypeOf(time.Time{}):
var sVar string
if typeField.Tag.Get("flag") != "" {
if len(parts) == 1 {
fs.StringVar(&sVar, parts[0], value, typeField.Tag.Get("description"))
} else {
fs.StringVarP(&sVar, parts[0], parts[1], value, typeField.Tag.Get("description"))
}
} else {
sVar = value
}
afterFuncs = append(afterFuncs, func(valField reflect.Value, sVar *string) func() error {
return func() error {
if *sVar == "" {
// No time, no problem
return nil
}
// Check whether we could have a timestamp
if ts, err := strconv.ParseInt(*sVar, 10, 64); err == nil {
t := time.Unix(ts, 0)
valField.Set(reflect.ValueOf(t))
return nil
}
// We haven't so lets walk through possible time formats
matched := false
for _, tf := range timeParserFormats {
if t, err := time.Parse(tf, *sVar); err == nil {
matched = true
valField.Set(reflect.ValueOf(t))
return nil
}
}
if !matched {
return fmt.Errorf("Value %q did not match expected time formats", *sVar)
}
return nil
}
}(valField, &sVar))
continue
} }
switch typeField.Type.Kind() { switch typeField.Type.Kind() {
@ -172,7 +262,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
vt = 0 vt = 0
} else { } else {
return err return nil, err
} }
} }
if typeField.Tag.Get("flag") != "" { if typeField.Tag.Get("flag") != "" {
@ -187,7 +277,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
vt = 0 vt = 0
} else { } else {
return err return nil, err
} }
} }
if typeField.Tag.Get("flag") != "" { if typeField.Tag.Get("flag") != "" {
@ -202,7 +292,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
vt = 0.0 vt = 0.0
} else { } else {
return err return nil, err
} }
} }
if typeField.Tag.Get("flag") != "" { if typeField.Tag.Get("flag") != "" {
@ -212,9 +302,11 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
} }
case reflect.Struct: case reflect.Struct:
if err := execTags(valField.Addr().Interface(), fs); err != nil { afs, err := execTags(valField.Addr().Interface(), fs)
return err if err != nil {
return nil, err
} }
afterFuncs = append(afterFuncs, afs...)
case reflect.Slice: case reflect.Slice:
switch typeField.Type.Elem().Kind() { switch typeField.Type.Elem().Kind() {
@ -223,7 +315,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
for _, v := range strings.Split(value, ",") { for _, v := range strings.Split(value, ",") {
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64) it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
if err != nil { if err != nil {
return err return nil, err
} }
def = append(def, int(it)) def = append(def, int(it))
} }
@ -237,7 +329,10 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if len(del) == 0 { if len(del) == 0 {
del = "," del = ","
} }
def := strings.Split(value, del) var def = []string{}
if value != "" {
def = strings.Split(value, del)
}
if len(parts) == 1 { if len(parts) == 1 {
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description")) fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
} else { } else {
@ -247,7 +342,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
} }
} }
return nil return afterFuncs, nil
} }
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) { func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {
@ -331,9 +426,14 @@ func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, part
} }
} }
func envDefault(env, def string) string { func envDefault(field reflect.StructField, def string) string {
value := def value := def
env := field.Tag.Get("env")
if env == "" && autoEnv {
env = deriveEnvVarName(field.Name)
}
if env != "" { if env != "" {
if e := os.Getenv(env); e != "" { if e := os.Getenv(env); e != "" {
value = e value = e

View file

@ -1,41 +0,0 @@
package rconfig
import (
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Duration", func() {
type t struct {
Test time.Duration `flag:"duration"`
TestS time.Duration `flag:"other-duration,o"`
TestDef time.Duration `default:"30h"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--duration=23s", "-o", "45m",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(23 * time.Second))
Expect(cfg.TestS).To(Equal(45 * time.Minute))
Expect(cfg.TestDef).To(Equal(30 * time.Hour))
})
})

View file

@ -1,56 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing errors", func() {
It("should not accept string as int", func() {
Expect(parse(&struct {
A int `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as float", func() {
Expect(parse(&struct {
A float32 `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as uint", func() {
Expect(parse(&struct {
A uint `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as uint in sub-struct", func() {
Expect(parse(&struct {
B struct {
A uint `default:"a"`
}
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string slice as int slice", func() {
Expect(parse(&struct {
A []int `default:"a,bn"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept variables not being pointers", func() {
cfg := struct {
A string `default:"a"`
}{}
Expect(parse(cfg, []string{})).To(HaveOccurred())
})
It("should not accept variables not being pointers to structs", func() {
cfg := "test"
Expect(parse(cfg, []string{})).To(HaveOccurred())
})
})

View file

@ -1,37 +0,0 @@
package rconfig
import (
"fmt"
"os"
)
func ExampleParse() {
// We're building an example configuration with a sub-struct to be filled
// by the Parse command.
config := struct {
Username string `default:"unknown" flag:"user,u" description:"Your name"`
Details struct {
Age int `default:"25" flag:"age" description:"Your age"`
}
}{}
// To have more relieable results we're setting os.Args to a known value.
// In real-life use cases you wouldn't do this but parse the original
// commandline arguments.
os.Args = []string{
"example",
"--user=Luzifer",
}
Parse(&config)
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
config.Username,
config.Details.Age)
// You can also show an usage message for your user
Usage()
// Output:
// Hello Luzifer, happy birthday for your 25th birthday.
}

View file

@ -1,44 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing float parsing", func() {
type t struct {
Test32 float32 `flag:"float32"`
Test32P float32 `flag:"float32p,3"`
Test64 float64 `flag:"float64"`
Test64P float64 `flag:"float64p,6"`
TestDef float32 `default:"66.256"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--float32=5.5", "-3", "6.6",
"--float64=7.7", "-6", "8.8",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test32).To(Equal(float32(5.5)))
Expect(cfg.Test32P).To(Equal(float32(6.6)))
Expect(cfg.Test64).To(Equal(float64(7.7)))
Expect(cfg.Test64P).To(Equal(float64(8.8)))
Expect(cfg.TestDef).To(Equal(float32(66.256)))
})
})

View file

@ -1,128 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing general parsing", func() {
type t struct {
Test string `default:"foo" env:"shell" flag:"shell" description:"Test"`
Test2 string `default:"blub" env:"testvar" flag:"testvar,t" description:"Test"`
DefaultFlag string `default:"goo"`
SadFlag string
}
type tValidated struct {
Test string `flag:"test" default:"" validate:"nonzero"`
}
var (
err error
args []string
cfg t
)
Context("with defined arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{
"--shell=test23",
"-t", "bla",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have parsed the expected values", func() {
Expect(cfg.Test).To(Equal("test23"))
Expect(cfg.Test2).To(Equal("bla"))
Expect(cfg.SadFlag).To(Equal(""))
Expect(cfg.DefaultFlag).To(Equal("goo"))
})
})
Context("with no arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the default value", func() {
Expect(cfg.Test).To(Equal("foo"))
})
})
Context("with no arguments and set env", func() {
BeforeEach(func() {
cfg = t{}
args = []string{}
os.Setenv("shell", "test546")
})
AfterEach(func() {
os.Unsetenv("shell")
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the value from env", func() {
Expect(cfg.Test).To(Equal("test546"))
})
})
Context("with additional arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{
"--shell=test23",
"-t", "bla",
"positional1", "positional2",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have parsed the expected values", func() {
Expect(cfg.Test).To(Equal("test23"))
Expect(cfg.Test2).To(Equal("bla"))
Expect(cfg.SadFlag).To(Equal(""))
Expect(cfg.DefaultFlag).To(Equal("goo"))
})
It("should have detected the positional arguments", func() {
Expect(Args()).To(Equal([]string{"positional1", "positional2"}))
})
})
Context("making use of the validator package", func() {
var cfgValidated tValidated
BeforeEach(func() {
cfgValidated = tValidated{}
args = []string{}
})
JustBeforeEach(func() {
err = parseAndValidate(&cfgValidated, args)
})
It("should have errored", func() { Expect(err).To(HaveOccurred()) })
})
})

View file

@ -1,54 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing int parsing", func() {
type t struct {
Test int `flag:"int"`
TestP int `flag:"intp,i"`
Test8 int8 `flag:"int8"`
Test8P int8 `flag:"int8p,8"`
Test32 int32 `flag:"int32"`
Test32P int32 `flag:"int32p,3"`
Test64 int64 `flag:"int64"`
Test64P int64 `flag:"int64p,6"`
TestDef int8 `default:"66"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=1", "-i", "2",
"--int8=3", "-8", "4",
"--int32=5", "-3", "6",
"--int64=7", "-6", "8",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(1))
Expect(cfg.TestP).To(Equal(2))
Expect(cfg.Test8).To(Equal(int8(3)))
Expect(cfg.Test8P).To(Equal(int8(4)))
Expect(cfg.Test32).To(Equal(int32(5)))
Expect(cfg.Test32P).To(Equal(int32(6)))
Expect(cfg.Test64).To(Equal(int64(7)))
Expect(cfg.Test64P).To(Equal(int64(8)))
Expect(cfg.TestDef).To(Equal(int8(66)))
})
})

View file

@ -1,40 +0,0 @@
package rconfig_test
import (
"os"
. "github.com/Luzifer/rconfig"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing os.Args", func() {
type t struct {
A string `default:"a" flag:"a"`
}
var (
err error
cfg t
)
JustBeforeEach(func() {
err = Parse(&cfg)
})
Context("With only valid arguments", func() {
BeforeEach(func() {
cfg = t{}
os.Args = []string{"--a=bar"}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.A).To(Equal("bar"))
})
})
})

View file

@ -1,87 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Precedence", func() {
type t struct {
A int `default:"1" vardefault:"a" env:"a" flag:"avar,a" description:"a"`
}
var (
err error
cfg t
args []string
vardefaults map[string]string
)
JustBeforeEach(func() {
cfg = t{}
SetVariableDefaults(vardefaults)
err = parse(&cfg, args)
})
Context("Provided: Flag, Env, Default, VarDefault", func() {
BeforeEach(func() {
args = []string{"-a", "5"}
os.Setenv("a", "8")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the flag value", func() {
Expect(cfg.A).To(Equal(5))
})
})
Context("Provided: Env, Default, VarDefault", func() {
BeforeEach(func() {
args = []string{}
os.Setenv("a", "8")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the env value", func() {
Expect(cfg.A).To(Equal(8))
})
})
Context("Provided: Default, VarDefault", func() {
BeforeEach(func() {
args = []string{}
os.Unsetenv("a")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the vardefault value", func() {
Expect(cfg.A).To(Equal(3))
})
})
Context("Provided: Default", func() {
BeforeEach(func() {
args = []string{}
os.Unsetenv("a")
vardefaults = map[string]string{}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the default value", func() {
Expect(cfg.A).To(Equal(1))
})
})
})

View file

@ -1,13 +0,0 @@
package rconfig_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestRconfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Rconfig Suite")
}

View file

@ -1,51 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing slices", func() {
type t struct {
Int []int `default:"1,2,3" flag:"int"`
String []string `default:"a,b,c" flag:"string"`
IntP []int `default:"1,2,3" flag:"intp,i"`
StringP []string `default:"a,b,c" flag:"stringp,s"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=4,5", "-s", "hallo,welt",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values for int-slice", func() {
Expect(len(cfg.Int)).To(Equal(2))
Expect(cfg.Int).To(Equal([]int{4, 5}))
Expect(cfg.Int).NotTo(Equal([]int{5, 4}))
})
It("should have the expected values for int-shorthand-slice", func() {
Expect(len(cfg.IntP)).To(Equal(3))
Expect(cfg.IntP).To(Equal([]int{1, 2, 3}))
})
It("should have the expected values for string-slice", func() {
Expect(len(cfg.String)).To(Equal(3))
Expect(cfg.String).To(Equal([]string{"a", "b", "c"}))
})
It("should have the expected values for string-shorthand-slice", func() {
Expect(len(cfg.StringP)).To(Equal(2))
Expect(cfg.StringP).To(Equal([]string{"hallo", "welt"}))
})
})

View file

@ -1,36 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing sub-structs", func() {
type t struct {
Test string `default:"blubb"`
Sub struct {
Test string `default:"Hallo"`
}
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal("blubb"))
Expect(cfg.Sub.Test).To(Equal("Hallo"))
})
})

View file

@ -1,59 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing uint parsing", func() {
type t struct {
Test uint `flag:"int"`
TestP uint `flag:"intp,i"`
Test8 uint8 `flag:"int8"`
Test8P uint8 `flag:"int8p,8"`
Test16 uint16 `flag:"int16"`
Test16P uint16 `flag:"int16p,1"`
Test32 uint32 `flag:"int32"`
Test32P uint32 `flag:"int32p,3"`
Test64 uint64 `flag:"int64"`
Test64P uint64 `flag:"int64p,6"`
TestDef uint8 `default:"66"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=1", "-i", "2",
"--int8=3", "-8", "4",
"--int32=5", "-3", "6",
"--int64=7", "-6", "8",
"--int16=9", "-1", "10",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(uint(1)))
Expect(cfg.TestP).To(Equal(uint(2)))
Expect(cfg.Test8).To(Equal(uint8(3)))
Expect(cfg.Test8P).To(Equal(uint8(4)))
Expect(cfg.Test32).To(Equal(uint32(5)))
Expect(cfg.Test32P).To(Equal(uint32(6)))
Expect(cfg.Test64).To(Equal(uint64(7)))
Expect(cfg.Test64P).To(Equal(uint64(8)))
Expect(cfg.Test16).To(Equal(uint16(9)))
Expect(cfg.Test16P).To(Equal(uint16(10)))
Expect(cfg.TestDef).To(Equal(uint8(66)))
})
})

View file

@ -1,122 +0,0 @@
package rconfig
import (
"io/ioutil"
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing variable defaults", func() {
type t struct {
MySecretValue string `default:"secret" env:"foo" vardefault:"my_secret_value"`
MyUsername string `default:"luzifer" vardefault:"username"`
SomeVar string `flag:"var" description:"some variable"`
IntVar int64 `vardefault:"int_var" default:"23"`
}
var (
err error
cfg t
args = []string{}
vardefaults = map[string]string{
"my_secret_value": "veryverysecretkey",
"unkownkey": "hi there",
"int_var": "42",
}
)
BeforeEach(func() {
cfg = t{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
Context("With manually provided variables", func() {
BeforeEach(func() {
SetVariableDefaults(vardefaults)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from YAML data", func() {
BeforeEach(func() {
yamlData := []byte("---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n")
SetVariableDefaults(VarDefaultsFromYAML(yamlData))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from YAML file", func() {
var tmp *os.File
BeforeEach(func() {
tmp, _ = ioutil.TempFile("", "")
yamlData := "---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n"
tmp.WriteString(yamlData)
SetVariableDefaults(VarDefaultsFromYAMLFile(tmp.Name()))
})
AfterEach(func() {
tmp.Close()
os.Remove(tmp.Name())
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from invalid YAML data", func() {
BeforeEach(func() {
yamlData := []byte("---\nmy_secret_value = veryverysecretkey\nunknownkey = hi there\nint_var = 42\n")
SetVariableDefaults(VarDefaultsFromYAML(yamlData))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(23)))
Expect(cfg.MySecretValue).To(Equal("secret"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from non existent YAML file", func() {
BeforeEach(func() {
file := "/tmp/this_file_should_not_exist_146e26723r"
SetVariableDefaults(VarDefaultsFromYAMLFile(file))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(23)))
Expect(cfg.MySecretValue).To(Equal("secret"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
})

View file

@ -1,40 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.project
EBNF.txt
test1.tpl
pongo2_internal_test.go
tpl-error.out
/count.out
/cover.out
*.swp
*.iml
/cpu.out
/mem.out
/pongo2.test
*.error
/profile
/coverage.out
/pongo2_internal_test.ignore

View file

@ -1,12 +0,0 @@
language: go
go:
- 1.3
- tip
install:
- go get code.google.com/p/go.tools/cmd/cover
- go get github.com/mattn/goveralls
- go get gopkg.in/check.v1
script:
- go test -v -covermode=count -coverprofile=coverage.out -bench . -cpu 1,4
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN || true'

View file

@ -1,248 +0,0 @@
# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2
[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.png)](https://godoc.org/github.com/flosch/pongo2)
[![Build Status](https://travis-ci.org/flosch/pongo2.svg?branch=master)](https://travis-ci.org/flosch/pongo2)
[![Coverage Status](https://coveralls.io/repos/flosch/pongo2/badge.png?branch=master)](https://coveralls.io/r/flosch/pongo2?branch=master)
[![gratipay](http://img.shields.io/badge/gratipay-support%20pongo-brightgreen.svg)](https://gratipay.com/flosch/)
[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=3654947)](https://www.bountysource.com/trackers/3654947-pongo2?utm_source=3654947&utm_medium=shield&utm_campaign=TRACKER_BADGE)
pongo2 is the successor of [pongo](https://github.com/flosch/pongo), a Django-syntax like templating-language.
Install/update using `go get` (no dependencies required by pongo2):
```
go get -u github.com/flosch/pongo2
```
Please use the [issue tracker](https://github.com/flosch/pongo2/issues) if you're encountering any problems with pongo2 or if you need help with implementing tags or filters ([create a ticket!](https://github.com/flosch/pongo2/issues/new)). If possible, please use [playground](https://www.florian-schlachter.de/pongo2/) to create a short test case on what's wrong and include the link to the snippet in your issue.
**New**: [Try pongo2 out in the pongo2 playground.](https://www.florian-schlachter.de/pongo2/)
## First impression of a template
```HTML+Django
<html><head><title>Our admins and users</title></head>
{# This is a short example to give you a quick overview of pongo2's syntax. #}
{% macro user_details(user, is_admin=false) %}
<div class="user_item">
<!-- Let's indicate a user's good karma -->
<h2 {% if (user.karma >= 40) || (user.karma > calc_avg_karma(userlist)+5) %}
class="karma-good"{% endif %}>
<!-- This will call user.String() automatically if available: -->
{{ user }}
</h2>
<!-- Will print a human-readable time duration like "3 weeks ago" -->
<p>This user registered {{ user.register_date|naturaltime }}.</p>
<!-- Let's allow the users to write down their biography using markdown;
we will only show the first 15 words as a preview -->
<p>The user's biography:</p>
<p>{{ user.biography|markdown|truncatewords_html:15 }}
<a href="/user/{{ user.id }}/">read more</a></p>
{% if is_admin %}<p>This user is an admin!</p>{% endif %}
</div>
{% endmacro %}
<body>
<!-- Make use of the macro defined above to avoid repetitive HTML code
since we want to use the same code for admins AND members -->
<h1>Our admins</h1>
{% for admin in adminlist %}
{{ user_details(admin, true) }}
{% endfor %}
<h1>Our members</h1>
{% for user in userlist %}
{{ user_details(user) }}
{% endfor %}
</body>
</html>
```
## Development status
**Latest stable release**: v3.0 (`go get -u gopkg.in/flosch/pongo2.v3` / [`v3`](https://github.com/flosch/pongo2/tree/v3)-branch) [[read the announcement](https://www.florian-schlachter.de/post/pongo2-v3/)]
**Current development**: v4 (`master`-branch)
*Note*: With the release of pongo v4 the branch v2 will be deprecated.
**Deprecated versions** (not supported anymore): v1
| Topic | Status |
| ------------------------------------ | -------------------------------------------------------------------------------------- |
| Django version compatibility: | [1.7](https://docs.djangoproject.com/en/1.7/ref/templates/builtins/) |
| *Missing* (planned) **filters**: | none ([hints](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3)) |
| *Missing* (planned) **tags**: | none ([hints](https://github.com/flosch/pongo2/blob/master/tags.go#L3)) |
Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveats) and on the [official add-ons](https://github.com/flosch/pongo2#official).
## Features (and new in pongo2)
* Entirely rewritten from the ground-up.
* [Advanced C-like expressions](https://github.com/flosch/pongo2/blob/master/template_tests/expressions.tpl).
* [Complex function calls within expressions](https://github.com/flosch/pongo2/blob/master/template_tests/function_calls_wrapper.tpl).
* [Easy API to create new filters and tags](http://godoc.org/github.com/flosch/pongo2#RegisterFilter) ([including parsing arguments](http://godoc.org/github.com/flosch/pongo2#Parser))
* Additional features:
* Macros including importing macros from other files (see [template_tests/macro.tpl](https://github.com/flosch/pongo2/blob/master/template_tests/macro.tpl))
* [Template sandboxing](https://godoc.org/github.com/flosch/pongo2#TemplateSet) ([directory patterns](http://golang.org/pkg/path/filepath/#Match), banned tags/filters)
## Recent API changes within pongo2
If you're using the `master`-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here.
* Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`).
* `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface.
* Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString).
* `Template.ExecuteRW()` is now [`Template.ExecuteWriter()`](https://godoc.org/github.com/flosch/pongo2#Template.ExecuteWriter)
* `Template.Execute*()` functions do now take a `pongo2.Context` directly (no pointer anymore).
## How you can help
* Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags](https://github.com/flosch/pongo2/blob/master/tags.go#L4) (see [tutorial](https://www.florian-schlachter.de/post/pongo2/)) by forking pongo2 and sending pull requests
* Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out`)
* Write/improve template tests (see the `template_tests/` directory)
* Write middleware, libraries and websites using pongo2. :-)
# Documentation
For a documentation on how the templating language works you can [head over to the Django documentation](https://docs.djangoproject.com/en/dev/topics/templates/). pongo2 aims to be compatible with it.
You can access pongo2's API documentation on [godoc](https://godoc.org/github.com/flosch/pongo2).
## Blog post series
* [pongo2 v2 released](https://www.florian-schlachter.de/post/pongo2-v2/)
* [pongo2 1.0 released](https://www.florian-schlachter.de/post/pongo2-10/) [August 8th 2014]
* [pongo2 playground](https://www.florian-schlachter.de/post/pongo2-playground/) [August 1st 2014]
* [Release of pongo2 1.0-rc1 + pongo2-addons](https://www.florian-schlachter.de/post/pongo2-10-rc1/) [July 30th 2014]
* [Introduction to pongo2 + migration- and "how to write tags/filters"-tutorial.](https://www.florian-schlachter.de/post/pongo2/) [June 29th 2014]
## Caveats
### Filters
* **date** / **time**: The `date` and `time` filter are taking the Golang specific time- and date-format (not Django's one) currently. [Take a look on the format here](http://golang.org/pkg/time/#Time.Format).
* **stringformat**: `stringformat` does **not** take Python's string format syntax as a parameter, instead it takes Go's. Essentially `{{ 3.14|stringformat:"pi is %.2f" }}` is `fmt.Sprintf("pi is %.2f", 3.14)`.
* **escape** / **force_escape**: Unlike Django's behaviour, the `escape`-filter is applied immediately. Therefore there is no need for a `force_escape`-filter yet.
### Tags
* **for**: All the `forloop` fields (like `forloop.counter`) are written with a capital letter at the beginning. For example, the `counter` can be accessed by `forloop.Counter` and the parentloop by `forloop.Parentloop`.
* **now**: takes Go's time format (see **date** and **time**-filter).
### Misc
* **not in-operator**: You can check whether a map/struct/string contains a key/field/substring by using the in-operator (or the negation of it):
`{% if key in map %}Key is in map{% else %}Key not in map{% endif %}` or `{% if !(key in map) %}Key is NOT in map{% else %}Key is in map{% endif %}`.
# Add-ons, libraries and helpers
## Official
* [ponginae](https://github.com/flosch/ponginae) - A web-framework for Go (using pongo2).
* [pongo2-tools](https://github.com/flosch/pongo2-tools) - Official tools and helpers for pongo2
* [pongo2-addons](https://github.com/flosch/pongo2-addons) - Official additional filters/tags for pongo2 (for example a **markdown**-filter). They are in their own repository because they're relying on 3rd-party-libraries.
## 3rd-party
* [beego-pongo2](https://github.com/oal/beego-pongo2) - A tiny little helper for using Pongo2 with [Beego](https://github.com/astaxie/beego).
* [beego-pongo2.v2](https://github.com/ipfans/beego-pongo2.v2) - Same as `beego-pongo2`, but for pongo2 v2.
* [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework.
* [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates
* [pongo2-trans](https://github.com/fromYukki/pongo2trans) - `trans`-tag implementation for internationalization
Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
# API-usage examples
Please see the documentation for a full list of provided API methods.
## A tiny example (template string)
```Go
// Compile the template first (i. e. creating the AST)
tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
if err != nil {
panic(err)
}
// Now you can render the template with the given
// pongo2.Context how often you want to.
out, err := tpl.Execute(pongo2.Context{"name": "florian"})
if err != nil {
panic(err)
}
fmt.Println(out) // Output: Hello Florian!
```
## Example server-usage (template file)
```Go
package main
import (
"github.com/flosch/pongo2"
"net/http"
)
// Pre-compiling the templates at application startup using the
// little Must()-helper function (Must() will panic if FromFile()
// or FromString() will return with an error - that's it).
// It's faster to pre-compile it anywhere at startup and only
// execute the template later.
var tplExample = pongo2.Must(pongo2.FromFile("example.html"))
func examplePage(w http.ResponseWriter, r *http.Request) {
// Execute the template per HTTP request
err := tplExample.ExecuteWriter(pongo2.Context{"query": r.FormValue("query")}, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/", examplePage)
http.ListenAndServe(":8080", nil)
}
```
# Benchmark
The benchmarks have been run on the my machine (`Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz`) using the command:
go test -bench . -cpu 1,2,4,8
All benchmarks are compiling (depends on the benchmark) and executing the `template_tests/complex.tpl` template.
The results are:
BenchmarkExecuteComplexWithSandboxActive 50000 60450 ns/op
BenchmarkExecuteComplexWithSandboxActive-2 50000 56998 ns/op
BenchmarkExecuteComplexWithSandboxActive-4 50000 60343 ns/op
BenchmarkExecuteComplexWithSandboxActive-8 50000 64229 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive 10000 164410 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-2 10000 156682 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-4 10000 164821 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-8 10000 171806 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive 50000 60428 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-2 50000 31887 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-4 100000 22810 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-8 100000 18820 ns/op
BenchmarkExecuteComplexWithoutSandbox 50000 56942 ns/op
BenchmarkExecuteComplexWithoutSandbox-2 50000 56168 ns/op
BenchmarkExecuteComplexWithoutSandbox-4 50000 57838 ns/op
BenchmarkExecuteComplexWithoutSandbox-8 50000 60539 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox 10000 162086 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-2 10000 159771 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-4 10000 163826 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-8 10000 169062 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox 50000 57152 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-2 50000 30276 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-4 100000 22065 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-8 100000 18034 ns/op
Benchmarked on October 2nd 2014.

View file

@ -1 +0,0 @@
(Stub, TBA)

View file

@ -1,68 +0,0 @@
TODO:
* What are filters?
* List+explain all existing filters (pongo2 + pongo2-addons)
Implemented filters so far which needs documentation:
* escape
* safe
* escapejs
* add
* addslashes
* capfirst
* center
* cut
* date
* default
* default_if_none
* divisibleby
* first
* floatformat
* get_digit
* iriencode
* join
* last
* length
* length_is
* linebreaks
* linebreaksbr
* linenumbers
* ljust
* lower
* make_list
* phone2numeric
* pluralize
* random
* removetags
* rjust
* slice
* stringformat
* striptags
* time
* title
* truncatechars
* truncatechars_html
* truncatewords
* truncatewords_html
* upper
* urlencode
* urlize
* urlizetrunc
* wordcount
* wordwrap
* yesno
* filesizeformat*
* slugify*
* truncatesentences*
* truncatesentences_html*
* markdown*
* intcomma*
* ordinal*
* naturalday*
* timesince*
* timeuntil*
* naturaltime*
Filters marked with * are available through [pongo2-addons](https://github.com/flosch/pongo2-addons).

View file

@ -1 +0,0 @@
(Stub, TBA)

View file

@ -1 +0,0 @@
(Stub, TBA)

View file

@ -1,31 +0,0 @@
TODO:
* What are tags?
* List+explain all existing tags (pongo2 + pongo2-addons)
Implemented tags so far which needs documentation:
* autoescape
* block
* comment
* cycle
* extends
* filter
* firstof
* for
* if
* ifchanged
* ifequal
* ifnotequal
* import
* include
* lorem
* macro
* now
* set
* spaceless
* ssi
* templatetag
* verbatim
* widthratio
* with

View file

@ -1 +0,0 @@
(Stub, TBA)

View file

View file

@ -1,20 +0,0 @@
package pongo2
import (
"testing"
. "gopkg.in/check.v1"
)
// Hook up gocheck into the "go test" runner.
func TestIssues(t *testing.T) { TestingT(t) }
type IssueTestSuite struct{}
var _ = Suite(&IssueTestSuite{})
func (s *TestSuite) TestIssues(c *C) {
// Add a test for any issue
c.Check(42, Equals, 42)
}

View file

@ -1,539 +0,0 @@
package pongo2
import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
)
var admin_list = []string{"user2"}
var time1 = time.Date(2014, 06, 10, 15, 30, 15, 0, time.UTC)
var time2 = time.Date(2011, 03, 21, 8, 37, 56, 12, time.UTC)
type post struct {
Text string
Created time.Time
}
type user struct {
Name string
Validated bool
}
type comment struct {
Author *user
Date time.Time
Text string
}
func is_admin(u *user) bool {
for _, a := range admin_list {
if a == u.Name {
return true
}
}
return false
}
func (u *user) Is_admin() *Value {
return AsValue(is_admin(u))
}
func (u *user) Is_admin2() bool {
return is_admin(u)
}
func (p *post) String() string {
return ":-)"
}
/*
* Start setup sandbox
*/
type tagSandboxDemoTag struct {
}
func (node *tagSandboxDemoTag) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
buffer.WriteString("hello")
return nil
}
func tagSandboxDemoTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
return &tagSandboxDemoTag{}, nil
}
func BannedFilterFn(in *Value, params *Value) (*Value, *Error) {
return in, nil
}
func init() {
DefaultSet.Debug = true
RegisterFilter("banned_filter", BannedFilterFn)
RegisterFilter("unbanned_filter", BannedFilterFn)
RegisterTag("banned_tag", tagSandboxDemoTagParser)
RegisterTag("unbanned_tag", tagSandboxDemoTagParser)
DefaultSet.BanFilter("banned_filter")
DefaultSet.BanTag("banned_tag")
// Allow different kind of levels inside template_tests/
abs_path, err := filepath.Abs("./template_tests/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
abs_path, err = filepath.Abs("./template_tests/*/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
abs_path, err = filepath.Abs("./template_tests/*/*/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
// Allow pongo2 temp files
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, "/tmp/pongo2_*")
f, err := ioutil.TempFile("/tmp/", "pongo2_")
if err != nil {
panic("cannot write to /tmp/")
}
f.Write([]byte("Hello from pongo2"))
DefaultSet.Globals["temp_file"] = f.Name()
}
/*
* End setup sandbox
*/
var tplContext = Context{
"number": 11,
"simple": map[string]interface{}{
"number": 42,
"name": "john doe",
"included_file": "INCLUDES.helper",
"nil": nil,
"uint": uint(8),
"float": float64(3.1415),
"str": "string",
"chinese_hello_world": "你好世界",
"bool_true": true,
"bool_false": false,
"newline_text": `this is a text
with a new line in it`,
"long_text": `This is a simple text.
This too, as a paragraph.
Right?
Yep!`,
"escape_js_test": `escape sequences \r\n\'\" special chars "?!=$<>`,
"one_item_list": []int{99},
"multiple_item_list": []int{1, 1, 2, 3, 5, 8, 13, 21, 34, 55},
"misc_list": []interface{}{"Hello", 99, 3.14, "good"},
"escape_text": "This is \\a Test. \"Yep\". 'Yep'.",
"xss": "<script>alert(\"uh oh\");</script>",
"intmap": map[int]string{
1: "one",
2: "two",
5: "five",
},
"func_add": func(a, b int) int {
return a + b
},
"func_add_iface": func(a, b interface{}) interface{} {
return a.(int) + b.(int)
},
"func_variadic": func(msg string, args ...interface{}) string {
return fmt.Sprintf(msg, args...)
},
"func_variadic_sum_int": func(args ...int) int {
// Create a sum
s := 0
for _, i := range args {
s += i
}
return s
},
"func_variadic_sum_int2": func(args ...*Value) *Value {
// Create a sum
s := 0
for _, i := range args {
s += i.Integer()
}
return AsValue(s)
},
},
"complex": map[string]interface{}{
"is_admin": is_admin,
"post": post{
Text: "<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>",
Created: time2,
},
"comments": []*comment{
&comment{
Author: &user{
Name: "user1",
Validated: true,
},
Date: time1,
Text: "\"pongo2 is nice!\"",
},
&comment{
Author: &user{
Name: "user2",
Validated: true,
},
Date: time2,
Text: "comment2 with <script>unsafe</script> tags in it",
},
&comment{
Author: &user{
Name: "user3",
Validated: false,
},
Date: time1,
Text: "<b>hello!</b> there",
},
},
"comments2": []*comment{
&comment{
Author: &user{
Name: "user1",
Validated: true,
},
Date: time2,
Text: "\"pongo2 is nice!\"",
},
&comment{
Author: &user{
Name: "user1",
Validated: true,
},
Date: time1,
Text: "comment2 with <script>unsafe</script> tags in it",
},
&comment{
Author: &user{
Name: "user3",
Validated: false,
},
Date: time1,
Text: "<b>hello!</b> there",
},
},
},
}
func TestTemplates(t *testing.T) {
debug = true
// Add a global to the default set
Globals["this_is_a_global_variable"] = "this is a global text"
matches, err := filepath.Glob("./template_tests/*.tpl")
if err != nil {
t.Fatal(err)
}
for idx, match := range matches {
t.Logf("[Template %3d] Testing '%s'", idx+1, match)
tpl, err := FromFile(match)
if err != nil {
t.Fatalf("Error on FromFile('%s'): %s", match, err.Error())
}
test_filename := fmt.Sprintf("%s.out", match)
test_out, rerr := ioutil.ReadFile(test_filename)
if rerr != nil {
t.Fatalf("Error on ReadFile('%s'): %s", test_filename, rerr.Error())
}
tpl_out, err := tpl.ExecuteBytes(tplContext)
if err != nil {
t.Fatalf("Error on Execute('%s'): %s", match, err.Error())
}
if bytes.Compare(test_out, tpl_out) != 0 {
t.Logf("Template (rendered) '%s': '%s'", match, tpl_out)
err_filename := filepath.Base(fmt.Sprintf("%s.error", match))
err := ioutil.WriteFile(err_filename, []byte(tpl_out), 0600)
if err != nil {
t.Fatalf(err.Error())
}
t.Logf("get a complete diff with command: 'diff -ya %s %s'", test_filename, err_filename)
t.Errorf("Failed: test_out != tpl_out for %s", match)
}
}
}
func TestExecutionErrors(t *testing.T) {
debug = true
matches, err := filepath.Glob("./template_tests/*-execution.err")
if err != nil {
t.Fatal(err)
}
for idx, match := range matches {
t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
test_data, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n")
check_filename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename)
if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", check_filename, err.Error())
}
checks := strings.Split(string(check_data), "\n")
if len(checks) != len(tests) {
t.Fatal("Template lines != Checks lines")
}
for idx, test := range tests {
if strings.TrimSpace(test) == "" {
continue
}
if strings.TrimSpace(checks[idx]) == "" {
t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).",
match, idx+1)
}
tpl, err := FromString(test)
if err != nil {
t.Fatalf("Error on FromString('%s'): %s", test, err.Error())
}
_, err = tpl.ExecuteBytes(tplContext)
if err == nil {
t.Fatalf("[%s Line %d] Expected error for (got none): %s",
match, idx+1, tests[idx])
}
re := regexp.MustCompile(fmt.Sprintf("^%s$", checks[idx]))
if !re.MatchString(err.Error()) {
t.Fatalf("[%s Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s",
match, idx+1, test, err.Error(), checks[idx])
}
}
}
}
func TestCompilationErrors(t *testing.T) {
debug = true
matches, err := filepath.Glob("./template_tests/*-compilation.err")
if err != nil {
t.Fatal(err)
}
for idx, match := range matches {
t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
test_data, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n")
check_filename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename)
if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", check_filename, err.Error())
}
checks := strings.Split(string(check_data), "\n")
if len(checks) != len(tests) {
t.Fatal("Template lines != Checks lines")
}
for idx, test := range tests {
if strings.TrimSpace(test) == "" {
continue
}
if strings.TrimSpace(checks[idx]) == "" {
t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).",
match, idx+1)
}
_, err = FromString(test)
if err == nil {
t.Fatalf("[%s | Line %d] Expected error for (got none): %s", match, idx+1, tests[idx])
}
re := regexp.MustCompile(fmt.Sprintf("^%s$", checks[idx]))
if !re.MatchString(err.Error()) {
t.Fatalf("[%s | Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s",
match, idx+1, test, err.Error(), checks[idx])
}
}
}
}
func TestBaseDirectory(t *testing.T) {
mustStr := "Hello from template_tests/base_dir_test/"
s := NewSet("test set with base directory")
s.Globals["base_directory"] = "template_tests/base_dir_test/"
if err := s.SetBaseDirectory(s.Globals["base_directory"].(string)); err != nil {
t.Fatal(err)
}
matches, err := filepath.Glob("./template_tests/base_dir_test/subdir/*")
if err != nil {
t.Fatal(err)
}
for _, match := range matches {
match = strings.Replace(match, "template_tests/base_dir_test/", "", -1)
tpl, err := s.FromFile(match)
if err != nil {
t.Fatal(err)
}
out, err := tpl.Execute(nil)
if err != nil {
t.Fatal(err)
}
if out != mustStr {
t.Errorf("%s: out ('%s') != mustStr ('%s')", match, out, mustStr)
}
}
}
func BenchmarkCache(b *testing.B) {
cache_set := NewSet("cache set")
for i := 0; i < b.N; i++ {
tpl, err := cache_set.FromCache("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCacheDebugOn(b *testing.B) {
cache_debug_set := NewSet("cache set")
cache_debug_set.Debug = true
for i := 0; i < b.N; i++ {
tpl, err := cache_debug_set.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCompileAndExecuteComplexWithSandboxActive(b *testing.B) {
buf, err := ioutil.ReadFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
preloadedTpl := string(buf)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tpl, err := FromString(preloadedTpl)
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkExecuteComplexWithoutSandbox(b *testing.B) {
s := NewSet("set without sandbox")
tpl, err := s.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
buf, err := ioutil.ReadFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
preloadedTpl := string(buf)
s := NewSet("set without sandbox")
b.ResetTimer()
for i := 0; i < b.N; i++ {
tpl, err := s.FromString(preloadedTpl)
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) {
s := NewSet("set without sandbox")
tpl, err := s.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
})
}

View file

@ -1,66 +0,0 @@
package pongo2
import (
"testing"
. "gopkg.in/check.v1"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct {
tpl *Template
}
var (
_ = Suite(&TestSuite{})
test_suite2 = NewSet("test suite 2")
)
func parseTemplate(s string, c Context) string {
t, err := test_suite2.FromString(s)
if err != nil {
panic(err)
}
out, err := t.Execute(c)
if err != nil {
panic(err)
}
return out
}
func parseTemplateFn(s string, c Context) func() {
return func() {
parseTemplate(s, c)
}
}
func (s *TestSuite) TestMisc(c *C) {
// Must
// TODO: Add better error message (see issue #18)
c.Check(func() { Must(test_suite2.FromFile("template_tests/inheritance/base2.tpl")) },
PanicMatches,
`\[Error \(where: fromfile\) in template_tests/inheritance/doesnotexist.tpl | Line 1 Col 12 near 'doesnotexist.tpl'\] open template_tests/inheritance/doesnotexist.tpl: no such file or directory`)
// Context
c.Check(parseTemplateFn("", Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*")
// Registers
c.Check(func() { RegisterFilter("escape", nil) }, PanicMatches, ".*is already registered.*")
c.Check(func() { RegisterTag("for", nil) }, PanicMatches, ".*is already registered.*")
// ApplyFilter
v, err := ApplyFilter("title", AsValue("this is a title"), nil)
if err != nil {
c.Fatal(err)
}
c.Check(v.String(), Equals, "This Is A Title")
c.Check(func() {
_, err := ApplyFilter("doesnotexist", nil, nil)
if err != nil {
panic(err)
}
}, PanicMatches, `\[Error \(where: applyfilter\)\] Filter with name 'doesnotexist' not found.`)
}

View file

@ -1,10 +0,0 @@
{{ "<script>alert('xss');</script>" }}
{% autoescape off %}
{{ "<script>alert('xss');</script>" }}
{% endautoescape %}
{% autoescape on %}
{{ "<script>alert('xss');</script>" }}
{% endautoescape %}
{% autoescape off %}
{{ "<script>alert('xss');</script>"|escape }}
{% endautoescape %}

View file

@ -1,9 +0,0 @@
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;
<script>alert('xss');</script>
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;

View file

@ -1 +0,0 @@
Hello from {{ base_directory }}

View file

@ -1 +0,0 @@
{% include "base.html" %}

View file

@ -1 +0,0 @@
{% extends "base.html" %}

View file

@ -1 +0,0 @@
{% ssi "base.html" parsed %}

View file

@ -1,32 +0,0 @@
{# A more complex template using pongo2 (fully django-compatible template) #}
<!DOCTYPE html>
<html>
<head>
<title>My blog page</title>
</head>
<body>
<h1>Blogpost</h1>
<div id="content">
{{ complex.post.Text|safe }}
</div>
<h1>Comments</h1>
{% for comment in complex.comments %}
<h2>{{ forloop.Counter }}. Comment ({{ forloop.Revcounter}} comment{{ forloop.Revcounter|pluralize:"s" }} left)</h2>
<p>From: {{ comment.Author.Name }} ({{ comment.Author.Validated|yesno:"validated,not validated,unknown validation status" }})</p>
{% if complex.is_admin(comment.Author) %}
<p>This user is an admin (verify: {{ comment.Author.Is_admin }})!</p>
{% else %}
<p>This user is not admin!</p>
{% endif %}
<p>Written {{ comment.Date }}</p>
<p>{{ comment.Text|striptags }}</p>
{% endfor %}
</body>
</html>

View file

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>My blog page</title>
</head>
<body>
<h1>Blogpost</h1>
<div id="content">
<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>
</div>
<h1>Comments</h1>
<h2>1. Comment (3 comments left)</h2>
<p>From: user1 (validated)</p>
<p>This user is not admin!</p>
<p>Written 2014-06-10 15:30:15 +0000 UTC</p>
<p>&quot;pongo2 is nice!&quot;</p>
<h2>2. Comment (2 comments left)</h2>
<p>From: user2 (validated)</p>
<p>This user is an admin (verify: True)!</p>
<p>Written 2011-03-21 08:37:56.000000012 +0000 UTC</p>
<p>comment2 with unsafe tags in it</p>
<h2>3. Comment (1 comment left)</h2>
<p>From: user3 (not validated)</p>
<p>This user is not admin!</p>
<p>Written 2014-06-10 15:30:15 +0000 UTC</p>
<p>hello! there</p>
</body>
</html>

View file

@ -1,22 +0,0 @@
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number %}'
{% endfor %}
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number as cycleitem %}'
May I present the cycle item again: '{{ cycleitem }}'
{% endfor %}
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
May I present the cycle item: '{{ cycleitem }}'
{% endfor %}
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
May I present the cycle item: '{{ cycleitem }}'
{% include "inheritance/cycle_include.tpl" %}
{% endfor %}
'{% cycle "item1" simple.name simple.number as cycleitem %}'
'{% cycle cycleitem %}'
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
'{{ cycleitem }}'
'{% cycle cycleitem %}'
'{{ cycleitem }}'

View file

@ -1,130 +0,0 @@
'item1'
'john doe'
'42'
'item1'
'john doe'
'42'
'item1'
'john doe'
'42'
'item1'
'item1'
May I present the cycle item again: 'item1'
'john doe'
May I present the cycle item again: 'john doe'
'42'
May I present the cycle item again: '42'
'item1'
May I present the cycle item again: 'item1'
'john doe'
May I present the cycle item again: 'john doe'
'42'
May I present the cycle item again: '42'
'item1'
May I present the cycle item again: 'item1'
'john doe'
May I present the cycle item again: 'john doe'
'42'
May I present the cycle item again: '42'
'item1'
May I present the cycle item again: 'item1'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'john doe'
''
May I present the cycle item: '42'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'john doe'
''
May I present the cycle item: '42'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'john doe'
''
May I present the cycle item: '42'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'item1'
Included 'item1'.
''
May I present the cycle item: 'john doe'
Included 'john doe'.
''
May I present the cycle item: '42'
Included '42'.
''
May I present the cycle item: 'item1'
Included 'item1'.
''
May I present the cycle item: 'john doe'
Included 'john doe'.
''
May I present the cycle item: '42'
Included '42'.
''
May I present the cycle item: 'item1'
Included 'item1'.
''
May I present the cycle item: 'john doe'
Included 'john doe'.
''
May I present the cycle item: '42'
Included '42'.
''
May I present the cycle item: 'item1'
Included 'item1'.
'item1'
'john doe'
''
'item1'
''
'john doe'

View file

@ -1,53 +0,0 @@
integers and complex expressions
{{ 10-100 }}
{{ -(10-100) }}
{{ -(-(10-100)) }}
{{ -1 * (-(-(10-100))) }}
{{ -1 * (-(-(10-100)) ^ 2) ^ 3 + 3 * (5 - 17) + 1 + 2 }}
floats
{{ 5.5 }}
{{ 5.172841 }}
{{ 5.5 - 1.5 == 4 }}
{{ 5.5 - 1.5 == 4.0 }}
mul/div
{{ 2 * 5 }}
{{ 2 * 5.0 }}
{{ 2 * 0 }}
{{ 2.5 * 5.3 }}
{{ 1/2 }}
{{ 1/2.0 }}
{{ 1/0.000001 }}
logic expressions
{{ !true }}
{{ !(true || false) }}
{{ true || false }}
{{ true or false }}
{{ false or false }}
{{ false || false }}
{{ true && (true && (true && (true && (1 == 1 || false)))) }}
float comparison
{{ 5.5 <= 5.5 }}
{{ 5.5 < 5.5 }}
{{ 5.5 > 5.5 }}
{{ 5.5 >= 5.5 }}
remainders
{{ (simple.number+7)%7 }}
{{ (simple.number+7)%7 == 0 }}
{{ (simple.number+7)%6 }}
in/not in
{{ 5 in simple.intmap }}
{{ 2 in simple.intmap }}
{{ 7 in simple.intmap }}
{{ !(5 in simple.intmap) }}
{{ not(7 in simple.intmap) }}
issue #48 (associativity for infix operators)
{{ 34/3*3 }}
{{ 10 + 24 / 6 / 2 }}
{{ 6 - 4 - 2 }}

View file

@ -1,53 +0,0 @@
integers and complex expressions
-90
90
-90
90
531440999967.000000
floats
5.500000
5.172841
False
True
mul/div
10
10.000000
0
13.250000
0
0.500000
1000000.000000
logic expressions
False
False
True
True
False
False
True
float comparison
True
False
False
True
remainders
0
True
1
in/not in
True
True
False
False
True
issue #48 (associativity for infix operators)
33
12
0

View file

@ -1,3 +0,0 @@
{% extends "inheritance/base.tpl" %}
{% block content %}Extends' content{% endblock %}

View file

@ -1 +0,0 @@
Start#This is base's bodyExtends' content#End

View file

@ -1,5 +0,0 @@
{{ }}
{{ (1 - 1 }}
{{ 1|float: }}
{{ "test"|non_existent_filter }}
{{ "test"|"test" }}

View file

@ -1,5 +0,0 @@
.*Expected either a number, string, keyword or identifier\.
.*Closing bracket expected after expression
.*Filter parameter required after ':'.*
.*Filter 'non_existent_filter' does not exist\.
.*Filter name must be an identifier\.

View file

@ -1,3 +0,0 @@
{{ -(true || false) }}
{{ simple.func_add("test", 5) }}
{{ simple.func_variadic_sum_int("foo") }}

View file

@ -1,3 +0,0 @@
.*where: execution.*Negative sign on a non\-number expression
.*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\).
.*Function variadic input argument of 'simple.func_variadic_sum_int' must be of type int or \*pongo2.Value \(not string\).

View file

@ -1,304 +0,0 @@
add
{{ 5|add:2 }}
{{ 5|add:simple.number }}
{{ 5|add:nothing }}
{{ 5|add:"test" }}
{{ "hello "|add:"john doe" }}
{{ "hello "|add:simple.name }}
addslashes
{{ "plain text"|addslashes|safe }}
{{ simple.escape_text|addslashes|safe }}
capfirst
{{ ""|capfirst }}
{{ 5|capfirst }}
{{ "h"|capfirst }}
{{ "hello there!"|capfirst }}
{{ simple.chinese_hello_world|capfirst }}
cut
{{ 15|cut:"5" }}
{{ "Hello world"|cut: " " }}
default
{{ simple.nothing|default:"n/a" }}
{{ nothing|default:simple.number }}
{{ simple.number|default:"n/a" }}
{{ 5|default:"n/a" }}
default_if_none
{{ simple.nothing|default_if_none:"n/a" }}
{{ ""|default_if_none:"n/a" }}
{{ nil|default_if_none:"n/a" }}
get_digit
{{ 1234567890|get_digit:0 }}
{{ 1234567890|get_digit }}
{{ 1234567890|get_digit:2 }}
{{ 1234567890|get_digit:"4" }}
{{ 1234567890|get_digit:10 }}
{{ 1234567890|get_digit:15 }}
safe
{{ "<script>" }}
{{ "<script>"|safe }}
escape
{{ "<script>"|safe|escape }}
title
{{ ""|title }}
{{ 5|title }}
{{ "h"|title }}
{{ "hello there!"|title }}
{{ "HELLO THERE!"|title }}
{{ "hELLO tHERE!"|title }}
{{ simple.chinese_hello_world|title }}
truncatechars
{{ "Joel is a slug"|truncatechars:9 }}
{{ "Joel is a slug"|truncatechars:13 }}
{{ "Joel is a slug"|truncatechars:14 }}
{{ simple.chinese_hello_world|truncatechars:1 }}
{{ simple.chinese_hello_world|truncatechars:2 }}
divisibleby
{{ 21|divisibleby:3 }}
{{ 21|divisibleby:"3" }}
{{ 21|float|divisibleby:"3" }}
{{ 22|divisibleby:"3" }}
{{ 85|divisibleby:simple.number }}
{{ 84|divisibleby:simple.number }}
striptags
{{ "<strong><i>Hello!</i></strong>"|striptags|safe }}
removetags
{{ "<strong><i>Hello!</i></strong>"|removetags:"i"|safe }}
yesno
{{ simple.bool_true|yesno }}
{{ simple.bool_false|yesno }}
{{ simple.nil|yesno }}
{{ simple.nothing|yesno }}
{{ simple.bool_true|yesno:"ja,nein,vielleicht" }}
{{ simple.bool_false|yesno:"ja,nein,vielleicht" }}
{{ simple.nothing|yesno:"ja,nein" }}
pluralize
customer{{ 0|pluralize }}
customer{{ 1|pluralize }}
customer{{ 2|pluralize }}
cherr{{ 0|pluralize:"y,ies" }}
cherr{{ 1|pluralize:"y,ies" }}
cherr{{ 2|pluralize:"y,ies" }}
walrus{{ 0|pluralize:"es" }}
walrus{{ 1|pluralize:"es" }}
walrus{{ simple.number|pluralize:"es" }}
random
{{ 5|random }}
{{ ""|random }}
{{ "h"|random }}
{{ simple.one_item_list|random }}
first
{{ "Test"|first }}
{{ complex.comments|first }}
{{ 5|first }}
{{ true|first }}
{{ nothing|first }}
{{ simple.chinese_hello_world|first }}
last
{{ "Test"|last }}
{{ complex.comments|last }}
{{ 5|last }}
{{ true|last }}
{{ nothing|last }}
{{ simple.chinese_hello_world|last }}
urlencode
{{ "http://www.example.org/foo?a=b&c=d"|urlencode }}
linebreaksbr
{{ simple.newline_text|linebreaksbr }}
{{ ""|linebreaksbr }}
{{ "hallo"|linebreaksbr }}
length_is
{{ simple.name|length_is:8 }}
{{ simple.name|length_is:10 }}
{{ simple.name|length_is:"8" }}
{{ simple.name|length_is:"10" }}
{{ 5|length_is:1 }}
{{ simple.chinese_hello_world|length_is:4 }}
{{ simple.chinese_hello_world|length_is:3 }}
{{ simple.chinese_hello_world|length_is:5 }}
integer
{{ "foobar"|integer }}
{{ nothing|integer }}
{{ "5.4"|float|integer }}
{{ "5.5"|float|integer }}
{{ "5.6"|float|integer }}
{{ 6|float|integer }}
{{ -100|integer }}
float
{{ "foobar"|float }}
{{ nil|float }}
{{ "5.5"|float }}
{{ 5|float }}
{{ "5.6"|integer|float }}
{{ -100|float }}
{% if 5.5 == 5.500000 %}5.5 is 5.500000{% endif %}
{% if 5.5 != 5.500001 %}5.5 is not 5.500001{% endif %}
floatformat
{{ 34.23234|floatformat }}
{{ 34.00000|floatformat }}
{{ 34.26000|floatformat }}
{{ "34.23234"|floatformat }}
{{ "34.00000"|floatformat }}
{{ "34.26000"|floatformat }}
{{ 34.23234|floatformat:3 }}
{{ 34.00000|floatformat:3 }}
{{ 34.26000|floatformat:3 }}
{{ 34.23234|floatformat:"0" }}
{{ 34.00000|floatformat:"0" }}
{{ 39.56000|floatformat:"0" }}
{{ 34.23234|floatformat:"-3" }}
{{ 34.00000|floatformat:"-3" }}
{{ 34.26000|floatformat:"-3" }}
join
{{ simple.misc_list|join:", " }}
stringformat
{{ simple.float|stringformat:"%.2f" }}
{{ simple.uint|stringformat:"Test: %d" }}
{{ simple.chinese_hello_world|stringformat:"Chinese: %s" }}
make_list
{{ simple.name|make_list|join:", " }}
{% for char in simple.name|make_list %}{{ char }}{% endfor %}
center
'{{ "test"|center:3 }}'
'{{ "test"|center:19 }}'
'{{ "test"|center:20 }}'
{{ "test"|center:20|length }}
'{{ "test2"|center:19 }}'
'{{ "test2"|center:20 }}'
{{ "test2"|center:20|length }}
'{{ simple.chinese_hello_world|center:20 }}'
ljust
'{{ "test"|ljust:"2" }}'
'{{ "test"|ljust:"20" }}'
{{ "test"|ljust:"20"|length }}
'{{ simple.chinese_hello_world|ljust:10 }}'
rjust
'{{ "test"|rjust:"2" }}'
'{{ "test"|rjust:"20" }}'
{{ "test"|rjust:"20"|length }}
'{{ simple.chinese_hello_world|rjust:10 }}'
wordcount
{{ ""|wordcount }}
{% filter wordcount %}{% lorem 25 w %}{% endfilter %}
wordwrap
{{ ""|wordwrap:2 }}
{% filter wordwrap:5 %}{% lorem 26 w %}{% endfilter %}
iriencode
{{ "?foo=123&bar=yes"|iriencode }}
linebreaks
{{ ""|linebreaks|safe }}
{{ simple.newline_text|linebreaks|safe }}
{{ simple.long_text|linebreaks|safe }}
{{ simple.name|linebreaks|safe }}
linenumbers
{% filter linenumbers %}{% lorem 10 %}{% endfilter %}
phone2numeric
{{ "999-PONGO2"|phone2numeric }}
truncatewords
{% filter truncatewords:9 %}{% lorem 25 w %}{% endfilter %}
{% filter wordcount %}{% filter truncatewords:9 %}{% lorem 25 w %}{% endfilter %}{% endfilter %}
{{ simple.chinese_hello_world|truncatewords:0 }}
{{ simple.chinese_hello_world|truncatewords:1 }}
{{ simple.chinese_hello_world|truncatewords:2 }}
urlize
{{ "http://www.florian-schlachter.de"|urlize|safe }}
{{ "www.florian-schlachter.de"|urlize|safe }}
{{ "florian-schlachter.de"|urlize|safe }}
{% filter urlize:true|safe %}
Please mail me at demo@example.com or visit mit on:
- lorem ipsum github.com/flosch/pongo2 lorem ipsum
- lorem ipsum http://www.florian-schlachter.de lorem ipsum
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
- lorem ipsum www.florian-schlachter.de lorem ipsum
- lorem ipsum www.florian-schlachter.de/test="test" lorem ipsum
{% endfilter %}
--
{% filter urlize:false|safe %}
Please mail me at demo@example.com or visit mit on:
- lorem ipsum github.com/flosch/pongo2 lorem ipsum
- lorem ipsum http://www.florian-schlachter.de lorem ipsum
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
- lorem ipsum www.florian-schlachter.de lorem ipsum
- lorem ipsum www.florian-schlachter.de/test="test" lorem ipsum
{% endfilter %}
urlizetrunc
{% filter urlizetrunc:15|safe %}
Please mail me at demo@example.com or visit mit on:
- lorem ipsum github.com/flosch/pongo2 lorem ipsum
- lorem ipsum http://www.florian-schlachter.de lorem ipsum
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
- lorem ipsum www.florian-schlachter.de lorem ipsum
- lorem ipsum www.florian-schlachter.de/test="test" lorem ipsum
{% endfilter %}
escapejs
{{ simple.escape_js_test|escapejs|safe }}
slice
{{ simple.multiple_item_list|slice:":99"|join:"," }}
{{ simple.multiple_item_list|slice:"99:"|join:"," }}
{{ simple.multiple_item_list|slice:":3"|join:"," }}
{{ simple.multiple_item_list|slice:"3:5"|join:"," }}
{{ simple.multiple_item_list|slice:"2:"|join:"," }}
{{ simple.multiple_item_list|slice:"2:3"|join:"," }}
{{ simple.multiple_item_list|slice:"2:1"|join:"," }}
{{ "Test"|slice:"1:3" }}
{{ simple.chinese_hello_world|slice:"1:3" }}
truncatechars_html
{{ "This is a long test which will be cutted after some chars."|truncatechars_html:25 }}
{{ "<div class=\"foo\"><ul class=\"foo\"><li class=\"foo\"><p class=\"foo\">This is a long test which will be cutted after some chars.</p></li></ul></div>"|truncatechars_html:25 }}
{{ "<p class='test' id='foo'>This is a long test which will be cutted after some chars.</p>"|truncatechars_html:25 }}
{{ "<a name='link'><p>This </a>is a long test which will be cutted after some chars.</p>"|truncatechars_html:25 }}
{{ "<p>This </a>is a long test which will be cutted after some chars.</p>"|truncatechars_html:25 }}
{{ "<p>This is a long test which will be cutted after some chars.</p>"|truncatechars_html:7 }}
truncatewords_html
{{ "This is a long test which will be cutted after some words."|truncatewords_html:25|safe }}
{{ "<div class=\"foo\"><ul class=\"foo\"><li class=\"foo\"><p class=\"foo\">This is a long test which will be cutted after some chars.</p></li></ul></div>"|truncatewords_html:5 }}
{{ "<p>This. is. a. long test. Test test, test.</p>"|truncatewords_html:8 }}
{{ "<a name='link' href=\"https://....\"><p class=\"foo\">This </a>is a long test, which will be cutted after some words.</p>"|truncatewords_html:5 }}
{{ "<p>This </a>is a long test, which will be cutted after some words.</p>"|truncatewords_html:5 }}
{{ "<p>This is a long test which will be cutted after some words.</p>"|truncatewords_html:2 }}
{{ "<p>This is a long test which will be cutted after some words.</p>"|truncatewords_html:0 }}

View file

@ -1,318 +0,0 @@
add
7
47
5
5test
hello john doe
hello john doe
addslashes
plain text
This is \\a Test. \"Yep\". \'Yep\'.
capfirst
H
Hello there!
你好世界
cut
1
Helloworld
default
n/a
42
42
5
default_if_none
n/a
n/a
get_digit
1234567890
1234567890
9
7
1
1234567890
safe
&lt;script&gt;
<script>
escape
&lt;script&gt;
title
H
Hello There!
Hello There!
Hello There!
你好世界
truncatechars
Joel i...
Joel is a ...
Joel is a slug
你好
divisibleby
True
True
True
False
False
True
striptags
Hello!
removetags
<strong>Hello!</strong>
yesno
yes
no
maybe
maybe
ja
nein
maybe
pluralize
customers
customer
customers
cherries
cherry
cherries
walruses
walrus
walruses
random
5
h
99
first
T
<pongo2.comment Value>
last
t
<pongo2.comment Value>
urlencode
http%3A%2F%2Fwww.example.org%2Ffoo%3Fa%3Db%26c%3Dd
linebreaksbr
this is a text&lt;br /&gt;with a new line in it
hallo
length_is
True
False
True
False
False
True
False
False
integer
0
0
5
5
5
6
-100
float
0.000000
0.000000
5.500000
5.000000
5.000000
-100.000000
5.5 is 5.500000
5.5 is not 5.500001
floatformat
34.2
34
34.3
34.2
34
34.3
34.232
34.000
34.260
34
34
40
34.232
34
34.260
join
Hello, 99, 3.140000, good
stringformat
3.14
Test: 8
Chinese: 你好世界
make_list
j, o, h, n, , d, o, e
john doe
center
'test'
' test '
' test '
20
' test2 '
' test2 '
20
' 你好世界 '
ljust
'test'
'test '
20
'你好世界 '
rjust
'test'
' test'
20
' 你好世界'
wordcount
0
25
wordwrap
Lorem ipsum dolor sit amet,
consectetur adipisici elit, sed eiusmod
tempor incidunt ut labore et
dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud
exercitation
iriencode
?foo=123&amp;bar=yes
linebreaks
<p>this is a text<br />with a new line in it</p>
<p>This is a simple text.</p><p>This too, as a paragraph.<br />Right?</p><p>Yep!</p>
<p>john doe</p>
linenumbers
1. Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
2. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
3. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
4. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
5. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
6. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.
7. Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
8. Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
9. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
10. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
phone2numeric
999-766462
truncatewords
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed ...
10
你好世界
你好世界
urlize
<a href="http://www.florian-schlachter.de" rel="nofollow">http://www.florian-schlachter.de</a>
<a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-schlachter.de</a>
<a href="http://florian-schlachter.de" rel="nofollow">florian-schlachter.de</a>
Please mail me at <a href="mailto:demo@example.com">demo@example.com</a> or visit mit on:
- lorem ipsum <a href="http://github.com/flosch/pongo2" rel="nofollow">github.com/flosch/pongo2</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">http://www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de/test=%22test%22" rel="nofollow">www.florian-schlachter.de/test=&quot;test&quot;</a> lorem ipsum
--
Please mail me at <a href="mailto:demo@example.com">demo@example.com</a> or visit mit on:
- lorem ipsum <a href="http://github.com/flosch/pongo2" rel="nofollow">github.com/flosch/pongo2</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">http://www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-schlachter.de</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de/test=%22test%22" rel="nofollow">www.florian-schlachter.de/test="test"</a> lorem ipsum
urlizetrunc
Please mail me at <a href="mailto:demo@example.com">demo@example...</a> or visit mit on:
- lorem ipsum <a href="http://github.com/flosch/pongo2" rel="nofollow">github.com/f...</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">http://www.f...</a> lorem ipsum
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www....</a> lorem ipsum
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www....</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-...</a> lorem ipsum
- lorem ipsum <a href="http://www.florian-schlachter.de/test=%22test%22" rel="nofollow">www.florian-...</a> lorem ipsum
escapejs
escape sequences \u000D\u000A\u005C\u0027\u005C\u0022 special chars \u0022\u003F\u0021\u003D\u0024\u003C\u003E
slice
1,1,2,3,5,8,13,21,34,55
1,1,2
3,5
2,3,5,8,13,21,34,55
2
2,3,5,8,13,21,34,55
es
好世
truncatechars_html
This is a long test wh...
<div class="foo"><ul class="foo"><li class="foo"><p class="foo">This is a long test wh...</p></li></ul></div>
<p class='test' id='foo'>This is a long test wh...</p>
<a name='link'><p>This </a>is a long test wh...</p>
<p>This </a>is a long test wh...</p>
<p>This...</p>
truncatewords_html
This is a long test which will be cutted after some words.
<div class="foo"><ul class="foo"><li class="foo"><p class="foo">This is a long test ...</p></li></ul></div>
<p>This. is. a. long test. Test test, test....</p>
<a name='link' href="https://...."><p class="foo">This </a>is a long test,...</p>
<p>This </a>is a long test,...</p>
<p>This is ...</p>
...

View file

@ -1,7 +0,0 @@
{% firstof doesnotexist 42 %}
{% firstof doesnotexist "<script>alert('xss');</script>" %}
{% firstof doesnotexist "<script>alert('xss');</script>"|safe %}
{% firstof doesnotexist simple.uint 42 %}
{% firstof doesnotexist "test" simple.number 42 %}
{% firstof %}
{% firstof "test" "test2" %}

View file

@ -1,7 +0,0 @@
42
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;
<script>alert('xss');</script>
8
test
test

View file

@ -1,9 +0,0 @@
{% for comment in complex.comments %}[{{ forloop.Counter }} {{ forloop.Counter0 }} {{ forloop.First }} {{ forloop.Last }} {{ forloop.Revcounter }} {{ forloop.Revcounter0 }}] {{ comment.Author.Name }}
{# nested loop #}
{% for char in comment.Text %}{{forloop.Parentloop.Counter0}}.{{forloop.Counter0}}:{{ char|safe }} {% endfor %}
{% endfor %}
reversed
'{% for item in simple.multiple_item_list reversed %}{{ item }} {% endfor %}'

View file

@ -1,19 +0,0 @@
[1 0 True False 3 2] user1
0.0:" 0.1:p 0.2:o 0.3:n 0.4:g 0.5:o 0.6:2 0.7: 0.8:i 0.9:s 0.10: 0.11:n 0.12:i 0.13:c 0.14:e 0.15:! 0.16:"
[2 1 False False 2 1] user2
1.0:c 1.1:o 1.2:m 1.3:m 1.4:e 1.5:n 1.6:t 1.7:2 1.8: 1.9:w 1.10:i 1.11:t 1.12:h 1.13: 1.14:< 1.15:s 1.16:c 1.17:r 1.18:i 1.19:p 1.20:t 1.21:> 1.22:u 1.23:n 1.24:s 1.25:a 1.26:f 1.27:e 1.28:< 1.29:/ 1.30:s 1.31:c 1.32:r 1.33:i 1.34:p 1.35:t 1.36:> 1.37: 1.38:t 1.39:a 1.40:g 1.41:s 1.42: 1.43:i 1.44:n 1.45: 1.46:i 1.47:t
[3 2 False True 1 0] user3
2.0:< 2.1:b 2.2:> 2.3:h 2.4:e 2.5:l 2.6:l 2.7:o 2.8:! 2.9:< 2.10:/ 2.11:b 2.12:> 2.13: 2.14:t 2.15:h 2.16:e 2.17:r 2.18:e
reversed
'55 34 21 13 8 5 3 2 1 1 '

View file

@ -1,11 +0,0 @@
{{ simple.func_add(simple.func_add(5, 15), simple.number) + 17 }}
{{ simple.func_add_iface(simple.func_add_iface(5, 15), simple.number) + 17 }}
{{ simple.func_variadic("hello") }}
{{ simple.func_variadic("hello, %s", simple.name) }}
{{ simple.func_variadic("%d + %d %s %d", 5, simple.number, "is", 49) }}
{{ simple.func_variadic_sum_int() }}
{{ simple.func_variadic_sum_int(1) }}
{{ simple.func_variadic_sum_int(1, 19, 185) }}
{{ simple.func_variadic_sum_int2() }}
{{ simple.func_variadic_sum_int2(2) }}
{{ simple.func_variadic_sum_int2(1, 7, 100) }}

View file

@ -1,11 +0,0 @@
79
79
hello
hello, john doe
5 + 42 is 49
0
1
205
0
2
108

View file

@ -1,17 +0,0 @@
{% if nothing %}false{% else %}true{% endif %}
{% if simple %}simple != nil{% endif %}
{% if simple.uint %}uint != 0{% endif %}
{% if simple.float %}float != 0.0{% endif %}
{% if !simple %}false{% else %}!simple{% endif %}
{% if !simple.uint %}false{% else %}!simple.uint{% endif %}
{% if !simple.float %}false{% else %}!simple.float{% endif %}
{% if "Text" in complex.post %}text field in complex.post{% endif %}
{% if 5 in simple.intmap %}5 in simple.intmap{% endif %}
{% if !0.0 %}!0.0{% endif %}
{% if !0 %}!0{% endif %}
{% if simple.number == 43 %}no{% else %}42{% endif %}
{% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number >= 42 %}yes{% else %}no{% endif %}
{% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number != 42 %}no{% else %}yes{% endif %}
{% if 0 %}!0{% elif nothing %}nothing{% else %}true{% endif %}
{% if 0 %}!0{% elif simple.float %}simple.float{% else %}false{% endif %}
{% if 0 %}!0{% elif !simple.float %}false{% elif "Text" in complex.post%}Elseif with no else{% endif %}

View file

@ -1,17 +0,0 @@
true
simple != nil
uint != 0
float != 0.0
!simple
!simple.uint
!simple.float
text field in complex.post
5 in simple.intmap
!0.0
!0
42
yes
yes
true
simple.float
Elseif with no else

View file

@ -1,9 +0,0 @@
{% for comment in complex.comments2 %}
{% ifchanged %}New comment from another user {{ comment.Author.Name }}{% endifchanged %}
{% ifchanged comment.Author.Validated %}
Validated changed to {{ comment.Author.Validated }}
{% else %}
Validated value not changed
{% endifchanged %}
{% ifchanged comment.Author.Name comment.Date %}Comment's author name or date changed{% endifchanged %}
{% endfor %}

View file

@ -1,18 +0,0 @@
New comment from another user user1
Validated changed to True
Comment's author name or date changed
Validated value not changed
Comment's author name or date changed
New comment from another user user3
Validated changed to False
Comment's author name or date changed

View file

@ -1 +0,0 @@
I'm {{ what_am_i }}{{ number }}

View file

@ -1,4 +0,0 @@
Start '{% include "includes.helper" %}' End
Start '{% include "includes.helper" with what_am_i=simple.name only %}' End
Start '{% include "includes.helper" with what_am_i=simple.name %}' End
Start '{% include simple.included_file|lower with number=7 what_am_i="guest" %}' End

View file

@ -1,4 +0,0 @@
Start 'I'm 11' End
Start 'I'm john doe' End
Start 'I'm john doe11' End
Start 'I'm guest7' End

View file

@ -1,3 +0,0 @@
{% extends "inheritance2/skeleton.tpl" %}
{% block body %}This is base's body{% block content %}Default content{% endblock %}{% endblock %}

View file

@ -1 +0,0 @@
{% include "doesnotexist.tpl" %}

View file

@ -1 +0,0 @@
Included '{{ cycleitem }}'.

View file

@ -1 +0,0 @@
Start#{% block body %}Default body{% endblock %}#End

Some files were not shown because too many files have changed in this diff Show more