Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8ac87a60c | |||
| 67d7b9ca60 | |||
| bb9b8c2585 | |||
| b4ab6497b5 | |||
| 2441e55972 | |||
|
|
75a1fb9b04 | ||
|
|
3bb41424ff | ||
|
|
d791a97f01 | ||
|
|
192fa01364 | ||
|
|
362a1d6915 | ||
|
|
60d22914a5 | ||
|
|
f7464201e6 | ||
|
|
d26d707c73 | ||
|
|
b6df672e9a | ||
|
|
c40a6e9dd5 | ||
|
|
be1a13b346 | ||
|
|
36588ae653 | ||
|
|
20f50a5bba | ||
|
|
5b0ba55f37 | ||
|
|
df996c3ae1 | ||
|
|
f46758caed | ||
|
|
4aa838b8c3 | ||
|
|
478f4d29b7 | ||
|
|
9091772533 | ||
|
|
3b92b162e1 | ||
|
|
b8c9a57c39 | ||
|
|
1891633250 | ||
|
|
75a3d22c53 | ||
|
|
2b59b30398 | ||
|
|
208807f5ca | ||
|
|
823ca32c7a | ||
|
|
99a9166ded | ||
|
|
6431cb3aea | ||
|
|
0e382cfe26 | ||
|
|
ff3209d159 | ||
|
|
0216b71049 | ||
|
|
37bac2fe57 | ||
|
|
7b73c0d36b | ||
|
|
477ce49ddf | ||
|
|
f28243c54b | ||
|
|
4cbf30d337 | ||
|
|
302c88af99 | ||
|
|
8e9d9df2e2 | ||
|
|
431a313411 | ||
|
|
3c52f5c10b | ||
|
|
7fd6ac336a | ||
|
|
9161ab1e62 | ||
|
|
2044aeae11 | ||
|
|
7c73cc2975 | ||
|
|
48726a37dd | ||
|
|
c62dae3c6b | ||
|
|
3adb2eae86 | ||
| 4172a9c4a7 | |||
| cd46239477 | |||
|
|
8c36835ece | ||
| a19c5fd581 | |||
|
|
92b69ffa4c |
24
.github/ISSUE_TEMPLATE.md
vendored
24
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,32 +1,14 @@
|
||||
# Please describe your issue
|
||||
|
||||
## Is this a bug, an improvement, a proposal or something else?
|
||||
|
||||
- [ ] Bug
|
||||
- [ ] Improvement
|
||||
- [ ] Proposal
|
||||
- [ ] Something else
|
||||
|
||||
|
||||
## Briefly explain your issue
|
||||
### Is this a bug, an improvement, a proposal or something else? Describe it.
|
||||
|
||||
...
|
||||
|
||||
|
||||
## What's the expected behaviour?
|
||||
### What's the expected behaviour, the current behaviour and the steps to reproduce it?
|
||||
|
||||
...
|
||||
|
||||
|
||||
## What's the actual behaviour?
|
||||
|
||||
...
|
||||
|
||||
|
||||
## What are the steps to reproduce the actual behaviour?
|
||||
|
||||
...
|
||||
|
||||
|
||||
## Comments
|
||||
### Comments
|
||||
|
||||
|
||||
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -6,26 +6,18 @@
|
||||
- For changes and improvements, new tests have to be provided to cover the new features.
|
||||
|
||||
|
||||
## Is this a fix, improvement or something else?
|
||||
|
||||
...
|
||||
|
||||
|
||||
## What does this change implement/fix?
|
||||
|
||||
... *answer here*
|
||||
...
|
||||
|
||||
## Is this a fix or an improvement?
|
||||
|
||||
- [ ] Fix
|
||||
- [ ] Improvement
|
||||
## I have ...
|
||||
|
||||
## Have discussed this change in an issue?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Was some test failing because of this issue or change needed?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Are you including tests to cover this change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
- [ ] answered the 2 questions above,
|
||||
- [ ] discussed this change in an issue,
|
||||
- [ ] included tests to cover this changes.
|
||||
|
||||
35
.github/workflows/build.yml
vendored
Normal file
35
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Gotext build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -u -t -d ./...
|
||||
|
||||
- name: Build package
|
||||
run: go build -v .
|
||||
|
||||
- name: Install xgotext CLI
|
||||
run: go install -v git.deineagentur.com/DeineAgenturUG/gotext/cli/xgotext
|
||||
|
||||
- name: Test
|
||||
run: go test -v -race ./...
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,9 @@
|
||||
.settings
|
||||
.buildpath
|
||||
|
||||
# golang jetbrains shit
|
||||
.idea
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
|
||||
19
.travis.yml
19
.travis.yml
@@ -1,17 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script: go test -v -race -coverprofile=coverage.txt -covermode=atomic .
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
- "1.9"
|
||||
- "1.10"
|
||||
- "1.11"
|
||||
- "1.12"
|
||||
- "tip"
|
||||
|
||||
15
Gopkg.lock
generated
15
Gopkg.lock
generated
@@ -1,15 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/kinako"
|
||||
packages = ["ast","parser","vm"]
|
||||
revision = "332c0a7e205a29536e672337a4bea6c7a96b04c1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "d3069fabe2d6f79fe33ad88133e861db84aef0400f6b949c4e64395913b3ae97"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
26
Gopkg.toml
26
Gopkg.toml
@@ -1,26 +0,0 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/kinako"
|
||||
126
README.md
126
README.md
@@ -1,13 +1,13 @@
|
||||
[](https://github.com/leonelquinteros/gotext)
|
||||
[](https://git.deineagentur.com/DeineAgenturUG/gotext)
|
||||
[](LICENSE)
|
||||
[](https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
[](https://godoc.org/git.deineagentur.com/DeineAgenturUG/gotext)
|
||||

|
||||
[](https://travis-ci.org/leonelquinteros/gotext)
|
||||
[](https://codecov.io/gh/leonelquinteros/gotext)
|
||||
[](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
|
||||
[](https://goreportcard.com/report/git.deineagentur.com/DeineAgenturUG/gotext)
|
||||
|
||||
# Gotext
|
||||
|
||||
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||
|
||||
|
||||
# Features
|
||||
@@ -18,11 +18,14 @@
|
||||
- Support for variables inside translation strings using Go's [fmt syntax](https://golang.org/pkg/fmt/).
|
||||
- Support for [pluralization rules](https://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html).
|
||||
- Support for [message contexts](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html).
|
||||
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
|
||||
- Support for MO files.
|
||||
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
|
||||
- It works with UTF-8 encoding as it's the default for Go language.
|
||||
- Unit tests available.
|
||||
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
|
||||
- Ready to use inside Go templates.
|
||||
- Objects are serializable to []byte to store them in cache.
|
||||
- Support for Go Modules.
|
||||
|
||||
|
||||
# License
|
||||
@@ -32,28 +35,55 @@
|
||||
|
||||
# Documentation
|
||||
|
||||
Refer to the Godoc package documentation at (https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
Refer to the Godoc package documentation at (https://godoc.org/git.deineagentur.com/DeineAgenturUG/gotext)
|
||||
|
||||
|
||||
# Installation
|
||||
# Installation
|
||||
|
||||
```
|
||||
go get github.com/leonelquinteros/gotext
|
||||
go get git.deineagentur.com/DeineAgenturUG/gotext
|
||||
```
|
||||
|
||||
- There are no requirements or dependencies to use this package.
|
||||
- There are no requirements or dependencies to use this package.
|
||||
- No need to install GNU gettext utilities (unless specific needs of CLI tools).
|
||||
- No need for environment variables. Some naming conventions are applied but not needed.
|
||||
- No need for environment variables. Some naming conventions are applied but not needed.
|
||||
|
||||
|
||||
#### Version vendoring
|
||||
## Version vendoring
|
||||
|
||||
Stable releases use [semantic versioning](http://semver.org/spec/v2.0.0.html) tagging on this repository.
|
||||
|
||||
You can rely on this to use your preferred vendoring tool or to manually retrieve the corresponding release tag from the GitHub repository.
|
||||
|
||||
|
||||
##### Vendoring with [gopkg.in](http://labix.org/gopkg.in)
|
||||
### Vendoring with [Go Modules](https://github.com/golang/go/wiki/Modules) (Recommended)
|
||||
|
||||
Add `git.deineagentur.com/DeineAgenturUG/gotext` inside the `require` section in your `go.mod` file.
|
||||
|
||||
i.e.
|
||||
```
|
||||
require (
|
||||
git.deineagentur.com/DeineAgenturUG/gotext v1.5.0
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
### Vendoring with [dep](https://golang.github.io/dep/)
|
||||
|
||||
To use last stable version (v1.5.0 at the moment of writing)
|
||||
|
||||
```
|
||||
dep ensure -add git.deineagentur.com/DeineAgenturUG/gotext@v1.5.0
|
||||
```
|
||||
|
||||
Import as
|
||||
|
||||
```go
|
||||
import "git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
```
|
||||
|
||||
|
||||
### Vendoring with [gopkg.in](http://labix.org/gopkg.in)
|
||||
|
||||
[http://gopkg.in/leonelquinteros/gotext.v1](http://gopkg.in/leonelquinteros/gotext.v1)
|
||||
|
||||
@@ -63,7 +93,7 @@ To get the latest v1 package stable release, execute:
|
||||
go get gopkg.in/leonelquinteros/gotext.v1
|
||||
```
|
||||
|
||||
To import this package, add the following line to your code:
|
||||
Import as
|
||||
|
||||
```go
|
||||
import "gopkg.in/leonelquinteros/gotext.v1"
|
||||
@@ -74,19 +104,19 @@ Refer to it as gotext.
|
||||
|
||||
# Locales directories structure
|
||||
|
||||
The package will assume a directories structure starting with a base path that will be provided to the package configuration
|
||||
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
|
||||
The package will assume a directories structure starting with a base path that will be provided to the package configuration
|
||||
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
|
||||
|
||||
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
|
||||
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
|
||||
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
|
||||
all package functions will be able to resolve this generalization and provide translations for the more general library.
|
||||
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
|
||||
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
|
||||
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
|
||||
all package functions will be able to resolve this generalization and provide translations for the more general library.
|
||||
|
||||
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
|
||||
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
|
||||
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
|
||||
|
||||
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
|
||||
A library directory structure can look like:
|
||||
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
|
||||
A library directory structure can look like:
|
||||
|
||||
```
|
||||
/path/to/locales
|
||||
@@ -111,7 +141,7 @@ A library directory structure can look like:
|
||||
/path/to/locales/fr
|
||||
/path/to/locales/fr/default.po
|
||||
/path/to/locales/fr/extras.po
|
||||
```
|
||||
```
|
||||
|
||||
And so on...
|
||||
|
||||
@@ -125,16 +155,16 @@ For quick/simple translations you can use the package level functions directly.
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
@@ -143,22 +173,22 @@ func main() {
|
||||
|
||||
## Using dynamic variables on translations
|
||||
|
||||
All translation strings support dynamic variables to be inserted without translate.
|
||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||
All translation strings support dynamic variables to be inserted without translate.
|
||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
|
||||
// Set variables
|
||||
name := "John"
|
||||
|
||||
|
||||
// Translate text with variables
|
||||
fmt.Println(gotext.Get("Hi, my name is %s", name))
|
||||
}
|
||||
@@ -168,35 +198,35 @@ func main() {
|
||||
|
||||
## Using Locale object
|
||||
|
||||
When having multiple languages/domains/libraries at the same time, you can create Locale objects for each variation
|
||||
When having multiple languages/domains/libraries at the same time, you can create Locale objects for each variation
|
||||
so you can handle each settings on their own.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/locales/root/dir", "es_UY")
|
||||
|
||||
|
||||
// Load domain '/path/to/locales/root/dir/es_UY/default.po'
|
||||
l.AddDomain("default")
|
||||
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
|
||||
// Load different domain
|
||||
l.AddDomain("translations")
|
||||
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("translations", "Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
|
||||
If you set the Locale object as "Loc" in the template, then the template code would look like:
|
||||
If you set the Locale object as "Loc" in the template, then the template code would look like:
|
||||
|
||||
```
|
||||
{{ .Loc.Get "Translate this" }}
|
||||
@@ -205,13 +235,13 @@ If you set the Locale object as "Loc" in the template, then the template code wo
|
||||
|
||||
## Using the Po object to handle .po files and PO-formatted strings
|
||||
|
||||
For when you need to work with PO files and strings,
|
||||
For when you need to work with PO files and strings,
|
||||
you can directly use the Po object to parse it and access the translations in there in the same way.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -226,11 +256,11 @@ msgstr ""
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
`
|
||||
|
||||
|
||||
// Create Po object
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
```
|
||||
@@ -247,7 +277,7 @@ Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -270,21 +300,21 @@ msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
`
|
||||
|
||||
|
||||
// Create Po object
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
|
||||
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
||||
// "This one is the plural: Variable"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Contribute
|
||||
# Contribute
|
||||
|
||||
- Please, contribute.
|
||||
- Use the package on your projects.
|
||||
- Report issues on Github.
|
||||
- Report issues on Github.
|
||||
- Send pull requests for bugfixes and improvements.
|
||||
- Send proposals on Github issues.
|
||||
|
||||
48
cli/xgotext/README.md
Normal file
48
cli/xgotext/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# xgotext
|
||||
|
||||
CLI tool to extract translation strings from Go packages into .POT files.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
go install git.deineagentur.com/DeineAgenturUG/gotext/cli/xgotext
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Usage of xgotext:
|
||||
-default string
|
||||
Name of default domain (default "default")
|
||||
-exclude string
|
||||
Comma separated list of directories to exclude (default ".git")
|
||||
-in string
|
||||
input dir: /path/to/go/pkg
|
||||
-out string
|
||||
output dir: /path/to/i18n/files
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
This is the first (naive) implementation for this tool.
|
||||
|
||||
It will scan the Go package provided for method calls that matches the method names from the gotext package and write the corresponding translation files to the output directory.
|
||||
|
||||
Isn't able to parse calls to translation functions using parameters inside variables, if the translation string is inside a variable and that variable is used to invoke the translation function, this tool won't be able to parse that string. See this example code:
|
||||
|
||||
```go
|
||||
// This line will be added to the .po file
|
||||
gotext.Get("Translate this")
|
||||
|
||||
tr := "Translate this string"
|
||||
// The following line will NOT be added to the .pot file
|
||||
gotext.Get(tr)
|
||||
```
|
||||
|
||||
The CLI tool traverse sub-directories based on the given input directory.
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
Please
|
||||
|
||||
28
cli/xgotext/fixtures/i18n/default.po
Normal file
28
cli/xgotext/fixtures/i18n/default.po
Normal file
@@ -0,0 +1,28 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: \n"
|
||||
"X-Generator: xgotext\n"
|
||||
|
||||
|
||||
#: fixtures/main.go:23
|
||||
#. gotext.Get
|
||||
msgid "My text on 'domain-name' domain"
|
||||
msgstr ""
|
||||
|
||||
#: fixtures/main.go:38
|
||||
#. l.GetN
|
||||
msgid "Singular"
|
||||
msgid_plural "Plural"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: fixtures/main.go:40
|
||||
#. l.GetN
|
||||
msgid "SingularVar"
|
||||
msgid_plural "PluralVar"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
15
cli/xgotext/fixtures/i18n/domain.po
Normal file
15
cli/xgotext/fixtures/i18n/domain.po
Normal file
@@ -0,0 +1,15 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: \n"
|
||||
"X-Generator: xgotext\n"
|
||||
|
||||
|
||||
#: fixtures/main.go:42
|
||||
#. l.GetDC
|
||||
msgctxt "ctx"
|
||||
msgid "string"
|
||||
msgstr ""
|
||||
14
cli/xgotext/fixtures/i18n/domain2.po
Normal file
14
cli/xgotext/fixtures/i18n/domain2.po
Normal file
@@ -0,0 +1,14 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: \n"
|
||||
"X-Generator: xgotext\n"
|
||||
|
||||
|
||||
#: fixtures/main.go:26
|
||||
#. gotext.GetD
|
||||
msgid "Another text on a different domain"
|
||||
msgstr ""
|
||||
22
cli/xgotext/fixtures/i18n/translations.po
Normal file
22
cli/xgotext/fixtures/i18n/translations.po
Normal file
@@ -0,0 +1,22 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: \n"
|
||||
"X-Generator: xgotext\n"
|
||||
|
||||
|
||||
#: fixtures/main.go:35
|
||||
#. l.GetD
|
||||
msgid "Translate this"
|
||||
msgstr ""
|
||||
|
||||
#: fixtures/main.go:43
|
||||
#. l.GetNDC
|
||||
msgctxt "NDC-CTX"
|
||||
msgid "ndc"
|
||||
msgid_plural "ndcs"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
90
cli/xgotext/fixtures/main.go
Normal file
90
cli/xgotext/fixtures/main.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
alias "git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext/cli/xgotext/fixtures/pkg"
|
||||
)
|
||||
|
||||
// Fake object with methods similar to gotext
|
||||
type Fake struct {
|
||||
}
|
||||
|
||||
// Get by id
|
||||
func (f Fake) Get(id int) int {
|
||||
return 42
|
||||
}
|
||||
|
||||
// Fake object with same methods as gotext
|
||||
type Fake2 struct {
|
||||
}
|
||||
|
||||
// Get by str
|
||||
func (f Fake2) Get(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
// same as before
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// unsupported function call
|
||||
trStr := "some string to translate"
|
||||
fmt.Println(gotext.Get(trStr))
|
||||
|
||||
// same with alias package name
|
||||
fmt.Println(alias.Get("alias call"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/locales/root/dir", "es_UY")
|
||||
|
||||
// Load domain '/path/to/locales/root/dir/es_UY/default.po'
|
||||
l.AddDomain("translations")
|
||||
l.SetDomain("translations")
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("translations", "Translate this"))
|
||||
|
||||
// Get plural translations
|
||||
l.GetN("Singular", "Plural", 4)
|
||||
num := 17
|
||||
l.GetN("SingularVar", "PluralVar", num)
|
||||
|
||||
l.GetDC("domain2", "string", "ctx")
|
||||
l.GetNDC("translations", "ndc", "ndcs", 7, "NDC-CTX")
|
||||
|
||||
// try fake structs
|
||||
f := Fake{}
|
||||
f.Get(3)
|
||||
|
||||
f2 := Fake2{}
|
||||
f2.Get("3")
|
||||
|
||||
// use translator of sub object
|
||||
t := pkg.Translate{}
|
||||
t.L.Get("translate package")
|
||||
t.S.L.Get("translate sub package")
|
||||
|
||||
// redefine alias with fake struct
|
||||
alias := Fake2{}
|
||||
alias.Get("3")
|
||||
|
||||
err := errors.New("test")
|
||||
fmt.Print(err.Error())
|
||||
}
|
||||
|
||||
// dummy function
|
||||
func dummy(locale *gotext.Locale) {
|
||||
locale.Get("inside dummy")
|
||||
}
|
||||
16
cli/xgotext/fixtures/pkg/pkg.go
Normal file
16
cli/xgotext/fixtures/pkg/pkg.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package pkg
|
||||
|
||||
import "git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
|
||||
type SubTranslate struct {
|
||||
L gotext.Locale
|
||||
}
|
||||
|
||||
type Translate struct {
|
||||
L gotext.Locale
|
||||
S SubTranslate
|
||||
}
|
||||
|
||||
func test() {
|
||||
gotext.Get("inside sub package")
|
||||
}
|
||||
45
cli/xgotext/main.go
Normal file
45
cli/xgotext/main.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext/cli/xgotext/parser"
|
||||
)
|
||||
|
||||
var (
|
||||
dirName = flag.String("in", "", "input dir: /path/to/go/pkg")
|
||||
outputDir = flag.String("out", "", "output dir: /path/to/i18n/files")
|
||||
defaultDomain = flag.String("default", "default", "Name of default domain")
|
||||
excludeDirs = flag.String("exclude", ".git", "Comma separated list of directories to exclude")
|
||||
verbose = flag.Bool("v", false, "print currently handled directory")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Init logger
|
||||
log.SetFlags(0)
|
||||
|
||||
if *dirName == "" {
|
||||
log.Fatal("No input directory given")
|
||||
}
|
||||
if *outputDir == "" {
|
||||
log.Fatal("No output directory given")
|
||||
}
|
||||
|
||||
data := &parser.DomainMap{
|
||||
Default: *defaultDomain,
|
||||
}
|
||||
|
||||
err := parser.ParseDirRec(*dirName, strings.Split(*excludeDirs, ","), data, *verbose)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = data.Save(*outputDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
195
cli/xgotext/parser/domain.go
Normal file
195
cli/xgotext/parser/domain.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Translation for a text to translate
|
||||
type Translation struct {
|
||||
MsgId string
|
||||
MsgIdPlural string
|
||||
Context string
|
||||
SourceLocations []string
|
||||
}
|
||||
|
||||
// AddLocations to translation
|
||||
func (t *Translation) AddLocations(locations []string) {
|
||||
if t.SourceLocations == nil {
|
||||
t.SourceLocations = locations
|
||||
} else {
|
||||
t.SourceLocations = append(t.SourceLocations, locations...)
|
||||
}
|
||||
}
|
||||
|
||||
// Dump translation as string
|
||||
func (t *Translation) Dump() string {
|
||||
data := make([]string, 0, len(t.SourceLocations)+5)
|
||||
|
||||
for _, location := range t.SourceLocations {
|
||||
data = append(data, "#: "+location)
|
||||
}
|
||||
|
||||
if t.Context != "" {
|
||||
data = append(data, "msgctxt "+t.Context)
|
||||
}
|
||||
|
||||
data = append(data, "msgid "+t.MsgId)
|
||||
|
||||
if t.MsgIdPlural == "" {
|
||||
data = append(data, "msgstr \"\"")
|
||||
} else {
|
||||
data = append(data,
|
||||
"msgid_plural "+t.MsgIdPlural,
|
||||
"msgstr[0] \"\"",
|
||||
"msgstr[1] \"\"")
|
||||
}
|
||||
|
||||
return strings.Join(data, "\n")
|
||||
}
|
||||
|
||||
// TranslationMap contains a map of translations with the ID as key
|
||||
type TranslationMap map[string]*Translation
|
||||
|
||||
// Dump the translation map as string
|
||||
func (m TranslationMap) Dump() string {
|
||||
// sort by translation id for consistence output
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
data := make([]string, 0, len(m))
|
||||
for _, key := range keys {
|
||||
data = append(data, (m)[key].Dump())
|
||||
}
|
||||
return strings.Join(data, "\n\n")
|
||||
}
|
||||
|
||||
// Domain holds all translations of one domain
|
||||
type Domain struct {
|
||||
Translations TranslationMap
|
||||
ContextTranslations map[string]TranslationMap
|
||||
}
|
||||
|
||||
// AddTranslation to the domain
|
||||
func (d *Domain) AddTranslation(translation *Translation) {
|
||||
if d.Translations == nil {
|
||||
d.Translations = make(TranslationMap)
|
||||
d.ContextTranslations = make(map[string]TranslationMap)
|
||||
}
|
||||
|
||||
if translation.Context == "" {
|
||||
if t, ok := d.Translations[translation.MsgId]; ok {
|
||||
t.AddLocations(translation.SourceLocations)
|
||||
} else {
|
||||
d.Translations[translation.MsgId] = translation
|
||||
}
|
||||
} else {
|
||||
if _, ok := d.ContextTranslations[translation.Context]; !ok {
|
||||
d.ContextTranslations[translation.Context] = make(TranslationMap)
|
||||
}
|
||||
|
||||
if t, ok := d.ContextTranslations[translation.Context][translation.MsgId]; ok {
|
||||
t.AddLocations(translation.SourceLocations)
|
||||
} else {
|
||||
d.ContextTranslations[translation.Context][translation.MsgId] = translation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dump the domain as string
|
||||
func (d *Domain) Dump() string {
|
||||
data := make([]string, 0, len(d.ContextTranslations)+1)
|
||||
data = append(data, d.Translations.Dump())
|
||||
|
||||
// sort context translations by context for consistence output
|
||||
keys := make([]string, 0, len(d.ContextTranslations))
|
||||
for k := range d.ContextTranslations {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
data = append(data, d.ContextTranslations[key].Dump())
|
||||
}
|
||||
return strings.Join(data, "\n\n")
|
||||
}
|
||||
|
||||
// Save domain to file
|
||||
func (d *Domain) Save(path string) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to domain: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// write header
|
||||
_, err = file.WriteString(`msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: \n"
|
||||
"X-Generator: xgotext\n"
|
||||
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write domain content
|
||||
_, err = file.WriteString(d.Dump())
|
||||
return err
|
||||
}
|
||||
|
||||
// DomainMap contains multiple domains as map with name as key
|
||||
type DomainMap struct {
|
||||
Domains map[string]*Domain
|
||||
Default string
|
||||
}
|
||||
|
||||
// AddTranslation to domain map
|
||||
func (m *DomainMap) AddTranslation(domain string, translation *Translation) {
|
||||
if m.Domains == nil {
|
||||
m.Domains = make(map[string]*Domain, 1)
|
||||
}
|
||||
|
||||
// use "default" as default domain if not set
|
||||
if m.Default == "" {
|
||||
m.Default = "default"
|
||||
}
|
||||
|
||||
// no domain given -> use default
|
||||
if domain == "" {
|
||||
domain = m.Default
|
||||
}
|
||||
|
||||
if _, ok := m.Domains[domain]; !ok {
|
||||
m.Domains[domain] = new(Domain)
|
||||
}
|
||||
m.Domains[domain].AddTranslation(translation)
|
||||
}
|
||||
|
||||
// Save domains to directory
|
||||
func (m *DomainMap) Save(directory string) error {
|
||||
// ensure output directory exist
|
||||
err := os.MkdirAll(directory, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output dir: %v", err)
|
||||
}
|
||||
|
||||
// save each domain in a separate po file
|
||||
for name, domain := range m.Domains {
|
||||
err := domain.Save(filepath.Join(directory, name+".pot"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save domain %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
271
cli/xgotext/parser/golang.go
Normal file
271
cli/xgotext/parser/golang.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// GetterDef describes a getter
|
||||
type GetterDef struct {
|
||||
Id int
|
||||
Plural int
|
||||
Context int
|
||||
Domain int
|
||||
}
|
||||
|
||||
// maxArgIndex returns the largest argument index
|
||||
func (d *GetterDef) maxArgIndex() int {
|
||||
m := d.Id
|
||||
if d.Plural > m {
|
||||
m = d.Plural
|
||||
}
|
||||
if d.Context > m {
|
||||
m = d.Context
|
||||
}
|
||||
if d.Domain > m {
|
||||
m = d.Domain
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// list of supported getter
|
||||
var gotextGetter = map[string]GetterDef{
|
||||
"Get": {0, -1, -1, -1},
|
||||
"GetN": {0, 1, -1, -1},
|
||||
"GetD": {1, -1, -1, 0},
|
||||
"GetND": {1, 2, -1, 0},
|
||||
"GetC": {0, -1, 1, -1},
|
||||
"GetNC": {0, 1, 3, -1},
|
||||
"GetDC": {1, -1, 2, 0},
|
||||
"GetNDC": {1, 2, 4, 0},
|
||||
}
|
||||
|
||||
// register go parser
|
||||
func init() {
|
||||
AddParser(goParser)
|
||||
}
|
||||
|
||||
// parse go package
|
||||
func goParser(dirPath, basePath string, data *DomainMap) error {
|
||||
fileSet := token.NewFileSet()
|
||||
|
||||
conf := packages.Config{
|
||||
Mode: packages.NeedName |
|
||||
packages.NeedFiles |
|
||||
packages.NeedSyntax |
|
||||
packages.NeedTypes |
|
||||
packages.NeedTypesInfo,
|
||||
Fset: fileSet,
|
||||
Dir: basePath,
|
||||
}
|
||||
|
||||
// load package from path
|
||||
pkgs, err := packages.Load(&packages.Config{
|
||||
Mode: conf.Mode,
|
||||
Fset: fileSet,
|
||||
Dir: dirPath,
|
||||
})
|
||||
if err != nil || len(pkgs) == 0 {
|
||||
// not a go package
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle each file
|
||||
for _, node := range pkgs[0].Syntax {
|
||||
file := GoFile{
|
||||
pkgConf: &conf,
|
||||
filePath: fileSet.Position(node.Package).Filename,
|
||||
basePath: basePath,
|
||||
data: data,
|
||||
fileSet: fileSet,
|
||||
|
||||
importedPackages: map[string]*packages.Package{
|
||||
pkgs[0].Name: pkgs[0],
|
||||
},
|
||||
}
|
||||
|
||||
ast.Inspect(node, file.inspectFile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GoFile handles the parsing of one go file
|
||||
type GoFile struct {
|
||||
filePath string
|
||||
basePath string
|
||||
data *DomainMap
|
||||
|
||||
fileSet *token.FileSet
|
||||
pkgConf *packages.Config
|
||||
|
||||
importedPackages map[string]*packages.Package
|
||||
}
|
||||
|
||||
// getPackage loads module by name
|
||||
func (g *GoFile) getPackage(name string) (*packages.Package, error) {
|
||||
pkgs, err := packages.Load(g.pkgConf, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pkgs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return pkgs[0], nil
|
||||
}
|
||||
|
||||
// getType from ident object
|
||||
func (g *GoFile) getType(ident *ast.Ident) types.Object {
|
||||
for _, pkg := range g.importedPackages {
|
||||
if obj, ok := pkg.TypesInfo.Uses[ident]; ok {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GoFile) inspectFile(n ast.Node) bool {
|
||||
switch x := n.(type) {
|
||||
// get names of imported packages
|
||||
case *ast.ImportSpec:
|
||||
packageName, _ := strconv.Unquote(x.Path.Value)
|
||||
|
||||
pkg, err := g.getPackage(packageName)
|
||||
if err != nil {
|
||||
log.Printf("failed to load package %s: %s", packageName, err)
|
||||
} else {
|
||||
if x.Name == nil {
|
||||
g.importedPackages[pkg.Name] = pkg
|
||||
} else {
|
||||
g.importedPackages[x.Name.Name] = pkg
|
||||
}
|
||||
}
|
||||
|
||||
// check each function call
|
||||
case *ast.CallExpr:
|
||||
g.inspectCallExpr(x)
|
||||
|
||||
default:
|
||||
print()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// checkType for gotext object
|
||||
func (g *GoFile) checkType(rawType types.Type) bool {
|
||||
switch t := rawType.(type) {
|
||||
case *types.Pointer:
|
||||
return g.checkType(t.Elem())
|
||||
|
||||
case *types.Named:
|
||||
if t.Obj().Pkg() == nil || t.Obj().Pkg().Path() != "git.deineagentur.com/DeineAgenturUG/gotext" {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *GoFile) inspectCallExpr(n *ast.CallExpr) {
|
||||
// must be a selector expression otherwise it is a local function call
|
||||
expr, ok := n.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch e := expr.X.(type) {
|
||||
// direct call
|
||||
case *ast.Ident:
|
||||
// object is a package if the Obj is not set
|
||||
if e.Obj == nil {
|
||||
pkg, ok := g.importedPackages[e.Name]
|
||||
if !ok || pkg.PkgPath != "git.deineagentur.com/DeineAgenturUG/gotext" {
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
// validate type of object
|
||||
t := g.getType(e)
|
||||
if t == nil || !g.checkType(t.Type()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// call to attribute
|
||||
case *ast.SelectorExpr:
|
||||
// validate type of object
|
||||
t := g.getType(e.Sel)
|
||||
if t == nil || !g.checkType(t.Type()) {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
// convert args
|
||||
args := make([]*ast.BasicLit, len(n.Args))
|
||||
for idx, arg := range n.Args {
|
||||
args[idx], _ = arg.(*ast.BasicLit)
|
||||
}
|
||||
|
||||
// get position
|
||||
path, _ := filepath.Rel(g.basePath, g.filePath)
|
||||
position := fmt.Sprintf("%s:%d", path, g.fileSet.Position(n.Lparen).Line)
|
||||
|
||||
// handle getters
|
||||
if def, ok := gotextGetter[expr.Sel.String()]; ok {
|
||||
g.parseGetter(def, args, position)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
|
||||
// check if enough arguments are given
|
||||
if len(args) < def.maxArgIndex() {
|
||||
return
|
||||
}
|
||||
|
||||
// get domain
|
||||
var domain string
|
||||
if def.Domain != -1 {
|
||||
domain, _ = strconv.Unquote(args[def.Domain].Value)
|
||||
}
|
||||
|
||||
// only handle function calls with strings as ID
|
||||
if args[def.Id] == nil || args[def.Id].Kind != token.STRING {
|
||||
log.Printf("ERR: Unsupported call at %s (ID not a string)", pos)
|
||||
return
|
||||
}
|
||||
|
||||
trans := Translation{
|
||||
MsgId: args[def.Id].Value,
|
||||
SourceLocations: []string{pos},
|
||||
}
|
||||
if def.Plural > 0 {
|
||||
// plural ID must be a string
|
||||
if args[def.Plural] == nil || args[def.Plural].Kind != token.STRING {
|
||||
log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos)
|
||||
return
|
||||
}
|
||||
trans.MsgIdPlural = args[def.Plural].Value
|
||||
}
|
||||
if def.Context > 0 {
|
||||
// Context must be a string
|
||||
if args[def.Context] == nil || args[def.Context].Kind != token.STRING {
|
||||
log.Printf("ERR: Unsupported call at %s (Context not a string)", pos)
|
||||
return
|
||||
}
|
||||
trans.Context = args[def.Context].Value
|
||||
}
|
||||
|
||||
g.data.AddTranslation(domain, &trans)
|
||||
}
|
||||
67
cli/xgotext/parser/parser.go
Normal file
67
cli/xgotext/parser/parser.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseDirFunc parses one directory
|
||||
type ParseDirFunc func(filePath, basePath string, data *DomainMap) error
|
||||
|
||||
var knownParser []ParseDirFunc
|
||||
|
||||
// AddParser to the known parser list
|
||||
func AddParser(parser ParseDirFunc) {
|
||||
if knownParser == nil {
|
||||
knownParser = []ParseDirFunc{parser}
|
||||
} else {
|
||||
knownParser = append(knownParser, parser)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDir calls all known parser for each directory
|
||||
func ParseDir(dirPath, basePath string, data *DomainMap) error {
|
||||
dirPath, _ = filepath.Abs(dirPath)
|
||||
basePath, _ = filepath.Abs(basePath)
|
||||
|
||||
for _, parser := range knownParser {
|
||||
err := parser(dirPath, basePath, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseDirRec calls all known parser for each directory
|
||||
func ParseDirRec(dirPath string, exclude []string, data *DomainMap, verbose bool) error {
|
||||
dirPath, _ = filepath.Abs(dirPath)
|
||||
|
||||
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
// skip directory if in exclude list
|
||||
subDir, _ := filepath.Rel(dirPath, path)
|
||||
for _, d := range exclude {
|
||||
if strings.HasPrefix(subDir, d) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if verbose {
|
||||
log.Print(path)
|
||||
}
|
||||
|
||||
err := ParseDir(path, dirPath, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
19
fixtures/ar/categories.po
Normal file
19
fixtures/ar/categories.po
Normal file
@@ -0,0 +1,19 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
||||
|
||||
msgid "Alcohol & Tobacco"
|
||||
msgstr "الكحول والتبغ"
|
||||
|
||||
# this test data is purposely missing msgstr
|
||||
msgid "%d selected"
|
||||
msgid_plural "%d selected"
|
||||
|
||||
msgid "Load %d more document"
|
||||
msgid_plural "Load %d more documents"
|
||||
msgstr[0] "حمّل %d مستندات إضافيّة"
|
||||
msgstr[1] "حمّل مستند واحد إضافي"
|
||||
msgstr[2] "حمّل مستندين إضافيين"
|
||||
msgstr[3] "حمّل %d مستندات إضافيّة"
|
||||
msgstr[4] "حمّل %d مستندا إضافيّا"
|
||||
msgstr[5] "حمّل %d مستند إضافي"
|
||||
2
fixtures/ar/no_plural_header.po
Normal file
2
fixtures/ar/no_plural_header.po
Normal file
@@ -0,0 +1,2 @@
|
||||
msgid "Alcohol & Tobacco"
|
||||
msgstr "الكحول والتبغ"
|
||||
BIN
fixtures/de/default.mo
Normal file
BIN
fixtures/de/default.mo
Normal file
Binary file not shown.
77
fixtures/de/default.po
Normal file
77
fixtures/de/default.po
Normal file
@@ -0,0 +1,77 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: de\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "de"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"plural\n"
|
||||
"id"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr ""
|
||||
"Multi \n"
|
||||
"line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
BIN
fixtures/de_DE/LC_MESSAGES/default.mo
Normal file
BIN
fixtures/de_DE/LC_MESSAGES/default.mo
Normal file
Binary file not shown.
77
fixtures/de_DE/LC_MESSAGES/default.po
Normal file
77
fixtures/de_DE/LC_MESSAGES/default.po
Normal file
@@ -0,0 +1,77 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: de_DE\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "de_DE"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"plural\n"
|
||||
"id"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr ""
|
||||
"Multi \n"
|
||||
"line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
68
fixtures/en_AU/default.po
Normal file
68
fixtures/en_AU/default.po
Normal file
@@ -0,0 +1,68 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en_US\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "en_AU"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid "multilineid"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid "multilinepluralid"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr "Multi line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
BIN
fixtures/en_GB/default.mo
Normal file
BIN
fixtures/en_GB/default.mo
Normal file
Binary file not shown.
BIN
fixtures/en_US/default.mo
Normal file
BIN
fixtures/en_US/default.mo
Normal file
Binary file not shown.
68
fixtures/en_US/default.po
Normal file
68
fixtures/en_US/default.po
Normal file
@@ -0,0 +1,68 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en_US\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "en_US"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid "multilineid"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid "multilinepluralid"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr "Multi line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
BIN
fixtures/fr/LC_MESSAGES/default.mo
Normal file
BIN
fixtures/fr/LC_MESSAGES/default.mo
Normal file
Binary file not shown.
77
fixtures/fr/LC_MESSAGES/default.po
Normal file
77
fixtures/fr/LC_MESSAGES/default.po
Normal file
@@ -0,0 +1,77 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "fr"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"plural\n"
|
||||
"id"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr ""
|
||||
"Multi \n"
|
||||
"line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
7
go.mod
Normal file
7
go.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
module git.deineagentur.com/DeineAgenturUG/gotext
|
||||
|
||||
// go: no requirements found in Gopkg.lock
|
||||
|
||||
require golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
|
||||
|
||||
go 1.13
|
||||
14
go.sum
Normal file
14
go.sum
Normal file
@@ -0,0 +1,14 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs=
|
||||
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
126
gotext.go
126
gotext.go
@@ -1,29 +1,29 @@
|
||||
/*
|
||||
Package gotext implements GNU gettext utilities.
|
||||
Package gotext implements GNU gettext utilities.
|
||||
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
// Translate text from default domain
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
|
||||
*/
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/gob"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ type config struct {
|
||||
// Language set.
|
||||
language string
|
||||
|
||||
// Path to library directory where all locale directories and translation files are.
|
||||
// Path to library directory where all locale directories and Translation files are.
|
||||
library string
|
||||
|
||||
// Storage for package level methods
|
||||
@@ -46,14 +46,17 @@ type config struct {
|
||||
|
||||
var globalConfig *config
|
||||
|
||||
// Init default configuration
|
||||
func init() {
|
||||
// Init default configuration
|
||||
globalConfig = &config{
|
||||
domain: "default",
|
||||
language: "en_US",
|
||||
library: "/usr/local/share/locale",
|
||||
storage: nil,
|
||||
}
|
||||
|
||||
// Register Translator types for gob encoding
|
||||
gob.Register(TranslatorEncoding{})
|
||||
}
|
||||
|
||||
// loadStorage creates a new Locale object at package level based on the Global variables settings.
|
||||
@@ -65,27 +68,37 @@ func loadStorage(force bool) {
|
||||
globalConfig.storage = NewLocale(globalConfig.library, globalConfig.language)
|
||||
}
|
||||
|
||||
if _, ok := globalConfig.storage.domains[globalConfig.domain]; !ok || force {
|
||||
if _, ok := globalConfig.storage.Domains[globalConfig.domain]; !ok || force {
|
||||
globalConfig.storage.AddDomain(globalConfig.domain)
|
||||
}
|
||||
globalConfig.storage.SetDomain(globalConfig.domain)
|
||||
|
||||
globalConfig.Unlock()
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
func GetDomain() string {
|
||||
var dom string
|
||||
globalConfig.RLock()
|
||||
dom := globalConfig.domain
|
||||
if globalConfig.storage != nil {
|
||||
dom = globalConfig.storage.GetDomain()
|
||||
}
|
||||
if dom == "" {
|
||||
dom = globalConfig.domain
|
||||
}
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return dom
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
// It reloads the corresponding Translation file.
|
||||
func SetDomain(dom string) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.domain = dom
|
||||
if globalConfig.storage != nil {
|
||||
globalConfig.storage.SetDomain(dom)
|
||||
}
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
@@ -101,10 +114,10 @@ func GetLanguage() string {
|
||||
}
|
||||
|
||||
// SetLanguage sets the language code to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
// It reloads the corresponding Translation file.
|
||||
func SetLanguage(lang string) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.language = lang
|
||||
globalConfig.language = SimplifiedLocale(lang)
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
@@ -120,7 +133,7 @@ func GetLibrary() string {
|
||||
}
|
||||
|
||||
// SetLibrary sets the root path for the loale directories and files to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
// It reloads the corresponding Translation file.
|
||||
func SetLibrary(lib string) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.library = lib
|
||||
@@ -129,91 +142,106 @@ func SetLibrary(lib string) {
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// Configure sets all configuration variables to be used at package level and reloads the corresponding translation file.
|
||||
// Configure sets all configuration variables to be used at package level and reloads the corresponding Translation file.
|
||||
// It receives the library path, language code and domain name.
|
||||
// This function is recommended to be used when changing more than one setting,
|
||||
// as using each setter will introduce a I/O overhead because the translation file will be loaded after each set.
|
||||
// as using each setter will introduce a I/O overhead because the Translation file will be loaded after each set.
|
||||
func Configure(lib, lang, dom string) {
|
||||
globalConfig.Lock()
|
||||
|
||||
globalConfig.library = lib
|
||||
globalConfig.language = lang
|
||||
globalConfig.language = SimplifiedLocale(lang)
|
||||
globalConfig.domain = dom
|
||||
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// Get uses the default domain globally set to return the corresponding translation of a given string.
|
||||
// Get uses the default domain globally set to return the corresponding Translation of a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func Get(str string, vars ...interface{}) string {
|
||||
return GetD(GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of translation for the given string in the default domain.
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string in the default domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return GetND(GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding translation in the given domain for a given string.
|
||||
// GetD returns the corresponding Translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetD(dom, str string, vars ...interface{}) string {
|
||||
return GetND(dom, str, str, 1, vars...)
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
|
||||
if _, ok := globalConfig.storage.Domains[dom]; !ok {
|
||||
globalConfig.storage.AddDomain(dom)
|
||||
}
|
||||
|
||||
tr := globalConfig.storage.GetD(dom, str, vars...)
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form of translation in the given domain for a given string.
|
||||
// GetND retrieves the (N)th plural form of Translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return translation
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
|
||||
if _, ok := globalConfig.storage.Domains[dom]; !ok {
|
||||
globalConfig.storage.AddDomain(dom)
|
||||
}
|
||||
|
||||
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetC uses the default domain globally set to return the corresponding translation of the given string in the given context.
|
||||
// GetC uses the default domain globally set to return the corresponding Translation of the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetC(str, ctx string, vars ...interface{}) string {
|
||||
return GetDC(GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the default domain.
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the default domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetDC returns the corresponding translation in the given domain for the given string in the given context.
|
||||
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||
return GetNDC(dom, str, str, 1, ctx, vars...)
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
tr := globalConfig.storage.GetDC(dom, str, ctx, vars...)
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of translation in the given domain for a given string.
|
||||
// GetNDC retrieves the (N)th plural form of Translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return translation
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
tr := globalConfig.storage.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// printf applies text formatting only when needed to parse variables.
|
||||
func printf(str string, vars ...interface{}) string {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
187
gotext_test.go
187
gotext_test.go
@@ -2,7 +2,7 @@ package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
@@ -65,14 +65,14 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
msgid "Untranslated"
|
||||
msgid_plural "Several untranslated"
|
||||
@@ -81,27 +81,58 @@ msgstr[1] ""
|
||||
|
||||
`
|
||||
|
||||
anotherStr := `
|
||||
msgid ""
|
||||
msgstr "Project-Id-Version: %s\n"
|
||||
"Report-Msgid-Bugs-To: %s\n"
|
||||
|
||||
# Initial comment
|
||||
# More Headers below
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Another text on a different domain"
|
||||
msgstr "Another text on another domain"
|
||||
`
|
||||
|
||||
// Create Locales directory on default location
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
dirname := filepath.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to default domain file
|
||||
filename := path.Join(dirname, "default.po")
|
||||
filename := filepath.Join(dirname, "default.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(str)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
anotherFilename := filepath.Join(dirname, "another.po")
|
||||
|
||||
af, err := os.Create(anotherFilename)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
|
||||
_, err = af.WriteString(anotherStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Move file close to write the file, so we can use it in the next step
|
||||
f.Close()
|
||||
af.Close()
|
||||
|
||||
// Set package configuration
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
@@ -125,8 +156,8 @@ msgstr[1] ""
|
||||
|
||||
// Test context translations
|
||||
tr = GetC("Some random in a context", "Ctx")
|
||||
if tr != "Some random translation in a context" {
|
||||
t.Errorf("Expected 'Some random translation in a context' but got '%s'", tr)
|
||||
if tr != "Some random Translation in a context" {
|
||||
t.Errorf("Expected 'Some random Translation in a context' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v = "Variable"
|
||||
@@ -139,6 +170,11 @@ msgstr[1] ""
|
||||
if tr != "This one is the plural in a Ctx context: Variable" {
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetD("another", "Another text on a different domain")
|
||||
if tr != "Another text on another domain" {
|
||||
t.Errorf("Expected 'Another text on another domain' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntranslated(t *testing.T) {
|
||||
@@ -161,14 +197,14 @@ msgstr[1] ""
|
||||
`
|
||||
|
||||
// Create Locales directory on default location
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
dirname := filepath.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to default domain file
|
||||
filename := path.Join(dirname, "default.po")
|
||||
filename := filepath.Join(dirname, "default.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -214,6 +250,38 @@ msgstr[1] ""
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoAndPoTranslator(t *testing.T) {
|
||||
|
||||
fixPath, _ := filepath.Abs("./fixtures/")
|
||||
|
||||
Configure(fixPath, "en_GB", "default")
|
||||
|
||||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr := Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
|
||||
}
|
||||
tr = Get("language")
|
||||
if tr != "en_GB" {
|
||||
t.Errorf("Expected 'en_GB'. Got '%s'", tr)
|
||||
}
|
||||
|
||||
// Change Language (locale)
|
||||
SetLanguage("en_AU")
|
||||
|
||||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr = Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
|
||||
}
|
||||
tr = Get("language")
|
||||
if tr != "en_AU" {
|
||||
t.Errorf("Expected 'en_AU'. Got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomains(t *testing.T) {
|
||||
// Set PO content
|
||||
strDefault := `
|
||||
@@ -222,13 +290,13 @@ msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Default text"
|
||||
msgid_plural "Default texts"
|
||||
msgstr[0] "Default translation"
|
||||
msgstr[0] "Default Translation"
|
||||
msgstr[1] "Default translations"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Default context"
|
||||
msgid_plural "Default contexts"
|
||||
msgstr[0] "Default ctx translation"
|
||||
msgstr[0] "Default ctx Translation"
|
||||
msgstr[1] "Default ctx translations"
|
||||
`
|
||||
|
||||
@@ -238,30 +306,30 @@ msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Custom text"
|
||||
msgid_plural "Custom texts"
|
||||
msgstr[0] "Custom translation"
|
||||
msgstr[0] "Custom Translation"
|
||||
msgstr[1] "Custom translations"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Custom context"
|
||||
msgid_plural "Custom contexts"
|
||||
msgstr[0] "Custom ctx translation"
|
||||
msgstr[0] "Custom ctx Translation"
|
||||
msgstr[1] "Custom ctx translations"
|
||||
`
|
||||
|
||||
// Create Locales directory and files on temp location
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
dirname := filepath.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
fDefault, err := os.Create(path.Join(dirname, "default.po"))
|
||||
fDefault, err := os.Create(filepath.Join(dirname, "default.po"))
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer fDefault.Close()
|
||||
|
||||
fCustom, err := os.Create(path.Join(dirname, "custom.po"))
|
||||
fCustom, err := os.Create(filepath.Join(dirname, "custom.po"))
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
@@ -278,19 +346,19 @@ msgstr[1] "Custom ctx translations"
|
||||
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
// Check default domain translation
|
||||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr := Get("Default text")
|
||||
if tr != "Default translation" {
|
||||
t.Errorf("Expected 'Default translation'. Got '%s'", tr)
|
||||
if tr != "Default Translation" {
|
||||
t.Errorf("Expected 'Default Translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetN("Default text", "Default texts", 23)
|
||||
if tr != "Default translations" {
|
||||
t.Errorf("Expected 'Default translations'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetC("Default context", "Ctx")
|
||||
if tr != "Default ctx translation" {
|
||||
t.Errorf("Expected 'Default ctx translation'. Got '%s'", tr)
|
||||
if tr != "Default ctx Translation" {
|
||||
t.Errorf("Expected 'Default ctx Translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetNC("Default context", "Default contexts", 23, "Ctx")
|
||||
if tr != "Default ctx translations" {
|
||||
@@ -299,16 +367,16 @@ msgstr[1] "Custom ctx translations"
|
||||
|
||||
SetDomain("custom")
|
||||
tr = Get("Custom text")
|
||||
if tr != "Custom translation" {
|
||||
t.Errorf("Expected 'Custom translation'. Got '%s'", tr)
|
||||
if tr != "Custom Translation" {
|
||||
t.Errorf("Expected 'Custom Translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetN("Custom text", "Custom texts", 23)
|
||||
if tr != "Custom translations" {
|
||||
t.Errorf("Expected 'Custom translations'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetC("Custom context", "Ctx")
|
||||
if tr != "Custom ctx translation" {
|
||||
t.Errorf("Expected 'Custom ctx translation'. Got '%s'", tr)
|
||||
if tr != "Custom ctx Translation" {
|
||||
t.Errorf("Expected 'Custom ctx Translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetNC("Custom context", "Custom contexts", 23, "Ctx")
|
||||
if tr != "Custom ctx translations" {
|
||||
@@ -334,19 +402,19 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory on default location
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
dirname := filepath.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to default domain file
|
||||
filename := path.Join("/tmp", GetDomain()+".po")
|
||||
filename := filepath.Join("/tmp", GetDomain()+".po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -368,7 +436,7 @@ msgstr "Some random translation in a context"
|
||||
defer wg.Done()
|
||||
|
||||
GetLibrary()
|
||||
SetLibrary(path.Join("/tmp", "gotextlib"))
|
||||
SetLibrary(filepath.Join("/tmp", "gotextlib"))
|
||||
GetDomain()
|
||||
SetDomain("default")
|
||||
GetLanguage()
|
||||
@@ -383,3 +451,60 @@ msgstr "Some random translation in a context"
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestPackageArabicTranslation(t *testing.T) {
|
||||
Configure("fixtures/", "ar", "categories")
|
||||
|
||||
// Plurals formula missing + Plural translation string missing
|
||||
tr := GetD("categories", "Alcohol & Tobacco")
|
||||
if tr != "الكحول والتبغ" {
|
||||
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Plural translation string present without translations, should get the msgid_plural
|
||||
tr = GetND("categories", "%d selected", "%d selected", 10)
|
||||
if tr != "%d selected" {
|
||||
t.Errorf("Expected to get '%%d selected', but got '%s'", tr)
|
||||
}
|
||||
|
||||
//Plurals formula present + Plural translation string present and complete
|
||||
tr = GetND("categories", "Load %d more document", "Load %d more documents", 0)
|
||||
if tr != "حمّل %d مستندات إضافيّة" {
|
||||
t.Errorf("Expected to get 'msgstr[0]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetND("categories", "Load %d more document", "Load %d more documents", 1)
|
||||
if tr != "حمّل مستند واحد إضافي" {
|
||||
t.Errorf("Expected to get 'msgstr[1]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetND("categories", "Load %d more document", "Load %d more documents", 2)
|
||||
if tr != "حمّل مستندين إضافيين" {
|
||||
t.Errorf("Expected to get 'msgstr[2]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetND("categories", "Load %d more document", "Load %d more documents", 6)
|
||||
if tr != "حمّل %d مستندات إضافيّة" {
|
||||
t.Errorf("Expected to get 'msgstr[3]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetND("categories", "Load %d more document", "Load %d more documents", 116)
|
||||
if tr != "حمّل %d مستندا إضافيّا" {
|
||||
t.Errorf("Expected to get 'msgstr[4]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetND("categories", "Load %d more document", "Load %d more documents", 102)
|
||||
if tr != "حمّل %d مستند إضافي" {
|
||||
t.Errorf("Expected to get 'msgstr[5]', but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageArabicMissingPluralForm(t *testing.T) {
|
||||
Configure("fixtures/", "ar", "no_plural_header")
|
||||
|
||||
// Get translation
|
||||
tr := GetD("no_plural_header", "Alcohol & Tobacco")
|
||||
if tr != "الكحول والتبغ" {
|
||||
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
86
helper.go
Normal file
86
helper.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var re = regexp.MustCompile(`%\(([a-zA-Z0-9_]+)\)[.0-9]*[svTtbcdoqXxUeEfFgGp]`)
|
||||
|
||||
// SimplifiedLocale simplified locale like " en_US"/"de_DE "/en_US.UTF-8/zh_CN/zh_TW/el_GR@euro/... to en_US, de_DE, zh_CN, el_GR...
|
||||
func SimplifiedLocale(lang string) string {
|
||||
// en_US/en_US.UTF-8/zh_CN/zh_TW/el_GR@euro/...
|
||||
if idx := strings.Index(lang, ":"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "@"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "."); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
return strings.TrimSpace(lang)
|
||||
}
|
||||
|
||||
// Printf applies text formatting only when needed to parse variables.
|
||||
func Printf(str string, vars ...interface{}) string {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// NPrintf support named format
|
||||
// NPrintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func NPrintf(format string, params map[string]interface{}) {
|
||||
f, p := parseSprintf(format, params)
|
||||
fmt.Printf(f, p...)
|
||||
}
|
||||
|
||||
// Sprintf support named format
|
||||
// Sprintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func Sprintf(format string, params map[string]interface{}) string {
|
||||
f, p := parseSprintf(format, params)
|
||||
return fmt.Sprintf(f, p...)
|
||||
}
|
||||
|
||||
func parseSprintf(format string, params map[string]interface{}) (string, []interface{}) {
|
||||
f, n := reformatSprintf(format)
|
||||
var p []interface{}
|
||||
for _, v := range n {
|
||||
p = append(p, params[v])
|
||||
}
|
||||
return f, p
|
||||
}
|
||||
|
||||
func reformatSprintf(f string) (string, []string) {
|
||||
m := re.FindAllStringSubmatch(f, -1)
|
||||
i := re.FindAllStringSubmatchIndex(f, -1)
|
||||
|
||||
ord := []string{}
|
||||
for _, v := range m {
|
||||
ord = append(ord, v[1])
|
||||
}
|
||||
|
||||
pair := []int{0}
|
||||
for _, v := range i {
|
||||
pair = append(pair, v[2]-1)
|
||||
pair = append(pair, v[3]+1)
|
||||
}
|
||||
pair = append(pair, len(f))
|
||||
plen := len(pair)
|
||||
|
||||
out := ""
|
||||
for n := 0; n < plen; n += 2 {
|
||||
out += f[pair[n]:pair[n+1]]
|
||||
}
|
||||
|
||||
return out, ord
|
||||
}
|
||||
112
helper_test.go
Normal file
112
helper_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimplifiedLocale(t *testing.T) {
|
||||
tr :=SimplifiedLocale("de_DE@euro")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr =SimplifiedLocale("de_DE.UTF-8")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr =SimplifiedLocale("de_DE:latin1")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReformattingSingleNamedPattern(t *testing.T) {
|
||||
pat := "%(name_me)x"
|
||||
|
||||
f, n := reformatSprintf(pat)
|
||||
|
||||
if f != "%x" {
|
||||
t.Errorf("pattern should be %%x but %v", f)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(n, []string{"name_me"}) {
|
||||
t.Errorf("named var should be {name_me} but %v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReformattingMultipleNamedPattern(t *testing.T) {
|
||||
pat := "%(name_me)x and %(another_name)v"
|
||||
|
||||
f, n := reformatSprintf(pat)
|
||||
|
||||
if f != "%x and %v" {
|
||||
t.Errorf("pattern should be %%x and %%v but %v", f)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(n, []string{"name_me", "another_name"}) {
|
||||
t.Errorf("named var should be {name_me, another_name} but %v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReformattingRepeatedNamedPattern(t *testing.T) {
|
||||
pat := "%(name_me)x and %(another_name)v and %(name_me)v"
|
||||
|
||||
f, n := reformatSprintf(pat)
|
||||
|
||||
if f != "%x and %v and %v" {
|
||||
t.Errorf("pattern should be %%x and %%v and %%v but %v", f)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(n, []string{"name_me", "another_name", "name_me"}) {
|
||||
t.Errorf("named var should be {name_me, another_name, name_me} but %v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSprintf(t *testing.T) {
|
||||
pat := "%(brother)s loves %(sister)s. %(sister)s also loves %(brother)s."
|
||||
params := map[string]interface{}{
|
||||
"sister": "Susan",
|
||||
"brother": "Louis",
|
||||
}
|
||||
|
||||
s := Sprintf(pat, params)
|
||||
|
||||
if s != "Louis loves Susan. Susan also loves Louis." {
|
||||
t.Errorf("result should be Louis loves Susan. Susan also love Louis. but %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNPrintf(t *testing.T) {
|
||||
pat := "%(brother)s loves %(sister)s. %(sister)s also loves %(brother)s.\n"
|
||||
params := map[string]interface{}{
|
||||
"sister": "Susan",
|
||||
"brother": "Louis",
|
||||
}
|
||||
|
||||
NPrintf(pat, params)
|
||||
|
||||
}
|
||||
|
||||
func TestSprintfFloatsWithPrecision(t *testing.T) {
|
||||
pat := "%(float)f / %(floatprecision).1f / %(long)g / %(longprecision).3g"
|
||||
params := map[string]interface{}{
|
||||
"float": 5.034560,
|
||||
"floatprecision": 5.03456,
|
||||
"long": 5.03456,
|
||||
"longprecision": 5.03456,
|
||||
}
|
||||
|
||||
s := Sprintf(pat, params)
|
||||
|
||||
expectedresult := "5.034560 / 5.0 / 5.03456 / 5.03"
|
||||
if s != expectedresult {
|
||||
t.Errorf("result should be (%v) but is (%v)", expectedresult, s)
|
||||
}
|
||||
}
|
||||
379
locale.go
379
locale.go
@@ -1,8 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -14,27 +21,28 @@ multiple languages at the same time by working with this object.
|
||||
Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"encoding/gob"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
|
||||
|
||||
// Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.po'
|
||||
// Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.{po,mo}'
|
||||
l.AddDomain("default")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.po')
|
||||
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.{po,mo}')
|
||||
l.AddDomain("extras")
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("extras", "Translate this"))
|
||||
}
|
||||
|
||||
fmt.Println(l.GetD("extras", "Translate this"))
|
||||
}
|
||||
*/
|
||||
type Locale struct {
|
||||
// Path to locale files.
|
||||
@@ -43,136 +51,403 @@ type Locale struct {
|
||||
// Language for this Locale
|
||||
lang string
|
||||
|
||||
// List of available domains for this locale.
|
||||
domains map[string]*Po
|
||||
// List of available Domains for this locale.
|
||||
Domains map[string]Translator
|
||||
|
||||
// First AddDomain is default Domain
|
||||
defaultDomain string
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewLocale creates and initializes a new Locale object for a given language.
|
||||
// It receives a path for the i18n files directory (p) and a language code to use (l).
|
||||
// It receives a path for the i18n .po/.mo files directory (p) and a language code to use (l).
|
||||
func NewLocale(p, l string) *Locale {
|
||||
return &Locale{
|
||||
path: p,
|
||||
lang: l,
|
||||
domains: make(map[string]*Po),
|
||||
lang: SimplifiedLocale(l),
|
||||
Domains: make(map[string]Translator),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Locale) findPO(dom string) string {
|
||||
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+".po")
|
||||
//SetLang ...
|
||||
func (l *Locale) SetLang(lang string) {
|
||||
l.lang = lang
|
||||
}
|
||||
|
||||
//GetLang ...
|
||||
func (l *Locale) GetLang() string {
|
||||
return l.lang
|
||||
}
|
||||
|
||||
//SetPath ...
|
||||
func (l *Locale) SetPath(path string) {
|
||||
l.path = path
|
||||
}
|
||||
|
||||
func (l *Locale) findExt(dom, ext string) string {
|
||||
filename := filepath.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+".po")
|
||||
filename = filepath.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
filename = path.Join(l.path, l.lang, dom+".po")
|
||||
filename = filepath.Join(l.path, l.lang, dom+"."+ext)
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], dom+".po")
|
||||
filename = filepath.Join(l.path, l.lang[:2], dom+"."+ext)
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
return filename
|
||||
return ""
|
||||
}
|
||||
|
||||
// AddDomain creates a new domain for a given locale object and initializes the Po object.
|
||||
// If the domain exists, it gets reloaded.
|
||||
func (l *Locale) AddDomain(dom string) {
|
||||
po := new(Po)
|
||||
var poObj Translator
|
||||
|
||||
// Parse file.
|
||||
po.ParseFile(l.findPO(dom))
|
||||
file := l.findExt(dom, "po")
|
||||
if file != "" {
|
||||
poObj = new(Po)
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
file = l.findExt(dom, "mo")
|
||||
if file != "" {
|
||||
poObj = new(Mo)
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
// fallback return if no file found with
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Save new domain
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if l.domains == nil {
|
||||
l.domains = make(map[string]*Po)
|
||||
if l.Domains == nil {
|
||||
l.Domains = make(map[string]Translator)
|
||||
}
|
||||
l.domains[dom] = po
|
||||
if l.defaultDomain == "" {
|
||||
l.defaultDomain = dom
|
||||
}
|
||||
l.Domains[dom] = poObj
|
||||
|
||||
// Unlock "Save new domain"
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// Get uses a domain "default" to return the corresponding translation of a given string.
|
||||
// AddTranslator takes a domain name and a Translator object to make it available in the Locale object.
|
||||
func (l *Locale) AddTranslator(dom string, tr Translator) {
|
||||
l.Lock()
|
||||
|
||||
if l.Domains == nil {
|
||||
l.Domains = make(map[string]Translator)
|
||||
}
|
||||
if l.defaultDomain == "" {
|
||||
l.defaultDomain = dom
|
||||
}
|
||||
l.Domains[dom] = tr
|
||||
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for Locale configuration
|
||||
func (l *Locale) GetDomain() string {
|
||||
l.RLock()
|
||||
dom := l.defaultDomain
|
||||
l.RUnlock()
|
||||
return dom
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used.
|
||||
func (l *Locale) SetDomain(dom string) {
|
||||
l.Lock()
|
||||
l.defaultDomain = dom
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// Get uses a domain "default" to return the corresponding Translation of a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) Get(str string, vars ...interface{}) string {
|
||||
return l.GetD("default", str, vars...)
|
||||
return l.GetD(l.GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of translation for the given string in the "default" domain.
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return l.GetND("default", str, plural, n, vars...)
|
||||
return l.GetND(l.GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding translation in the given domain for the given string.
|
||||
// GetD returns the corresponding Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
||||
return l.GetND(dom, str, str, 1, vars...)
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].Get(str, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form of translation in the given domain for the given string.
|
||||
// GetND retrieves the (N)th plural form of Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.domains != nil {
|
||||
if _, ok := l.domains[dom]; ok {
|
||||
if l.domains[dom] != nil {
|
||||
return l.domains[dom].GetN(str, plural, n, vars...)
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetN(str, plural, n, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return printf(plural, vars...)
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC uses a domain "default" to return the corresponding translation of the given string in the given context.
|
||||
// GetC uses a domain "default" to return the corresponding Translation of the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return l.GetDC("default", str, ctx, vars...)
|
||||
return l.GetDC(l.GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the "default" domain.
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return l.GetNDC("default", str, plural, n, ctx, vars...)
|
||||
return l.GetNDC(l.GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetDC returns the corresponding translation in the given domain for the given string in the given context.
|
||||
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||
return l.GetNDC(dom, str, str, 1, ctx, vars...)
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetC(str, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of translation in the given domain for the given string in the given context.
|
||||
// GetNDC retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.domains != nil {
|
||||
if _, ok := l.domains[dom]; ok {
|
||||
if l.domains[dom] != nil {
|
||||
return l.domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return printf(plural, vars...)
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetE uses a domain "default" to return the corresponding Translation of a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (l *Locale) GetE(str string, vars ...interface{}) (string, bool) {
|
||||
return l.GetDE(l.GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetNE retrieves the (N)th plural form of Translation for the given string in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (l *Locale) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||
return l.GetNDE(l.GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetDE returns the corresponding Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (l *Locale) GetDE(dom, str string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetE(str, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetNDE retrieves the (N)th plural form of Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (l *Locale) GetNDE(dom, str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetNE(str, plural, n, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetCE uses a domain "default" to return the corresponding Translation of the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (l *Locale) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
|
||||
return l.GetDCE(l.GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNCE retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (l *Locale) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||
return l.GetNDCE(l.GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetDCE returns the corresponding Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetDCE(dom, str, ctx string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetCE(str, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetNDCE retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (l *Locale) GetNDCE(dom, str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetNCE(str, plural, n, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
return "", false
|
||||
}
|
||||
|
||||
// LocaleEncoding is used as intermediary storage to encode Locale objects to Gob.
|
||||
type LocaleEncoding struct {
|
||||
Path string
|
||||
Lang string
|
||||
Domains map[string][]byte
|
||||
DefaultDomain string
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding BinaryMarshaler interface
|
||||
func (l *Locale) MarshalBinary() ([]byte, error) {
|
||||
obj := new(LocaleEncoding)
|
||||
obj.DefaultDomain = l.defaultDomain
|
||||
obj.Domains = make(map[string][]byte)
|
||||
for k, v := range l.Domains {
|
||||
var err error
|
||||
obj.Domains[k], err = v.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
obj.Lang = l.lang
|
||||
obj.Path = l.path
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding BinaryUnmarshaler interface
|
||||
func (l *Locale) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(LocaleEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.defaultDomain = obj.DefaultDomain
|
||||
l.lang = obj.Lang
|
||||
l.path = obj.Path
|
||||
|
||||
// Decode Domains
|
||||
l.Domains = make(map[string]Translator)
|
||||
for k, v := range obj.Domains {
|
||||
var tr TranslatorEncoding
|
||||
buff := bytes.NewBuffer(v)
|
||||
trDecoder := gob.NewDecoder(buff)
|
||||
err := trDecoder.Decode(&tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Domains[k] = tr.GetTranslator()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
339
locale_test.go
339
locale_test.go
@@ -1,8 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -45,26 +50,26 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory with simplified language code
|
||||
dirname := path.Join("/tmp", "en", "LC_MESSAGES")
|
||||
dirname := filepath.Join("/tmp", "en", "LC_MESSAGES")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to file
|
||||
filename := path.Join(dirname, "my_domain.po")
|
||||
filename := filepath.Join(dirname, "my_domain.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -81,7 +86,7 @@ msgstr "More translation"
|
||||
l := NewLocale("/tmp", "en_US")
|
||||
|
||||
// Force nil domain storage
|
||||
l.domains = nil
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("my_domain")
|
||||
@@ -105,7 +110,17 @@ msgstr "More translation"
|
||||
}
|
||||
|
||||
// Test context translations
|
||||
tr = l.GetC("Some random in a context", "Ctx")
|
||||
if tr != "Some random Translation in a context" {
|
||||
t.Errorf("Expected 'Some random Translation in a context'. Got '%s'", tr)
|
||||
}
|
||||
|
||||
v = "Test"
|
||||
tr = l.GetNC("One with var: %s", "Several with vars: %s", 23, "Ctx", v)
|
||||
if tr != "This one is the plural in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Test'. Got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetDC("my_domain", "One with var: %s", "Ctx", v)
|
||||
if tr != "This one is the singular in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the singular in a Ctx context: Test' but got '%s'", tr)
|
||||
@@ -117,10 +132,10 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last translation
|
||||
// Test last Translation
|
||||
tr = l.GetD("my_domain", "More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
if tr != "More Translation" {
|
||||
t.Errorf("Expected 'More Translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,26 +180,26 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory with simplified language code
|
||||
dirname := path.Join("/tmp", "en", "LC_MESSAGES")
|
||||
dirname := filepath.Join("/tmp", "en", "LC_MESSAGES")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to file
|
||||
filename := path.Join(dirname, "my_domain.po")
|
||||
filename := filepath.Join(dirname, "my_domain.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -201,13 +216,28 @@ msgstr "More translation"
|
||||
l := NewLocale("/tmp", "en_US")
|
||||
|
||||
// Force nil domain storage
|
||||
l.domains = nil
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("my_domain")
|
||||
|
||||
// Test non-existent "deafult" domain responses
|
||||
tr := l.Get("My text")
|
||||
// Test non-existent "default" domain responses
|
||||
tr := l.GetDomain()
|
||||
if tr != "my_domain" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Set default domain to make it fail
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'default' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.Get("My text")
|
||||
if tr != "My text" {
|
||||
t.Errorf("Expected 'My text' but got '%s'", tr)
|
||||
}
|
||||
@@ -225,6 +255,11 @@ msgstr "More translation"
|
||||
}
|
||||
|
||||
tr = l.GetN("This is a test", "This are tests", 1)
|
||||
if tr != "This is a test" {
|
||||
t.Errorf("Expected 'This is a test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This is a test", "This are tests", 7)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
@@ -236,8 +271,138 @@ msgstr "More translation"
|
||||
}
|
||||
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 2)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("/tmp", "golem")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("my_domain")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "my_domain" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 111)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("fixtures/", "fr_FR")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 21)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("fixtures/", "de_DE")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 2)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("fixtures/", "de_AT")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.GetNDC("mega", "This one has invalid syntax translations", "plural", 2, "ctx")
|
||||
if tr != "plural" {
|
||||
t.Errorf("Expected 'plural' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 14)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,14 +425,14 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
`
|
||||
|
||||
// Create Locales directory with simplified language code
|
||||
dirname := path.Join("/tmp", "es")
|
||||
dirname := filepath.Join("/tmp", "es")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to file
|
||||
filename := path.Join(dirname, "race.po")
|
||||
filename := filepath.Join(dirname, "race.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -280,7 +445,7 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
// Create Locale
|
||||
l := NewLocale("/tmp", "es")
|
||||
|
||||
// Init sync channels
|
||||
@@ -306,3 +471,133 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
<-ac
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestAddTranslator(t *testing.T) {
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Parse file
|
||||
po.ParseFile("fixtures/en_US/default.po")
|
||||
|
||||
// Create Locale
|
||||
l := NewLocale("", "en")
|
||||
|
||||
// Add PO Translator to Locale object
|
||||
l.AddTranslator("default", po)
|
||||
|
||||
// Test translations
|
||||
tr := l.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = l.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArabicTranslation(t *testing.T) {
|
||||
// Create Locale
|
||||
l := NewLocale("fixtures/", "ar")
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("categories")
|
||||
|
||||
// Plurals formula missing + Plural translation string missing
|
||||
tr := l.GetD("categories", "Alcohol & Tobacco")
|
||||
if tr != "الكحول والتبغ" {
|
||||
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Plural translation string present without translations, should get the msgid_plural
|
||||
tr = l.GetND("categories", "%d selected", "%d selected", 10)
|
||||
if tr != "%d selected" {
|
||||
t.Errorf("Expected to get '%%d selected', but got '%s'", tr)
|
||||
}
|
||||
|
||||
//Plurals formula present + Plural translation string present and complete
|
||||
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 0)
|
||||
if tr != "حمّل %d مستندات إضافيّة" {
|
||||
t.Errorf("Expected to get 'msgstr[0]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 1)
|
||||
if tr != "حمّل مستند واحد إضافي" {
|
||||
t.Errorf("Expected to get 'msgstr[1]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 2)
|
||||
if tr != "حمّل مستندين إضافيين" {
|
||||
t.Errorf("Expected to get 'msgstr[2]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 6)
|
||||
if tr != "حمّل %d مستندات إضافيّة" {
|
||||
t.Errorf("Expected to get 'msgstr[3]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 116)
|
||||
if tr != "حمّل %d مستندا إضافيّا" {
|
||||
t.Errorf("Expected to get 'msgstr[4]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 102)
|
||||
if tr != "حمّل %d مستند إضافي" {
|
||||
t.Errorf("Expected to get 'msgstr[5]', but got '%s'", tr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestArabicMissingPluralForm(t *testing.T) {
|
||||
// Create Locale
|
||||
l := NewLocale("fixtures/", "ar")
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("no_plural_header")
|
||||
|
||||
// Get translation
|
||||
tr := l.GetD("no_plural_header", "Alcohol & Tobacco")
|
||||
if tr != "الكحول والتبغ" {
|
||||
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleBinaryEncoding(t *testing.T) {
|
||||
// Create Locale
|
||||
l := NewLocale("fixtures/", "en_US")
|
||||
l.AddDomain("default")
|
||||
|
||||
buff, err := l.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l2 := new(Locale)
|
||||
err = l2.UnmarshalBinary(buff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check object properties
|
||||
if l.path != l2.path {
|
||||
t.Fatalf("path doesn't match: '%s' vs '%s'", l.path, l2.path)
|
||||
}
|
||||
if l.lang != l2.lang {
|
||||
t.Fatalf("lang doesn't match: '%s' vs '%s'", l.lang, l2.lang)
|
||||
}
|
||||
if l.defaultDomain != l2.defaultDomain {
|
||||
t.Fatalf("defaultDomain doesn't match: '%s' vs '%s'", l.defaultDomain, l2.defaultDomain)
|
||||
}
|
||||
|
||||
// Check translations
|
||||
if l.Get("My text") != l2.Get("My text") {
|
||||
t.Errorf("'%s' is different from '%s", l.Get("My text"), l2.Get("My text"))
|
||||
}
|
||||
if l.Get("More") != l2.Get("More") {
|
||||
t.Errorf("'%s' is different from '%s", l.Get("More"), l2.Get("More"))
|
||||
}
|
||||
if l.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE") != l2.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE") {
|
||||
t.Errorf("'%s' is different from '%s", l.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE"), l2.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE"))
|
||||
}
|
||||
}
|
||||
|
||||
556
mo.go
Normal file
556
mo.go
Normal file
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext/plurals"
|
||||
)
|
||||
|
||||
const (
|
||||
// MoMagicLittleEndian encoding
|
||||
MoMagicLittleEndian = 0x950412de
|
||||
// MoMagicBigEndian encoding
|
||||
MoMagicBigEndian = 0xde120495
|
||||
|
||||
// EotSeparator msgctxt and msgid separator
|
||||
EotSeparator = "\x04"
|
||||
// NulSeparator msgid and msgstr separator
|
||||
NulSeparator = "\x00"
|
||||
)
|
||||
|
||||
/*
|
||||
Mo parses the content of any MO file and provides all the Translation functions needed.
|
||||
It's the base object used by all package methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||
|
||||
Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create po object
|
||||
po := gotext.NewMoTranslator()
|
||||
|
||||
// Parse .po file
|
||||
po.ParseFile("/path/to/po/file/translations.mo")
|
||||
|
||||
// Get Translation
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
|
||||
*/
|
||||
type Mo struct {
|
||||
// Headers storage
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*Translation
|
||||
contexts map[string]map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
}
|
||||
|
||||
// NewMoTranslator creates a new Mo object with the Translator interface
|
||||
func NewMoTranslator() Translator {
|
||||
return new(Mo)
|
||||
}
|
||||
|
||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||
func (mo *Mo) ParseFile(f string) {
|
||||
// Check if file exists
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that isn't a directory
|
||||
if info.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse file content
|
||||
data, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mo.Parse(data)
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
func (mo *Mo) Parse(buf []byte) {
|
||||
// Lock while parsing
|
||||
mo.Lock()
|
||||
|
||||
// Init storage
|
||||
if mo.translations == nil {
|
||||
mo.translations = make(map[string]*Translation)
|
||||
mo.contexts = make(map[string]map[string]*Translation)
|
||||
}
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
|
||||
var magicNumber uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
var bo binary.ByteOrder
|
||||
switch magicNumber {
|
||||
case MoMagicLittleEndian:
|
||||
bo = binary.LittleEndian
|
||||
case MoMagicBigEndian:
|
||||
bo = binary.BigEndian
|
||||
default:
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid magic number")
|
||||
}
|
||||
|
||||
var header struct {
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
MsgIDCount uint32
|
||||
MsgIDOffset uint32
|
||||
MsgStrOffset uint32
|
||||
HashSize uint32
|
||||
HashOffset uint32
|
||||
}
|
||||
if err := binary.Read(r, bo, &header); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if v := header.MajorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
if v := header.MinorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
|
||||
msgIDStart := make([]uint32, header.MsgIDCount)
|
||||
msgIDLen := make([]uint32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgIDOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgIDLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgIDStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
msgStrStart := make([]int32, header.MsgIDCount)
|
||||
msgStrLen := make([]int32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if _, err := r.Seek(int64(msgIDStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgIDData := make([]byte, msgIDLen[i])
|
||||
if _, err := r.Read(msgIDData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgStrData := make([]byte, msgStrLen[i])
|
||||
if _, err := r.Read(msgStrData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if len(msgIDData) == 0 {
|
||||
mo.addTranslation(msgIDData, msgStrData)
|
||||
} else {
|
||||
mo.addTranslation(msgIDData, msgStrData)
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock to parse headers
|
||||
mo.Unlock()
|
||||
|
||||
// Parse headers
|
||||
mo.parseHeaders()
|
||||
return
|
||||
// return nil
|
||||
}
|
||||
|
||||
func (mo *Mo) addTranslation(msgid, msgstr []byte) {
|
||||
translation := NewTranslation()
|
||||
var msgctxt []byte
|
||||
var msgidPlural []byte
|
||||
|
||||
d := bytes.Split(msgid, []byte(EotSeparator))
|
||||
if len(d) == 1 {
|
||||
msgid = d[0]
|
||||
} else {
|
||||
msgid, msgctxt = d[1], d[0]
|
||||
}
|
||||
|
||||
dd := bytes.Split(msgid, []byte(NulSeparator))
|
||||
if len(dd) > 1 {
|
||||
msgid = dd[0]
|
||||
dd = dd[1:]
|
||||
}
|
||||
|
||||
translation.ID = string(msgid)
|
||||
|
||||
msgidPlural = bytes.Join(dd, []byte(NulSeparator))
|
||||
if len(msgidPlural) > 0 {
|
||||
translation.PluralID = string(msgidPlural)
|
||||
}
|
||||
|
||||
ddd := bytes.Split(msgstr, []byte(NulSeparator))
|
||||
if len(ddd) > 0 {
|
||||
for i, s := range ddd {
|
||||
translation.Trs[i] = string(s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(msgctxt) > 0 {
|
||||
// With context...
|
||||
if _, ok := mo.contexts[string(msgctxt)]; !ok {
|
||||
mo.contexts[string(msgctxt)] = make(map[string]*Translation)
|
||||
}
|
||||
mo.contexts[string(msgctxt)][translation.ID] = translation
|
||||
} else {
|
||||
mo.translations[translation.ID] = translation
|
||||
}
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers
|
||||
func (mo *Mo) parseHeaders() {
|
||||
// Make sure we end with 2 carriage returns.
|
||||
raw := mo.Get("") + "\n\n"
|
||||
|
||||
// Read
|
||||
reader := bufio.NewReader(strings.NewReader(raw))
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
var err error
|
||||
|
||||
// Sync Headers write.
|
||||
mo.Lock()
|
||||
defer mo.Unlock()
|
||||
|
||||
mo.Headers, err = tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get/save needed headers
|
||||
mo.Language = mo.Headers.Get("Language")
|
||||
mo.PluralForms = mo.Headers.Get("Plural-Forms")
|
||||
|
||||
// Parse Plural-Forms formula
|
||||
if mo.PluralForms == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Split plural form header value
|
||||
pfs := strings.Split(mo.PluralForms, ";")
|
||||
|
||||
// Parse values
|
||||
for _, i := range pfs {
|
||||
vs := strings.SplitN(i, "=", 2)
|
||||
if len(vs) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(vs[0]) {
|
||||
case "nplurals":
|
||||
mo.nplurals, _ = strconv.Atoi(vs[1])
|
||||
|
||||
case "plural":
|
||||
mo.plural = vs[1]
|
||||
|
||||
if expr, err := plurals.Compile(mo.plural); err == nil {
|
||||
mo.pluralforms = expr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pluralForm calculates the plural form index corresponding to n.
|
||||
// Returns 0 on error
|
||||
func (mo *Mo) pluralForm(n int) int {
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
// Failure fallback
|
||||
if mo.pluralforms == nil {
|
||||
/* Use the Germanic plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
|
||||
}
|
||||
return mo.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// Get retrieves the corresponding Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (mo *Mo) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
return Printf(mo.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
return Printf(mo.translations[str].GetN(mo.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.contexts != nil {
|
||||
if _, ok := mo.contexts[ctx]; ok {
|
||||
if mo.contexts[ctx] != nil {
|
||||
if _, ok := mo.contexts[ctx][str]; ok {
|
||||
return Printf(mo.contexts[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.contexts != nil {
|
||||
if _, ok := mo.contexts[ctx]; ok {
|
||||
if mo.contexts[ctx] != nil {
|
||||
if _, ok := mo.contexts[ctx][str]; ok {
|
||||
return Printf(mo.contexts[ctx][str].GetN(mo.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetE retrieves the corresponding Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (mo *Mo) GetE(str string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
if fmt, ok := mo.translations[str].GetE(); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetNE retrieves the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (mo *Mo) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
if fmt, ok := mo.translations[str].GetNE(mo.pluralForm(n)); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetCE retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (mo *Mo) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.contexts != nil {
|
||||
if _, ok := mo.contexts[ctx]; ok {
|
||||
if mo.contexts[ctx] != nil {
|
||||
if _, ok := mo.contexts[ctx][str]; ok {
|
||||
if fmt, ok := mo.contexts[ctx][str].GetE(); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetNCE retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (mo *Mo) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.contexts != nil {
|
||||
if _, ok := mo.contexts[ctx]; ok {
|
||||
if mo.contexts[ctx] != nil {
|
||||
if _, ok := mo.contexts[ctx][str]; ok {
|
||||
if fmt, ok := mo.contexts[ctx][str].GetNE(mo.pluralForm(n)); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||
func (mo *Mo) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
obj.Headers = mo.Headers
|
||||
obj.Language = mo.Language
|
||||
obj.PluralForms = mo.PluralForms
|
||||
obj.Nplurals = mo.nplurals
|
||||
obj.Plural = mo.plural
|
||||
obj.Translations = mo.translations
|
||||
obj.Contexts = mo.contexts
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
|
||||
func (mo *Mo) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mo.Headers = obj.Headers
|
||||
mo.Language = obj.Language
|
||||
mo.PluralForms = obj.PluralForms
|
||||
mo.nplurals = obj.Nplurals
|
||||
mo.plural = obj.Plural
|
||||
mo.translations = obj.Translations
|
||||
mo.contexts = obj.Contexts
|
||||
|
||||
if expr, err := plurals.Compile(mo.plural); err == nil {
|
||||
mo.pluralforms = expr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
234
mo_test.go
Normal file
234
mo_test.go
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMo_Get(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
mo := new(Mo)
|
||||
|
||||
// Try to parse a directory
|
||||
mo.ParseFile(path.Clean(os.TempDir()))
|
||||
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
|
||||
// Test translations
|
||||
tr := mo.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = mo.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMo(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
mo := new(Mo)
|
||||
|
||||
// Try to parse a directory
|
||||
mo.ParseFile(path.Clean(os.TempDir()))
|
||||
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
|
||||
// Test translations
|
||||
tr := mo.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
tr = mo.Get("One with var: %s", v)
|
||||
if tr != "This one is the singular: Variable" {
|
||||
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line id
|
||||
tr = mo.Get("multilineid")
|
||||
if tr != "id with multiline content" {
|
||||
t.Errorf("Expected 'id with multiline content' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line plural id
|
||||
tr = mo.Get("multilinepluralid")
|
||||
if tr != "plural id with multiline content" {
|
||||
t.Errorf("Expected 'plural id with multiline content' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line
|
||||
tr = mo.Get("Multi-line")
|
||||
if tr != "Multi line" {
|
||||
t.Errorf("Expected 'Multi line' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = mo.GetN("One with var: %s", "Several with vars: %s", 2, v)
|
||||
if tr != "This one is the plural: Variable" {
|
||||
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test not existent translations
|
||||
tr = mo.Get("This is a test")
|
||||
if tr != "This is a test" {
|
||||
t.Errorf("Expected 'This is a test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.GetN("This is a test", "This are tests", 100)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test context translations
|
||||
v = "Test"
|
||||
tr = mo.GetC("One with var: %s", "Ctx", v)
|
||||
if tr != "This one is the singular in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the singular in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = mo.GetNC("One with var: %s", "Several with vars: %s", 17, "Ctx", v)
|
||||
if tr != "This one is the plural in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test default plural vs singular return responses
|
||||
tr = mo.GetN("Original", "Original plural", 4)
|
||||
if tr != "Original plural" {
|
||||
t.Errorf("Expected 'Original plural' but got '%s'", tr)
|
||||
}
|
||||
tr = mo.GetN("Original", "Original plural", 1)
|
||||
if tr != "Original" {
|
||||
t.Errorf("Expected 'Original' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test empty Translation strings
|
||||
tr = mo.Get("Empty Translation")
|
||||
if tr != "Empty Translation" {
|
||||
t.Errorf("Expected 'Empty Translation' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.Get("Empty plural form singular")
|
||||
if tr != "Singular translated" {
|
||||
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.GetN("Empty plural form singular", "Empty plural form", 1)
|
||||
if tr != "Singular translated" {
|
||||
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.GetN("Empty plural form singular", "Empty plural form", 2)
|
||||
if tr != "Empty plural form" {
|
||||
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last Translation
|
||||
tr = mo.Get("More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := new(Mo)
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
rc := make(chan bool)
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(mo *Mo, done chan bool) {
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
done <- true
|
||||
}(mo, pc)
|
||||
|
||||
// Read some Translation on a goroutine
|
||||
go func(mo *Mo, done chan bool) {
|
||||
mo.Get("My text")
|
||||
done <- true
|
||||
}(mo, rc)
|
||||
|
||||
// Read something at top level
|
||||
mo.Get("My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestNewMoTranslatorRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := NewMoTranslator()
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
rc := make(chan bool)
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
done <- true
|
||||
}(mo, pc)
|
||||
|
||||
// Read some Translation on a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
mo.Get("My text")
|
||||
done <- true
|
||||
}(mo, rc)
|
||||
|
||||
// Read something at top level
|
||||
mo.Get("My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestMoBinaryEncoding(t *testing.T) {
|
||||
// Create mo objects
|
||||
mo := new(Mo)
|
||||
mo2 := new(Mo)
|
||||
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
|
||||
buff, err := mo.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = mo2.UnmarshalBinary(buff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test translations
|
||||
tr := mo2.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = mo2.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
429
plurals/compiler.go
Normal file
429
plurals/compiler.go
Normal file
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package plurals is the pluralform compiler to get the correct translation id of the plural string
|
||||
*/
|
||||
package plurals
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type match struct {
|
||||
openPos int
|
||||
closePos int
|
||||
}
|
||||
|
||||
var pat = regexp.MustCompile(`(\?|:|\|\||&&|==|!=|>=|>|<=|<|%|\d+|n)`)
|
||||
|
||||
type testToken interface {
|
||||
compile(tokens []string) (test test, err error)
|
||||
}
|
||||
|
||||
type cmpTestBuilder func(val uint32, flipped bool) test
|
||||
type logicTestBuild func(left test, right test) test
|
||||
|
||||
var ternaryToken ternaryStruct
|
||||
|
||||
type ternaryStruct struct{}
|
||||
|
||||
func (ternaryStruct) compile(tokens []string) (expr Expression, err error) {
|
||||
main, err := splitTokens(tokens, "?")
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
test, err := compileTest(strings.Join(main.Left, ""))
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
actions, err := splitTokens(main.Right, ":")
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
trueAction, err := compileExpression(strings.Join(actions.Left, ""))
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
falseAction, err := compileExpression(strings.Join(actions.Right, ""))
|
||||
if err != nil {
|
||||
return expr, nil
|
||||
}
|
||||
return ternary{
|
||||
test: test,
|
||||
trueExpr: trueAction,
|
||||
falseExpr: falseAction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var constToken constValStruct
|
||||
|
||||
type constValStruct struct{}
|
||||
|
||||
func (constValStruct) compile(tokens []string) (expr Expression, err error) {
|
||||
if len(tokens) == 0 {
|
||||
return expr, errors.New("got nothing instead of constant")
|
||||
}
|
||||
if len(tokens) != 1 {
|
||||
return expr, fmt.Errorf("invalid constant: %s", strings.Join(tokens, ""))
|
||||
}
|
||||
i, err := strconv.Atoi(tokens[0])
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
return constValue{value: i}, nil
|
||||
}
|
||||
|
||||
func compileLogicTest(tokens []string, sep string, builder logicTestBuild) (test test, err error) {
|
||||
split, err := splitTokens(tokens, sep)
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
left, err := compileTest(strings.Join(split.Left, ""))
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
right, err := compileTest(strings.Join(split.Right, ""))
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
return builder(left, right), nil
|
||||
}
|
||||
|
||||
var orToken orStruct
|
||||
|
||||
type orStruct struct{}
|
||||
|
||||
func (orStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileLogicTest(tokens, "||", buildOr)
|
||||
}
|
||||
func buildOr(left test, right test) test {
|
||||
return or{left: left, right: right}
|
||||
}
|
||||
|
||||
var andToken andStruct
|
||||
|
||||
type andStruct struct{}
|
||||
|
||||
func (andStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileLogicTest(tokens, "&&", buildAnd)
|
||||
}
|
||||
func buildAnd(left test, right test) test {
|
||||
return and{left: left, right: right}
|
||||
}
|
||||
|
||||
func compileMod(tokens []string) (math math, err error) {
|
||||
split, err := splitTokens(tokens, "%")
|
||||
if err != nil {
|
||||
return math, err
|
||||
}
|
||||
if len(split.Left) != 1 || split.Left[0] != "n" {
|
||||
return math, errors.New("Modulus operation requires 'n' as left operand")
|
||||
}
|
||||
if len(split.Right) != 1 {
|
||||
return math, errors.New("Modulus operation requires simple integer as right operand")
|
||||
}
|
||||
i, err := parseUint32(split.Right[0])
|
||||
if err != nil {
|
||||
return math, err
|
||||
}
|
||||
return mod{value: uint32(i)}, nil
|
||||
}
|
||||
|
||||
func subPipe(modTokens []string, actionTokens []string, builder cmpTestBuilder, flipped bool) (test test, err error) {
|
||||
modifier, err := compileMod(modTokens)
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
if len(actionTokens) != 1 {
|
||||
return test, errors.New("can only get modulus of integer")
|
||||
}
|
||||
i, err := parseUint32(actionTokens[0])
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
action := builder(uint32(i), flipped)
|
||||
return pipe{
|
||||
modifier: modifier,
|
||||
action: action,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func compileEquality(tokens []string, sep string, builder cmpTestBuilder) (test test, err error) {
|
||||
split, err := splitTokens(tokens, sep)
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
if len(split.Left) == 1 && split.Left[0] == "n" {
|
||||
if len(split.Right) != 1 {
|
||||
return test, errors.New("test can only compare n to integers")
|
||||
}
|
||||
i, err := parseUint32(split.Right[0])
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
return builder(i, false), nil
|
||||
} else if len(split.Right) == 1 && split.Right[0] == "n" {
|
||||
if len(split.Left) != 1 {
|
||||
return test, errors.New("test can only compare n to integers")
|
||||
}
|
||||
i, err := parseUint32(split.Left[0])
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
return builder(i, true), nil
|
||||
} else if contains(split.Left, "n") && contains(split.Left, "%") {
|
||||
return subPipe(split.Left, split.Right, builder, false)
|
||||
}
|
||||
return test, errors.New("equality test must have 'n' as one of the two tests")
|
||||
|
||||
}
|
||||
|
||||
var eqToken eqStruct
|
||||
|
||||
type eqStruct struct{}
|
||||
|
||||
func (eqStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "==", buildEq)
|
||||
}
|
||||
func buildEq(val uint32, flipped bool) test {
|
||||
return equal{value: val}
|
||||
}
|
||||
|
||||
var neqToken neqStruct
|
||||
|
||||
type neqStruct struct{}
|
||||
|
||||
func (neqStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "!=", buildNeq)
|
||||
}
|
||||
func buildNeq(val uint32, flipped bool) test {
|
||||
return notequal{value: val}
|
||||
}
|
||||
|
||||
var gtToken gtStruct
|
||||
|
||||
type gtStruct struct{}
|
||||
|
||||
func (gtStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, ">", buildGt)
|
||||
}
|
||||
func buildGt(val uint32, flipped bool) test {
|
||||
return gt{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
var gteToken gteStruct
|
||||
|
||||
type gteStruct struct{}
|
||||
|
||||
func (gteStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, ">=", buildGte)
|
||||
}
|
||||
func buildGte(val uint32, flipped bool) test {
|
||||
return gte{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
var ltToken ltStruct
|
||||
|
||||
type ltStruct struct{}
|
||||
|
||||
func (ltStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "<", buildLt)
|
||||
}
|
||||
func buildLt(val uint32, flipped bool) test {
|
||||
return lt{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
var lteToken lteStruct
|
||||
|
||||
type lteStruct struct{}
|
||||
|
||||
func (lteStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "<=", buildLte)
|
||||
}
|
||||
func buildLte(val uint32, flipped bool) test {
|
||||
return lte{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
type testTokenDef struct {
|
||||
op string
|
||||
token testToken
|
||||
}
|
||||
|
||||
var precedence = []testTokenDef{
|
||||
{op: "||", token: orToken},
|
||||
{op: "&&", token: andToken},
|
||||
{op: "==", token: eqToken},
|
||||
{op: "!=", token: neqToken},
|
||||
{op: ">=", token: gteToken},
|
||||
{op: ">", token: gtToken},
|
||||
{op: "<=", token: lteToken},
|
||||
{op: "<", token: ltToken},
|
||||
}
|
||||
|
||||
type splitted struct {
|
||||
Left []string
|
||||
Right []string
|
||||
}
|
||||
|
||||
// Find index of token in list of tokens
|
||||
func index(tokens []string, sep string) int {
|
||||
for index, token := range tokens {
|
||||
if token == sep {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Split a list of tokens by a token into a splitted struct holding the tokens
|
||||
// before and after the token to be split by.
|
||||
func splitTokens(tokens []string, sep string) (s splitted, err error) {
|
||||
index := index(tokens, sep)
|
||||
if index == -1 {
|
||||
return s, fmt.Errorf("'%s' not found in ['%s']", sep, strings.Join(tokens, "','"))
|
||||
}
|
||||
return splitted{
|
||||
Left: tokens[:index],
|
||||
Right: tokens[index+1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Scan a string for parenthesis
|
||||
func scan(s string) <-chan match {
|
||||
ch := make(chan match)
|
||||
go func() {
|
||||
depth := 0
|
||||
opener := 0
|
||||
for index, char := range s {
|
||||
switch char {
|
||||
case '(':
|
||||
if depth == 0 {
|
||||
opener = index
|
||||
}
|
||||
depth++
|
||||
case ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
ch <- match{
|
||||
openPos: opener,
|
||||
closePos: index + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Split the string into tokens
|
||||
func split(s string) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
s = strings.Replace(s, " ", "", -1)
|
||||
if !strings.Contains(s, "(") {
|
||||
ch <- s
|
||||
} else {
|
||||
last := 0
|
||||
end := len(s)
|
||||
for info := range scan(s) {
|
||||
if last != info.openPos {
|
||||
ch <- s[last:info.openPos]
|
||||
}
|
||||
ch <- s[info.openPos:info.closePos]
|
||||
last = info.closePos
|
||||
}
|
||||
if last != end {
|
||||
ch <- s[last:]
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Tokenizes a string into a list of strings, tokens grouped by parenthesis are
|
||||
// not split! If the string starts with ( and ends in ), those are stripped.
|
||||
func tokenize(s string) []string {
|
||||
/*
|
||||
TODO: Properly detect if the string starts with a ( and ends with a )
|
||||
and that those two form a matching pair.
|
||||
|
||||
Eg: (foo) -> true; (foo)(bar) -> false;
|
||||
*/
|
||||
if s[0] == '(' && s[len(s)-1] == ')' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
ret := []string{}
|
||||
for chunk := range split(s) {
|
||||
if len(chunk) != 0 {
|
||||
if chunk[0] == '(' && chunk[len(chunk)-1] == ')' {
|
||||
ret = append(ret, chunk)
|
||||
} else {
|
||||
for _, token := range pat.FindAllStringSubmatch(chunk, -1) {
|
||||
ret = append(ret, token[0])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Empty chunk in string '%s'\n", s)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Compile a string containing a plural form expression to a Expression object.
|
||||
func Compile(s string) (expr Expression, err error) {
|
||||
if s == "0" {
|
||||
return constValue{value: 0}, nil
|
||||
}
|
||||
if !strings.Contains(s, "?") {
|
||||
s += "?1:0"
|
||||
}
|
||||
return compileExpression(s)
|
||||
}
|
||||
|
||||
// Check if a token is in a slice of strings
|
||||
func contains(haystack []string, needle string) bool {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Compiles an expression (ternary or constant)
|
||||
func compileExpression(s string) (expr Expression, err error) {
|
||||
tokens := tokenize(s)
|
||||
if contains(tokens, "?") {
|
||||
return ternaryToken.compile(tokens)
|
||||
}
|
||||
return constToken.compile(tokens)
|
||||
}
|
||||
|
||||
// Compiles a test (comparison)
|
||||
func compileTest(s string) (test test, err error) {
|
||||
tokens := tokenize(s)
|
||||
for _, tokenDef := range precedence {
|
||||
if contains(tokens, tokenDef.op) {
|
||||
return tokenDef.token.compile(tokens)
|
||||
}
|
||||
}
|
||||
return test, errors.New("cannot compile")
|
||||
}
|
||||
|
||||
func parseUint32(s string) (ui uint32, err error) {
|
||||
i, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return ui, err
|
||||
}
|
||||
return uint32(i), nil
|
||||
}
|
||||
50
plurals/compiler_test.go
Normal file
50
plurals/compiler_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package plurals
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
PluralForm string
|
||||
Fixture []int
|
||||
}
|
||||
|
||||
func TestCompiler(t *testing.T) {
|
||||
f, err := os.Open("testdata/pluralforms.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dec := json.NewDecoder(f)
|
||||
var fixtures []fixture
|
||||
err = dec.Decode(&fixtures)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, data := range fixtures {
|
||||
expr, err := Compile(data.PluralForm)
|
||||
if err != nil {
|
||||
t.Errorf("'%s' triggered error: %s", data.PluralForm, err)
|
||||
} else if expr == nil {
|
||||
t.Logf("'%s' compiled to nil", data.PluralForm)
|
||||
t.Fail()
|
||||
} else {
|
||||
for n, e := range data.Fixture {
|
||||
i := expr.Eval(uint32(n))
|
||||
if i != e {
|
||||
t.Logf("'%s' with n = %d, expected %d, got %d, compiled to %s", data.PluralForm, n, e, i, expr)
|
||||
t.Fail()
|
||||
}
|
||||
if i == -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
plurals/expression.go
Normal file
43
plurals/expression.go
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package plurals
|
||||
|
||||
// Expression is a plurals expression. Eval evaluates the expression for
|
||||
// a given n value. Use plurals.Compile to generate Expression instances.
|
||||
type Expression interface {
|
||||
Eval(n uint32) int
|
||||
}
|
||||
|
||||
type constValue struct {
|
||||
value int
|
||||
}
|
||||
|
||||
func (c constValue) Eval(n uint32) int {
|
||||
return c.value
|
||||
}
|
||||
|
||||
type test interface {
|
||||
test(n uint32) bool
|
||||
}
|
||||
|
||||
type ternary struct {
|
||||
test test
|
||||
trueExpr Expression
|
||||
falseExpr Expression
|
||||
}
|
||||
|
||||
func (t ternary) Eval(n uint32) int {
|
||||
if t.test.test(n) {
|
||||
if t.trueExpr == nil {
|
||||
return -1
|
||||
}
|
||||
return t.trueExpr.Eval(n)
|
||||
}
|
||||
if t.falseExpr == nil {
|
||||
return -1
|
||||
}
|
||||
return t.falseExpr.Eval(n)
|
||||
}
|
||||
18
plurals/math.go
Normal file
18
plurals/math.go
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package plurals
|
||||
|
||||
type math interface {
|
||||
calc(n uint32) uint32
|
||||
}
|
||||
|
||||
type mod struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (m mod) calc(n uint32) uint32 {
|
||||
return n % m.value
|
||||
}
|
||||
1
plurals/testdata/pluralforms.json
vendored
Normal file
1
plurals/testdata/pluralforms.json
vendored
Normal file
File diff suppressed because one or more lines are too long
104
plurals/tests.go
Normal file
104
plurals/tests.go
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package plurals
|
||||
|
||||
type equal struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (e equal) test(n uint32) bool {
|
||||
return n == e.value
|
||||
}
|
||||
|
||||
type notequal struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (e notequal) test(n uint32) bool {
|
||||
return n != e.value
|
||||
}
|
||||
|
||||
type gt struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e gt) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value > n
|
||||
} else {
|
||||
return n > e.value
|
||||
}
|
||||
}
|
||||
|
||||
type lt struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e lt) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value < n
|
||||
}
|
||||
return n < e.value
|
||||
}
|
||||
|
||||
type gte struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e gte) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value >= n
|
||||
}
|
||||
return n >= e.value
|
||||
}
|
||||
|
||||
type lte struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e lte) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value <= n
|
||||
}
|
||||
return n <= e.value
|
||||
}
|
||||
|
||||
type and struct {
|
||||
left test
|
||||
right test
|
||||
}
|
||||
|
||||
func (e and) test(n uint32) bool {
|
||||
if !e.left.test(n) {
|
||||
return false
|
||||
}
|
||||
return e.right.test(n)
|
||||
}
|
||||
|
||||
type or struct {
|
||||
left test
|
||||
right test
|
||||
}
|
||||
|
||||
func (e or) test(n uint32) bool {
|
||||
if e.left.test(n) {
|
||||
return true
|
||||
}
|
||||
return e.right.test(n)
|
||||
}
|
||||
|
||||
type pipe struct {
|
||||
modifier math
|
||||
action test
|
||||
}
|
||||
|
||||
func (e pipe) test(n uint32) bool {
|
||||
return e.action.test(e.modifier.calc(n))
|
||||
}
|
||||
337
po.go
337
po.go
@@ -1,7 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
@@ -9,53 +16,11 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/kinako/vm"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext/plurals"
|
||||
)
|
||||
|
||||
type translation struct {
|
||||
id string
|
||||
pluralID string
|
||||
trs map[int]string
|
||||
}
|
||||
|
||||
func newTranslation() *translation {
|
||||
tr := new(translation)
|
||||
tr.trs = make(map[int]string)
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
func (t *translation) get() string {
|
||||
// Look for translation index 0
|
||||
if _, ok := t.trs[0]; ok {
|
||||
if t.trs[0] != "" {
|
||||
return t.trs[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return unstranlated id by default
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *translation) getN(n int) string {
|
||||
// Look for translation index
|
||||
if _, ok := t.trs[n]; ok {
|
||||
if t.trs[n] != "" {
|
||||
return t.trs[n]
|
||||
}
|
||||
}
|
||||
|
||||
// Return unstranlated singular if corresponding
|
||||
if n == 0 {
|
||||
return t.id
|
||||
}
|
||||
|
||||
// Return untranslated plural by default
|
||||
return t.pluralID
|
||||
}
|
||||
|
||||
/*
|
||||
Po parses the content of any PO file and provides all the translation functions needed.
|
||||
Po parses the content of any PO file and provides all the Translation functions needed.
|
||||
It's the base object used by all package methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||
|
||||
@@ -63,17 +28,17 @@ Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create po object
|
||||
po := new(gotext.Po)
|
||||
po := gotext.NewPoTranslator()
|
||||
|
||||
// Parse .po file
|
||||
po.ParseFile("/path/to/po/file/translations.po")
|
||||
|
||||
// Get translation
|
||||
// Get Translation
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
|
||||
@@ -89,18 +54,19 @@ type Po struct {
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
nplurals int
|
||||
plural string
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*translation
|
||||
contexts map[string]map[string]*translation
|
||||
translations map[string]*Translation
|
||||
contexts map[string]map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *translation
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
}
|
||||
|
||||
@@ -114,6 +80,11 @@ const (
|
||||
msgStr
|
||||
)
|
||||
|
||||
// NewPoTranslator creates a new Po object with the Translator interface
|
||||
func NewPoTranslator() Translator {
|
||||
return new(Po)
|
||||
}
|
||||
|
||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||
func (po *Po) ParseFile(f string) {
|
||||
// Check if file exists
|
||||
@@ -133,25 +104,25 @@ func (po *Po) ParseFile(f string) {
|
||||
return
|
||||
}
|
||||
|
||||
po.Parse(string(data))
|
||||
po.Parse(data)
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
func (po *Po) Parse(str string) {
|
||||
func (po *Po) Parse(buf []byte) {
|
||||
// Lock while parsing
|
||||
po.Lock()
|
||||
|
||||
// Init storage
|
||||
if po.translations == nil {
|
||||
po.translations = make(map[string]*translation)
|
||||
po.contexts = make(map[string]map[string]*translation)
|
||||
po.translations = make(map[string]*Translation)
|
||||
po.contexts = make(map[string]map[string]*Translation)
|
||||
}
|
||||
|
||||
// Get lines
|
||||
lines := strings.Split(str, "\n")
|
||||
lines := strings.Split(string(buf), "\n")
|
||||
|
||||
// Init buffer
|
||||
po.trBuffer = newTranslation()
|
||||
po.trBuffer = NewTranslation()
|
||||
po.ctxBuffer = ""
|
||||
|
||||
state := head
|
||||
@@ -185,7 +156,7 @@ func (po *Po) Parse(str string) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Save translation
|
||||
// Save Translation
|
||||
if strings.HasPrefix(l, "msgstr") {
|
||||
po.parseMessage(l)
|
||||
state = msgStr
|
||||
@@ -199,7 +170,7 @@ func (po *Po) Parse(str string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Save last translation buffer.
|
||||
// Save last Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Unlock to parse headers
|
||||
@@ -209,33 +180,33 @@ func (po *Po) Parse(str string) {
|
||||
po.parseHeaders()
|
||||
}
|
||||
|
||||
// saveBuffer takes the context and translation buffers
|
||||
// saveBuffer takes the context and Translation buffers
|
||||
// and saves it on the translations collection
|
||||
func (po *Po) saveBuffer() {
|
||||
// With no context...
|
||||
if po.ctxBuffer == "" {
|
||||
po.translations[po.trBuffer.id] = po.trBuffer
|
||||
po.translations[po.trBuffer.ID] = po.trBuffer
|
||||
} else {
|
||||
// With context...
|
||||
if _, ok := po.contexts[po.ctxBuffer]; !ok {
|
||||
po.contexts[po.ctxBuffer] = make(map[string]*translation)
|
||||
po.contexts[po.ctxBuffer] = make(map[string]*Translation)
|
||||
}
|
||||
po.contexts[po.ctxBuffer][po.trBuffer.id] = po.trBuffer
|
||||
po.contexts[po.ctxBuffer][po.trBuffer.ID] = po.trBuffer
|
||||
|
||||
// Cleanup current context buffer if needed
|
||||
if po.trBuffer.id != "" {
|
||||
if po.trBuffer.ID != "" {
|
||||
po.ctxBuffer = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Flush translation buffer
|
||||
po.trBuffer = newTranslation()
|
||||
// Flush Translation buffer
|
||||
po.trBuffer = NewTranslation()
|
||||
}
|
||||
|
||||
// parseContext takes a line starting with "msgctxt",
|
||||
// saves the current translation buffer and creates a new context.
|
||||
// saves the current Translation buffer and creates a new context.
|
||||
func (po *Po) parseContext(l string) {
|
||||
// Save current translation buffer.
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Buffer context
|
||||
@@ -243,25 +214,25 @@ func (po *Po) parseContext(l string) {
|
||||
}
|
||||
|
||||
// parseID takes a line starting with "msgid",
|
||||
// saves the current translation and creates a new msgid buffer.
|
||||
// saves the current Translation and creates a new msgid buffer.
|
||||
func (po *Po) parseID(l string) {
|
||||
// Save current translation buffer.
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Set id
|
||||
po.trBuffer.id, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
po.trBuffer.ID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
}
|
||||
|
||||
// parsePluralID saves the plural id buffer from a line starting with "msgid_plural"
|
||||
func (po *Po) parsePluralID(l string) {
|
||||
po.trBuffer.pluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
po.trBuffer.PluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
}
|
||||
|
||||
// parseMessage takes a line starting with "msgstr" and saves it into the current buffer.
|
||||
func (po *Po) parseMessage(l string) {
|
||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||
|
||||
// Check for indexed translation forms
|
||||
// Check for indexed Translation forms
|
||||
if strings.HasPrefix(l, "[") {
|
||||
idx := strings.Index(l, "]")
|
||||
if idx == -1 {
|
||||
@@ -276,15 +247,15 @@ func (po *Po) parseMessage(l string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse translation string
|
||||
po.trBuffer.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
// Parse Translation string
|
||||
po.trBuffer.Trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
|
||||
// Loop
|
||||
return
|
||||
}
|
||||
|
||||
// Save single translation form under 0 index
|
||||
po.trBuffer.trs[0], _ = strconv.Unquote(l)
|
||||
// Save single Translation form under 0 index
|
||||
po.trBuffer.Trs[0], _ = strconv.Unquote(l)
|
||||
}
|
||||
|
||||
// parseString takes a well formatted string without prefix
|
||||
@@ -294,16 +265,16 @@ func (po *Po) parseString(l string, state parseState) {
|
||||
|
||||
switch state {
|
||||
case msgStr:
|
||||
// Append to last translation found
|
||||
po.trBuffer.trs[len(po.trBuffer.trs)-1] += clean
|
||||
// Append to last Translation found
|
||||
po.trBuffer.Trs[len(po.trBuffer.Trs)-1] += clean
|
||||
|
||||
case msgID:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.id += clean
|
||||
po.trBuffer.ID += clean
|
||||
|
||||
case msgIDPlural:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.pluralID += clean
|
||||
po.trBuffer.PluralID += clean
|
||||
|
||||
case msgCtxt:
|
||||
// Multiline context - Append to current context
|
||||
@@ -377,6 +348,11 @@ func (po *Po) parseHeaders() {
|
||||
|
||||
case "plural":
|
||||
po.plural = vs[1]
|
||||
|
||||
if expr, err := plurals.Compile(po.plural); err == nil {
|
||||
po.pluralforms = expr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,38 +363,18 @@ func (po *Po) pluralForm(n int) int {
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
// Failsafe
|
||||
if po.nplurals < 1 {
|
||||
return 0
|
||||
}
|
||||
if po.plural == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Init compiler
|
||||
env := vm.NewEnv()
|
||||
env.Define("n", n)
|
||||
|
||||
plural, err := env.Execute(po.plural)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
if plural.Type().Name() == "bool" {
|
||||
if plural.Bool() {
|
||||
return 1
|
||||
// Failure fallback
|
||||
if po.pluralforms == nil {
|
||||
/* Use Western plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
// Else
|
||||
return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
if int(plural.Int()) > po.nplurals {
|
||||
return 0
|
||||
}
|
||||
|
||||
return int(plural.Int())
|
||||
return po.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// Get retrieves the corresponding translation for the given string.
|
||||
// Get retrieves the corresponding Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -427,15 +383,15 @@ func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return printf(po.translations[str].get(), vars...)
|
||||
return Printf(po.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return printf(str, vars...)
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of translation for the given string.
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -444,17 +400,18 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return printf(po.translations[str].getN(po.pluralForm(n)), vars...)
|
||||
return Printf(po.translations[str].GetN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return printf(str, vars...)
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if po.pluralForm(n) == 0 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return printf(plural, vars...)
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding translation for a given string in the given context.
|
||||
// GetC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -465,17 +422,17 @@ func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return printf(po.contexts[ctx][str].get(), vars...)
|
||||
return Printf(po.contexts[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return printf(str, vars...)
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context.
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -486,14 +443,144 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return printf(po.contexts[ctx][str].getN(po.pluralForm(n)), vars...)
|
||||
return Printf(po.contexts[ctx][str].GetN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return printf(str, vars...)
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if po.pluralForm(n) == 0 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return printf(plural, vars...)
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetE retrieves the corresponding Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (po *Po) GetE(str string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
if fmt, ok := po.translations[str].GetE(); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetNE retrieves the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (po *Po) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
if fmt, ok := po.translations[str].GetNE(po.pluralForm(n)); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetCE retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (po *Po) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.contexts != nil {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
if fmt, ok := po.contexts[ctx][str].GetE(); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetNCE retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
// The second return value is true iff the string was found.
|
||||
func (po *Po) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.contexts != nil {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
if fmt, ok := po.contexts[ctx][str].GetNE(po.pluralForm(n)); ok {
|
||||
return Printf(fmt, vars...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
return "", false
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||
func (po *Po) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
obj.Headers = po.Headers
|
||||
obj.Language = po.Language
|
||||
obj.PluralForms = po.PluralForms
|
||||
obj.Nplurals = po.nplurals
|
||||
obj.Plural = po.plural
|
||||
obj.Translations = po.translations
|
||||
obj.Contexts = po.contexts
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
|
||||
func (po *Po) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
po.Headers = obj.Headers
|
||||
po.Language = obj.Language
|
||||
po.PluralForms = obj.PluralForms
|
||||
po.nplurals = obj.Nplurals
|
||||
po.plural = obj.Plural
|
||||
po.translations = obj.Translations
|
||||
po.contexts = obj.Contexts
|
||||
|
||||
if expr, err := plurals.Compile(po.plural); err == nil {
|
||||
po.pluralforms = expr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
196
po_test.go
196
po_test.go
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
@@ -6,6 +11,28 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPo_Get(t *testing.T) {
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Try to parse a directory
|
||||
po.ParseFile(path.Clean(os.TempDir()))
|
||||
|
||||
// Parse file
|
||||
po.ParseFile("fixtures/en_US/default.po")
|
||||
|
||||
// Test translations
|
||||
tr := po.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = po.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPo(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
@@ -28,13 +55,15 @@ msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid "multi"
|
||||
msgid ""
|
||||
"multi"
|
||||
"line"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid "multi"
|
||||
msgid ""
|
||||
"multi"
|
||||
"line"
|
||||
"plural"
|
||||
"id"
|
||||
@@ -42,7 +71,8 @@ msgstr "plural id with multiline content"
|
||||
|
||||
#Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr "Multi "
|
||||
msgstr ""
|
||||
"Multi "
|
||||
"line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
@@ -58,13 +88,13 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgid "Empty Translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
@@ -73,7 +103,7 @@ msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
`
|
||||
|
||||
@@ -136,7 +166,7 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test inexistent translations
|
||||
// Test not existent translations
|
||||
tr = po.Get("This is a test")
|
||||
if tr != "This is a test" {
|
||||
t.Errorf("Expected 'This is a test' but got '%s'", tr)
|
||||
@@ -147,6 +177,22 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test translations with existence check
|
||||
tr, exists := po.GetE("My text")
|
||||
if (tr != "Translated text") || (!exists) {
|
||||
t.Errorf("Expected 'Translated text', true but got '%s', %v", tr, exists)
|
||||
}
|
||||
|
||||
tr, exists = po.GetE("I don't exist")
|
||||
if exists {
|
||||
t.Errorf("Expected 'I don't exist' not to exist but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.GetN("I don't exist", "We don't exist", 100)
|
||||
if exists {
|
||||
t.Errorf("Expected 'I/We don't exist' not to exist but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test context translations
|
||||
v = "Test"
|
||||
tr = po.GetC("One with var: %s", "Ctx", v)
|
||||
@@ -170,10 +216,10 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'Original' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test empty translation strings
|
||||
tr = po.Get("Empty translation")
|
||||
if tr != "Empty translation" {
|
||||
t.Errorf("Expected 'Empty translation' but got '%s'", tr)
|
||||
// Test empty Translation strings
|
||||
tr = po.Get("Empty Translation")
|
||||
if tr != "Empty Translation" {
|
||||
t.Errorf("Expected 'Empty Translation' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.Get("Empty plural form singular")
|
||||
@@ -191,10 +237,10 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last translation
|
||||
// Test last Translation
|
||||
tr = po.Get("More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
if tr != "More Translation" {
|
||||
t.Errorf("Expected 'More Translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,11 +257,41 @@ msgstr[0] "TR Singular: %s"
|
||||
msgstr[1] "TR Plural: %s"
|
||||
msgstr[2] "TR Plural 2: %s"
|
||||
|
||||
|
||||
|
||||
`
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
v := "Var"
|
||||
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
||||
if tr != "TR Plural: Var" {
|
||||
t.Errorf("Expected 'TR Plural: Var' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.GetN("Singular: %s", "Plural: %s", 1, v)
|
||||
if tr != "TR Singular: Var" {
|
||||
t.Errorf("Expected 'TR Singular: Var' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralNoHeaderInformation(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
msgid "Singular: %s"
|
||||
msgid_plural "Plural: %s"
|
||||
msgstr[0] "TR Singular: %s"
|
||||
msgstr[1] "TR Plural: %s"
|
||||
msgstr[2] "TR Plural 2: %s"
|
||||
|
||||
|
||||
`
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
v := "Var"
|
||||
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
||||
@@ -250,7 +326,7 @@ msgstr "Translated example"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check headers expected
|
||||
if po.Language != "en" {
|
||||
@@ -274,9 +350,9 @@ msgstr "Translated example"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check translation expected
|
||||
// Check Translation expected
|
||||
if po.Get("Example") != "Translated example" {
|
||||
t.Errorf("Expected 'Translated example' but got '%s'", po.Get("Example"))
|
||||
}
|
||||
@@ -302,7 +378,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
@@ -347,7 +423,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
@@ -388,7 +464,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
@@ -438,7 +514,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(1)
|
||||
@@ -464,19 +540,18 @@ msgstr[3] "Plural form 3"
|
||||
}
|
||||
|
||||
func TestTranslationObject(t *testing.T) {
|
||||
tr := newTranslation()
|
||||
str := tr.get()
|
||||
tr := NewTranslation()
|
||||
str := tr.Get()
|
||||
|
||||
if str != "" {
|
||||
t.Errorf("Expected '' but got '%s'", str)
|
||||
}
|
||||
|
||||
// Set id
|
||||
tr.id = "Text"
|
||||
tr.ID = "Text"
|
||||
str = tr.Get()
|
||||
|
||||
// Get again
|
||||
str = tr.get()
|
||||
|
||||
if str != "Text" {
|
||||
t.Errorf("Expected 'Text' but got '%s'", str)
|
||||
}
|
||||
@@ -509,11 +584,11 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(po *Po, done chan bool) {
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
done <- true
|
||||
}(po, pc)
|
||||
|
||||
// Read some translation on a goroutine
|
||||
// Read some Translation on a goroutine
|
||||
go func(po *Po, done chan bool) {
|
||||
po.Get("My text")
|
||||
done <- true
|
||||
@@ -526,3 +601,62 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestNewPoTranslatorRace(t *testing.T) {
|
||||
// Create Po object
|
||||
mo := NewPoTranslator()
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
rc := make(chan bool)
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.po")
|
||||
done <- true
|
||||
}(mo, pc)
|
||||
|
||||
// Read some Translation on a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
mo.Get("My text")
|
||||
done <- true
|
||||
}(mo, rc)
|
||||
|
||||
// Read something at top level
|
||||
mo.Get("My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestPoBinaryEncoding(t *testing.T) {
|
||||
// Create po objects
|
||||
po := new(Po)
|
||||
po2 := new(Po)
|
||||
|
||||
// Parse file
|
||||
po.ParseFile("fixtures/en_US/default.po")
|
||||
|
||||
buff, err := po.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = po2.UnmarshalBinary(buff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test translations
|
||||
tr := po2.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = po2.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
78
translation.go
Normal file
78
translation.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
// Translation is the struct for the Translations parsed via Po or Mo files and all coming parsers
|
||||
type Translation struct {
|
||||
ID string
|
||||
PluralID string
|
||||
Trs map[int]string
|
||||
}
|
||||
|
||||
// NewTranslation returns the Translation object and initialized it.
|
||||
func NewTranslation() *Translation {
|
||||
tr := new(Translation)
|
||||
tr.Trs = make(map[int]string)
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// Get returns the string of the translation
|
||||
func (t *Translation) Get() string {
|
||||
// Look for Translation index 0
|
||||
if _, ok := t.Trs[0]; ok {
|
||||
if t.Trs[0] != "" {
|
||||
return t.Trs[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated id by default
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// GetN returns the string of the plural translation
|
||||
func (t *Translation) GetN(n int) string {
|
||||
// Look for Translation index
|
||||
if _, ok := t.Trs[n]; ok {
|
||||
if t.Trs[n] != "" {
|
||||
return t.Trs[n]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated singular if corresponding
|
||||
if n == 0 {
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// Return untranslated plural by default
|
||||
return t.PluralID
|
||||
}
|
||||
|
||||
// GetE returns the string of the translation. The second return value is true
|
||||
// iff the string was found.
|
||||
func (t *Translation) GetE() (string, bool) {
|
||||
// Look for Translation index 0
|
||||
if _, ok := t.Trs[0]; ok {
|
||||
if t.Trs[0] != "" {
|
||||
return t.Trs[0], true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetNE returns the string of the plural translation. The second return value
|
||||
// is true iff the string was found.
|
||||
func (t *Translation) GetNE(n int) (string, bool) {
|
||||
// Look for Translation index
|
||||
if _, ok := t.Trs[n]; ok {
|
||||
if t.Trs[n] != "" {
|
||||
return t.Trs[n], true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
65
translator.go
Normal file
65
translator.go
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import "net/textproto"
|
||||
|
||||
// Translator interface is used by Locale and Po objects.Translator
|
||||
// It contains all methods needed to parse translation sources and obtain corresponding translations.
|
||||
// Also implements gob.GobEncoder/gob.DobDecoder interfaces to allow serialization of Locale objects.
|
||||
type Translator interface {
|
||||
ParseFile(f string)
|
||||
Parse(buf []byte)
|
||||
|
||||
Get(str string, vars ...interface{}) string
|
||||
GetN(str, plural string, n int, vars ...interface{}) string
|
||||
GetC(str, ctx string, vars ...interface{}) string
|
||||
GetNC(str, plural string, n int, ctx string, vars ...interface{}) string
|
||||
|
||||
GetE(str string, vars ...interface{}) (string, bool)
|
||||
GetNE(str, plural string, n int, vars ...interface{}) (string, bool)
|
||||
GetCE(str, ctx string, vars ...interface{}) (string, bool)
|
||||
GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool)
|
||||
|
||||
MarshalBinary() ([]byte, error)
|
||||
UnmarshalBinary([]byte) error
|
||||
}
|
||||
|
||||
// TranslatorEncoding is used as intermediary storage to encode Translator objects to Gob.
|
||||
type TranslatorEncoding struct {
|
||||
// Headers storage
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
Nplurals int
|
||||
Plural string
|
||||
|
||||
// Storage
|
||||
Translations map[string]*Translation
|
||||
Contexts map[string]map[string]*Translation
|
||||
}
|
||||
|
||||
// GetTranslator is used to recover a Translator object after unmarshaling the TranslatorEncoding object.
|
||||
// Internally uses a Po object as it should be switcheable with Mo objects without problem.
|
||||
// External Translator implementations should be able to serialize into a TranslatorEncoding object in order to unserialize into a Po-compatible object.
|
||||
func (te *TranslatorEncoding) GetTranslator() Translator {
|
||||
po := new(Po)
|
||||
po.Headers = te.Headers
|
||||
po.Language = te.Language
|
||||
po.PluralForms = te.PluralForms
|
||||
po.nplurals = te.Nplurals
|
||||
po.plural = te.Plural
|
||||
po.translations = te.Translations
|
||||
po.contexts = te.Contexts
|
||||
|
||||
return po
|
||||
}
|
||||
37
vendor/github.com/mattn/kinako/README.md
generated
vendored
37
vendor/github.com/mattn/kinako/README.md
generated
vendored
@@ -1,37 +0,0 @@
|
||||
# kinako
|
||||
|
||||
Kinako is small VM written in Go.
|
||||
|
||||

|
||||
|
||||
(Picture licensed under CC BY-SA 3.0 by wikipedia)
|
||||
|
||||
## Installation
|
||||
Requires Go.
|
||||
```
|
||||
$ go get -u github.com/mattn/kinako
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Embedding the interpreter into your own program:
|
||||
|
||||
```Go
|
||||
var env = vm.NewEnv()
|
||||
|
||||
env.Define("foo", 1)
|
||||
val, err := env.Execute(`foo + 3`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(val)
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
18
vendor/github.com/mattn/kinako/_example/main.go
generated
vendored
18
vendor/github.com/mattn/kinako/_example/main.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/mattn/kinako/vm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
env := vm.NewEnv()
|
||||
v, err := env.Execute(`foo=1; foo+3`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(v)
|
||||
}
|
||||
112
vendor/github.com/mattn/kinako/ast/expr.go
generated
vendored
112
vendor/github.com/mattn/kinako/ast/expr.go
generated
vendored
@@ -1,112 +0,0 @@
|
||||
package ast
|
||||
|
||||
type Token struct {
|
||||
Tok int
|
||||
Lit string
|
||||
}
|
||||
|
||||
// Position provides interface to store code locations.
|
||||
type Position struct {
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// Expr provides all of interfaces for expression.
|
||||
type Expr interface {
|
||||
expr()
|
||||
}
|
||||
|
||||
// ExprImpl provide commonly implementations for Expr.
|
||||
type ExprImpl struct {
|
||||
}
|
||||
|
||||
// expr provide restraint interface.
|
||||
func (x *ExprImpl) expr() {}
|
||||
|
||||
// NumberExpr provide Number expression.
|
||||
type NumberExpr struct {
|
||||
ExprImpl
|
||||
Lit string
|
||||
}
|
||||
|
||||
// UnaryExpr provide unary minus expression. ex: -1, ^1, ~1.
|
||||
type UnaryExpr struct {
|
||||
ExprImpl
|
||||
Operator string
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// IdentExpr provide identity expression.
|
||||
type IdentExpr struct {
|
||||
ExprImpl
|
||||
Lit string
|
||||
}
|
||||
|
||||
// Stmt provides all of interfaces for statement.
|
||||
type Stmt interface {
|
||||
stmt()
|
||||
}
|
||||
|
||||
// StmtImpl provide commonly implementations for Stmt..
|
||||
type StmtImpl struct {
|
||||
}
|
||||
|
||||
// stmt provide restraint interface.
|
||||
func (x *StmtImpl) stmt() {}
|
||||
|
||||
// LetsStmt provide multiple statement of let.
|
||||
type LetsStmt struct {
|
||||
StmtImpl
|
||||
Lhss []Expr
|
||||
Operator string
|
||||
Rhss []Expr
|
||||
}
|
||||
|
||||
// StringExpr provide String expression.
|
||||
type StringExpr struct {
|
||||
ExprImpl
|
||||
Lit string
|
||||
}
|
||||
|
||||
type TernaryOpExpr struct {
|
||||
ExprImpl
|
||||
Expr Expr
|
||||
Lhs Expr
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
// CallExpr provide calling expression.
|
||||
type CallExpr struct {
|
||||
ExprImpl
|
||||
Func interface{}
|
||||
Name string
|
||||
SubExprs []Expr
|
||||
}
|
||||
|
||||
// ParenExpr provide parent block expression.
|
||||
type ParenExpr struct {
|
||||
ExprImpl
|
||||
SubExpr Expr
|
||||
}
|
||||
|
||||
// BinOpExpr provide binary operator expression.
|
||||
type BinOpExpr struct {
|
||||
ExprImpl
|
||||
Lhs Expr
|
||||
Operator string
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
// ExprStmt provide expression statement.
|
||||
type ExprStmt struct {
|
||||
StmtImpl
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// LetStmt provide statement of let.
|
||||
type LetStmt struct {
|
||||
StmtImpl
|
||||
Lhs Expr
|
||||
Operator string
|
||||
Rhs Expr
|
||||
}
|
||||
BIN
vendor/github.com/mattn/kinako/kinako.png
generated
vendored
BIN
vendor/github.com/mattn/kinako/kinako.png
generated
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 450 KiB |
4
vendor/github.com/mattn/kinako/parser/Makefile
generated
vendored
4
vendor/github.com/mattn/kinako/parser/Makefile
generated
vendored
@@ -1,4 +0,0 @@
|
||||
all : parser.go
|
||||
|
||||
parser.go : parser.go.y
|
||||
goyacc -o $@ parser.go.y
|
||||
427
vendor/github.com/mattn/kinako/parser/lexer.go
generated
vendored
427
vendor/github.com/mattn/kinako/parser/lexer.go
generated
vendored
@@ -1,427 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unicode"
|
||||
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
const (
|
||||
EOF = -1 // End of file.
|
||||
EOL = '\n' // End of line.
|
||||
)
|
||||
|
||||
// Error provides a convenient interface for handling runtime error.
|
||||
// It can be Error inteface with type cast which can call Pos().
|
||||
type Error struct {
|
||||
Message string
|
||||
Filename string
|
||||
Fatal bool
|
||||
}
|
||||
|
||||
// Error returns the error message.
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Scanner stores informations for lexer.
|
||||
type Scanner struct {
|
||||
src []rune
|
||||
offset int
|
||||
lineHead int
|
||||
line int
|
||||
}
|
||||
|
||||
// Init resets code to scan.
|
||||
func (s *Scanner) Init(src string) {
|
||||
s.src = []rune(src)
|
||||
}
|
||||
|
||||
// Scan analyses token, and decide identify or literals.
|
||||
func (s *Scanner) Scan() (tok int, lit string, pos ast.Position, err error) {
|
||||
retry:
|
||||
s.skipBlank()
|
||||
pos = s.pos()
|
||||
switch ch := s.peek(); {
|
||||
case isLetter(ch):
|
||||
tok = IDENT
|
||||
lit, err = s.scanIdentifier()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case isDigit(ch):
|
||||
tok = NUMBER
|
||||
lit, err = s.scanNumber()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ch == '"':
|
||||
tok = STRING
|
||||
lit, err = s.scanString('"')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ch == '\'':
|
||||
tok = STRING
|
||||
lit, err = s.scanString('\'')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ch == '`':
|
||||
tok = STRING
|
||||
lit, err = s.scanRawString()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
switch ch {
|
||||
case EOF:
|
||||
tok = EOF
|
||||
case '#':
|
||||
for !isEOL(s.peek()) {
|
||||
s.next()
|
||||
}
|
||||
goto retry
|
||||
case '!':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = NEQ
|
||||
lit = "!="
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '=':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = EQEQ
|
||||
lit = "=="
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '+':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '-':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '*':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '/':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '>':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = GE
|
||||
lit = ">="
|
||||
case '>':
|
||||
tok = SHIFTRIGHT
|
||||
lit = ">>"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '<':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = LE
|
||||
lit = "<="
|
||||
case '<':
|
||||
tok = SHIFTLEFT
|
||||
lit = "<<"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '|':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '|':
|
||||
tok = OROR
|
||||
lit = "||"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '&':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '&':
|
||||
tok = ANDAND
|
||||
lit = "&&"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '.':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '\n':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '(', ')', ':', ';', '%', '?', '{', '}', ',', '[', ']', '^':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
default:
|
||||
err = fmt.Errorf(`syntax error "%s"`, string(ch))
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
return
|
||||
}
|
||||
s.next()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isLetter returns true if the rune is a letter for identity.
|
||||
func isLetter(ch rune) bool {
|
||||
return unicode.IsLetter(ch) || ch == '_'
|
||||
}
|
||||
|
||||
// isDigit returns true if the rune is a number.
|
||||
func isDigit(ch rune) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
// isHex returns true if the rune is a hex digits.
|
||||
func isHex(ch rune) bool {
|
||||
return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
|
||||
}
|
||||
|
||||
// isEOL returns true if the rune is at end-of-line or end-of-file.
|
||||
func isEOL(ch rune) bool {
|
||||
return ch == '\n' || ch == -1
|
||||
}
|
||||
|
||||
// isBlank returns true if the rune is empty character..
|
||||
func isBlank(ch rune) bool {
|
||||
return ch == ' ' || ch == '\t' || ch == '\r'
|
||||
}
|
||||
|
||||
// peek returns current rune in the code.
|
||||
func (s *Scanner) peek() rune {
|
||||
if s.reachEOF() {
|
||||
return EOF
|
||||
}
|
||||
return s.src[s.offset]
|
||||
}
|
||||
|
||||
// next moves offset to next.
|
||||
func (s *Scanner) next() {
|
||||
if !s.reachEOF() {
|
||||
if s.peek() == '\n' {
|
||||
s.lineHead = s.offset + 1
|
||||
s.line++
|
||||
}
|
||||
s.offset++
|
||||
}
|
||||
}
|
||||
|
||||
// current returns the current offset.
|
||||
func (s *Scanner) current() int {
|
||||
return s.offset
|
||||
}
|
||||
|
||||
// offset sets the offset value.
|
||||
func (s *Scanner) set(o int) {
|
||||
s.offset = o
|
||||
}
|
||||
|
||||
// back moves back offset once to top.
|
||||
func (s *Scanner) back() {
|
||||
s.offset--
|
||||
}
|
||||
|
||||
// reachEOF returns true if offset is at end-of-file.
|
||||
func (s *Scanner) reachEOF() bool {
|
||||
return len(s.src) <= s.offset
|
||||
}
|
||||
|
||||
// pos returns the position of current.
|
||||
func (s *Scanner) pos() ast.Position {
|
||||
return ast.Position{Line: s.line + 1, Column: s.offset - s.lineHead + 1}
|
||||
}
|
||||
|
||||
// skipBlank moves position into non-black character.
|
||||
func (s *Scanner) skipBlank() {
|
||||
for isBlank(s.peek()) {
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
|
||||
// scanIdentifier returns identifier begining at current position.
|
||||
func (s *Scanner) scanIdentifier() (string, error) {
|
||||
var ret []rune
|
||||
for {
|
||||
if !isLetter(s.peek()) && !isDigit(s.peek()) {
|
||||
break
|
||||
}
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// scanNumber returns number begining at current position.
|
||||
func (s *Scanner) scanNumber() (string, error) {
|
||||
var ret []rune
|
||||
ch := s.peek()
|
||||
ret = append(ret, ch)
|
||||
s.next()
|
||||
if ch == '0' && s.peek() == 'x' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
for isHex(s.peek()) {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
} else {
|
||||
for isDigit(s.peek()) || s.peek() == '.' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
if s.peek() == 'e' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
if isDigit(s.peek()) || s.peek() == '+' || s.peek() == '-' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
for isDigit(s.peek()) || s.peek() == '.' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
for isDigit(s.peek()) || s.peek() == '.' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
if isLetter(s.peek()) {
|
||||
return "", errors.New("identifier starts immediately after numeric literal")
|
||||
}
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// scanRawString returns raw-string starting at current position.
|
||||
func (s *Scanner) scanRawString() (string, error) {
|
||||
var ret []rune
|
||||
for {
|
||||
s.next()
|
||||
if s.peek() == EOF {
|
||||
return "", errors.New("unexpected EOF")
|
||||
break
|
||||
}
|
||||
if s.peek() == '`' {
|
||||
s.next()
|
||||
break
|
||||
}
|
||||
ret = append(ret, s.peek())
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// scanString returns string starting at current position.
|
||||
// This handles backslash escaping.
|
||||
func (s *Scanner) scanString(l rune) (string, error) {
|
||||
var ret []rune
|
||||
eos:
|
||||
for {
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case EOL:
|
||||
return "", errors.New("unexpected EOL")
|
||||
case EOF:
|
||||
return "", errors.New("unexpected EOF")
|
||||
case l:
|
||||
s.next()
|
||||
break eos
|
||||
case '\\':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case 'b':
|
||||
ret = append(ret, '\b')
|
||||
continue
|
||||
case 'f':
|
||||
ret = append(ret, '\f')
|
||||
continue
|
||||
case 'r':
|
||||
ret = append(ret, '\r')
|
||||
continue
|
||||
case 'n':
|
||||
ret = append(ret, '\n')
|
||||
continue
|
||||
case 't':
|
||||
ret = append(ret, '\t')
|
||||
continue
|
||||
}
|
||||
ret = append(ret, s.peek())
|
||||
continue
|
||||
default:
|
||||
ret = append(ret, s.peek())
|
||||
}
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// Lexer provides inteface to parse codes.
|
||||
type Lexer struct {
|
||||
s *Scanner
|
||||
lit string
|
||||
pos ast.Position
|
||||
e error
|
||||
stmts []ast.Stmt
|
||||
}
|
||||
|
||||
// Lex scans the token and literals.
|
||||
func (l *Lexer) Lex(lval *yySymType) int {
|
||||
tok, lit, pos, err := l.s.Scan()
|
||||
if err != nil {
|
||||
l.e = &Error{Message: fmt.Sprintf("%s", err.Error()), Fatal: true}
|
||||
}
|
||||
lval.tok = ast.Token{Tok: tok, Lit: lit}
|
||||
l.lit = lit
|
||||
l.pos = pos
|
||||
return tok
|
||||
}
|
||||
|
||||
// Error sets parse error.
|
||||
func (l *Lexer) Error(msg string) {
|
||||
l.e = &Error{Message: msg, Fatal: false}
|
||||
}
|
||||
|
||||
// Parser provides way to parse the code using Scanner.
|
||||
func Parse(s *Scanner) ([]ast.Stmt, error) {
|
||||
l := Lexer{s: s}
|
||||
if yyParse(&l) != 0 {
|
||||
return nil, l.e
|
||||
}
|
||||
return l.stmts, l.e
|
||||
}
|
||||
|
||||
func EnableErrorVerbose() {
|
||||
yyErrorVerbose = true
|
||||
}
|
||||
|
||||
// ParserSrc provides way to parse the code from source.
|
||||
func ParseSrc(src string) ([]ast.Stmt, error) {
|
||||
scanner := &Scanner{
|
||||
src: []rune(src),
|
||||
}
|
||||
return Parse(scanner)
|
||||
}
|
||||
778
vendor/github.com/mattn/kinako/parser/parser.go
generated
vendored
778
vendor/github.com/mattn/kinako/parser/parser.go
generated
vendored
@@ -1,778 +0,0 @@
|
||||
//line parser.go.y:2
|
||||
package parser
|
||||
|
||||
import __yyfmt__ "fmt"
|
||||
|
||||
//line parser.go.y:2
|
||||
import (
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
//line parser.go.y:16
|
||||
type yySymType struct {
|
||||
yys int
|
||||
compstmt []ast.Stmt
|
||||
stmts []ast.Stmt
|
||||
stmt ast.Stmt
|
||||
expr ast.Expr
|
||||
exprs []ast.Expr
|
||||
tok ast.Token
|
||||
term ast.Token
|
||||
terms ast.Token
|
||||
opt_terms ast.Token
|
||||
}
|
||||
|
||||
const IDENT = 57346
|
||||
const NUMBER = 57347
|
||||
const STRING = 57348
|
||||
const EQEQ = 57349
|
||||
const NEQ = 57350
|
||||
const GE = 57351
|
||||
const LE = 57352
|
||||
const OROR = 57353
|
||||
const ANDAND = 57354
|
||||
const POW = 57355
|
||||
const SHIFTLEFT = 57356
|
||||
const SHIFTRIGHT = 57357
|
||||
const PLUSPLUS = 57358
|
||||
const MINUSMINUS = 57359
|
||||
const UNARY = 57360
|
||||
|
||||
var yyToknames = [...]string{
|
||||
"$end",
|
||||
"error",
|
||||
"$unk",
|
||||
"IDENT",
|
||||
"NUMBER",
|
||||
"STRING",
|
||||
"EQEQ",
|
||||
"NEQ",
|
||||
"GE",
|
||||
"LE",
|
||||
"OROR",
|
||||
"ANDAND",
|
||||
"POW",
|
||||
"'='",
|
||||
"'?'",
|
||||
"':'",
|
||||
"','",
|
||||
"'>'",
|
||||
"'<'",
|
||||
"SHIFTLEFT",
|
||||
"SHIFTRIGHT",
|
||||
"'+'",
|
||||
"'-'",
|
||||
"PLUSPLUS",
|
||||
"MINUSMINUS",
|
||||
"'*'",
|
||||
"'/'",
|
||||
"'%'",
|
||||
"UNARY",
|
||||
"'!'",
|
||||
"'^'",
|
||||
"'('",
|
||||
"')'",
|
||||
"'|'",
|
||||
"'&'",
|
||||
"';'",
|
||||
"'\\n'",
|
||||
}
|
||||
var yyStatenames = [...]string{}
|
||||
|
||||
const yyEofCode = 1
|
||||
const yyErrCode = 2
|
||||
const yyInitialStackSize = 16
|
||||
|
||||
//line parser.go.y:213
|
||||
|
||||
//line yacctab:1
|
||||
var yyExca = [...]int{
|
||||
-1, 1,
|
||||
1, -1,
|
||||
-2, 0,
|
||||
-1, 50,
|
||||
7, 0,
|
||||
8, 0,
|
||||
-2, 20,
|
||||
-1, 51,
|
||||
7, 0,
|
||||
8, 0,
|
||||
-2, 21,
|
||||
}
|
||||
|
||||
const yyNprod = 40
|
||||
const yyPrivate = 57344
|
||||
|
||||
var yyTokenNames []string
|
||||
var yyStates []string
|
||||
|
||||
const yyLast = 251
|
||||
|
||||
var yyAct = [...]int{
|
||||
|
||||
9, 6, 7, 33, 35, 37, 22, 23, 60, 3,
|
||||
24, 25, 26, 38, 39, 40, 1, 41, 33, 35,
|
||||
8, 43, 44, 45, 46, 47, 48, 49, 50, 51,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 61, 42,
|
||||
27, 28, 30, 32, 34, 36, 65, 0, 21, 63,
|
||||
4, 29, 31, 2, 18, 22, 23, 17, 0, 24,
|
||||
25, 26, 64, 0, 66, 0, 67, 33, 35, 27,
|
||||
28, 30, 32, 34, 36, 0, 0, 21, 0, 0,
|
||||
29, 31, 0, 0, 22, 23, 0, 0, 24, 25,
|
||||
26, 0, 0, 0, 0, 62, 33, 35, 27, 28,
|
||||
30, 32, 34, 36, 0, 20, 21, 0, 0, 29,
|
||||
31, 0, 0, 22, 23, 5, 0, 24, 25, 26,
|
||||
19, 0, 0, 0, 0, 33, 35, 27, 28, 30,
|
||||
32, 34, 36, 0, 19, 21, 0, 0, 29, 31,
|
||||
0, 0, 22, 23, 0, 0, 24, 25, 26, 0,
|
||||
0, 0, 0, 0, 33, 35, 27, 28, 30, 32,
|
||||
0, 36, 0, 0, 0, 0, 0, 29, 31, 0,
|
||||
0, 22, 23, 0, 0, 24, 25, 26, 27, 28,
|
||||
30, 32, 0, 33, 35, 0, 0, 0, 0, 29,
|
||||
31, 0, 0, 22, 23, 0, 0, 24, 25, 26,
|
||||
30, 32, 10, 11, 15, 33, 35, 0, 0, 29,
|
||||
31, 0, 0, 22, 23, 0, 0, 24, 25, 26,
|
||||
0, 12, 10, 11, 15, 33, 35, 0, 13, 14,
|
||||
16, 24, 25, 26, 6, 7, 0, 0, 0, 33,
|
||||
35, 12, 0, 0, 0, 0, 0, 0, 13, 14,
|
||||
16,
|
||||
}
|
||||
var yyPact = [...]int{
|
||||
|
||||
-35, -1000, 218, -35, -35, -1000, -1000, -1000, -1000, 91,
|
||||
-27, -1000, 218, 218, 218, -1000, 218, -1000, 198, -1000,
|
||||
218, 218, 218, 218, 218, 218, 218, 218, 218, 218,
|
||||
218, 218, 218, 218, 218, 218, 218, 218, -31, -31,
|
||||
-31, 62, -1000, 120, 33, 205, 205, -31, -31, -31,
|
||||
191, 191, -16, -16, -16, -16, 120, 149, 120, 171,
|
||||
29, 120, -1000, 218, -1000, 218, 120, 120,
|
||||
}
|
||||
var yyPgo = [...]int{
|
||||
|
||||
0, 16, 9, 20, 0, 8, 53, 50, 115,
|
||||
}
|
||||
var yyR1 = [...]int{
|
||||
|
||||
0, 1, 1, 2, 2, 3, 3, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 5, 5, 5, 6, 6, 7, 7, 8, 8,
|
||||
}
|
||||
var yyR2 = [...]int{
|
||||
|
||||
0, 1, 2, 2, 3, 3, 1, 1, 1, 2,
|
||||
2, 2, 1, 5, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 0, 1, 3, 0, 1, 1, 2, 1, 1,
|
||||
}
|
||||
var yyChk = [...]int{
|
||||
|
||||
-1000, -1, -6, -2, -7, -8, 36, 37, -3, -4,
|
||||
4, 5, 23, 30, 31, 6, 32, -6, -7, -8,
|
||||
14, 15, 22, 23, 26, 27, 28, 7, 8, 18,
|
||||
9, 19, 10, 34, 11, 35, 12, 32, -4, -4,
|
||||
-4, -4, -3, -4, -4, -4, -4, -4, -4, -4,
|
||||
-4, -4, -4, -4, -4, -4, -4, -4, -4, -4,
|
||||
-5, -4, 33, 16, 33, 17, -4, -4,
|
||||
}
|
||||
var yyDef = [...]int{
|
||||
|
||||
34, -2, 1, 34, 35, 36, 38, 39, 3, 6,
|
||||
7, 8, 0, 0, 0, 12, 0, 2, 35, 37,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 31, 9, 10,
|
||||
11, 0, 4, 5, 0, 15, 16, 17, 18, 19,
|
||||
-2, -2, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
0, 32, 14, 0, 30, 0, 13, 33,
|
||||
}
|
||||
var yyTok1 = [...]int{
|
||||
|
||||
1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
37, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 30, 3, 3, 3, 28, 35, 3,
|
||||
32, 33, 26, 22, 17, 23, 3, 27, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 16, 36,
|
||||
19, 14, 18, 15, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 31, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 34,
|
||||
}
|
||||
var yyTok2 = [...]int{
|
||||
|
||||
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
12, 13, 20, 21, 24, 25, 29,
|
||||
}
|
||||
var yyTok3 = [...]int{
|
||||
0,
|
||||
}
|
||||
|
||||
var yyErrorMessages = [...]struct {
|
||||
state int
|
||||
token int
|
||||
msg string
|
||||
}{}
|
||||
|
||||
//line yaccpar:1
|
||||
|
||||
/* parser for yacc output */
|
||||
|
||||
var (
|
||||
yyDebug = 0
|
||||
yyErrorVerbose = false
|
||||
)
|
||||
|
||||
type yyLexer interface {
|
||||
Lex(lval *yySymType) int
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
type yyParser interface {
|
||||
Parse(yyLexer) int
|
||||
Lookahead() int
|
||||
}
|
||||
|
||||
type yyParserImpl struct {
|
||||
lval yySymType
|
||||
stack [yyInitialStackSize]yySymType
|
||||
char int
|
||||
}
|
||||
|
||||
func (p *yyParserImpl) Lookahead() int {
|
||||
return p.char
|
||||
}
|
||||
|
||||
func yyNewParser() yyParser {
|
||||
return &yyParserImpl{}
|
||||
}
|
||||
|
||||
const yyFlag = -1000
|
||||
|
||||
func yyTokname(c int) string {
|
||||
if c >= 1 && c-1 < len(yyToknames) {
|
||||
if yyToknames[c-1] != "" {
|
||||
return yyToknames[c-1]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("tok-%v", c)
|
||||
}
|
||||
|
||||
func yyStatname(s int) string {
|
||||
if s >= 0 && s < len(yyStatenames) {
|
||||
if yyStatenames[s] != "" {
|
||||
return yyStatenames[s]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("state-%v", s)
|
||||
}
|
||||
|
||||
func yyErrorMessage(state, lookAhead int) string {
|
||||
const TOKSTART = 4
|
||||
|
||||
if !yyErrorVerbose {
|
||||
return "syntax error"
|
||||
}
|
||||
|
||||
for _, e := range yyErrorMessages {
|
||||
if e.state == state && e.token == lookAhead {
|
||||
return "syntax error: " + e.msg
|
||||
}
|
||||
}
|
||||
|
||||
res := "syntax error: unexpected " + yyTokname(lookAhead)
|
||||
|
||||
// To match Bison, suggest at most four expected tokens.
|
||||
expected := make([]int, 0, 4)
|
||||
|
||||
// Look for shiftable tokens.
|
||||
base := yyPact[state]
|
||||
for tok := TOKSTART; tok-1 < len(yyToknames); tok++ {
|
||||
if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok {
|
||||
if len(expected) == cap(expected) {
|
||||
return res
|
||||
}
|
||||
expected = append(expected, tok)
|
||||
}
|
||||
}
|
||||
|
||||
if yyDef[state] == -2 {
|
||||
i := 0
|
||||
for yyExca[i] != -1 || yyExca[i+1] != state {
|
||||
i += 2
|
||||
}
|
||||
|
||||
// Look for tokens that we accept or reduce.
|
||||
for i += 2; yyExca[i] >= 0; i += 2 {
|
||||
tok := yyExca[i]
|
||||
if tok < TOKSTART || yyExca[i+1] == 0 {
|
||||
continue
|
||||
}
|
||||
if len(expected) == cap(expected) {
|
||||
return res
|
||||
}
|
||||
expected = append(expected, tok)
|
||||
}
|
||||
|
||||
// If the default action is to accept or reduce, give up.
|
||||
if yyExca[i+1] != 0 {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
for i, tok := range expected {
|
||||
if i == 0 {
|
||||
res += ", expecting "
|
||||
} else {
|
||||
res += " or "
|
||||
}
|
||||
res += yyTokname(tok)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func yylex1(lex yyLexer, lval *yySymType) (char, token int) {
|
||||
token = 0
|
||||
char = lex.Lex(lval)
|
||||
if char <= 0 {
|
||||
token = yyTok1[0]
|
||||
goto out
|
||||
}
|
||||
if char < len(yyTok1) {
|
||||
token = yyTok1[char]
|
||||
goto out
|
||||
}
|
||||
if char >= yyPrivate {
|
||||
if char < yyPrivate+len(yyTok2) {
|
||||
token = yyTok2[char-yyPrivate]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(yyTok3); i += 2 {
|
||||
token = yyTok3[i+0]
|
||||
if token == char {
|
||||
token = yyTok3[i+1]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if token == 0 {
|
||||
token = yyTok2[1] /* unknown char */
|
||||
}
|
||||
if yyDebug >= 3 {
|
||||
__yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char))
|
||||
}
|
||||
return char, token
|
||||
}
|
||||
|
||||
func yyParse(yylex yyLexer) int {
|
||||
return yyNewParser().Parse(yylex)
|
||||
}
|
||||
|
||||
func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int {
|
||||
var yyn int
|
||||
var yyVAL yySymType
|
||||
var yyDollar []yySymType
|
||||
_ = yyDollar // silence set and not used
|
||||
yyS := yyrcvr.stack[:]
|
||||
|
||||
Nerrs := 0 /* number of errors */
|
||||
Errflag := 0 /* error recovery flag */
|
||||
yystate := 0
|
||||
yyrcvr.char = -1
|
||||
yytoken := -1 // yyrcvr.char translated into internal numbering
|
||||
defer func() {
|
||||
// Make sure we report no lookahead when not parsing.
|
||||
yystate = -1
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
}()
|
||||
yyp := -1
|
||||
goto yystack
|
||||
|
||||
ret0:
|
||||
return 0
|
||||
|
||||
ret1:
|
||||
return 1
|
||||
|
||||
yystack:
|
||||
/* put a state and value onto the stack */
|
||||
if yyDebug >= 4 {
|
||||
__yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate))
|
||||
}
|
||||
|
||||
yyp++
|
||||
if yyp >= len(yyS) {
|
||||
nyys := make([]yySymType, len(yyS)*2)
|
||||
copy(nyys, yyS)
|
||||
yyS = nyys
|
||||
}
|
||||
yyS[yyp] = yyVAL
|
||||
yyS[yyp].yys = yystate
|
||||
|
||||
yynewstate:
|
||||
yyn = yyPact[yystate]
|
||||
if yyn <= yyFlag {
|
||||
goto yydefault /* simple state */
|
||||
}
|
||||
if yyrcvr.char < 0 {
|
||||
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
|
||||
}
|
||||
yyn += yytoken
|
||||
if yyn < 0 || yyn >= yyLast {
|
||||
goto yydefault
|
||||
}
|
||||
yyn = yyAct[yyn]
|
||||
if yyChk[yyn] == yytoken { /* valid shift */
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
yyVAL = yyrcvr.lval
|
||||
yystate = yyn
|
||||
if Errflag > 0 {
|
||||
Errflag--
|
||||
}
|
||||
goto yystack
|
||||
}
|
||||
|
||||
yydefault:
|
||||
/* default state action */
|
||||
yyn = yyDef[yystate]
|
||||
if yyn == -2 {
|
||||
if yyrcvr.char < 0 {
|
||||
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
|
||||
}
|
||||
|
||||
/* look through exception table */
|
||||
xi := 0
|
||||
for {
|
||||
if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate {
|
||||
break
|
||||
}
|
||||
xi += 2
|
||||
}
|
||||
for xi += 2; ; xi += 2 {
|
||||
yyn = yyExca[xi+0]
|
||||
if yyn < 0 || yyn == yytoken {
|
||||
break
|
||||
}
|
||||
}
|
||||
yyn = yyExca[xi+1]
|
||||
if yyn < 0 {
|
||||
goto ret0
|
||||
}
|
||||
}
|
||||
if yyn == 0 {
|
||||
/* error ... attempt to resume parsing */
|
||||
switch Errflag {
|
||||
case 0: /* brand new error */
|
||||
yylex.Error(yyErrorMessage(yystate, yytoken))
|
||||
Nerrs++
|
||||
if yyDebug >= 1 {
|
||||
__yyfmt__.Printf("%s", yyStatname(yystate))
|
||||
__yyfmt__.Printf(" saw %s\n", yyTokname(yytoken))
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 1, 2: /* incompletely recovered error ... try again */
|
||||
Errflag = 3
|
||||
|
||||
/* find a state where "error" is a legal shift action */
|
||||
for yyp >= 0 {
|
||||
yyn = yyPact[yyS[yyp].yys] + yyErrCode
|
||||
if yyn >= 0 && yyn < yyLast {
|
||||
yystate = yyAct[yyn] /* simulate a shift of "error" */
|
||||
if yyChk[yystate] == yyErrCode {
|
||||
goto yystack
|
||||
}
|
||||
}
|
||||
|
||||
/* the current p has no shift on "error", pop stack */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys)
|
||||
}
|
||||
yyp--
|
||||
}
|
||||
/* there is no state on the stack with an error shift ... abort */
|
||||
goto ret1
|
||||
|
||||
case 3: /* no shift yet; clobber input char */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken))
|
||||
}
|
||||
if yytoken == yyEofCode {
|
||||
goto ret1
|
||||
}
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
goto yynewstate /* try again in the same state */
|
||||
}
|
||||
}
|
||||
|
||||
/* reduction by production yyn */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate))
|
||||
}
|
||||
|
||||
yynt := yyn
|
||||
yypt := yyp
|
||||
_ = yypt // guard against "declared and not used"
|
||||
|
||||
yyp -= yyR2[yyn]
|
||||
// yyp is now the index of $0. Perform the default action. Iff the
|
||||
// reduced production is ε, $1 is possibly out of range.
|
||||
if yyp+1 >= len(yyS) {
|
||||
nyys := make([]yySymType, len(yyS)*2)
|
||||
copy(nyys, yyS)
|
||||
yyS = nyys
|
||||
}
|
||||
yyVAL = yyS[yyp+1]
|
||||
|
||||
/* consult goto table to find next state */
|
||||
yyn = yyR1[yyn]
|
||||
yyg := yyPgo[yyn]
|
||||
yyj := yyg + yyS[yyp].yys + 1
|
||||
|
||||
if yyj >= yyLast {
|
||||
yystate = yyAct[yyg]
|
||||
} else {
|
||||
yystate = yyAct[yyj]
|
||||
if yyChk[yystate] != -yyn {
|
||||
yystate = yyAct[yyg]
|
||||
}
|
||||
}
|
||||
// dummy call; replaced with literal code
|
||||
switch yynt {
|
||||
|
||||
case 1:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:45
|
||||
{
|
||||
yyVAL.compstmt = nil
|
||||
}
|
||||
case 2:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:49
|
||||
{
|
||||
yyVAL.compstmt = yyDollar[1].stmts
|
||||
}
|
||||
case 3:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:55
|
||||
{
|
||||
yyVAL.stmts = []ast.Stmt{yyDollar[2].stmt}
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = yyVAL.stmts
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:62
|
||||
{
|
||||
if yyDollar[3].stmt != nil {
|
||||
yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[3].stmt)
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = yyVAL.stmts
|
||||
}
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:73
|
||||
{
|
||||
yyVAL.stmt = &ast.LetStmt{Lhs: yyDollar[1].expr, Operator: "=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 6:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:77
|
||||
{
|
||||
yyVAL.stmt = &ast.ExprStmt{Expr: yyDollar[1].expr}
|
||||
}
|
||||
case 7:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:83
|
||||
{
|
||||
yyVAL.expr = &ast.IdentExpr{Lit: yyDollar[1].tok.Lit}
|
||||
}
|
||||
case 8:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:87
|
||||
{
|
||||
yyVAL.expr = &ast.NumberExpr{Lit: yyDollar[1].tok.Lit}
|
||||
}
|
||||
case 9:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:91
|
||||
{
|
||||
yyVAL.expr = &ast.UnaryExpr{Operator: "-", Expr: yyDollar[2].expr}
|
||||
}
|
||||
case 10:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:95
|
||||
{
|
||||
yyVAL.expr = &ast.UnaryExpr{Operator: "!", Expr: yyDollar[2].expr}
|
||||
}
|
||||
case 11:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:99
|
||||
{
|
||||
yyVAL.expr = &ast.UnaryExpr{Operator: "^", Expr: yyDollar[2].expr}
|
||||
}
|
||||
case 12:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:103
|
||||
{
|
||||
yyVAL.expr = &ast.StringExpr{Lit: yyDollar[1].tok.Lit}
|
||||
}
|
||||
case 13:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line parser.go.y:107
|
||||
{
|
||||
yyVAL.expr = &ast.TernaryOpExpr{Expr: yyDollar[1].expr, Lhs: yyDollar[3].expr, Rhs: yyDollar[5].expr}
|
||||
}
|
||||
case 14:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:111
|
||||
{
|
||||
yyVAL.expr = &ast.ParenExpr{SubExpr: yyDollar[2].expr}
|
||||
}
|
||||
case 15:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:115
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 16:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:119
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 17:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:123
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 18:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:127
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 19:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:131
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 20:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:135
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 21:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:139
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "!=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 22:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:143
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 23:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:147
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 24:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:151
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 25:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:155
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 26:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:159
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "|", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 27:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:163
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "||", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 28:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:167
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 29:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:171
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&&", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 30:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line parser.go.y:175
|
||||
{
|
||||
yyVAL.expr = &ast.CallExpr{Name: yyDollar[1].tok.Lit, SubExprs: yyDollar[3].exprs}
|
||||
}
|
||||
case 31:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
//line parser.go.y:180
|
||||
{
|
||||
yyVAL.exprs = nil
|
||||
}
|
||||
case 32:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:184
|
||||
{
|
||||
yyVAL.exprs = []ast.Expr{yyDollar[1].expr}
|
||||
}
|
||||
case 33:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:188
|
||||
{
|
||||
yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[3].expr)
|
||||
}
|
||||
case 36:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:198
|
||||
{
|
||||
}
|
||||
case 37:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:201
|
||||
{
|
||||
}
|
||||
case 38:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:206
|
||||
{
|
||||
}
|
||||
case 39:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:209
|
||||
{
|
||||
}
|
||||
}
|
||||
goto yystack /* stack new state and value */
|
||||
}
|
||||
214
vendor/github.com/mattn/kinako/parser/parser.go.y
generated
vendored
214
vendor/github.com/mattn/kinako/parser/parser.go.y
generated
vendored
@@ -1,214 +0,0 @@
|
||||
%{
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
%}
|
||||
|
||||
%type<compstmt> compstmt
|
||||
%type<stmts> stmts
|
||||
%type<stmt> stmt
|
||||
%type<expr> expr
|
||||
%type<exprs> exprs
|
||||
|
||||
%union{
|
||||
compstmt []ast.Stmt
|
||||
stmts []ast.Stmt
|
||||
stmt ast.Stmt
|
||||
expr ast.Expr
|
||||
exprs []ast.Expr
|
||||
tok ast.Token
|
||||
term ast.Token
|
||||
terms ast.Token
|
||||
opt_terms ast.Token
|
||||
}
|
||||
|
||||
%token<tok> IDENT NUMBER STRING EQEQ NEQ GE LE OROR ANDAND POW
|
||||
|
||||
%right '='
|
||||
%right '?' ':'
|
||||
%left OROR
|
||||
%left ANDAND
|
||||
%left IDENT
|
||||
%nonassoc EQEQ NEQ ','
|
||||
%left '>' GE '<' LE SHIFTLEFT SHIFTRIGHT
|
||||
|
||||
%left '+' '-' PLUSPLUS MINUSMINUS
|
||||
%left '*' '/' '%'
|
||||
%right UNARY
|
||||
|
||||
%%
|
||||
|
||||
compstmt : opt_terms
|
||||
{
|
||||
$$ = nil
|
||||
}
|
||||
| stmts opt_terms
|
||||
{
|
||||
$$ = $1
|
||||
}
|
||||
|
||||
stmts :
|
||||
opt_terms stmt
|
||||
{
|
||||
$$ = []ast.Stmt{$2}
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = $$
|
||||
}
|
||||
}
|
||||
| stmts terms stmt
|
||||
{
|
||||
if $3 != nil {
|
||||
$$ = append($1, $3)
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = $$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stmt :
|
||||
expr '=' expr
|
||||
{
|
||||
$$ = &ast.LetStmt{Lhs: $1, Operator: "=", Rhs: $3}
|
||||
}
|
||||
| expr
|
||||
{
|
||||
$$ = &ast.ExprStmt{Expr: $1}
|
||||
}
|
||||
|
||||
expr :
|
||||
IDENT
|
||||
{
|
||||
$$ = &ast.IdentExpr{Lit: $1.Lit}
|
||||
}
|
||||
| NUMBER
|
||||
{
|
||||
$$ = &ast.NumberExpr{Lit: $1.Lit}
|
||||
}
|
||||
| '-' expr %prec UNARY
|
||||
{
|
||||
$$ = &ast.UnaryExpr{Operator: "-", Expr: $2}
|
||||
}
|
||||
| '!' expr %prec UNARY
|
||||
{
|
||||
$$ = &ast.UnaryExpr{Operator: "!", Expr: $2}
|
||||
}
|
||||
| '^' expr %prec UNARY
|
||||
{
|
||||
$$ = &ast.UnaryExpr{Operator: "^", Expr: $2}
|
||||
}
|
||||
| STRING
|
||||
{
|
||||
$$ = &ast.StringExpr{Lit: $1.Lit}
|
||||
}
|
||||
| expr '?' expr ':' expr
|
||||
{
|
||||
$$ = &ast.TernaryOpExpr{Expr: $1, Lhs: $3, Rhs: $5}
|
||||
}
|
||||
| '(' expr ')'
|
||||
{
|
||||
$$ = &ast.ParenExpr{SubExpr: $2}
|
||||
}
|
||||
| expr '+' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "+", Rhs: $3}
|
||||
}
|
||||
| expr '-' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "-", Rhs: $3}
|
||||
}
|
||||
| expr '*' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "*", Rhs: $3}
|
||||
}
|
||||
| expr '/' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "/", Rhs: $3}
|
||||
}
|
||||
| expr '%' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "%", Rhs: $3}
|
||||
}
|
||||
| expr EQEQ expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "==", Rhs: $3}
|
||||
}
|
||||
| expr NEQ expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "!=", Rhs: $3}
|
||||
}
|
||||
| expr '>' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">", Rhs: $3}
|
||||
}
|
||||
| expr GE expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">=", Rhs: $3}
|
||||
}
|
||||
| expr '<' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<", Rhs: $3}
|
||||
}
|
||||
| expr LE expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<=", Rhs: $3}
|
||||
}
|
||||
| expr '|' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "|", Rhs: $3}
|
||||
}
|
||||
| expr OROR expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "||", Rhs: $3}
|
||||
}
|
||||
| expr '&' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&", Rhs: $3}
|
||||
}
|
||||
| expr ANDAND expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&&", Rhs: $3}
|
||||
}
|
||||
| IDENT '(' exprs ')'
|
||||
{
|
||||
$$ = &ast.CallExpr{Name: $1.Lit, SubExprs: $3}
|
||||
}
|
||||
|
||||
exprs :
|
||||
{
|
||||
$$ = nil
|
||||
}
|
||||
| expr
|
||||
{
|
||||
$$ = []ast.Expr{$1}
|
||||
}
|
||||
| exprs ',' expr
|
||||
{
|
||||
$$ = append($1, $3)
|
||||
}
|
||||
|
||||
opt_terms : /* none */
|
||||
| terms
|
||||
;
|
||||
|
||||
|
||||
terms : term
|
||||
{
|
||||
}
|
||||
| terms term
|
||||
{
|
||||
}
|
||||
;
|
||||
|
||||
term : ';'
|
||||
{
|
||||
}
|
||||
| '\n'
|
||||
{
|
||||
}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
1381
vendor/github.com/mattn/kinako/parser/y.output
generated
vendored
1381
vendor/github.com/mattn/kinako/parser/y.output
generated
vendored
File diff suppressed because it is too large
Load Diff
258
vendor/github.com/mattn/kinako/vm/env.go
generated
vendored
258
vendor/github.com/mattn/kinako/vm/env.go
generated
vendored
@@ -1,258 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/kinako/parser"
|
||||
)
|
||||
|
||||
// Env provides interface to run VM. This mean function scope and blocked-scope.
|
||||
// If stack goes to blocked-scope, it will make new Env.
|
||||
type Env struct {
|
||||
name string
|
||||
env map[string]reflect.Value
|
||||
typ map[string]reflect.Type
|
||||
parent *Env
|
||||
interrupt *bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewEnv creates new global scope.
|
||||
func NewEnv() *Env {
|
||||
b := false
|
||||
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: nil,
|
||||
interrupt: &b,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEnv creates new child scope.
|
||||
func (e *Env) NewEnv() *Env {
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: e,
|
||||
name: e.name,
|
||||
interrupt: e.interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewPackage(n string) *Env {
|
||||
b := false
|
||||
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: nil,
|
||||
name: n,
|
||||
interrupt: &b,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Env) NewPackage(n string) *Env {
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: e,
|
||||
name: n,
|
||||
interrupt: e.interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy deletes current scope.
|
||||
func (e *Env) Destroy() {
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
if e.parent == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range e.parent.env {
|
||||
if v.IsValid() && v.Interface() == e {
|
||||
delete(e.parent.env, k)
|
||||
}
|
||||
}
|
||||
e.parent = nil
|
||||
e.env = nil
|
||||
}
|
||||
|
||||
// NewModule creates new module scope as global.
|
||||
func (e *Env) NewModule(n string) *Env {
|
||||
m := &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
parent: e,
|
||||
name: n,
|
||||
}
|
||||
e.Define(n, m)
|
||||
return m
|
||||
}
|
||||
|
||||
// SetName sets a name of the scope. This means that the scope is module.
|
||||
func (e *Env) SetName(n string) {
|
||||
e.Lock()
|
||||
e.name = n
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
// GetName returns module name.
|
||||
func (e *Env) GetName() string {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
return e.name
|
||||
}
|
||||
|
||||
// Addr returns pointer value which specified symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Addr(k string) (reflect.Value, error) {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
if v, ok := e.env[k]; ok {
|
||||
return v.Addr(), nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return NilValue, fmt.Errorf("Undefined symbol '%s'", k)
|
||||
}
|
||||
return e.parent.Addr(k)
|
||||
}
|
||||
|
||||
// Type returns type which specified symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Type(k string) (reflect.Type, error) {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
if v, ok := e.typ[k]; ok {
|
||||
return v, nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return NilType, fmt.Errorf("Undefined type '%s'", k)
|
||||
}
|
||||
return e.parent.Type(k)
|
||||
}
|
||||
|
||||
// Get returns value which specified symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Get(k string) (reflect.Value, error) {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
if v, ok := e.env[k]; ok {
|
||||
return v, nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return NilValue, fmt.Errorf("Undefined symbol '%s'", k)
|
||||
}
|
||||
return e.parent.Get(k)
|
||||
}
|
||||
|
||||
// Set modifies value which specified as symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Set(k string, v interface{}) error {
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
if _, ok := e.env[k]; ok {
|
||||
val, ok := v.(reflect.Value)
|
||||
if !ok {
|
||||
val = reflect.ValueOf(v)
|
||||
}
|
||||
e.env[k] = val
|
||||
return nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return fmt.Errorf("Unknown symbol '%s'", k)
|
||||
}
|
||||
return e.parent.Set(k, v)
|
||||
}
|
||||
|
||||
// DefineGlobal defines symbol in global scope.
|
||||
func (e *Env) DefineGlobal(k string, v interface{}) error {
|
||||
if e.parent == nil {
|
||||
return e.Define(k, v)
|
||||
}
|
||||
return e.parent.DefineGlobal(k, v)
|
||||
}
|
||||
|
||||
// DefineType defines type which specifis symbol in global scope.
|
||||
func (e *Env) DefineType(k string, t interface{}) error {
|
||||
if strings.Contains(k, ".") {
|
||||
return fmt.Errorf("Unknown symbol '%s'", k)
|
||||
}
|
||||
global := e
|
||||
keys := []string{k}
|
||||
|
||||
e.RLock()
|
||||
for global.parent != nil {
|
||||
if global.name != "" {
|
||||
keys = append(keys, global.name)
|
||||
}
|
||||
global = global.parent
|
||||
}
|
||||
e.RUnlock()
|
||||
|
||||
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
|
||||
keys[i], keys[j] = keys[j], keys[i]
|
||||
}
|
||||
|
||||
typ, ok := t.(reflect.Type)
|
||||
if !ok {
|
||||
typ = reflect.TypeOf(t)
|
||||
}
|
||||
|
||||
global.Lock()
|
||||
global.typ[strings.Join(keys, ".")] = typ
|
||||
global.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Define defines symbol in current scope.
|
||||
func (e *Env) Define(k string, v interface{}) error {
|
||||
if strings.Contains(k, ".") {
|
||||
return fmt.Errorf("Unknown symbol '%s'", k)
|
||||
}
|
||||
val, ok := v.(reflect.Value)
|
||||
if !ok {
|
||||
val = reflect.ValueOf(v)
|
||||
}
|
||||
|
||||
e.Lock()
|
||||
e.env[k] = val
|
||||
e.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String return the name of current scope.
|
||||
func (e *Env) String() string {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
return e.name
|
||||
}
|
||||
|
||||
// Dump show symbol values in the scope.
|
||||
func (e *Env) Dump() {
|
||||
e.RLock()
|
||||
for k, v := range e.env {
|
||||
fmt.Printf("%v = %#v\n", k, v)
|
||||
}
|
||||
e.RUnlock()
|
||||
}
|
||||
|
||||
// Execute parses and runs source in current scope.
|
||||
func (e *Env) Execute(src string) (reflect.Value, error) {
|
||||
stmts, err := parser.ParseSrc(src)
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
return Run(stmts, e)
|
||||
}
|
||||
476
vendor/github.com/mattn/kinako/vm/vm.go
generated
vendored
476
vendor/github.com/mattn/kinako/vm/vm.go
generated
vendored
@@ -1,476 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
var (
|
||||
NilValue = reflect.ValueOf((*interface{})(nil))
|
||||
NilType = reflect.TypeOf((*interface{})(nil))
|
||||
TrueValue = reflect.ValueOf(true)
|
||||
FalseValue = reflect.ValueOf(false)
|
||||
)
|
||||
|
||||
// Error provides a convenient interface for handling runtime error.
|
||||
// It can be Error interface with type cast which can call Pos().
|
||||
type Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
var (
|
||||
BreakError = errors.New("Unexpected break statement")
|
||||
ContinueError = errors.New("Unexpected continue statement")
|
||||
ReturnError = errors.New("Unexpected return statement")
|
||||
InterruptError = errors.New("Execution interrupted")
|
||||
)
|
||||
|
||||
// Error returns the error message.
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Func is function interface to reflect functions internaly.
|
||||
type Func func(args ...reflect.Value) (reflect.Value, error)
|
||||
|
||||
// Run executes statements in the specified environment.
|
||||
func Run(stmts []ast.Stmt, env *Env) (reflect.Value, error) {
|
||||
rv := NilValue
|
||||
var err error
|
||||
for _, stmt := range stmts {
|
||||
rv, err = RunSingleStmt(stmt, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// Interrupts the execution of any running statements in the specified environment.
|
||||
//
|
||||
// Note that the execution is not instantly aborted: after a call to Interrupt,
|
||||
// the current running statement will finish, but the next statement will not run,
|
||||
// and instead will return a NilValue and an InterruptError.
|
||||
func Interrupt(env *Env) {
|
||||
env.Lock()
|
||||
*(env.interrupt) = true
|
||||
env.Unlock()
|
||||
}
|
||||
|
||||
// RunSingleStmt executes one statement in the specified environment.
|
||||
func RunSingleStmt(stmt ast.Stmt, env *Env) (reflect.Value, error) {
|
||||
env.Lock()
|
||||
if *(env.interrupt) {
|
||||
*(env.interrupt) = false
|
||||
env.Unlock()
|
||||
|
||||
return NilValue, InterruptError
|
||||
}
|
||||
env.Unlock()
|
||||
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.ExprStmt:
|
||||
rv, err := invokeExpr(stmt.Expr, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
return rv, nil
|
||||
case *ast.LetStmt:
|
||||
rv := NilValue
|
||||
var err error
|
||||
rv, err = invokeExpr(stmt.Rhs, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
_, err = invokeLetExpr(stmt.Lhs, rv, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
return rv, nil
|
||||
default:
|
||||
return NilValue, errors.New("unknown statement")
|
||||
}
|
||||
}
|
||||
|
||||
// toString converts all reflect.Value-s into string.
|
||||
func toString(v reflect.Value) string {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() == reflect.String {
|
||||
return v.String()
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "nil"
|
||||
}
|
||||
return fmt.Sprint(v.Interface())
|
||||
}
|
||||
|
||||
// toBool converts all reflect.Value-s into bool.
|
||||
func toBool(v reflect.Value) bool {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() != 0.0
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
return v.Int() != 0
|
||||
case reflect.Bool:
|
||||
return v.Bool()
|
||||
case reflect.String:
|
||||
if v.String() == "true" {
|
||||
return true
|
||||
}
|
||||
if toInt64(v) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// toFloat64 converts all reflect.Value-s into float64.
|
||||
func toFloat64(v reflect.Value) float64 {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float()
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
return float64(v.Int())
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func isNil(v reflect.Value) bool {
|
||||
if !v.IsValid() || v.Kind().String() == "unsafe.Pointer" {
|
||||
return true
|
||||
}
|
||||
if (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isNum(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// equal returns true when lhsV and rhsV is same value.
|
||||
func equal(lhsV, rhsV reflect.Value) bool {
|
||||
lhsIsNil, rhsIsNil := isNil(lhsV), isNil(rhsV)
|
||||
if lhsIsNil && rhsIsNil {
|
||||
return true
|
||||
}
|
||||
if (!lhsIsNil && rhsIsNil) || (lhsIsNil && !rhsIsNil) {
|
||||
return false
|
||||
}
|
||||
if lhsV.Kind() == reflect.Interface || lhsV.Kind() == reflect.Ptr {
|
||||
lhsV = lhsV.Elem()
|
||||
}
|
||||
if rhsV.Kind() == reflect.Interface || rhsV.Kind() == reflect.Ptr {
|
||||
rhsV = rhsV.Elem()
|
||||
}
|
||||
if !lhsV.IsValid() || !rhsV.IsValid() {
|
||||
return true
|
||||
}
|
||||
if isNum(lhsV) && isNum(rhsV) {
|
||||
if rhsV.Type().ConvertibleTo(lhsV.Type()) {
|
||||
rhsV = rhsV.Convert(lhsV.Type())
|
||||
}
|
||||
}
|
||||
if lhsV.CanInterface() && rhsV.CanInterface() {
|
||||
return reflect.DeepEqual(lhsV.Interface(), rhsV.Interface())
|
||||
}
|
||||
return reflect.DeepEqual(lhsV, rhsV)
|
||||
}
|
||||
|
||||
// toInt64 converts all reflect.Value-s into int64.
|
||||
func toInt64(v reflect.Value) int64 {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return int64(v.Float())
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
return v.Int()
|
||||
case reflect.String:
|
||||
s := v.String()
|
||||
var i int64
|
||||
var err error
|
||||
if strings.HasPrefix(s, "0x") {
|
||||
i, err = strconv.ParseInt(s, 16, 64)
|
||||
} else {
|
||||
i, err = strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
if err == nil {
|
||||
return int64(i)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func invokeLetExpr(expr ast.Expr, rv reflect.Value, env *Env) (reflect.Value, error) {
|
||||
switch lhs := expr.(type) {
|
||||
case *ast.IdentExpr:
|
||||
if env.Set(lhs.Lit, rv) != nil {
|
||||
if strings.Contains(lhs.Lit, ".") {
|
||||
return NilValue, fmt.Errorf("Undefined symbol '%s'", lhs.Lit)
|
||||
}
|
||||
env.Define(lhs.Lit, rv)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
return NilValue, errors.New("Invalid operation")
|
||||
}
|
||||
|
||||
// invokeExpr evaluates one expression.
|
||||
func invokeExpr(expr ast.Expr, env *Env) (reflect.Value, error) {
|
||||
switch e := expr.(type) {
|
||||
case *ast.NumberExpr:
|
||||
if strings.Contains(e.Lit, ".") || strings.Contains(e.Lit, "e") {
|
||||
v, err := strconv.ParseFloat(e.Lit, 64)
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
return reflect.ValueOf(float64(v)), nil
|
||||
}
|
||||
var i int64
|
||||
var err error
|
||||
if strings.HasPrefix(e.Lit, "0x") {
|
||||
i, err = strconv.ParseInt(e.Lit[2:], 16, 64)
|
||||
} else {
|
||||
i, err = strconv.ParseInt(e.Lit, 10, 64)
|
||||
}
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
return reflect.ValueOf(i), nil
|
||||
case *ast.IdentExpr:
|
||||
return env.Get(e.Lit)
|
||||
case *ast.StringExpr:
|
||||
return reflect.ValueOf(e.Lit), nil
|
||||
case *ast.UnaryExpr:
|
||||
v, err := invokeExpr(e.Expr, env)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
switch e.Operator {
|
||||
case "-":
|
||||
if v.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(-v.Float()), nil
|
||||
}
|
||||
return reflect.ValueOf(-v.Int()), nil
|
||||
case "^":
|
||||
return reflect.ValueOf(^toInt64(v)), nil
|
||||
case "!":
|
||||
return reflect.ValueOf(!toBool(v)), nil
|
||||
default:
|
||||
return NilValue, errors.New("Unknown operator ''")
|
||||
}
|
||||
case *ast.ParenExpr:
|
||||
v, err := invokeExpr(e.SubExpr, env)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
case *ast.BinOpExpr:
|
||||
lhsV := NilValue
|
||||
rhsV := NilValue
|
||||
var err error
|
||||
|
||||
lhsV, err = invokeExpr(e.Lhs, env)
|
||||
if err != nil {
|
||||
return lhsV, err
|
||||
}
|
||||
if lhsV.Kind() == reflect.Interface {
|
||||
lhsV = lhsV.Elem()
|
||||
}
|
||||
if e.Rhs != nil {
|
||||
rhsV, err = invokeExpr(e.Rhs, env)
|
||||
if err != nil {
|
||||
return rhsV, err
|
||||
}
|
||||
if rhsV.Kind() == reflect.Interface {
|
||||
rhsV = rhsV.Elem()
|
||||
}
|
||||
}
|
||||
switch e.Operator {
|
||||
case "+":
|
||||
if lhsV.Kind() == reflect.String || rhsV.Kind() == reflect.String {
|
||||
return reflect.ValueOf(toString(lhsV) + toString(rhsV)), nil
|
||||
}
|
||||
if (lhsV.Kind() == reflect.Array || lhsV.Kind() == reflect.Slice) && (rhsV.Kind() != reflect.Array && rhsV.Kind() != reflect.Slice) {
|
||||
return reflect.Append(lhsV, rhsV), nil
|
||||
}
|
||||
if (lhsV.Kind() == reflect.Array || lhsV.Kind() == reflect.Slice) && (rhsV.Kind() == reflect.Array || rhsV.Kind() == reflect.Slice) {
|
||||
return reflect.AppendSlice(lhsV, rhsV), nil
|
||||
}
|
||||
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(toFloat64(lhsV) + toFloat64(rhsV)), nil
|
||||
}
|
||||
return reflect.ValueOf(toInt64(lhsV) + toInt64(rhsV)), nil
|
||||
case "-":
|
||||
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(toFloat64(lhsV) - toFloat64(rhsV)), nil
|
||||
}
|
||||
return reflect.ValueOf(toInt64(lhsV) - toInt64(rhsV)), nil
|
||||
case "*":
|
||||
if lhsV.Kind() == reflect.String && (rhsV.Kind() == reflect.Int || rhsV.Kind() == reflect.Int32 || rhsV.Kind() == reflect.Int64) {
|
||||
return reflect.ValueOf(strings.Repeat(toString(lhsV), int(toInt64(rhsV)))), nil
|
||||
}
|
||||
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(toFloat64(lhsV) * toFloat64(rhsV)), nil
|
||||
}
|
||||
return reflect.ValueOf(toInt64(lhsV) * toInt64(rhsV)), nil
|
||||
case "/":
|
||||
return reflect.ValueOf(toFloat64(lhsV) / toFloat64(rhsV)), nil
|
||||
case "%":
|
||||
return reflect.ValueOf(toInt64(lhsV) % toInt64(rhsV)), nil
|
||||
case "==":
|
||||
return reflect.ValueOf(equal(lhsV, rhsV)), nil
|
||||
case "!=":
|
||||
return reflect.ValueOf(equal(lhsV, rhsV) == false), nil
|
||||
case ">":
|
||||
return reflect.ValueOf(toFloat64(lhsV) > toFloat64(rhsV)), nil
|
||||
case ">=":
|
||||
return reflect.ValueOf(toFloat64(lhsV) >= toFloat64(rhsV)), nil
|
||||
case "<":
|
||||
return reflect.ValueOf(toFloat64(lhsV) < toFloat64(rhsV)), nil
|
||||
case "<=":
|
||||
return reflect.ValueOf(toFloat64(lhsV) <= toFloat64(rhsV)), nil
|
||||
case "|":
|
||||
return reflect.ValueOf(toInt64(lhsV) | toInt64(rhsV)), nil
|
||||
case "||":
|
||||
if toBool(lhsV) {
|
||||
return lhsV, nil
|
||||
}
|
||||
return rhsV, nil
|
||||
case "&":
|
||||
return reflect.ValueOf(toInt64(lhsV) & toInt64(rhsV)), nil
|
||||
case "&&":
|
||||
if toBool(lhsV) {
|
||||
return rhsV, nil
|
||||
}
|
||||
return lhsV, nil
|
||||
case "**":
|
||||
if lhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(math.Pow(toFloat64(lhsV), toFloat64(rhsV))), nil
|
||||
}
|
||||
return reflect.ValueOf(int64(math.Pow(toFloat64(lhsV), toFloat64(rhsV)))), nil
|
||||
case ">>":
|
||||
return reflect.ValueOf(toInt64(lhsV) >> uint64(toInt64(rhsV))), nil
|
||||
case "<<":
|
||||
return reflect.ValueOf(toInt64(lhsV) << uint64(toInt64(rhsV))), nil
|
||||
default:
|
||||
return NilValue, errors.New("Unknown operator")
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
f, err := env.Get(e.Name)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
||||
args := []reflect.Value{}
|
||||
for i, expr := range e.SubExprs {
|
||||
arg, err := invokeExpr(expr, env)
|
||||
if err != nil {
|
||||
return arg, err
|
||||
}
|
||||
|
||||
if i < f.Type().NumIn() {
|
||||
if !f.Type().IsVariadic() {
|
||||
it := f.Type().In(i)
|
||||
if arg.Kind().String() == "unsafe.Pointer" {
|
||||
arg = reflect.New(it).Elem()
|
||||
}
|
||||
if arg.Kind() != it.Kind() && arg.IsValid() && arg.Type().ConvertibleTo(it) {
|
||||
arg = arg.Convert(it)
|
||||
} else if arg.Kind() == reflect.Func {
|
||||
if _, isFunc := arg.Interface().(Func); isFunc {
|
||||
rfunc := arg
|
||||
arg = reflect.MakeFunc(it, func(args []reflect.Value) []reflect.Value {
|
||||
for i := range args {
|
||||
args[i] = reflect.ValueOf(args[i])
|
||||
}
|
||||
return rfunc.Call(args)[:it.NumOut()]
|
||||
})
|
||||
}
|
||||
} else if !arg.IsValid() {
|
||||
arg = reflect.Zero(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !arg.IsValid() {
|
||||
arg = NilValue
|
||||
}
|
||||
|
||||
args = append(args, arg)
|
||||
}
|
||||
ret := NilValue
|
||||
fnc := func() {
|
||||
defer func() {
|
||||
if os.Getenv("KINAKO_DEBUG") == "" {
|
||||
if ex := recover(); ex != nil {
|
||||
if e, ok := ex.(error); ok {
|
||||
err = e
|
||||
} else {
|
||||
err = errors.New(fmt.Sprint(ex))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
if f.Kind() == reflect.Interface {
|
||||
f = f.Elem()
|
||||
}
|
||||
rets := f.Call(args)
|
||||
if f.Type().NumOut() == 1 {
|
||||
ret = rets[0]
|
||||
} else {
|
||||
var result []interface{}
|
||||
for _, r := range rets {
|
||||
result = append(result, r.Interface())
|
||||
}
|
||||
ret = reflect.ValueOf(result)
|
||||
}
|
||||
}
|
||||
fnc()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
return ret, nil
|
||||
case *ast.TernaryOpExpr:
|
||||
rv, err := invokeExpr(e.Expr, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
if toBool(rv) {
|
||||
lhsV, err := invokeExpr(e.Lhs, env)
|
||||
if err != nil {
|
||||
return lhsV, err
|
||||
}
|
||||
return lhsV, nil
|
||||
}
|
||||
rhsV, err := invokeExpr(e.Rhs, env)
|
||||
if err != nil {
|
||||
return rhsV, err
|
||||
}
|
||||
return rhsV, nil
|
||||
default:
|
||||
return NilValue, errors.New("Unknown expression")
|
||||
}
|
||||
}
|
||||
54
vendor/github.com/mattn/kinako/vm/vm_test.go
generated
vendored
54
vendor/github.com/mattn/kinako/vm/vm_test.go
generated
vendored
@@ -1,54 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecute(t *testing.T) {
|
||||
e := NewEnv()
|
||||
e.Define("foo", int64(1))
|
||||
e.Define("bar", int64(2))
|
||||
e.Define("baz", int64(3))
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
want interface{}
|
||||
}{
|
||||
{
|
||||
input: "foo+bar",
|
||||
want: int64(3),
|
||||
},
|
||||
{
|
||||
input: "foo-bar",
|
||||
want: int64(-1),
|
||||
},
|
||||
{
|
||||
input: "foo*bar",
|
||||
want: int64(2),
|
||||
},
|
||||
{
|
||||
input: "foo/bar",
|
||||
want: float64(0.5),
|
||||
},
|
||||
{
|
||||
input: "baz*(foo+bar)",
|
||||
want: int64(9),
|
||||
},
|
||||
{
|
||||
input: "baz > 2 ? foo : bar",
|
||||
want: int64(1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
r, err := e.Execute(tt.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := r.Interface()
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("want %v, but %v:", tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user