From 62fc831407239172f183f7f491222a3aad9d2afa Mon Sep 17 00:00:00 2001 From: mjarkk Date: Mon, 13 Aug 2018 15:33:45 +0200 Subject: [PATCH 01/45] Added a view basic translation functions + tranlation file --- Gopkg.toml | 2 +- gui.go | 12 ++++++------ i18n.go | 40 ++++++++++++++++++++++++++++++++++++++++ i18n/nl.toml | 19 +++++++++++++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 i18n.go create mode 100644 i18n/nl.toml diff --git a/Gopkg.toml b/Gopkg.toml index a47fe5e93..16fd76083 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -39,4 +39,4 @@ [[constraint]] name = "gopkg.in/src-d/go-git.v4" - revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" + revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" \ No newline at end of file diff --git a/gui.go b/gui.go index 589dabea3..f41f2891b 100644 --- a/gui.go +++ b/gui.go @@ -157,7 +157,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Status" + v.Title = ShortLocalize("StatusTitle", "Status") v.FgColor = gocui.ColorWhite } @@ -167,7 +167,7 @@ func layout(g *gocui.Gui) error { return err } filesView.Highlight = true - filesView.Title = "Files" + filesView.Title = ShortLocalize("FilesTitle", "Files") v.FgColor = gocui.ColorWhite } @@ -175,7 +175,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Branches" + v.Title = ShortLocalize("BranchesTitle", "Branches") v.FgColor = gocui.ColorWhite } @@ -183,7 +183,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Commits" + v.Title = ShortLocalize("CommitsTitle", "Commits") v.FgColor = gocui.ColorWhite } @@ -191,7 +191,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Stash" + v.Title = ShortLocalize("StashTitle", "Stash") v.FgColor = gocui.ColorWhite } @@ -210,7 +210,7 @@ func layout(g *gocui.Gui) error { return err } g.SetViewOnBottom("commitMessage") - commitMessageView.Title = "Commit message" + commitMessageView.Title = ShortLocalize("CommitMessage", "Commit message") commitMessageView.FgColor = gocui.ColorWhite commitMessageView.Editable = true } diff --git a/i18n.go b/i18n.go new file mode 100644 index 000000000..a82c29255 --- /dev/null +++ b/i18n.go @@ -0,0 +1,40 @@ +package main + +import ( + "github.com/BurntSushi/toml" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" +) + +// the function to setup the localizer +func getlocalizer() *i18n.Localizer { + + // TODO: currently the system language issn't detected + // I'm not sure how to detect it + var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} + i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) + i18nObject.MustLoadMessageFile("i18n/nl.toml") + return i18n.NewLocalizer(i18nObject) +} + +// setup the localizer for later use +var localizer = getlocalizer() + +// MustLocalize handels the translations +// expects i18n.LocalizeConfig as input: https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n#Localizer.MustLocalize +// output: translated string +func MustLocalize(config *i18n.LocalizeConfig) string { + return localizer.MustLocalize(config) +} + +// ShortLocalize is for 1 line localizations +// ID: The id that is used in the .toml translation files +// Other: the default message it needs to return if there is no translation found or the system is english +func ShortLocalize(ID string, Other string) string { + return MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: ID, + Other: Other, + }, + }) +} diff --git a/i18n/nl.toml b/i18n/nl.toml new file mode 100644 index 000000000..7d2d3a9a1 --- /dev/null +++ b/i18n/nl.toml @@ -0,0 +1,19 @@ +# The dutch translation for this program + +[FilesTitle] +other = "Bestanden" + +[BranchesTitle] +other = "Branches" + +[CommitsTitle] +other = "Commits" + +[StashTitle] +other = "Stash" + +[CommitMessage] +other = "Commit Bericht" + +[StatusTitle] +other = "Status" \ No newline at end of file From d9959eb998007fac65b76b61796214dc08903cba Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Mon, 13 Aug 2018 20:46:53 +0200 Subject: [PATCH 02/45] fixed @jesseduffield comment #137 --- i18n.go | 27 +++++++++++++++++++++++++-- i18n/nl.toml | 19 ------------------- 2 files changed, 25 insertions(+), 21 deletions(-) delete mode 100644 i18n/nl.toml diff --git a/i18n.go b/i18n.go index a82c29255..04f6c3e35 100644 --- a/i18n.go +++ b/i18n.go @@ -11,9 +11,32 @@ func getlocalizer() *i18n.Localizer { // TODO: currently the system language issn't detected // I'm not sure how to detect it - var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} + var i18nObject = &i18n.Bundle{DefaultLanguage: language.Dutch} i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) - i18nObject.MustLoadMessageFile("i18n/nl.toml") + + // Dutch translation for some words + i18nObject.AddMessages(language.Dutch, + &i18n.Message{ + ID: "FilesTitle", + Other: "Bestanden", + }, &i18n.Message{ + ID: "BranchesTitle", + Other: "Branches", + }, &i18n.Message{ + ID: "CommitsTitle", + Other: "Commits", + }, &i18n.Message{ + ID: "StashTitle", + Other: "Stash", + }, &i18n.Message{ + ID: "CommitMessage", + Other: "Commit Bericht", + }, &i18n.Message{ + ID: "StatusTitle", + Other: "Status", + }, + ) + return i18n.NewLocalizer(i18nObject) } diff --git a/i18n/nl.toml b/i18n/nl.toml deleted file mode 100644 index 7d2d3a9a1..000000000 --- a/i18n/nl.toml +++ /dev/null @@ -1,19 +0,0 @@ -# The dutch translation for this program - -[FilesTitle] -other = "Bestanden" - -[BranchesTitle] -other = "Branches" - -[CommitsTitle] -other = "Commits" - -[StashTitle] -other = "Stash" - -[CommitMessage] -other = "Commit Bericht" - -[StatusTitle] -other = "Status" \ No newline at end of file From 65eb3780a00c7c31d5995b68b1a1c34cc3eea93f Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Mon, 13 Aug 2018 20:48:49 +0200 Subject: [PATCH 03/45] fixed typo --- i18n.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n.go b/i18n.go index 04f6c3e35..dfa2a8175 100644 --- a/i18n.go +++ b/i18n.go @@ -11,7 +11,7 @@ func getlocalizer() *i18n.Localizer { // TODO: currently the system language issn't detected // I'm not sure how to detect it - var i18nObject = &i18n.Bundle{DefaultLanguage: language.Dutch} + var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) // Dutch translation for some words From f2dfcb6e12d78f3e7b890d5bd43be7b032e1df88 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Mon, 13 Aug 2018 22:00:44 +0200 Subject: [PATCH 04/45] Added more messages and text issue: #137 --- files_panel.go | 26 +++++++++++++------------- i18n.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/files_panel.go b/files_panel.go index 4de8caca7..0957f6f71 100644 --- a/files_panel.go +++ b/files_panel.go @@ -129,21 +129,21 @@ func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error { optionsMap := map[string]string{ - "← → ↑ ↓": "navigate", - "S": "stash files", - "c": "commit changes", - "o": "open", - "i": "ignore", - "d": "delete", - "space": "toggle staged", - "R": "refresh", - "t": "add patch", - "e": "edit", - "PgUp/PgDn": "scroll", + "← → ↑ ↓": ShortLocalize("navigate", "navigate"), + "S": ShortLocalize("stashFiles", "stash files"), + "c": ShortLocalize("commitChanges", "commit changes"), + "o": ShortLocalize("open", "open"), + "i": ShortLocalize("ignore", "ignore"), + "d": ShortLocalize("delete", "delete"), + "space": ShortLocalize("toggleStaged", "toggle staged"), + "R": ShortLocalize("refresh", "refresh"), + "t": ShortLocalize("addPatch", "add patch"), + "e": ShortLocalize("edit", "edit"), + "PgUp/PgDn": ShortLocalize("scroll", "scroll"), } if state.HasMergeConflicts { - optionsMap["a"] = "abort merge" - optionsMap["m"] = "resolve merge conflicts" + optionsMap["a"] = ShortLocalize("abortMerge", "abort merge") + optionsMap["m"] = ShortLocalize("resolveMergeConflicts", "resolve merge conflicts") } if gitFile == nil { return renderOptionsMap(g, optionsMap) diff --git a/i18n.go b/i18n.go index dfa2a8175..346b370fa 100644 --- a/i18n.go +++ b/i18n.go @@ -14,6 +14,10 @@ func getlocalizer() *i18n.Localizer { var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) + // To add more translations do: + // AddMessages(tag language.Tag, messages ...*Message) + // https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n#Bundle.AddMessages + // Dutch translation for some words i18nObject.AddMessages(language.Dutch, &i18n.Message{ @@ -31,10 +35,51 @@ func getlocalizer() *i18n.Localizer { }, &i18n.Message{ ID: "CommitMessage", Other: "Commit Bericht", + }, &i18n.Message{ + ID: "CommitChanges", + Other: "Commit Veranderingen", }, &i18n.Message{ ID: "StatusTitle", Other: "Status", + }, &i18n.Message{ + ID: "navigate", + Other: "navigeer", + }, &i18n.Message{ + ID: "stashFiles", + Other: "stash-bestanden", + }, &i18n.Message{ + ID: "open", + Other: "open", + }, &i18n.Message{ + ID: "ignore", + Other: "negeren", + }, &i18n.Message{ + ID: "delete", + Other: "verwijderen", + }, &i18n.Message{ + ID: "toggleStaged", + Other: "toggle staged", + }, &i18n.Message{ + ID: "refresh", + Other: "verversen", + }, &i18n.Message{ + ID: "addPatch", + Other: "verandering toevoegen", + }, &i18n.Message{ + ID: "edit", + Other: "veranderen", + }, &i18n.Message{ + ID: "scroll", + Other: "scroll", + }, &i18n.Message{ + ID: "abortMerge", + Other: "samenvoegen afbreken", + }, &i18n.Message{ + ID: "resolveMergeConflicts", + Other: "verhelp samenvoegen fouten", }, + + // ) return i18n.NewLocalizer(i18nObject) From dfafb988713a79664139d15ad471736a5a4b1b90 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 11:05:26 +0200 Subject: [PATCH 05/45] tried to update to latest master --- .gitignore | 3 +- Gopkg.lock | 25 +- Gopkg.toml | 2 +- VERSION | 2 +- branches_panel.go | 135 --- commit_message_panel.go | 45 - commits_panel.go | 176 ---- docs/Keybindings.md | 11 +- files_panel.go | 362 ------- gitcommands.go | 529 ---------- main.go | 94 +- merge_panel.go | 263 ----- pkg/app/app.go | 71 ++ branch.go => pkg/commands/branch.go | 12 +- pkg/commands/git.go | 497 +++++++++ pkg/commands/git_structs.go | 36 + pkg/commands/os.go | 174 ++++ pkg/config/app_config.go | 45 + .../git/branch_list_builder.go | 74 +- pkg/gui/branches_panel.go | 141 +++ pkg/gui/commit_message_panel.go | 50 + pkg/gui/commits_panel.go | 176 ++++ .../gui/confirmation_panel.go | 74 +- pkg/gui/files_panel.go | 400 ++++++++ gui.go => pkg/gui/gui.go | 213 ++-- keybindings.go => pkg/gui/keybindings.go | 124 +-- pkg/gui/merge_panel.go | 260 +++++ pkg/gui/stash_panel.go | 97 ++ status_panel.go => pkg/gui/status_panel.go | 19 +- view_helpers.go => pkg/gui/view_helpers.go | 94 +- i18n.go => pkg/i18n/i18n.go | 0 pkg/utils/utils.go | 65 ++ stash_panel.go | 93 -- test/repos/gpg.sh | 14 + test/repos/pre_commit_hook.sh | 2 +- utils.go | 54 - vendor/github.com/Sirupsen/logrus/LICENSE | 21 + vendor/github.com/Sirupsen/logrus/alt_exit.go | 64 ++ vendor/github.com/Sirupsen/logrus/doc.go | 26 + vendor/github.com/Sirupsen/logrus/entry.go | 300 ++++++ vendor/github.com/Sirupsen/logrus/exported.go | 201 ++++ .../github.com/Sirupsen/logrus/formatter.go | 51 + vendor/github.com/Sirupsen/logrus/hooks.go | 34 + .../Sirupsen/logrus/json_formatter.go | 89 ++ vendor/github.com/Sirupsen/logrus/logger.go | 337 +++++++ vendor/github.com/Sirupsen/logrus/logrus.go | 143 +++ .../Sirupsen/logrus/terminal_bsd.go | 10 + .../logrus/terminal_check_appengine.go | 11 + .../logrus/terminal_check_notappengine.go | 19 + .../Sirupsen/logrus/terminal_linux.go | 14 + .../Sirupsen/logrus/text_formatter.go | 195 ++++ vendor/github.com/Sirupsen/logrus/writer.go | 62 ++ vendor/github.com/jesseduffield/gocui/gui.go | 3 + vendor/github.com/mgutz/str/LICENSE | 21 + vendor/github.com/mgutz/str/doc.go | 19 + vendor/github.com/mgutz/str/funcsAO.go | 337 +++++++ vendor/github.com/mgutz/str/funcsPZ.go | 534 ++++++++++ .../x/crypto/ssh/terminal/terminal.go | 951 ++++++++++++++++++ .../golang.org/x/crypto/ssh/terminal/util.go | 114 +++ .../x/crypto/ssh/terminal/util_bsd.go | 12 + .../x/crypto/ssh/terminal/util_linux.go | 10 + .../x/crypto/ssh/terminal/util_plan9.go | 58 ++ .../x/crypto/ssh/terminal/util_solaris.go | 124 +++ .../x/crypto/ssh/terminal/util_windows.go | 103 ++ 64 files changed, 6273 insertions(+), 2022 deletions(-) delete mode 100644 branches_panel.go delete mode 100644 commit_message_panel.go delete mode 100644 commits_panel.go delete mode 100644 files_panel.go delete mode 100644 gitcommands.go delete mode 100644 merge_panel.go create mode 100644 pkg/app/app.go rename branch.go => pkg/commands/branch.go (56%) create mode 100644 pkg/commands/git.go create mode 100644 pkg/commands/git_structs.go create mode 100644 pkg/commands/os.go create mode 100644 pkg/config/app_config.go rename branch_list_builder.go => pkg/git/branch_list_builder.go (54%) create mode 100644 pkg/gui/branches_panel.go create mode 100644 pkg/gui/commit_message_panel.go create mode 100644 pkg/gui/commits_panel.go rename confirmation_panel.go => pkg/gui/confirmation_panel.go (53%) create mode 100644 pkg/gui/files_panel.go rename gui.go => pkg/gui/gui.go (51%) rename keybindings.go => pkg/gui/keybindings.go (66%) create mode 100644 pkg/gui/merge_panel.go create mode 100644 pkg/gui/stash_panel.go rename status_panel.go => pkg/gui/status_panel.go (54%) rename view_helpers.go => pkg/gui/view_helpers.go (60%) rename i18n.go => pkg/i18n/i18n.go (100%) create mode 100644 pkg/utils/utils.go delete mode 100644 stash_panel.go create mode 100755 test/repos/gpg.sh delete mode 100644 utils.go create mode 100644 vendor/github.com/Sirupsen/logrus/LICENSE create mode 100644 vendor/github.com/Sirupsen/logrus/alt_exit.go create mode 100644 vendor/github.com/Sirupsen/logrus/doc.go create mode 100644 vendor/github.com/Sirupsen/logrus/entry.go create mode 100644 vendor/github.com/Sirupsen/logrus/exported.go create mode 100644 vendor/github.com/Sirupsen/logrus/formatter.go create mode 100644 vendor/github.com/Sirupsen/logrus/hooks.go create mode 100644 vendor/github.com/Sirupsen/logrus/json_formatter.go create mode 100644 vendor/github.com/Sirupsen/logrus/logger.go create mode 100644 vendor/github.com/Sirupsen/logrus/logrus.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_bsd.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_linux.go create mode 100644 vendor/github.com/Sirupsen/logrus/text_formatter.go create mode 100644 vendor/github.com/Sirupsen/logrus/writer.go create mode 100644 vendor/github.com/mgutz/str/LICENSE create mode 100644 vendor/github.com/mgutz/str/doc.go create mode 100644 vendor/github.com/mgutz/str/funcsAO.go create mode 100644 vendor/github.com/mgutz/str/funcsPZ.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/terminal.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_linux.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_windows.go diff --git a/.gitignore b/.gitignore index dac0b46b6..3d59e16c5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ extra/lgit.rb notes/go.notes TODO.notes TODO.md -test/testrepo/ -test/repos/repo \ No newline at end of file +test/repos/repo diff --git a/Gopkg.lock b/Gopkg.lock index af729a2cb..7b167c7d8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,14 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + digest = "1:b2339e83ce9b5c4f79405f949429a7f68a9a904fed903c672aac1e7ceb7f5f02" + name = "github.com/Sirupsen/logrus" + packages = ["."] + pruneopts = "NUT" + revision = "3e01752db0189b9157070a0e1668a620f9a85da2" + version = "v1.0.6" + [[projects]] digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" @@ -50,11 +58,11 @@ [[projects]] branch = "master" - digest = "1:e9b2b07a20f19d886267876b72ba15f2cbdeeeadd18030a4ce174b864e97c39e" + digest = "1:c9a848b0484a72da2dae28957b4f67501fe27fa38bc73f4713e454353c0a4a60" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "8cecad864fb0b099a5f55bf1c97fbc1daca103e0" + revision = "432b7f6215f81ef1aaa1b2d9b69887822923cf79" [[projects]] digest = "1:8021af4dcbd531ae89433c8c3a6520e51064114aaf8eb1724c3cf911c497c9ba" @@ -88,6 +96,14 @@ revision = "9e777a8366cce605130a531d2cd6363d07ad7317" version = "v0.0.2" +[[projects]] + digest = "1:a25c9a6b41e100f4ce164db80260f2b687095ba9d8b46a1d6072d3686cc020db" + name = "github.com/mgutz/str" + packages = ["."] + pruneopts = "NUT" + revision = "968bf66e3da857419e4f6e71b2d5c9ae95682dc4" + version = "v1.2.0" + [[projects]] branch = "master" digest = "1:a4df73029d2c42fabcb6b41e327d2f87e685284ec03edf76921c267d9cfc9c23" @@ -151,7 +167,7 @@ [[projects]] branch = "master" - digest = "1:c76f8b24a4d9b99b502fb7b61ad769125075cb570efff9b9b73e6c428629532d" + digest = "1:dfcb1b2db354cafa48fc3cdafe4905a08bec4a9757919ab07155db0ca23855b4" name = "golang.org/x/crypto" packages = [ "cast5", @@ -170,6 +186,7 @@ "ssh", "ssh/agent", "ssh/knownhosts", + "ssh/terminal", ] pruneopts = "NUT" revision = "de0752318171da717af4ce24d0a2e8626afaeb11" @@ -282,10 +299,12 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/Sirupsen/logrus", "github.com/davecgh/go-spew/spew", "github.com/fatih/color", "github.com/golang-collections/collections/stack", "github.com/jesseduffield/gocui", + "github.com/mgutz/str", "github.com/tcnksm/go-gitconfig", "gopkg.in/src-d/go-git.v4", "gopkg.in/src-d/go-git.v4/plumbing", diff --git a/Gopkg.toml b/Gopkg.toml index 16fd76083..a47fe5e93 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -39,4 +39,4 @@ [[constraint]] name = "gopkg.in/src-d/go-git.v4" - revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" \ No newline at end of file + revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" diff --git a/VERSION b/VERSION index 388e9d45b..bcb7cc884 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.1.55 \ No newline at end of file +v0.1.58 \ No newline at end of file diff --git a/branches_panel.go b/branches_panel.go deleted file mode 100644 index a73d28bb3..000000000 --- a/branches_panel.go +++ /dev/null @@ -1,135 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" -) - -func handleBranchPress(g *gocui.Gui, v *gocui.View) error { - index := getItemPosition(v) - if index == 0 { - return createErrorPanel(g, "You have already checked out this branch") - } - branch := getSelectedBranch(v) - if output, err := gitCheckout(branch.Name, false); err != nil { - createErrorPanel(g, output) - } - return refreshSidePanels(g) -} - -func handleForceCheckout(g *gocui.Gui, v *gocui.View) error { - branch := getSelectedBranch(v) - return createConfirmationPanel(g, v, "Force Checkout Branch", "Are you sure you want force checkout? You will lose all local changes", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitCheckout(branch.Name, true); err != nil { - createErrorPanel(g, output) - } - return refreshSidePanels(g) - }, nil) -} - -func handleCheckoutByName(g *gocui.Gui, v *gocui.View) error { - createPromptPanel(g, v, "Branch Name:", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitCheckout(trimmedContent(v), false); err != nil { - return createErrorPanel(g, output) - } - return refreshSidePanels(g) - }) - return nil -} - -func handleNewBranch(g *gocui.Gui, v *gocui.View) error { - branch := state.Branches[0] - createPromptPanel(g, v, "New Branch Name (Branch is off of "+branch.Name+")", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitNewBranch(trimmedContent(v)); err != nil { - return createErrorPanel(g, output) - } - refreshSidePanels(g) - return handleBranchSelect(g, v) - }) - return nil -} - -func handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { - checkedOutBranch := state.Branches[0] - selectedBranch := getSelectedBranch(v) - if checkedOutBranch.Name == selectedBranch.Name { - return createErrorPanel(g, "You cannot delete the checked out branch!") - } - return createConfirmationPanel(g, v, "Delete Branch", "Are you sure you want delete the branch "+selectedBranch.Name+" ?", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitDeleteBranch(selectedBranch.Name); err != nil { - return createErrorPanel(g, output) - } - return refreshSidePanels(g) - }, nil) -} - -func handleMerge(g *gocui.Gui, v *gocui.View) error { - checkedOutBranch := state.Branches[0] - selectedBranch := getSelectedBranch(v) - defer refreshSidePanels(g) - if checkedOutBranch.Name == selectedBranch.Name { - return createErrorPanel(g, "You cannot merge a branch into itself") - } - if output, err := gitMerge(selectedBranch.Name); err != nil { - return createErrorPanel(g, output) - } - return nil -} - -func getSelectedBranch(v *gocui.View) Branch { - lineNumber := getItemPosition(v) - return state.Branches[lineNumber] -} - -func renderBranchesOptions(g *gocui.Gui) error { - return renderOptionsMap(g, map[string]string{ - "space": "checkout", - "f": "force checkout", - "m": "merge", - "c": "checkout by name", - "n": "new branch", - "d": "delete branch", - "← → ↑ ↓": "navigate", - }) -} - -// may want to standardise how these select methods work -func handleBranchSelect(g *gocui.Gui, v *gocui.View) error { - if err := renderBranchesOptions(g); err != nil { - return err - } - // This really shouldn't happen: there should always be a master branch - if len(state.Branches) == 0 { - return renderString(g, "main", "No branches for this repo") - } - go func() { - branch := getSelectedBranch(v) - diff, err := getBranchGraph(branch.Name) - if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { - diff = "There is no tracking for this branch" - } - renderString(g, "main", diff) - }() - return nil -} - -// refreshStatus is called at the end of this because that's when we can -// be sure there is a state.Branches array to pick the current branch from -func refreshBranches(g *gocui.Gui) error { - g.Update(func(g *gocui.Gui) error { - v, err := g.View("branches") - if err != nil { - panic(err) - } - state.Branches = getGitBranches() - v.Clear() - for _, branch := range state.Branches { - fmt.Fprintln(v, branch.getDisplayString()) - } - resetOrigin(v) - return refreshStatus(g) - }) - return nil -} diff --git a/commit_message_panel.go b/commit_message_panel.go deleted file mode 100644 index baef870cf..000000000 --- a/commit_message_panel.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import "github.com/jesseduffield/gocui" - -func handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { - message := trimmedContent(v) - if message == "" { - return createErrorPanel(g, "You cannot commit without a commit message") - } - if output, err := gitCommit(g, message); err != nil { - if err == errNoUsername { - return createErrorPanel(g, err.Error()) - } - return createErrorPanel(g, output) - } - refreshFiles(g) - v.Clear() - v.SetCursor(0, 0) - g.SetViewOnBottom("commitMessage") - switchFocus(g, v, getFilesView(g)) - return refreshCommits(g) -} - -func handleCommitClose(g *gocui.Gui, v *gocui.View) error { - g.SetViewOnBottom("commitMessage") - return switchFocus(g, v, getFilesView(g)) -} - -func handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { - // resising ahead of time so that the top line doesn't get hidden to make - // room for the cursor on the second line - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer()) - if _, err := g.SetView("commitMessage", x0, y0, x1, y1+1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - } - - v.EditNewLine() - return nil -} - -func handleCommitFocused(g *gocui.Gui, v *gocui.View) error { - return renderString(g, "options", "esc: close, enter: confirm") -} diff --git a/commits_panel.go b/commits_panel.go deleted file mode 100644 index 1a9976ffe..000000000 --- a/commits_panel.go +++ /dev/null @@ -1,176 +0,0 @@ -package main - -import ( - "errors" - - "github.com/fatih/color" - "github.com/jesseduffield/gocui" -) - -var ( - // ErrNoCommits : When no commits are found for the branch - ErrNoCommits = errors.New("No commits for this branch") -) - -func refreshCommits(g *gocui.Gui) error { - g.Update(func(*gocui.Gui) error { - state.Commits = getCommits() - v, err := g.View("commits") - if err != nil { - panic(err) - } - v.Clear() - red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) - white := color.New(color.FgWhite) - shaColor := white - for _, commit := range state.Commits { - if commit.Pushed { - shaColor = red - } else { - shaColor = yellow - } - shaColor.Fprint(v, commit.Sha+" ") - white.Fprintln(v, commit.Name) - } - refreshStatus(g) - if g.CurrentView().Name() == "commits" { - handleCommitSelect(g, v) - } - return nil - }) - return nil -} - -func handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { - return createConfirmationPanel(g, commitView, "Reset To Commit", "Are you sure you want to reset to this commit?", func(g *gocui.Gui, v *gocui.View) error { - commit, err := getSelectedCommit(g) - if err != nil { - panic(err) - } - if output, err := gitResetToCommit(commit.Sha); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - if err := refreshFiles(g); err != nil { - panic(err) - } - resetOrigin(commitView) - return handleCommitSelect(g, nil) - }, nil) -} - -func renderCommitsOptions(g *gocui.Gui) error { - return renderOptionsMap(g, map[string]string{ - "s": "squash down", - "r": "rename", - "g": "reset to this commit", - "f": "fixup commit", - "← → ↑ ↓": "navigate", - }) -} - -func handleCommitSelect(g *gocui.Gui, v *gocui.View) error { - if err := renderCommitsOptions(g); err != nil { - return err - } - commit, err := getSelectedCommit(g) - if err != nil { - if err != ErrNoCommits { - return err - } - return renderString(g, "main", "No commits for this branch") - } - commitText := gitShow(commit.Sha) - return renderString(g, "main", commitText) -} - -func handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { - if getItemPosition(v) != 0 { - return createErrorPanel(g, "Can only squash topmost commit") - } - if len(state.Commits) == 1 { - return createErrorPanel(g, "You have no commits to squash with") - } - commit, err := getSelectedCommit(g) - if err != nil { - return err - } - if output, err := gitSquashPreviousTwoCommits(commit.Name); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - refreshStatus(g) - return handleCommitSelect(g, v) -} - -// TODO: move to files panel -func anyUnStagedChanges(files []GitFile) bool { - for _, file := range files { - if file.Tracked && file.HasUnstagedChanges { - return true - } - } - return false -} - -func handleCommitFixup(g *gocui.Gui, v *gocui.View) error { - if len(state.Commits) == 1 { - return createErrorPanel(g, "You have no commits to squash with") - } - objectLog(state.GitFiles) - if anyUnStagedChanges(state.GitFiles) { - return createErrorPanel(g, "Can't fixup while there are unstaged changes") - } - branch := state.Branches[0] - commit, err := getSelectedCommit(g) - if err != nil { - return err - } - createConfirmationPanel(g, v, "Fixup", "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitSquashFixupCommit(branch.Name, commit.Sha); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - return refreshStatus(g) - }, nil) - return nil -} - -func handleRenameCommit(g *gocui.Gui, v *gocui.View) error { - if getItemPosition(v) != 0 { - return createErrorPanel(g, "Can only rename topmost commit") - } - createPromptPanel(g, v, "Rename Commit", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitRenameCommit(v.Buffer()); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - return handleCommitSelect(g, v) - }) - return nil -} - -func getSelectedCommit(g *gocui.Gui) (Commit, error) { - v, err := g.View("commits") - if err != nil { - panic(err) - } - if len(state.Commits) == 0 { - return Commit{}, ErrNoCommits - } - lineNumber := getItemPosition(v) - if lineNumber > len(state.Commits)-1 { - devLog("potential error in getSelected Commit (mismatched ui and state)", state.Commits, lineNumber) - return state.Commits[len(state.Commits)-1], nil - } - return state.Commits[lineNumber], nil -} diff --git a/docs/Keybindings.md b/docs/Keybindings.md index 8ef749277..2b9f9d952 100644 --- a/docs/Keybindings.md +++ b/docs/Keybindings.md @@ -2,11 +2,12 @@ ## Global:
-  /hjkl:  navigate
-  PgUp/PgDn:           scroll diff panel (use fn+up/fn+down on osx)
-  q:                    quit
-  p:                    pull
-  shift+P:             push
+  /hjkl:               navigate
+  PgUp/PgDn or ctrl+u/ctrl+d:   scroll diff panel 
+                                     (for PgUp and PgDn, use fn+up/fn+down on osx)
+  q:                                quit
+  p:                                pull
+  shift+P:                         push
 
## Files Panel: diff --git a/files_panel.go b/files_panel.go deleted file mode 100644 index 0957f6f71..000000000 --- a/files_panel.go +++ /dev/null @@ -1,362 +0,0 @@ -package main - -import ( - - // "io" - // "io/ioutil" - - // "strings" - - "errors" - "strings" - - "github.com/fatih/color" - "github.com/jesseduffield/gocui" -) - -var ( - errNoFiles = errors.New("No changed files") - errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`) -) - -func stagedFiles(files []GitFile) []GitFile { - result := make([]GitFile, 0) - for _, file := range files { - if file.HasStagedChanges { - result = append(result, file) - } - } - return result -} - -func stageSelectedFile(g *gocui.Gui) error { - file, err := getSelectedFile(g) - if err != nil { - return err - } - return stageFile(file.Name) -} - -func handleFilePress(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - if err == errNoFiles { - return nil - } - return err - } - - if file.HasMergeConflicts { - return handleSwitchToMerge(g, v) - } - - if file.HasUnstagedChanges { - stageFile(file.Name) - } else { - unStageFile(file.Name, file.Tracked) - } - - if err := refreshFiles(g); err != nil { - return err - } - - return handleFileSelect(g, v) -} - -func handleAddPatch(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - if err == errNoFiles { - return nil - } - return err - } - if !file.HasUnstagedChanges { - return createErrorPanel(g, "File has no unstaged changes to add") - } - if !file.Tracked { - return createErrorPanel(g, "Cannot git add --patch untracked files") - } - gitAddPatch(g, file.Name) - return err -} - -func getSelectedFile(g *gocui.Gui) (GitFile, error) { - if len(state.GitFiles) == 0 { - return GitFile{}, errNoFiles - } - filesView, err := g.View("files") - if err != nil { - panic(err) - } - lineNumber := getItemPosition(filesView) - return state.GitFiles[lineNumber], nil -} - -func handleFileRemove(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - if err == errNoFiles { - return nil - } - return err - } - var deleteVerb string - if file.Tracked { - deleteVerb = "checkout" - } else { - deleteVerb = "delete" - } - return createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error { - if err := removeFile(file); err != nil { - panic(err) - } - return refreshFiles(g) - }, nil) -} - -func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - return createErrorPanel(g, err.Error()) - } - if file.Tracked { - return createErrorPanel(g, "Cannot ignore tracked files") - } - gitIgnore(file.Name) - return refreshFiles(g) -} - -func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error { - optionsMap := map[string]string{ - "← → ↑ ↓": ShortLocalize("navigate", "navigate"), - "S": ShortLocalize("stashFiles", "stash files"), - "c": ShortLocalize("commitChanges", "commit changes"), - "o": ShortLocalize("open", "open"), - "i": ShortLocalize("ignore", "ignore"), - "d": ShortLocalize("delete", "delete"), - "space": ShortLocalize("toggleStaged", "toggle staged"), - "R": ShortLocalize("refresh", "refresh"), - "t": ShortLocalize("addPatch", "add patch"), - "e": ShortLocalize("edit", "edit"), - "PgUp/PgDn": ShortLocalize("scroll", "scroll"), - } - if state.HasMergeConflicts { - optionsMap["a"] = ShortLocalize("abortMerge", "abort merge") - optionsMap["m"] = ShortLocalize("resolveMergeConflicts", "resolve merge conflicts") - } - if gitFile == nil { - return renderOptionsMap(g, optionsMap) - } - if gitFile.Tracked { - optionsMap["d"] = "checkout" - } - return renderOptionsMap(g, optionsMap) -} - -func handleFileSelect(g *gocui.Gui, v *gocui.View) error { - gitFile, err := getSelectedFile(g) - if err != nil { - if err != errNoFiles { - return err - } - renderString(g, "main", "No changed files") - return renderfilesOptions(g, nil) - } - renderfilesOptions(g, &gitFile) - var content string - if gitFile.HasMergeConflicts { - return refreshMergePanel(g) - } - - content = getDiff(gitFile) - return renderString(g, "main", content) -} - -func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { - if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts { - return createErrorPanel(g, "There are no staged files to commit") - } - commitMessageView := getCommitMessageView(g) - g.Update(func(g *gocui.Gui) error { - g.SetViewOnTop("commitMessage") - switchFocus(g, filesView, commitMessageView) - return nil - }) - return nil -} - -func handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error { - if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts { - return createErrorPanel(g, "There are no staged files to commit") - } - runSubProcess(g, "git", "commit") - return nil -} - -func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(*gocui.Gui, string) (string, error)) error { - file, err := getSelectedFile(g) - if err != nil { - if err != errNoFiles { - return err - } - return nil - } - if _, err := open(g, file.Name); err != nil { - return createErrorPanel(g, err.Error()) - } - return nil -} - -func handleFileEdit(g *gocui.Gui, v *gocui.View) error { - return genericFileOpen(g, v, editFile) -} - -func handleFileOpen(g *gocui.Gui, v *gocui.View) error { - return genericFileOpen(g, v, openFile) -} - -func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error { - return genericFileOpen(g, v, sublimeOpenFile) -} - -func handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error { - return genericFileOpen(g, v, vsCodeOpenFile) -} - -func handleRefreshFiles(g *gocui.Gui, v *gocui.View) error { - return refreshFiles(g) -} - -func refreshStateGitFiles() { - // get files to stage - gitFiles := getGitStatusFiles() - state.GitFiles = mergeGitStatusFiles(state.GitFiles, gitFiles) - updateHasMergeConflictStatus() -} - -func updateHasMergeConflictStatus() error { - merging, err := isInMergeState() - if err != nil { - return err - } - state.HasMergeConflicts = merging - return nil -} - -func renderGitFile(gitFile GitFile, filesView *gocui.View) { - // potentially inefficient to be instantiating these color - // objects with each render - red := color.New(color.FgRed) - green := color.New(color.FgGreen) - if !gitFile.Tracked && !gitFile.HasStagedChanges { - red.Fprintln(filesView, gitFile.DisplayString) - return - } - green.Fprint(filesView, gitFile.DisplayString[0:1]) - red.Fprint(filesView, gitFile.DisplayString[1:3]) - if gitFile.HasUnstagedChanges { - red.Fprintln(filesView, gitFile.Name) - } else { - green.Fprintln(filesView, gitFile.Name) - } -} - -func catSelectedFile(g *gocui.Gui) (string, error) { - item, err := getSelectedFile(g) - if err != nil { - if err != errNoFiles { - return "", err - } - return "", renderString(g, "main", "No file to display") - } - cat, err := catFile(item.Name) - if err != nil { - panic(err) - } - return cat, nil -} - -func refreshFiles(g *gocui.Gui) error { - filesView, err := g.View("files") - if err != nil { - return err - } - refreshStateGitFiles() - filesView.Clear() - for _, gitFile := range state.GitFiles { - renderGitFile(gitFile, filesView) - } - correctCursor(filesView) - if filesView == g.CurrentView() { - handleFileSelect(g, filesView) - } - return nil -} - -func pullFiles(g *gocui.Gui, v *gocui.View) error { - createMessagePanel(g, v, "", "Pulling...") - go func() { - if output, err := gitPull(); err != nil { - createErrorPanel(g, output) - } else { - closeConfirmationPrompt(g) - refreshCommits(g) - refreshStatus(g) - } - refreshFiles(g) - }() - return nil -} - -func pushFiles(g *gocui.Gui, v *gocui.View) error { - createMessagePanel(g, v, "", "Pushing...") - go func() { - if output, err := gitPush(); err != nil { - createErrorPanel(g, output) - } else { - closeConfirmationPrompt(g) - refreshCommits(g) - refreshStatus(g) - } - }() - return nil -} - -func handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { - mergeView, err := g.View("main") - if err != nil { - return err - } - file, err := getSelectedFile(g) - if err != nil { - if err != errNoFiles { - return err - } - return nil - } - if !file.HasMergeConflicts { - return createErrorPanel(g, "This file has no merge conflicts") - } - switchFocus(g, v, mergeView) - return refreshMergePanel(g) -} - -func handleAbortMerge(g *gocui.Gui, v *gocui.View) error { - output, err := gitAbortMerge() - if err != nil { - return createErrorPanel(g, output) - } - createMessagePanel(g, v, "", "Merge aborted") - refreshStatus(g) - return refreshFiles(g) -} - -func handleResetHard(g *gocui.Gui, v *gocui.View) error { - return createConfirmationPanel(g, v, "Clear file panel", "Are you sure you want `reset --hard HEAD`? You may lose changes", func(g *gocui.Gui, v *gocui.View) error { - if err := gitResetHard(); err != nil { - createErrorPanel(g, err.Error()) - } - return refreshFiles(g) - }, nil) -} diff --git a/gitcommands.go b/gitcommands.go deleted file mode 100644 index c65841e99..000000000 --- a/gitcommands.go +++ /dev/null @@ -1,529 +0,0 @@ -package main - -import ( - - // "log" - "errors" - "fmt" - "os" - "os/exec" - "strings" - - "github.com/jesseduffield/gocui" - gitconfig "github.com/tcnksm/go-gitconfig" - git "gopkg.in/src-d/go-git.v4" -) - -var ( - // ErrNoOpenCommand : When we don't know which command to use to open a file - ErrNoOpenCommand = errors.New("Unsure what command to use to open this file") -) - -// GitFile : A staged/unstaged file -// TODO: decide whether to give all of these the Git prefix -type GitFile struct { - Name string - HasStagedChanges bool - HasUnstagedChanges bool - Tracked bool - Deleted bool - HasMergeConflicts bool - DisplayString string -} - -// Commit : A git commit -type Commit struct { - Sha string - Name string - Pushed bool - DisplayString string -} - -// StashEntry : A git stash entry -type StashEntry struct { - Index int - Name string - DisplayString string -} - -// Map (from https://gobyexample.com/collection-functions) -func Map(vs []string, f func(string) string) []string { - vsm := make([]string, len(vs)) - for i, v := range vs { - vsm[i] = f(v) - } - return vsm -} - -func includesString(list []string, a string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -// not sure how to genericise this because []interface{} doesn't accept e.g. -// []int arguments -func includesInt(list []int, a int) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -func mergeGitStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile { - if len(oldGitFiles) == 0 { - return newGitFiles - } - - appendedIndexes := make([]int, 0) - - // retain position of files we already could see - result := make([]GitFile, 0) - for _, oldGitFile := range oldGitFiles { - for newIndex, newGitFile := range newGitFiles { - if oldGitFile.Name == newGitFile.Name { - result = append(result, newGitFile) - appendedIndexes = append(appendedIndexes, newIndex) - break - } - } - } - - // append any new files to the end - for index, newGitFile := range newGitFiles { - if !includesInt(appendedIndexes, index) { - result = append(result, newGitFile) - } - } - - return result -} - -// only to be used when you're already in an error state -func runDirectCommandIgnoringError(command string) string { - output, _ := runDirectCommand(command) - return output -} - -func runDirectCommand(command string) (string, error) { - commandLog(command) - - cmdOut, err := exec. - Command(state.Platform.shell, state.Platform.shellArg, command). - CombinedOutput() - return sanitisedCommandOutput(cmdOut, err) -} - -func branchStringParts(branchString string) (string, string) { - // expect string to be something like '4w master` - splitBranchName := strings.Split(branchString, "\t") - // if we have no \t then we have no recency, so just output that as blank - if len(splitBranchName) == 1 { - return "", branchString - } - return splitBranchName[0], splitBranchName[1] -} - -// TODO: DRY up this function and getGitBranches -func getGitStashEntries() []StashEntry { - stashEntries := make([]StashEntry, 0) - rawString, _ := runDirectCommand("git stash list --pretty='%gs'") - for i, line := range splitLines(rawString) { - stashEntries = append(stashEntries, stashEntryFromLine(line, i)) - } - return stashEntries -} - -func stashEntryFromLine(line string, index int) StashEntry { - return StashEntry{ - Name: line, - Index: index, - DisplayString: line, - } -} - -func getStashEntryDiff(index int) (string, error) { - return runCommand("git stash show -p --color stash@{" + fmt.Sprint(index) + "}") -} - -func includes(array []string, str string) bool { - for _, arrayStr := range array { - if arrayStr == str { - return true - } - } - return false -} - -func getGitStatusFiles() []GitFile { - statusOutput, _ := getGitStatus() - statusStrings := splitLines(statusOutput) - gitFiles := make([]GitFile, 0) - - for _, statusString := range statusStrings { - change := statusString[0:2] - stagedChange := change[0:1] - unstagedChange := statusString[1:2] - filename := statusString[3:] - tracked := !includes([]string{"??", "A "}, change) - gitFile := GitFile{ - Name: filename, - DisplayString: statusString, - HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange), - HasUnstagedChanges: unstagedChange != " ", - Tracked: tracked, - Deleted: unstagedChange == "D" || stagedChange == "D", - HasMergeConflicts: change == "UU", - } - gitFiles = append(gitFiles, gitFile) - } - objectLog(gitFiles) - return gitFiles -} - -func gitStashDo(index int, method string) (string, error) { - return runCommand("git stash " + method + " stash@{" + fmt.Sprint(index) + "}") -} - -func gitStashSave(message string) (string, error) { - output, err := runCommand("git stash save \"" + message + "\"") - if err != nil { - return output, err - } - // if there are no local changes to save, the exit code is 0, but we want - // to raise an error - if output == "No local changes to save\n" { - return output, errors.New(output) - } - return output, nil -} - -func gitCheckout(branch string, force bool) (string, error) { - forceArg := "" - if force { - forceArg = "--force " - } - return runCommand("git checkout " + forceArg + branch) -} - -func sanitisedCommandOutput(output []byte, err error) (string, error) { - outputString := string(output) - if outputString == "" && err != nil { - return err.Error(), err - } - return outputString, err -} - -func runCommand(command string) (string, error) { - commandLog(command) - splitCmd := strings.Split(command, " ") - cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() - return sanitisedCommandOutput(cmdOut, err) -} - -func vsCodeOpenFile(g *gocui.Gui, filename string) (string, error) { - return runCommand("code -r " + filename) -} - -func sublimeOpenFile(g *gocui.Gui, filename string) (string, error) { - return runCommand("subl " + filename) -} - -func openFile(g *gocui.Gui, filename string) (string, error) { - cmdName, cmdTrail, err := getOpenCommand() - if err != nil { - return "", err - } - return runCommand(cmdName + " " + filename + cmdTrail) -} - -func getOpenCommand() (string, string, error) { - //NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX) - trailMap := map[string]string{ - "xdg-open": " &>/dev/null &", - "cygstart": "", - "open": "", - } - for name, trail := range trailMap { - if out, _ := runCommand("which " + name); out != "exit status 1" { - return name, trail, nil - } - } - return "", "", ErrNoOpenCommand -} - -func gitAddPatch(g *gocui.Gui, filename string) { - runSubProcess(g, "git", "add", "--patch", filename) -} - -func editFile(g *gocui.Gui, filename string) (string, error) { - editor, _ := gitconfig.Global("core.editor") - if editor == "" { - editor = os.Getenv("VISUAL") - } - if editor == "" { - editor = os.Getenv("EDITOR") - } - if editor == "" { - if _, err := runCommand("which vi"); err == nil { - editor = "vi" - } - } - if editor == "" { - return "", createErrorPanel(g, "No editor defined in $VISUAL, $EDITOR, or git config.") - } - runSubProcess(g, editor, filename) - return "", nil -} - -func runSubProcess(g *gocui.Gui, cmdName string, commandArgs ...string) { - subprocess = exec.Command(cmdName, commandArgs...) - subprocess.Stdin = os.Stdin - subprocess.Stdout = os.Stdout - subprocess.Stderr = os.Stderr - - g.Update(func(g *gocui.Gui) error { - return ErrSubprocess - }) -} - -func getBranchGraph(branch string) (string, error) { - return runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branch) -} - -func verifyInGitRepo() { - if output, err := runCommand("git status"); err != nil { - fmt.Println(output) - os.Exit(1) - } -} - -func getCommits() []Commit { - pushables := gitCommitsToPush() - log := getLog() - commits := make([]Commit, 0) - // now we can split it up and turn it into commits - lines := splitLines(log) - for _, line := range lines { - splitLine := strings.Split(line, " ") - sha := splitLine[0] - pushed := includesString(pushables, sha) - commits = append(commits, Commit{ - Sha: sha, - Name: strings.Join(splitLine[1:], " "), - Pushed: pushed, - DisplayString: strings.Join(splitLine, " "), - }) - } - return commits -} - -func getLog() string { - // currently limiting to 30 for performance reasons - // TODO: add lazyloading when you scroll down - result, err := runDirectCommand("git log --oneline -30") - if err != nil { - // assume if there is an error there are no commits yet for this branch - return "" - } - return result -} - -func gitIgnore(filename string) { - if _, err := runDirectCommand("echo '" + filename + "' >> .gitignore"); err != nil { - panic(err) - } -} - -func gitShow(sha string) string { - result, err := runDirectCommand("git show --color " + sha) - if err != nil { - panic(err) - } - return result -} - -func getDiff(file GitFile) string { - cachedArg := "" - if file.HasStagedChanges && !file.HasUnstagedChanges { - cachedArg = "--cached " - } - deletedArg := "" - if file.Deleted { - deletedArg = "-- " - } - trackedArg := "" - if !file.Tracked && !file.HasStagedChanges { - trackedArg = "--no-index /dev/null " - } - command := "git diff --color " + cachedArg + deletedArg + trackedArg + file.Name - // for now we assume an error means the file was deleted - s, _ := runCommand(command) - return s -} - -func catFile(file string) (string, error) { - return runDirectCommand("cat " + file) -} - -func stageFile(file string) error { - _, err := runCommand("git add " + file) - return err -} - -func unStageFile(file string, tracked bool) error { - var command string - if tracked { - command = "git reset HEAD " - } else { - command = "git rm --cached " - } - _, err := runCommand(command + file) - return err -} - -func getGitStatus() (string, error) { - return runCommand("git status --untracked-files=all --short") -} - -func isInMergeState() (bool, error) { - output, err := runCommand("git status --untracked-files=all") - if err != nil { - return false, err - } - return strings.Contains(output, "conclude merge") || strings.Contains(output, "unmerged paths"), nil -} - -func removeFile(file GitFile) error { - // if the file isn't tracked, we assume you want to delete it - if !file.Tracked { - _, err := runCommand("rm -rf ./" + file.Name) - return err - } - // if the file is tracked, we assume you want to just check it out - _, err := runCommand("git checkout " + file.Name) - return err -} - -func gitCommit(g *gocui.Gui, message string) (string, error) { - gpgsign, _ := gitconfig.Global("commit.gpgsign") - if gpgsign != "" { - runSubProcess(g, "git", "commit") - return "", nil - } - return runDirectCommand("git commit -m \"" + message + "\"") -} - -func gitPull() (string, error) { - return runDirectCommand("git pull --no-edit") -} - -func gitPush() (string, error) { - return runDirectCommand("git push -u origin " + state.Branches[0].Name) -} - -func gitSquashPreviousTwoCommits(message string) (string, error) { - return runDirectCommand("git reset --soft HEAD^ && git commit --amend -m \"" + message + "\"") -} - -func gitSquashFixupCommit(branchName string, shaValue string) (string, error) { - var err error - commands := []string{ - "git checkout -q " + shaValue, - "git reset --soft " + shaValue + "^", - "git commit --amend -C " + shaValue + "^", - "git rebase --onto HEAD " + shaValue + " " + branchName, - } - ret := "" - for _, command := range commands { - devLog(command) - output, err := runDirectCommand(command) - ret += output - if err != nil { - devLog(ret) - break - } - } - if err != nil { - // We are already in an error state here so we're just going to append - // the output of these commands - ret += runDirectCommandIgnoringError("git branch -d " + shaValue) - ret += runDirectCommandIgnoringError("git checkout " + branchName) - } - return ret, err -} - -func gitRenameCommit(message string) (string, error) { - return runDirectCommand("git commit --allow-empty --amend -m \"" + message + "\"") -} - -func gitFetch() (string, error) { - return runDirectCommand("git fetch") -} - -func gitResetToCommit(sha string) (string, error) { - return runDirectCommand("git reset " + sha) -} - -func gitNewBranch(name string) (string, error) { - return runDirectCommand("git checkout -b " + name) -} - -func gitDeleteBranch(branch string) (string, error) { - return runCommand("git branch -d " + branch) -} - -func gitListStash() (string, error) { - return runDirectCommand("git stash list") -} - -func gitMerge(branchName string) (string, error) { - return runDirectCommand("git merge --no-edit " + branchName) -} - -func gitAbortMerge() (string, error) { - return runDirectCommand("git merge --abort") -} - -func gitUpstreamDifferenceCount() (string, string) { - pushableCount, err := runDirectCommand("git rev-list @{u}..head --count") - if err != nil { - return "?", "?" - } - pullableCount, err := runDirectCommand("git rev-list head..@{u} --count") - if err != nil { - return "?", "?" - } - return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) -} - -func gitCommitsToPush() []string { - pushables, err := runDirectCommand("git rev-list @{u}..head --abbrev-commit") - if err != nil { - return make([]string, 0) - } - return splitLines(pushables) -} - -func getGitBranches() []Branch { - builder := newBranchListBuilder() - return builder.build() -} - -func branchIncluded(branchName string, branches []Branch) bool { - for _, existingBranch := range branches { - if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) { - return true - } - } - return false -} - -func gitResetHard() error { - return w.Reset(&git.ResetOptions{Mode: git.HardReset}) -} diff --git a/main.go b/main.go index 28ecb9ec6..c7fec1ae1 100644 --- a/main.go +++ b/main.go @@ -1,36 +1,25 @@ package main import ( - "errors" "flag" "fmt" "io/ioutil" "log" "os" - "os/exec" "os/user" "path/filepath" - "github.com/davecgh/go-spew/spew" - - "github.com/jesseduffield/gocui" - git "gopkg.in/src-d/go-git.v4" + "github.com/jesseduffield/lazygit/pkg/app" + "github.com/jesseduffield/lazygit/pkg/config" ) -// ErrSubProcess is raised when we are running a subprocess var ( - ErrSubprocess = errors.New("running subprocess") - subprocess *exec.Cmd - commit string version = "unversioned" + date string - date string debuggingFlag = flag.Bool("debug", false, "a boolean") versionFlag = flag.Bool("v", false, "Print the current version") - - w *git.Worktree - r *git.Repository ) func homeDirectory() string { @@ -46,46 +35,6 @@ func projectPath(path string) string { return filepath.FromSlash(gopath + "/src/github.com/jesseduffield/lazygit/" + path) } -func devLog(objects ...interface{}) { - localLog("development.log", objects...) -} - -func objectLog(object interface{}) { - if !*debuggingFlag { - return - } - str := spew.Sdump(object) - localLog("development.log", str) -} - -func commandLog(objects ...interface{}) { - localLog("commands.log", objects...) -} - -func localLog(path string, objects ...interface{}) { - if !*debuggingFlag { - return - } - f, err := os.OpenFile(projectPath(path), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - panic(err.Error()) - } - defer f.Close() - log.SetOutput(f) - for _, object := range objects { - log.Println(fmt.Sprint(object)) - } -} - -func navigateToRepoRootDirectory() { - _, err := os.Stat(".git") - for os.IsNotExist(err) { - devLog("going up a directory to find the root") - os.Chdir("..") - _, err = os.Stat(".git") - } -} - // when building the binary, `version` is set as a compile-time variable, along // with `date` and `commit`. If this program has been opened directly via go, // we will populate the `version` with VERSION in the lazygit root directory @@ -98,21 +47,7 @@ func fallbackVersion() string { return string(byteVersion) } -func setupWorktree() { - var err error - r, err = git.PlainOpen(".") - if err != nil { - panic(err) - } - - w, err = r.Worktree() - if err != nil { - panic(err) - } -} - func main() { - devLog("\n\n\n\n\n\n\n\n\n\n") flag.Parse() if version == "unversioned" { version = fallbackVersion() @@ -121,18 +56,15 @@ func main() { fmt.Printf("commit=%s, build date=%s, version=%s\n", commit, date, version) os.Exit(0) } - verifyInGitRepo() - navigateToRepoRootDirectory() - setupWorktree() - for { - if err := run(); err != nil { - if err == gocui.ErrQuit { - break - } else if err == ErrSubprocess { - subprocess.Run() - } else { - log.Panicln(err) - } - } + appConfig := &config.AppConfig{ + Name: "lazygit", + Version: version, + Commit: commit, + BuildDate: date, + Debug: *debuggingFlag, } + app, err := app.NewApp(appConfig) + app.Log.Info(err) + app.GitCommand.SetupGit() + app.Gui.RunWithSubprocesses() } diff --git a/merge_panel.go b/merge_panel.go deleted file mode 100644 index f5ca12a23..000000000 --- a/merge_panel.go +++ /dev/null @@ -1,263 +0,0 @@ -// though this panel is called the merge panel, it's really going to use the main panel. This may change in the future - -package main - -import ( - "bufio" - "bytes" - "io/ioutil" - "math" - "os" - "strings" - - "github.com/fatih/color" - "github.com/jesseduffield/gocui" -) - -func findConflicts(content string) ([]conflict, error) { - conflicts := make([]conflict, 0) - var newConflict conflict - for i, line := range splitLines(content) { - if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" { - newConflict = conflict{start: i} - } else if line == "=======" { - newConflict.middle = i - } else if strings.HasPrefix(line, ">>>>>>> ") { - newConflict.end = i - conflicts = append(conflicts, newConflict) - } - } - return conflicts, nil -} - -func shiftConflict(conflicts []conflict) (conflict, []conflict) { - return conflicts[0], conflicts[1:] -} - -func shouldHighlightLine(index int, conflict conflict, top bool) bool { - return (index >= conflict.start && index <= conflict.middle && top) || (index >= conflict.middle && index <= conflict.end && !top) -} - -func coloredConflictFile(content string, conflicts []conflict, conflictIndex int, conflictTop, hasFocus bool) (string, error) { - if len(conflicts) == 0 { - return content, nil - } - conflict, remainingConflicts := shiftConflict(conflicts) - var outputBuffer bytes.Buffer - for i, line := range splitLines(content) { - colourAttr := color.FgWhite - if i == conflict.start || i == conflict.middle || i == conflict.end { - colourAttr = color.FgRed - } - colour := color.New(colourAttr) - if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && shouldHighlightLine(i, conflict, conflictTop) { - colour.Add(color.Bold) - } - if i == conflict.end && len(remainingConflicts) > 0 { - conflict, remainingConflicts = shiftConflict(remainingConflicts) - } - outputBuffer.WriteString(coloredStringDirect(line, colour) + "\n") - } - return outputBuffer.String(), nil -} - -func handleSelectTop(g *gocui.Gui, v *gocui.View) error { - state.ConflictTop = true - return refreshMergePanel(g) -} - -func handleSelectBottom(g *gocui.Gui, v *gocui.View) error { - state.ConflictTop = false - return refreshMergePanel(g) -} - -func handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error { - if state.ConflictIndex >= len(state.Conflicts)-1 { - return nil - } - state.ConflictIndex++ - return refreshMergePanel(g) -} - -func handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error { - if state.ConflictIndex <= 0 { - return nil - } - state.ConflictIndex-- - return refreshMergePanel(g) -} - -func isIndexToDelete(i int, conflict conflict, pick string) bool { - return i == conflict.middle || - i == conflict.start || - i == conflict.end || - pick != "both" && - (pick == "bottom" && i > conflict.start && i < conflict.middle) || - (pick == "top" && i > conflict.middle && i < conflict.end) -} - -func resolveConflict(g *gocui.Gui, conflict conflict, pick string) error { - gitFile, err := getSelectedFile(g) - if err != nil { - return err - } - file, err := os.Open(gitFile.Name) - if err != nil { - return err - } - defer file.Close() - - reader := bufio.NewReader(file) - output := "" - for i := 0; true; i++ { - line, err := reader.ReadString('\n') - if err != nil { - break - } - if !isIndexToDelete(i, conflict, pick) { - output += line - } - } - devLog(output) - return ioutil.WriteFile(gitFile.Name, []byte(output), 0644) -} - -func pushFileSnapshot(g *gocui.Gui) error { - gitFile, err := getSelectedFile(g) - if err != nil { - return err - } - content, err := catFile(gitFile.Name) - if err != nil { - return err - } - state.EditHistory.Push(content) - return nil -} - -func handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error { - if state.EditHistory.Len() == 0 { - return nil - } - prevContent := state.EditHistory.Pop().(string) - gitFile, err := getSelectedFile(g) - if err != nil { - return err - } - ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644) - return refreshMergePanel(g) -} - -func handlePickHunk(g *gocui.Gui, v *gocui.View) error { - conflict := state.Conflicts[state.ConflictIndex] - pushFileSnapshot(g) - pick := "bottom" - if state.ConflictTop { - pick = "top" - } - err := resolveConflict(g, conflict, pick) - if err != nil { - panic(err) - } - refreshMergePanel(g) - return nil -} - -func handlePickBothHunks(g *gocui.Gui, v *gocui.View) error { - conflict := state.Conflicts[state.ConflictIndex] - pushFileSnapshot(g) - err := resolveConflict(g, conflict, "both") - if err != nil { - panic(err) - } - return refreshMergePanel(g) -} - -func currentViewName(g *gocui.Gui) string { - currentView := g.CurrentView() - return currentView.Name() -} - -func refreshMergePanel(g *gocui.Gui) error { - cat, err := catSelectedFile(g) - if err != nil { - return err - } - state.Conflicts, err = findConflicts(cat) - if err != nil { - return err - } - - if len(state.Conflicts) == 0 { - return handleCompleteMerge(g) - } else if state.ConflictIndex > len(state.Conflicts)-1 { - state.ConflictIndex = len(state.Conflicts) - 1 - } - hasFocus := currentViewName(g) == "main" - if hasFocus { - renderMergeOptions(g) - } - content, err := coloredConflictFile(cat, state.Conflicts, state.ConflictIndex, state.ConflictTop, hasFocus) - if err != nil { - return err - } - if err := scrollToConflict(g); err != nil { - return err - } - return renderString(g, "main", content) -} - -func scrollToConflict(g *gocui.Gui) error { - mainView, err := g.View("main") - if err != nil { - return err - } - if len(state.Conflicts) == 0 { - return nil - } - conflict := state.Conflicts[state.ConflictIndex] - ox, _ := mainView.Origin() - _, height := mainView.Size() - conflictMiddle := (conflict.end + conflict.start) / 2 - newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2)))) - return mainView.SetOrigin(ox, newOriginY) -} - -func switchToMerging(g *gocui.Gui) error { - state.ConflictIndex = 0 - state.ConflictTop = true - _, err := g.SetCurrentView("main") - if err != nil { - return err - } - return refreshMergePanel(g) -} - -func renderMergeOptions(g *gocui.Gui) error { - return renderOptionsMap(g, map[string]string{ - "↑ ↓": "select hunk", - "← →": "navigate conflicts", - "space": "pick hunk", - "b": "pick both hunks", - "z": "undo", - }) -} - -func handleEscapeMerge(g *gocui.Gui, v *gocui.View) error { - filesView, err := g.View("files") - if err != nil { - return err - } - refreshFiles(g) - return switchFocus(g, v, filesView) -} - -func handleCompleteMerge(g *gocui.Gui) error { - filesView, err := g.View("files") - if err != nil { - return err - } - stageSelectedFile(g) - refreshFiles(g) - return switchFocus(g, nil, filesView) -} diff --git a/pkg/app/app.go b/pkg/app/app.go new file mode 100644 index 000000000..d558ed250 --- /dev/null +++ b/pkg/app/app.go @@ -0,0 +1,71 @@ +package app + +import ( + "io" + "io/ioutil" + "os" + + "github.com/Sirupsen/logrus" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui" +) + +// App struct +type App struct { + closers []io.Closer + + Config config.AppConfigurer + Log *logrus.Logger + OSCommand *commands.OSCommand + GitCommand *commands.GitCommand + Gui *gui.Gui +} + +func newLogger(config config.AppConfigurer) *logrus.Logger { + log := logrus.New() + if !config.GetDebug() { + log.Out = ioutil.Discard + return log + } + file, err := os.OpenFile("development.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function) + } + log.SetOutput(file) + return log +} + +// NewApp retruns a new applications +func NewApp(config config.AppConfigurer) (*App, error) { + app := &App{ + closers: []io.Closer{}, + Config: config, + } + var err error + app.Log = newLogger(config) + app.OSCommand, err = commands.NewOSCommand(app.Log) + if err != nil { + return nil, err + } + app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand) + if err != nil { + return nil, err + } + app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, config.GetVersion()) + if err != nil { + return nil, err + } + return app, nil +} + +// Close closes any resources +func (app *App) Close() error { + for _, closer := range app.closers { + err := closer.Close() + if err != nil { + return err + } + } + return nil +} diff --git a/branch.go b/pkg/commands/branch.go similarity index 56% rename from branch.go rename to pkg/commands/branch.go index 78c2e55aa..13c26e766 100644 --- a/branch.go +++ b/pkg/commands/branch.go @@ -1,22 +1,26 @@ -package main +package commands import ( "strings" "github.com/fatih/color" + "github.com/jesseduffield/lazygit/pkg/utils" ) // Branch : A git branch +// duplicating this for now type Branch struct { Name string Recency string } -func (b *Branch) getDisplayString() string { - return withPadding(b.Recency, 4) + coloredString(b.Name, b.getColor()) +// GetDisplayString returns the dispaly string of branch +func (b *Branch) GetDisplayString() string { + return utils.WithPadding(b.Recency, 4) + utils.ColoredString(b.Name, b.GetColor()) } -func (b *Branch) getColor() color.Attribute { +// GetColor branch color +func (b *Branch) GetColor() color.Attribute { switch b.getType() { case "feature": return color.FgGreen diff --git a/pkg/commands/git.go b/pkg/commands/git.go new file mode 100644 index 000000000..44fd57f1c --- /dev/null +++ b/pkg/commands/git.go @@ -0,0 +1,497 @@ +package commands + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/utils" + gitconfig "github.com/tcnksm/go-gitconfig" + gogit "gopkg.in/src-d/go-git.v4" +) + +// GitCommand is our main git interface +type GitCommand struct { + Log *logrus.Logger + OSCommand *OSCommand + Worktree *gogit.Worktree + Repo *gogit.Repository +} + +// NewGitCommand it runs git commands +func NewGitCommand(log *logrus.Logger, osCommand *OSCommand) (*GitCommand, error) { + gitCommand := &GitCommand{ + Log: log, + OSCommand: osCommand, + } + return gitCommand, nil +} + +// SetupGit sets git repo up +func (c *GitCommand) SetupGit() { + c.verifyInGitRepo() + c.navigateToRepoRootDirectory() + c.setupWorktree() +} + +// GetStashEntries stash entryies +func (c *GitCommand) GetStashEntries() []StashEntry { + stashEntries := make([]StashEntry, 0) + rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'") + for i, line := range utils.SplitLines(rawString) { + stashEntries = append(stashEntries, stashEntryFromLine(line, i)) + } + return stashEntries +} + +func stashEntryFromLine(line string, index int) StashEntry { + return StashEntry{ + Name: line, + Index: index, + DisplayString: line, + } +} + +// GetStashEntryDiff stash diff +func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { + return c.OSCommand.RunCommandWithOutput("git stash show -p --color stash@{" + fmt.Sprint(index) + "}") +} + +func includes(array []string, str string) bool { + for _, arrayStr := range array { + if arrayStr == str { + return true + } + } + return false +} + +// GetStatusFiles git status files +func (c *GitCommand) GetStatusFiles() []File { + statusOutput, _ := c.GitStatus() + statusStrings := utils.SplitLines(statusOutput) + files := make([]File, 0) + + for _, statusString := range statusStrings { + change := statusString[0:2] + stagedChange := change[0:1] + unstagedChange := statusString[1:2] + filename := statusString[3:] + tracked := !includes([]string{"??", "A "}, change) + file := File{ + Name: filename, + DisplayString: statusString, + HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange), + HasUnstagedChanges: unstagedChange != " ", + Tracked: tracked, + Deleted: unstagedChange == "D" || stagedChange == "D", + HasMergeConflicts: change == "UU", + } + files = append(files, file) + } + c.Log.Info(files) // TODO: use a dumper-esque log here + return files +} + +// StashDo modify stash +func (c *GitCommand) StashDo(index int, method string) error { + return c.OSCommand.RunCommand("git stash " + method + " stash@{" + fmt.Sprint(index) + "}") +} + +// StashSave save stash +// TODO: before calling this, check if there is anything to save +func (c *GitCommand) StashSave(message string) error { + return c.OSCommand.RunCommand("git stash save " + c.OSCommand.Quote(message)) +} + +// MergeStatusFiles merge status files +func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { + if len(oldFiles) == 0 { + return newFiles + } + + appendedIndexes := make([]int, 0) + + // retain position of files we already could see + result := make([]File, 0) + for _, oldFile := range oldFiles { + for newIndex, newFile := range newFiles { + if oldFile.Name == newFile.Name { + result = append(result, newFile) + appendedIndexes = append(appendedIndexes, newIndex) + break + } + } + } + + // append any new files to the end + for index, newFile := range newFiles { + if !includesInt(appendedIndexes, index) { + result = append(result, newFile) + } + } + + return result +} + +func (c *GitCommand) verifyInGitRepo() { + if output, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil { + fmt.Println(output) + os.Exit(1) + } +} + +// GetBranchName branch name +func (c *GitCommand) GetBranchName() (string, error) { + return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") +} + +func (c *GitCommand) navigateToRepoRootDirectory() { + _, err := os.Stat(".git") + for os.IsNotExist(err) { + c.Log.Debug("going up a directory to find the root") + os.Chdir("..") + _, err = os.Stat(".git") + } +} + +func (c *GitCommand) setupWorktree() { + r, err := gogit.PlainOpen(".") + if err != nil { + panic(err) + } + c.Repo = r + + w, err := r.Worktree() + if err != nil { + panic(err) + } + c.Worktree = w +} + +// ResetHard does the equivalent of `git reset --hard HEAD` +func (c *GitCommand) ResetHard() error { + return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset}) +} + +// UpstreamDifferenceCount checks how many pushables/pullables there are for the +// current branch +func (c *GitCommand) UpstreamDifferenceCount() (string, string) { + pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --count") + if err != nil { + return "?", "?" + } + pullableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list head..@{u} --count") + if err != nil { + return "?", "?" + } + return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) +} + +// GetCommitsToPush Returns the sha's of the commits that have not yet been pushed +// to the remote branch of the current branch +func (c *GitCommand) GetCommitsToPush() []string { + pushables, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit") + if err != nil { + return make([]string, 0) + } + return utils.SplitLines(pushables) +} + +// RenameCommit renames the topmost commit with the given name +func (c *GitCommand) RenameCommit(name string) error { + return c.OSCommand.RunCommand("git commit --allow-empty --amend -m " + c.OSCommand.Quote(name)) +} + +// Fetch fetch git repo +func (c *GitCommand) Fetch() error { + return c.OSCommand.RunCommand("git fetch") +} + +// ResetToCommit reset to commit +func (c *GitCommand) ResetToCommit(sha string) error { + return c.OSCommand.RunCommand("git reset " + sha) +} + +// NewBranch create new branch +func (c *GitCommand) NewBranch(name string) error { + return c.OSCommand.RunCommand("git checkout -b " + name) +} + +// DeleteBranch delete branch +func (c *GitCommand) DeleteBranch(branch string) error { + return c.OSCommand.RunCommand("git branch -d " + branch) +} + +// ListStash list stash +func (c *GitCommand) ListStash() (string, error) { + return c.OSCommand.RunCommandWithOutput("git stash list") +} + +// Merge merge +func (c *GitCommand) Merge(branchName string) error { + return c.OSCommand.RunCommand("git merge --no-edit " + branchName) +} + +// AbortMerge abort merge +func (c *GitCommand) AbortMerge() error { + return c.OSCommand.RunCommand("git merge --abort") +} + +// UsingGpg tells us whether the user has gpg enabled so that we can know +// whether we need to run a subprocess to allow them to enter their password +func (c *GitCommand) UsingGpg() bool { + gpgsign, _ := gitconfig.Global("commit.gpgsign") + if gpgsign == "" { + gpgsign, _ = gitconfig.Local("commit.gpgsign") + } + if gpgsign == "" { + return false + } + return true +} + +// Commit commit to git +func (c *GitCommand) Commit(g *gocui.Gui, message string) (*exec.Cmd, error) { + command := "git commit -m " + c.OSCommand.Quote(message) + if c.UsingGpg() { + return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command) + } + return nil, c.OSCommand.RunCommand(command) +} + +// Pull pull from repo +func (c *GitCommand) Pull() error { + return c.OSCommand.RunCommand("git pull --no-edit") +} + +// Push push to a branch +func (c *GitCommand) Push(branchName string) error { + return c.OSCommand.RunCommand("git push -u origin " + branchName) +} + +// SquashPreviousTwoCommits squashes a commit down to the one below it +// retaining the message of the higher commit +func (c *GitCommand) SquashPreviousTwoCommits(message string) error { + // TODO: test this + err := c.OSCommand.RunCommand("git reset --soft HEAD^") + if err != nil { + return err + } + // TODO: if password is required, we need to return a subprocess + return c.OSCommand.RunCommand("git commit --amend -m " + c.OSCommand.Quote(message)) +} + +// SquashFixupCommit squashes a 'FIXUP' commit into the commit beneath it, +// retaining the commit message of the lower commit +func (c *GitCommand) SquashFixupCommit(branchName string, shaValue string) error { + var err error + commands := []string{ + "git checkout -q " + shaValue, + "git reset --soft " + shaValue + "^", + "git commit --amend -C " + shaValue + "^", + "git rebase --onto HEAD " + shaValue + " " + branchName, + } + ret := "" + for _, command := range commands { + c.Log.Info(command) + output, err := c.OSCommand.RunCommandWithOutput(command) + ret += output + if err != nil { + c.Log.Info(ret) + break + } + } + if err != nil { + // We are already in an error state here so we're just going to append + // the output of these commands + output, _ := c.OSCommand.RunCommandWithOutput("git branch -d " + shaValue) + ret += output + output, _ = c.OSCommand.RunCommandWithOutput("git checkout " + branchName) + ret += output + } + if err != nil { + return errors.New(ret) + } + return nil +} + +// CatFile obtain the contents of a file +func (c *GitCommand) CatFile(file string) (string, error) { + return c.OSCommand.RunCommandWithOutput("cat " + file) +} + +// StageFile stages a file +func (c *GitCommand) StageFile(file string) error { + return c.OSCommand.RunCommand("git add " + c.OSCommand.Quote(file)) +} + +// UnStageFile unstages a file +func (c *GitCommand) UnStageFile(file string, tracked bool) error { + var command string + if tracked { + command = "git reset HEAD " + } else { + command = "git rm --cached " + } + return c.OSCommand.RunCommand(command + file) +} + +// GitStatus returns the plaintext short status of the repo +func (c *GitCommand) GitStatus() (string, error) { + return c.OSCommand.RunCommandWithOutput("git status --untracked-files=all --short") +} + +// IsInMergeState states whether we are still mid-merge +func (c *GitCommand) IsInMergeState() (bool, error) { + output, err := c.OSCommand.RunCommandWithOutput("git status --untracked-files=all") + if err != nil { + return false, err + } + return strings.Contains(output, "conclude merge") || strings.Contains(output, "unmerged paths"), nil +} + +// RemoveFile directly +func (c *GitCommand) RemoveFile(file File) error { + // if the file isn't tracked, we assume you want to delete it + if !file.Tracked { + return c.OSCommand.RunCommand("rm -rf ./" + file.Name) + } + // if the file is tracked, we assume you want to just check it out + return c.OSCommand.RunCommand("git checkout " + file.Name) +} + +// Checkout checks out a branch, with --force if you set the force arg to true +func (c *GitCommand) Checkout(branch string, force bool) error { + forceArg := "" + if force { + forceArg = "--force " + } + return c.OSCommand.RunCommand("git checkout " + forceArg + branch) +} + +// AddPatch prepares a subprocess for adding a patch by patch +// this will eventually be swapped out for a better solution inside the Gui +func (c *GitCommand) AddPatch(filename string) (*exec.Cmd, error) { + return c.OSCommand.PrepareSubProcess("git", "add", "--patch", filename) +} + +// PrepareCommitSubProcess prepares a subprocess for `git commit` +func (c *GitCommand) PrepareCommitSubProcess() (*exec.Cmd, error) { + return c.OSCommand.PrepareSubProcess("git", "commit") +} + +// GetBranchGraph gets the color-formatted graph of the log for the given branch +// Currently it limits the result to 100 commits, but when we get async stuff +// working we can do lazy loading +func (c *GitCommand) GetBranchGraph(branchName string) (string, error) { + return c.OSCommand.RunCommandWithOutput("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branchName) +} + +// Map (from https://gobyexample.com/collection-functions) +func Map(vs []string, f func(string) string) []string { + vsm := make([]string, len(vs)) + for i, v := range vs { + vsm[i] = f(v) + } + return vsm +} + +func includesString(list []string, a string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// not sure how to genericise this because []interface{} doesn't accept e.g. +// []int arguments +func includesInt(list []int, a int) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// GetCommits obtains the commits of the current branch +func (c *GitCommand) GetCommits() []Commit { + pushables := c.GetCommitsToPush() + log := c.GetLog() + commits := make([]Commit, 0) + // now we can split it up and turn it into commits + lines := utils.SplitLines(log) + for _, line := range lines { + splitLine := strings.Split(line, " ") + sha := splitLine[0] + pushed := includesString(pushables, sha) + commits = append(commits, Commit{ + Sha: sha, + Name: strings.Join(splitLine[1:], " "), + Pushed: pushed, + DisplayString: strings.Join(splitLine, " "), + }) + } + return commits +} + +// GetLog gets the git log (currently limited to 30 commits for performance +// until we work out lazy loading +func (c *GitCommand) GetLog() string { + // currently limiting to 30 for performance reasons + // TODO: add lazyloading when you scroll down + result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30") + if err != nil { + // assume if there is an error there are no commits yet for this branch + return "" + } + return result +} + +// Ignore adds a file to the gitignore for the repo +func (c *GitCommand) Ignore(filename string) { + if _, err := c.OSCommand.RunDirectCommand("echo '" + filename + "' >> .gitignore"); err != nil { + panic(err) + } +} + +// Show shows the diff of a commit +func (c *GitCommand) Show(sha string) string { + result, err := c.OSCommand.RunCommandWithOutput("git show --color " + sha) + if err != nil { + panic(err) + } + return result +} + +// Diff returns the diff of a file +func (c *GitCommand) Diff(file File) string { + cachedArg := "" + fileName := file.Name + if file.HasStagedChanges && !file.HasUnstagedChanges { + cachedArg = "--cached" + } else { + // if the file is staged and has spaces in it, it comes pre-quoted + fileName = c.OSCommand.Quote(fileName) + } + deletedArg := "" + if file.Deleted { + deletedArg = "--" + } + trackedArg := "" + if !file.Tracked && !file.HasStagedChanges { + trackedArg = "--no-index /dev/null" + } + command := fmt.Sprintf("%s %s %s %s %s", "git diff --color ", cachedArg, deletedArg, trackedArg, fileName) + + // for now we assume an error means the file was deleted + s, _ := c.OSCommand.RunCommandWithOutput(command) + return s +} diff --git a/pkg/commands/git_structs.go b/pkg/commands/git_structs.go new file mode 100644 index 000000000..6b10b18bb --- /dev/null +++ b/pkg/commands/git_structs.go @@ -0,0 +1,36 @@ +package commands + +// File : A staged/unstaged file +// TODO: decide whether to give all of these the Git prefix +type File struct { + Name string + HasStagedChanges bool + HasUnstagedChanges bool + Tracked bool + Deleted bool + HasMergeConflicts bool + DisplayString string +} + +// Commit : A git commit +type Commit struct { + Sha string + Name string + Pushed bool + DisplayString string +} + +// StashEntry : A git stash entry +type StashEntry struct { + Index int + Name string + DisplayString string +} + +// Conflict : A git conflict with a start middle and end corresponding to line +// numbers in the file where the conflict bars appear +type Conflict struct { + Start int + Middle int + End int +} diff --git a/pkg/commands/os.go b/pkg/commands/os.go new file mode 100644 index 000000000..9f9819a5a --- /dev/null +++ b/pkg/commands/os.go @@ -0,0 +1,174 @@ +package commands + +import ( + "errors" + "os" + "os/exec" + "runtime" + + "github.com/davecgh/go-spew/spew" + + "github.com/mgutz/str" + + "github.com/Sirupsen/logrus" + gitconfig "github.com/tcnksm/go-gitconfig" +) + +var ( + // ErrNoOpenCommand : When we don't know which command to use to open a file + ErrNoOpenCommand = errors.New("Unsure what command to use to open this file") + // ErrNoEditorDefined : When we can't find an editor to edit a file + ErrNoEditorDefined = errors.New("No editor defined in $VISUAL, $EDITOR, or git config") +) + +// Platform stores the os state +type Platform struct { + os string + shell string + shellArg string + escapedQuote string +} + +// OSCommand holds all the os commands +type OSCommand struct { + Log *logrus.Logger + Platform *Platform +} + +// NewOSCommand os command runner +func NewOSCommand(log *logrus.Logger) (*OSCommand, error) { + osCommand := &OSCommand{ + Log: log, + Platform: getPlatform(), + } + return osCommand, nil +} + +// RunCommandWithOutput wrapper around commands returning their output and error +func (c *OSCommand) RunCommandWithOutput(command string) (string, error) { + c.Log.WithField("command", command).Info("RunCommand") + splitCmd := str.ToArgv(command) + c.Log.Info(splitCmd) + cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() + return sanitisedCommandOutput(cmdOut, err) +} + +// RunCommand runs a command and just returns the error +func (c *OSCommand) RunCommand(command string) error { + _, err := c.RunCommandWithOutput(command) + return err +} + +// RunDirectCommand wrapper around direct commands +func (c *OSCommand) RunDirectCommand(command string) (string, error) { + c.Log.WithField("command", command).Info("RunDirectCommand") + args := str.ToArgv(c.Platform.shellArg + " " + command) + c.Log.Info(spew.Sdump(args)) + + cmdOut, err := exec. + Command(c.Platform.shell, args...). + CombinedOutput() + return sanitisedCommandOutput(cmdOut, err) +} + +func sanitisedCommandOutput(output []byte, err error) (string, error) { + outputString := string(output) + if err != nil { + // errors like 'exit status 1' are not very useful so we'll create an error + // from the combined output + return outputString, errors.New(outputString) + } + return outputString, nil +} + +func getPlatform() *Platform { + switch runtime.GOOS { + case "windows": + return &Platform{ + os: "windows", + shell: "cmd", + shellArg: "/c", + escapedQuote: "\\\"", + } + default: + return &Platform{ + os: runtime.GOOS, + shell: "bash", + shellArg: "-c", + escapedQuote: "\"", + } + } +} + +// GetOpenCommand get open command +func (c *OSCommand) GetOpenCommand() (string, string, error) { + //NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX) + trailMap := map[string]string{ + "xdg-open": " &>/dev/null &", + "cygstart": "", + "open": "", + } + for name, trail := range trailMap { + if err := c.RunCommand("which " + name); err == nil { + return name, trail, nil + } + } + return "", "", ErrNoOpenCommand +} + +// VsCodeOpenFile opens the file in code, with the -r flag to open in the +// current window +// each of these open files needs to have the same function signature because +// they're being passed as arguments into another function, +// but only editFile actually returns a *exec.Cmd +func (c *OSCommand) VsCodeOpenFile(filename string) (*exec.Cmd, error) { + return nil, c.RunCommand("code -r " + filename) +} + +// SublimeOpenFile opens the filein sublime +// may be deprecated in the future +func (c *OSCommand) SublimeOpenFile(filename string) (*exec.Cmd, error) { + return nil, c.RunCommand("subl " + filename) +} + +// OpenFile opens a file with the given +func (c *OSCommand) OpenFile(filename string) (*exec.Cmd, error) { + cmdName, cmdTrail, err := c.GetOpenCommand() + if err != nil { + return nil, err + } + err = c.RunCommand(cmdName + " " + filename + cmdTrail) // TODO: test on linux + return nil, err +} + +// EditFile opens a file in a subprocess using whatever editor is available, +// falling back to core.editor, VISUAL, EDITOR, then vi +func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) { + editor, _ := gitconfig.Global("core.editor") + if editor == "" { + editor = os.Getenv("VISUAL") + } + if editor == "" { + editor = os.Getenv("EDITOR") + } + if editor == "" { + if err := c.RunCommand("which vi"); err == nil { + editor = "vi" + } + } + if editor == "" { + return nil, ErrNoEditorDefined + } + return c.PrepareSubProcess(editor, filename) +} + +// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it +func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) (*exec.Cmd, error) { + subprocess := exec.Command(cmdName, commandArgs...) + return subprocess, nil +} + +// Quote wraps a message in platform-specific quotation marks +func (c *OSCommand) Quote(message string) string { + return c.Platform.escapedQuote + message + c.Platform.escapedQuote +} diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go new file mode 100644 index 000000000..98e56dea2 --- /dev/null +++ b/pkg/config/app_config.go @@ -0,0 +1,45 @@ +package config + +// AppConfig contains the base configuration fields required for lazygit. +type AppConfig struct { + Debug bool `long:"debug" env:"DEBUG" default:"false"` + Version string `long:"version" env:"VERSION" default:"unversioned"` + Commit string `long:"commit" env:"COMMIT"` + BuildDate string `long:"build-date" env:"BUILD_DATE"` + Name string `long:"name" env:"NAME" default:"lazygit"` +} + +// AppConfigurer interface allows individual app config structs to inherit Fields +// from AppConfig and still be used by lazygit. +type AppConfigurer interface { + GetDebug() bool + GetVersion() string + GetCommit() string + GetBuildDate() string + GetName() string +} + +// GetDebug returns debug flag +func (c *AppConfig) GetDebug() bool { + return c.Debug +} + +// GetVersion returns debug flag +func (c *AppConfig) GetVersion() string { + return c.Version +} + +// GetCommit returns debug flag +func (c *AppConfig) GetCommit() string { + return c.Commit +} + +// GetBuildDate returns debug flag +func (c *AppConfig) GetBuildDate() string { + return c.BuildDate +} + +// GetName returns debug flag +func (c *AppConfig) GetName() string { + return c.Name +} diff --git a/branch_list_builder.go b/pkg/git/branch_list_builder.go similarity index 54% rename from branch_list_builder.go rename to pkg/git/branch_list_builder.go index 1d4dc338d..41e59c093 100644 --- a/branch_list_builder.go +++ b/pkg/git/branch_list_builder.go @@ -1,9 +1,14 @@ -package main +package git import ( "regexp" "strings" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" + + "github.com/Sirupsen/logrus" + "gopkg.in/src-d/go-git.v4/plumbing" ) @@ -15,53 +20,64 @@ import ( // our safe branches, then add the remaining safe branches, ensuring uniqueness // along the way -type branchListBuilder struct{} - -func newBranchListBuilder() *branchListBuilder { - return &branchListBuilder{} +// BranchListBuilder returns a list of Branch objects for the current repo +type BranchListBuilder struct { + Log *logrus.Logger + GitCommand *commands.GitCommand } -func (b *branchListBuilder) obtainCurrentBranch() Branch { +// NewBranchListBuilder builds a new branch list builder +func NewBranchListBuilder(log *logrus.Logger, gitCommand *commands.GitCommand) (*BranchListBuilder, error) { + return &BranchListBuilder{ + Log: log, + GitCommand: gitCommand, + }, nil +} + +func (b *BranchListBuilder) obtainCurrentBranch() commands.Branch { // I used go-git for this, but that breaks if you've just done a git init, // even though you're on 'master' - branchName, _ := runDirectCommand("git symbolic-ref --short HEAD") - return Branch{Name: strings.TrimSpace(branchName), Recency: " *"} + branchName, err := b.GitCommand.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") + if err != nil { + panic(err.Error()) + } + return commands.Branch{Name: strings.TrimSpace(branchName), Recency: " *"} } -func (*branchListBuilder) obtainReflogBranches() []Branch { - branches := make([]Branch, 0) - rawString, err := runDirectCommand("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD") +func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch { + branches := make([]commands.Branch, 0) + rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD") if err != nil { return branches } - branchLines := splitLines(rawString) + branchLines := utils.SplitLines(rawString) for _, line := range branchLines { timeNumber, timeUnit, branchName := branchInfoFromLine(line) timeUnit = abbreviatedTimeUnit(timeUnit) - branch := Branch{Name: branchName, Recency: timeNumber + timeUnit} + branch := commands.Branch{Name: branchName, Recency: timeNumber + timeUnit} branches = append(branches, branch) } return branches } -func (b *branchListBuilder) obtainSafeBranches() []Branch { - branches := make([]Branch, 0) +func (b *BranchListBuilder) obtainSafeBranches() []commands.Branch { + branches := make([]commands.Branch, 0) - bIter, err := r.Branches() + bIter, err := b.GitCommand.Repo.Branches() if err != nil { panic(err) } err = bIter.ForEach(func(b *plumbing.Reference) error { name := b.Name().Short() - branches = append(branches, Branch{Name: name}) + branches = append(branches, commands.Branch{Name: name}) return nil }) return branches } -func (b *branchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []Branch, included bool) []Branch { +func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []commands.Branch, included bool) []commands.Branch { for _, newBranch := range newBranches { if included == branchIncluded(newBranch.Name, existingBranches) { finalBranches = append(finalBranches, newBranch) @@ -70,7 +86,7 @@ func (b *branchListBuilder) appendNewBranches(finalBranches, newBranches, existi return finalBranches } -func sanitisedReflogName(reflogBranch Branch, safeBranches []Branch) string { +func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.Branch) string { for _, safeBranch := range safeBranches { if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) { return safeBranch.Name @@ -79,15 +95,16 @@ func sanitisedReflogName(reflogBranch Branch, safeBranches []Branch) string { return reflogBranch.Name } -func (b *branchListBuilder) build() []Branch { - branches := make([]Branch, 0) +// Build the list of branches for the current repo +func (b *BranchListBuilder) Build() []commands.Branch { + branches := make([]commands.Branch, 0) head := b.obtainCurrentBranch() safeBranches := b.obtainSafeBranches() if len(safeBranches) == 0 { return append(branches, head) } reflogBranches := b.obtainReflogBranches() - reflogBranches = uniqueByName(append([]Branch{head}, reflogBranches...)) + reflogBranches = uniqueByName(append([]commands.Branch{head}, reflogBranches...)) for i, reflogBranch := range reflogBranches { reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches) } @@ -98,8 +115,17 @@ func (b *branchListBuilder) build() []Branch { return branches } -func uniqueByName(branches []Branch) []Branch { - finalBranches := make([]Branch, 0) +func branchIncluded(branchName string, branches []commands.Branch) bool { + for _, existingBranch := range branches { + if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) { + return true + } + } + return false +} + +func uniqueByName(branches []commands.Branch) []commands.Branch { + finalBranches := make([]commands.Branch, 0) for _, branch := range branches { if branchIncluded(branch.Name, finalBranches) { continue diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go new file mode 100644 index 000000000..c4786d39f --- /dev/null +++ b/pkg/gui/branches_panel.go @@ -0,0 +1,141 @@ +package gui + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/git" +) + +func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { + index := gui.getItemPosition(v) + if index == 0 { + return gui.createErrorPanel(g, "You have already checked out this branch") + } + branch := gui.getSelectedBranch(v) + if err := gui.GitCommand.Checkout(branch.Name, false); err != nil { + gui.createErrorPanel(g, err.Error()) + } + return gui.refreshSidePanels(g) +} + +func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error { + branch := gui.getSelectedBranch(v) + return gui.createConfirmationPanel(g, v, "Force Checkout Branch", "Are you sure you want force checkout? You will lose all local changes", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.Checkout(branch.Name, true); err != nil { + gui.createErrorPanel(g, err.Error()) + } + return gui.refreshSidePanels(g) + }, nil) +} + +func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error { + gui.createPromptPanel(g, v, "Branch Name:", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.Checkout(gui.trimmedContent(v), false); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + return gui.refreshSidePanels(g) + }) + return nil +} + +func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error { + branch := gui.State.Branches[0] + gui.createPromptPanel(g, v, "New Branch Name (Branch is off of "+branch.Name+")", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.NewBranch(gui.trimmedContent(v)); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + gui.refreshSidePanels(g) + return gui.handleBranchSelect(g, v) + }) + return nil +} + +func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { + checkedOutBranch := gui.State.Branches[0] + selectedBranch := gui.getSelectedBranch(v) + if checkedOutBranch.Name == selectedBranch.Name { + return gui.createErrorPanel(g, "You cannot delete the checked out branch!") + } + return gui.createConfirmationPanel(g, v, "Delete Branch", "Are you sure you want delete the branch "+selectedBranch.Name+" ?", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.DeleteBranch(selectedBranch.Name); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + return gui.refreshSidePanels(g) + }, nil) +} + +func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { + checkedOutBranch := gui.State.Branches[0] + selectedBranch := gui.getSelectedBranch(v) + defer gui.refreshSidePanels(g) + if checkedOutBranch.Name == selectedBranch.Name { + return gui.createErrorPanel(g, "You cannot merge a branch into itself") + } + if err := gui.GitCommand.Merge(selectedBranch.Name); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + return nil +} + +func (gui *Gui) getSelectedBranch(v *gocui.View) commands.Branch { + lineNumber := gui.getItemPosition(v) + return gui.State.Branches[lineNumber] +} + +func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error { + return gui.renderOptionsMap(g, map[string]string{ + "space": "checkout", + "f": "force checkout", + "m": "merge", + "c": "checkout by name", + "n": "new branch", + "d": "delete branch", + "← → ↑ ↓": "navigate", + }) +} + +// may want to standardise how these select methods work +func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { + if err := gui.renderBranchesOptions(g); err != nil { + return err + } + // This really shouldn't happen: there should always be a master branch + if len(gui.State.Branches) == 0 { + return gui.renderString(g, "main", "No branches for this repo") + } + go func() { + branch := gui.getSelectedBranch(v) + diff, err := gui.GitCommand.GetBranchGraph(branch.Name) + if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { + diff = "There is no tracking for this branch" + } + gui.renderString(g, "main", diff) + }() + return nil +} + +// gui.refreshStatus is called at the end of this because that's when we can +// be sure there is a state.Branches array to pick the current branch from +func (gui *Gui) refreshBranches(g *gocui.Gui) error { + g.Update(func(g *gocui.Gui) error { + v, err := g.View("branches") + if err != nil { + panic(err) + } + builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand) + if err != nil { + return err + } + gui.State.Branches = builder.Build() + v.Clear() + for _, branch := range gui.State.Branches { + fmt.Fprintln(v, branch.GetDisplayString()) + } + gui.resetOrigin(v) + return gui.refreshStatus(g) + }) + return nil +} diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go new file mode 100644 index 000000000..f765ab308 --- /dev/null +++ b/pkg/gui/commit_message_panel.go @@ -0,0 +1,50 @@ +package gui + +import "github.com/jesseduffield/gocui" + +func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { + message := gui.trimmedContent(v) + if message == "" { + return gui.createErrorPanel(g, "You cannot commit without a commit message") + } + sub, err := gui.GitCommand.Commit(g, message) + if err != nil { + // TODO need to find a way to send through this error + if err != ErrSubProcess { + return gui.createErrorPanel(g, err.Error()) + } + } + if sub != nil { + gui.SubProcess = sub + return ErrSubProcess + } + gui.refreshFiles(g) + v.Clear() + v.SetCursor(0, 0) + g.SetViewOnBottom("commitMessage") + gui.switchFocus(g, v, gui.getFilesView(g)) + return gui.refreshCommits(g) +} + +func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { + g.SetViewOnBottom("commitMessage") + return gui.switchFocus(g, v, gui.getFilesView(g)) +} + +func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { + // resising ahead of time so that the top line doesn't get hidden to make + // room for the cursor on the second line + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer()) + if _, err := g.SetView("commitMessage", x0, y0, x1, y1+1, 0); err != nil { + if err != gocui.ErrUnknownView { + return err + } + } + + v.EditNewLine() + return nil +} + +func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { + return gui.renderString(g, "options", "esc: close, enter: confirm") +} diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go new file mode 100644 index 000000000..60ae1c315 --- /dev/null +++ b/pkg/gui/commits_panel.go @@ -0,0 +1,176 @@ +package gui + +import ( + "errors" + + "github.com/fatih/color" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" +) + +var ( + // ErrNoCommits : When no commits are found for the branch + ErrNoCommits = errors.New("No commits for this branch") +) + +func (gui *Gui) refreshCommits(g *gocui.Gui) error { + g.Update(func(*gocui.Gui) error { + gui.State.Commits = gui.GitCommand.GetCommits() + v, err := g.View("commits") + if err != nil { + panic(err) + } + v.Clear() + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow) + white := color.New(color.FgWhite) + shaColor := white + for _, commit := range gui.State.Commits { + if commit.Pushed { + shaColor = red + } else { + shaColor = yellow + } + shaColor.Fprint(v, commit.Sha+" ") + white.Fprintln(v, commit.Name) + } + gui.refreshStatus(g) + if g.CurrentView().Name() == "commits" { + gui.handleCommitSelect(g, v) + } + return nil + }) + return nil +} + +func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { + return gui.createConfirmationPanel(g, commitView, "Reset To Commit", "Are you sure you want to reset to this commit?", func(g *gocui.Gui, v *gocui.View) error { + commit, err := gui.getSelectedCommit(g) + if err != nil { + panic(err) + } + if err := gui.GitCommand.ResetToCommit(commit.Sha); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + if err := gui.refreshCommits(g); err != nil { + panic(err) + } + if err := gui.refreshFiles(g); err != nil { + panic(err) + } + gui.resetOrigin(commitView) + return gui.handleCommitSelect(g, nil) + }, nil) +} + +func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error { + return gui.renderOptionsMap(g, map[string]string{ + "s": "squash down", + "r": "rename", + "g": "reset to this commit", + "f": "fixup commit", + "← → ↑ ↓": "navigate", + }) +} + +func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { + if err := gui.renderCommitsOptions(g); err != nil { + return err + } + commit, err := gui.getSelectedCommit(g) + if err != nil { + if err != ErrNoCommits { + return err + } + return gui.renderString(g, "main", "No commits for this branch") + } + commitText := gui.GitCommand.Show(commit.Sha) + return gui.renderString(g, "main", commitText) +} + +func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { + if gui.getItemPosition(v) != 0 { + return gui.createErrorPanel(g, "Can only squash topmost commit") + } + if len(gui.State.Commits) == 1 { + return gui.createErrorPanel(g, "You have no commits to squash with") + } + commit, err := gui.getSelectedCommit(g) + if err != nil { + return err + } + if err := gui.GitCommand.SquashPreviousTwoCommits(commit.Name); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + if err := gui.refreshCommits(g); err != nil { + panic(err) + } + gui.refreshStatus(g) + return gui.handleCommitSelect(g, v) +} + +// TODO: move to files panel +func (gui *Gui) anyUnStagedChanges(files []commands.File) bool { + for _, file := range files { + if file.Tracked && file.HasUnstagedChanges { + return true + } + } + return false +} + +func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { + if len(gui.State.Commits) == 1 { + return gui.createErrorPanel(g, "You have no commits to squash with") + } + if gui.anyUnStagedChanges(gui.State.Files) { + return gui.createErrorPanel(g, "Can't fixup while there are unstaged changes") + } + branch := gui.State.Branches[0] + commit, err := gui.getSelectedCommit(g) + if err != nil { + return err + } + gui.createConfirmationPanel(g, v, "Fixup", "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.SquashFixupCommit(branch.Name, commit.Sha); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + if err := gui.refreshCommits(g); err != nil { + panic(err) + } + return gui.refreshStatus(g) + }, nil) + return nil +} + +func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error { + if gui.getItemPosition(v) != 0 { + return gui.createErrorPanel(g, "Can only rename topmost commit") + } + gui.createPromptPanel(g, v, "Rename Commit", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.RenameCommit(v.Buffer()); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + if err := gui.refreshCommits(g); err != nil { + panic(err) + } + return gui.handleCommitSelect(g, v) + }) + return nil +} + +func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) { + v, err := g.View("commits") + if err != nil { + panic(err) + } + if len(gui.State.Commits) == 0 { + return commands.Commit{}, ErrNoCommits + } + lineNumber := gui.getItemPosition(v) + if lineNumber > len(gui.State.Commits)-1 { + gui.Log.Info("potential error in getSelected Commit (mismatched ui and state)", gui.State.Commits, lineNumber) + return gui.State.Commits[len(gui.State.Commits)-1], nil + } + return gui.State.Commits[lineNumber], nil +} diff --git a/confirmation_panel.go b/pkg/gui/confirmation_panel.go similarity index 53% rename from confirmation_panel.go rename to pkg/gui/confirmation_panel.go index a8719d237..3bec18419 100644 --- a/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -4,39 +4,40 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package gui import ( "strings" "github.com/fatih/color" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error { +func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { if function != nil { if err := function(g, v); err != nil { panic(err) } } - return closeConfirmationPrompt(g) + return gui.closeConfirmationPrompt(g) } } -func closeConfirmationPrompt(g *gocui.Gui) error { +func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error { view, err := g.View("confirmation") if err != nil { panic(err) } - if err := returnFocus(g, view); err != nil { + if err := gui.returnFocus(g, view); err != nil { panic(err) } g.DeleteKeybindings("confirmation") return g.DeleteView("confirmation") } -func getMessageHeight(message string, width int) int { +func (gui *Gui) getMessageHeight(message string, width int) int { lines := strings.Split(message, "\n") lineCount := 0 for _, line := range lines { @@ -45,20 +46,20 @@ func getMessageHeight(message string, width int) int { return lineCount } -func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) { +func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) { width, height := g.Size() panelWidth := width / 2 - panelHeight := getMessageHeight(prompt, panelWidth) + panelHeight := gui.getMessageHeight(prompt, panelWidth) return width/2 - panelWidth/2, height/2 - panelHeight/2 - panelHeight%2 - 1, width/2 + panelWidth/2, height/2 + panelHeight/2 } -func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error { +func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error { g.SetViewOnBottom("commitMessage") // only need to fit one line - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "") + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, "") if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { if err != gocui.ErrUnknownView { return err @@ -66,41 +67,41 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, hand confirmationView.Editable = true confirmationView.Title = title - switchFocus(g, currentView, confirmationView) - return setKeyBindings(g, handleConfirm, nil) + gui.switchFocus(g, currentView, confirmationView) + return gui.setKeyBindings(g, handleConfirm, nil) } return nil } -func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { +func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { g.SetViewOnBottom("commitMessage") g.Update(func(g *gocui.Gui) error { // delete the existing confirmation panel if it exists if view, _ := g.View("confirmation"); view != nil { - if err := closeConfirmationPrompt(g); err != nil { + if err := gui.closeConfirmationPrompt(g); err != nil { panic(err) } } - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, prompt) + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, prompt) if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { if err != gocui.ErrUnknownView { return err } confirmationView.Title = title confirmationView.FgColor = gocui.ColorWhite - renderString(g, "confirmation", prompt) - switchFocus(g, currentView, confirmationView) - return setKeyBindings(g, handleConfirm, handleClose) + gui.renderString(g, "confirmation", prompt) + gui.switchFocus(g, currentView, confirmationView) + return gui.setKeyBindings(g, handleConfirm, handleClose) } return nil }) return nil } -func handleNewline(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) handleNewline(g *gocui.Gui, v *gocui.View) error { // resising ahead of time so that the top line doesn't get hidden to make // room for the cursor on the second line - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer()) + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer()) if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil { if err != gocui.ErrUnknownView { return err @@ -111,45 +112,38 @@ func handleNewline(g *gocui.Gui, v *gocui.View) error { return nil } -func setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { - renderString(g, "options", "esc: close, enter: confirm") - if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, wrappedConfirmationFunction(handleConfirm)); err != nil { +func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { + gui.renderString(g, "options", "esc: close, enter: confirm") + if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm)); err != nil { return err } - if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, handleNewline); err != nil { + if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, gui.handleNewline); err != nil { return err } - return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, wrappedConfirmationFunction(handleClose)) + return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose)) } -func createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error { - return createConfirmationPanel(g, currentView, title, prompt, nil, nil) +func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error { + return gui.createConfirmationPanel(g, currentView, title, prompt, nil, nil) } -func createErrorPanel(g *gocui.Gui, message string) error { +func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error { currentView := g.CurrentView() colorFunction := color.New(color.FgRed).SprintFunc() coloredMessage := colorFunction(strings.TrimSpace(message)) - return createConfirmationPanel(g, currentView, "Error", coloredMessage, nil, nil) + return gui.createConfirmationPanel(g, currentView, "Error", coloredMessage, nil, nil) } -func trimTrailingNewline(str string) string { - if strings.HasSuffix(str, "\n") { - return str[:len(str)-1] - } - return str -} - -func resizePopupPanel(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { // If the confirmation panel is already displayed, just resize the width, // otherwise continue - content := trimTrailingNewline(v.Buffer()) - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content) + content := utils.TrimTrailingNewline(v.Buffer()) + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content) vx0, vy0, vx1, vy1 := v.Dimensions() if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { return nil } - devLog("resizing popup panel") + gui.Log.Info("resizing popup panel") _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) return err } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go new file mode 100644 index 000000000..a7e9683d2 --- /dev/null +++ b/pkg/gui/files_panel.go @@ -0,0 +1,400 @@ +package gui + +import ( + + // "io" + // "io/ioutil" + + // "strings" + + "errors" + "os/exec" + "strings" + + "github.com/fatih/color" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" +) + +var ( + errNoFiles = errors.New("No changed files") + errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`) +) + +func (gui *Gui) stagedFiles() []commands.File { + files := gui.State.Files + result := make([]commands.File, 0) + for _, file := range files { + if file.HasStagedChanges { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) trackedFiles() []commands.File { + files := gui.State.Files + result := make([]commands.File, 0) + for _, file := range files { + if file.Tracked { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) stageSelectedFile(g *gocui.Gui) error { + file, err := gui.getSelectedFile(g) + if err != nil { + return err + } + return gui.GitCommand.StageFile(file.Name) +} + +func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error { + file, err := gui.getSelectedFile(g) + if err != nil { + if err == errNoFiles { + return nil + } + return err + } + + if file.HasMergeConflicts { + return gui.handleSwitchToMerge(g, v) + } + + if file.HasUnstagedChanges { + gui.GitCommand.StageFile(file.Name) + } else { + gui.GitCommand.UnStageFile(file.Name, file.Tracked) + } + + if err := gui.refreshFiles(g); err != nil { + return err + } + + return gui.handleFileSelect(g, v) +} + +func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { + file, err := gui.getSelectedFile(g) + if err != nil { + if err == errNoFiles { + return nil + } + return err + } + if !file.HasUnstagedChanges { + return gui.createErrorPanel(g, "File has no unstaged changes to add") + } + if !file.Tracked { + return gui.createErrorPanel(g, "Cannot git add --patch untracked files") + } + sub, err := gui.GitCommand.AddPatch(file.Name) + if err != nil { + return err + } + gui.SubProcess = sub + return ErrSubProcess +} + +func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) { + if len(gui.State.Files) == 0 { + return commands.File{}, errNoFiles + } + filesView, err := g.View("files") + if err != nil { + panic(err) + } + lineNumber := gui.getItemPosition(filesView) + return gui.State.Files[lineNumber], nil +} + +func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { + file, err := gui.getSelectedFile(g) + if err != nil { + if err == errNoFiles { + return nil + } + return err + } + var deleteVerb string + if file.Tracked { + deleteVerb = "checkout" + } else { + deleteVerb = "delete" + } + return gui.createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.RemoveFile(file); err != nil { + panic(err) + } + return gui.refreshFiles(g) + }, nil) +} + +func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { + file, err := gui.getSelectedFile(g) + if err != nil { + return gui.createErrorPanel(g, err.Error()) + } + if file.Tracked { + return gui.createErrorPanel(g, "Cannot ignore tracked files") + } + gui.GitCommand.Ignore(file.Name) + return gui.refreshFiles(g) +} + +func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error { + optionsMap := map[string]string{ + "← → ↑ ↓": "navigate", + "S": "stash files", + "c": "commit changes", + "o": "open", + "i": "ignore", + "d": "delete", + "space": "toggle staged", + "R": "refresh", + "t": "add patch", + "e": "edit", + "PgUp/PgDn": "scroll", + } + if gui.State.HasMergeConflicts { + optionsMap["a"] = "abort merge" + optionsMap["m"] = "resolve merge conflicts" + } + if file == nil { + return gui.renderOptionsMap(g, optionsMap) + } + if file.Tracked { + optionsMap["d"] = "checkout" + } + return gui.renderOptionsMap(g, optionsMap) +} + +func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { + file, err := gui.getSelectedFile(g) + if err != nil { + if err != errNoFiles { + return err + } + gui.renderString(g, "main", "No changed files") + return gui.renderfilesOptions(g, nil) + } + gui.renderfilesOptions(g, &file) + var content string + if file.HasMergeConflicts { + return gui.refreshMergePanel(g) + } + + content = gui.GitCommand.Diff(file) + return gui.renderString(g, "main", content) +} + +func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { + if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { + return gui.createErrorPanel(g, "There are no staged files to commit") + } + commitMessageView := gui.getCommitMessageView(g) + g.Update(func(g *gocui.Gui) error { + g.SetViewOnTop("commitMessage") + gui.switchFocus(g, filesView, commitMessageView) + return nil + }) + return nil +} + +// handleCommitEditorPress - handle when the user wants to commit changes via +// their editor rather than via the popup panel +func (gui *Gui) handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error { + if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { + return gui.createErrorPanel(g, "There are no staged files to commit") + } + gui.PrepareSubProcess(g, "git", "commit") + return nil +} + +// PrepareSubProcess - prepare a subprocess for execution and tell the gui to switch to it +func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) error { + sub, err := gui.GitCommand.PrepareCommitSubProcess() + if err != nil { + return err + } + gui.SubProcess = sub + g.Update(func(g *gocui.Gui) error { + return ErrSubProcess + }) + return nil +} + +func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) (*exec.Cmd, error)) error { + file, err := gui.getSelectedFile(g) + if err != nil { + if err != errNoFiles { + return err + } + return nil + } + sub, err := open(file.Name) + if err != nil { + return gui.createErrorPanel(g, err.Error()) + } + if sub != nil { + gui.SubProcess = sub + return ErrSubProcess + } + return nil +} + +func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error { + return gui.genericFileOpen(g, v, gui.OSCommand.EditFile) +} + +func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error { + return gui.genericFileOpen(g, v, gui.OSCommand.OpenFile) +} + +func (gui *Gui) handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error { + return gui.genericFileOpen(g, v, gui.OSCommand.SublimeOpenFile) +} + +func (gui *Gui) handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error { + return gui.genericFileOpen(g, v, gui.OSCommand.VsCodeOpenFile) +} + +func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error { + return gui.refreshFiles(g) +} + +func (gui *Gui) refreshStateFiles() { + // get files to stage + files := gui.GitCommand.GetStatusFiles() + gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files) + gui.updateHasMergeConflictStatus() +} + +func (gui *Gui) updateHasMergeConflictStatus() error { + merging, err := gui.GitCommand.IsInMergeState() + if err != nil { + return err + } + gui.State.HasMergeConflicts = merging + return nil +} + +func (gui *Gui) renderFile(file commands.File, filesView *gocui.View) { + // potentially inefficient to be instantiating these color + // objects with each render + red := color.New(color.FgRed) + green := color.New(color.FgGreen) + if !file.Tracked && !file.HasStagedChanges { + red.Fprintln(filesView, file.DisplayString) + return + } + green.Fprint(filesView, file.DisplayString[0:1]) + red.Fprint(filesView, file.DisplayString[1:3]) + if file.HasUnstagedChanges { + red.Fprintln(filesView, file.Name) + } else { + green.Fprintln(filesView, file.Name) + } +} + +func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { + item, err := gui.getSelectedFile(g) + if err != nil { + if err != errNoFiles { + return "", err + } + return "", gui.renderString(g, "main", "No file to display") + } + cat, err := gui.GitCommand.CatFile(item.Name) + if err != nil { + panic(err) + } + return cat, nil +} + +func (gui *Gui) refreshFiles(g *gocui.Gui) error { + filesView, err := g.View("files") + if err != nil { + return err + } + gui.refreshStateFiles() + filesView.Clear() + for _, file := range gui.State.Files { + gui.renderFile(file, filesView) + } + gui.correctCursor(filesView) + if filesView == g.CurrentView() { + gui.handleFileSelect(g, filesView) + } + return nil +} + +func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { + gui.createMessagePanel(g, v, "", "Pulling...") + go func() { + if err := gui.GitCommand.Pull(); err != nil { + gui.createErrorPanel(g, err.Error()) + } else { + gui.closeConfirmationPrompt(g) + gui.refreshCommits(g) + gui.refreshStatus(g) + } + gui.refreshFiles(g) + }() + return nil +} + +func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { + gui.createMessagePanel(g, v, "", "Pushing...") + go func() { + branchName := gui.State.Branches[0].Name + if err := gui.GitCommand.Push(branchName); err != nil { + gui.createErrorPanel(g, err.Error()) + } else { + gui.closeConfirmationPrompt(g) + gui.refreshCommits(g) + gui.refreshStatus(g) + } + }() + return nil +} + +func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { + mergeView, err := g.View("main") + if err != nil { + return err + } + file, err := gui.getSelectedFile(g) + if err != nil { + if err != errNoFiles { + return err + } + return nil + } + if !file.HasMergeConflicts { + return gui.createErrorPanel(g, "This file has no merge conflicts") + } + gui.switchFocus(g, v, mergeView) + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.AbortMerge(); err != nil { + return gui.createErrorPanel(g, err.Error()) + } + gui.createMessagePanel(g, v, "", "Merge aborted") + gui.refreshStatus(g) + return gui.refreshFiles(g) +} + +func (gui *Gui) handleResetHard(g *gocui.Gui, v *gocui.View) error { + return gui.createConfirmationPanel(g, v, "Clear file panel", "Are you sure you want `reset --hard HEAD`? You may lose changes", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.ResetHard(); err != nil { + gui.createErrorPanel(g, err.Error()) + } + return gui.refreshFiles(g) + }, nil) +} diff --git a/gui.go b/pkg/gui/gui.go similarity index 51% rename from gui.go rename to pkg/gui/gui.go index f41f2891b..7fd396f05 100644 --- a/gui.go +++ b/pkg/gui/gui.go @@ -1,82 +1,86 @@ -package main +package gui import ( // "io" // "io/ioutil" - "runtime" + "errors" + "io/ioutil" + "log" + "os" + "os/exec" "strings" "time" // "strings" + "github.com/Sirupsen/logrus" "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" ) // OverlappingEdges determines if panel edges overlap var OverlappingEdges = false -type stateType struct { - GitFiles []GitFile - Branches []Branch - Commits []Commit - StashEntries []StashEntry +// ErrSubProcess tells us we're switching to a subprocess so we need to +// close the Gui until it is finished +var ( + ErrSubProcess = errors.New("running subprocess") +) + +// Gui wraps the gocui Gui object which handles rendering and events +type Gui struct { + g *gocui.Gui + Log *logrus.Logger + GitCommand *commands.GitCommand + OSCommand *commands.OSCommand + Version string + SubProcess *exec.Cmd + State guiState +} + +type guiState struct { + Files []commands.File + Branches []commands.Branch + Commits []commands.Commit + StashEntries []commands.StashEntry PreviousView string HasMergeConflicts bool ConflictIndex int ConflictTop bool - Conflicts []conflict + Conflicts []commands.Conflict EditHistory *stack.Stack - Platform platform + Platform commands.Platform + Version string } -type conflict struct { - start int - middle int - end int -} - -var state = stateType{ - GitFiles: make([]GitFile, 0), - PreviousView: "files", - Commits: make([]Commit, 0), - StashEntries: make([]StashEntry, 0), - ConflictIndex: 0, - ConflictTop: true, - Conflicts: make([]conflict, 0), - EditHistory: stack.New(), - Platform: getPlatform(), -} - -type platform struct { - os string - shell string - shellArg string - escapedQuote string -} - -func getPlatform() platform { - switch runtime.GOOS { - case "windows": - return platform{ - os: "windows", - shell: "cmd", - shellArg: "/c", - escapedQuote: "\\\"", - } - default: - return platform{ - os: runtime.GOOS, - shell: "bash", - shellArg: "-c", - escapedQuote: "\"", - } +// NewGui builds a new gui handler +func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, version string) (*Gui, error) { + initialState := guiState{ + Files: make([]commands.File, 0), + PreviousView: "files", + Commits: make([]commands.Commit, 0), + StashEntries: make([]commands.StashEntry, 0), + ConflictIndex: 0, + ConflictTop: true, + Conflicts: make([]commands.Conflict, 0), + EditHistory: stack.New(), + Platform: *oSCommand.Platform, + Version: "test version", // TODO: send version in } + + return &Gui{ + Log: log, + GitCommand: gitCommand, + OSCommand: oSCommand, + Version: version, + State: initialState, + }, nil } -func scrollUpMain(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) scrollUpMain(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") ox, oy := mainView.Origin() if oy >= 1 { @@ -85,7 +89,7 @@ func scrollUpMain(g *gocui.Gui, v *gocui.View) error { return nil } -func scrollDownMain(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) scrollDownMain(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") ox, oy := mainView.Origin() if oy < len(mainView.BufferLines()) { @@ -94,8 +98,8 @@ func scrollDownMain(g *gocui.Gui, v *gocui.View) error { return nil } -func handleRefresh(g *gocui.Gui, v *gocui.View) error { - return refreshSidePanels(g) +func (gui *Gui) handleRefresh(g *gocui.Gui, v *gocui.View) error { + return gui.refreshSidePanels(g) } func max(a, b int) int { @@ -106,7 +110,7 @@ func max(a, b int) int { } // layout is called for every screen re-render e.g. when the screen is resized -func layout(g *gocui.Gui) error { +func (gui *Gui) layout(g *gocui.Gui) error { g.Highlight = true g.SelFgColor = gocui.ColorWhite | gocui.AttrBold width, height := g.Size() @@ -157,7 +161,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = ShortLocalize("StatusTitle", "Status") + v.Title = "Status" v.FgColor = gocui.ColorWhite } @@ -167,7 +171,7 @@ func layout(g *gocui.Gui) error { return err } filesView.Highlight = true - filesView.Title = ShortLocalize("FilesTitle", "Files") + filesView.Title = "Files" v.FgColor = gocui.ColorWhite } @@ -175,7 +179,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = ShortLocalize("BranchesTitle", "Branches") + v.Title = "Branches" v.FgColor = gocui.ColorWhite } @@ -183,7 +187,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = ShortLocalize("CommitsTitle", "Commits") + v.Title = "Commits" v.FgColor = gocui.ColorWhite } @@ -191,11 +195,11 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = ShortLocalize("StashTitle", "Stash") + v.Title = "Stash" v.FgColor = gocui.ColorWhite } - if v, err := g.SetView("options", -1, optionsTop, width-len(version)-2, optionsTop+2, 0); err != nil { + if v, err := g.SetView("options", -1, optionsTop, width-len(gui.Version)-2, optionsTop+2, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -203,60 +207,60 @@ func layout(g *gocui.Gui) error { v.Frame = false } - if getCommitMessageView(g) == nil { + if gui.getCommitMessageView(g) == nil { // doesn't matter where this view starts because it will be hidden if commitMessageView, err := g.SetView("commitMessage", 0, 0, width, height, 0); err != nil { if err != gocui.ErrUnknownView { return err } g.SetViewOnBottom("commitMessage") - commitMessageView.Title = ShortLocalize("CommitMessage", "Commit message") + commitMessageView.Title = "Commit message" commitMessageView.FgColor = gocui.ColorWhite commitMessageView.Editable = true } } - if v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil { + if v, err := g.SetView("version", width-len(gui.Version)-1, optionsTop, width, optionsTop+2, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.BgColor = gocui.ColorDefault v.FgColor = gocui.ColorGreen v.Frame = false - renderString(g, "version", version) + gui.renderString(g, "version", gui.Version) // these are only called once - handleFileSelect(g, filesView) - refreshFiles(g) - refreshBranches(g) - refreshCommits(g) - refreshStashEntries(g) - nextView(g, nil) + gui.handleFileSelect(g, filesView) + gui.refreshFiles(g) + gui.refreshBranches(g) + gui.refreshCommits(g) + gui.refreshStashEntries(g) + gui.nextView(g, nil) } - resizePopupPanels(g) + gui.resizePopupPanels(g) return nil } -func fetch(g *gocui.Gui) error { - gitFetch() - refreshStatus(g) +func (gui *Gui) fetch(g *gocui.Gui) error { + gui.GitCommand.Fetch() + gui.refreshStatus(g) return nil } -func updateLoader(g *gocui.Gui) error { +func (gui *Gui) updateLoader(g *gocui.Gui) error { if confirmationView, _ := g.View("confirmation"); confirmationView != nil { - content := trimmedContent(confirmationView) + content := gui.trimmedContent(confirmationView) if strings.Contains(content, "...") { staticContent := strings.Split(content, "...")[0] + "..." - renderString(g, "confirmation", staticContent+" "+loader()) + gui.renderString(g, "confirmation", staticContent+" "+gui.loader()) } } return nil } -func goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) { +func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) { go func() { for range time.Tick(interval) { function(g) @@ -264,37 +268,64 @@ func goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) err }() } -func resizePopupPanels(g *gocui.Gui) error { +func (gui *Gui) resizePopupPanels(g *gocui.Gui) error { v := g.CurrentView() if v.Name() == "commitMessage" || v.Name() == "confirmation" { - return resizePopupPanel(g, v) + return gui.resizePopupPanel(g, v) } return nil } -func run() (err error) { +// Run setup the gui with keybindings and start the mainloop +func (gui *Gui) Run() error { g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges) if err != nil { - return + return err } defer g.Close() + gui.g = g // TODO: always use gui.g rather than passing g around everywhere + g.FgColor = gocui.ColorDefault - goEvery(g, time.Second*60, fetch) - goEvery(g, time.Second*10, refreshFiles) - goEvery(g, time.Millisecond*10, updateLoader) + gui.goEvery(g, time.Second*60, gui.fetch) + gui.goEvery(g, time.Second*10, gui.refreshFiles) + gui.goEvery(g, time.Millisecond*10, gui.updateLoader) - g.SetManagerFunc(layout) + g.SetManagerFunc(gui.layout) - if err = keybindings(g); err != nil { - return + if err = gui.keybindings(g); err != nil { + return err } err = g.MainLoop() - return + return err } -func quit(g *gocui.Gui, v *gocui.View) error { +// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration +// if the error returned from a run is a ErrSubProcess, it runs the subprocess +// otherwise it handles the error, possibly by quitting the application +func (gui *Gui) RunWithSubprocesses() { + for { + if err := gui.Run(); err != nil { + if err == gocui.ErrQuit { + break + } else if err == ErrSubProcess { + gui.SubProcess.Stdin = os.Stdin + gui.SubProcess.Stdout = os.Stdout + gui.SubProcess.Stderr = os.Stderr + gui.SubProcess.Run() + gui.SubProcess.Stdout = ioutil.Discard + gui.SubProcess.Stderr = ioutil.Discard + gui.SubProcess.Stdin = nil + gui.SubProcess = nil + } else { + log.Panicln(err) + } + } + } +} + +func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } diff --git a/keybindings.go b/pkg/gui/keybindings.go similarity index 66% rename from keybindings.go rename to pkg/gui/keybindings.go index afaa09527..b4f2bdc57 100644 --- a/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1,4 +1,4 @@ -package main +package gui import "github.com/jesseduffield/gocui" @@ -12,73 +12,75 @@ type Binding struct { Modifier gocui.Modifier } -func keybindings(g *gocui.Gui) error { +func (gui *Gui) keybindings(g *gocui.Gui) error { bindings := []Binding{ - {ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit}, - {ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit}, - {ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain}, - {ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain}, - {ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles}, - {ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: pullFiles}, - {ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: handleRefresh}, - {ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: handleCommitPress}, - {ViewName: "files", Key: 'C', Modifier: gocui.ModNone, Handler: handleCommitEditorPress}, - {ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress}, - {ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove}, - {ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge}, - {ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: handleFileEdit}, - {ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen}, - {ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen}, - {ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen}, - {ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: handleIgnoreFile}, - {ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles}, - {ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave}, - {ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge}, - {ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: handleAddPatch}, - {ViewName: "files", Key: 'D', Modifier: gocui.ModNone, Handler: handleResetHard}, - {ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge}, - {ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk}, - {ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks}, - {ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict}, - {ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict}, - {ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop}, - {ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom}, - {ViewName: "main", Key: 'h', Modifier: gocui.ModNone, Handler: handleSelectPrevConflict}, - {ViewName: "main", Key: 'l', Modifier: gocui.ModNone, Handler: handleSelectNextConflict}, - {ViewName: "main", Key: 'k', Modifier: gocui.ModNone, Handler: handleSelectTop}, - {ViewName: "main", Key: 'j', Modifier: gocui.ModNone, Handler: handleSelectBottom}, - {ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot}, - {ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress}, - {ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName}, - {ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: handleForceCheckout}, - {ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: handleNewBranch}, - {ViewName: "branches", Key: 'd', Modifier: gocui.ModNone, Handler: handleDeleteBranch}, - {ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: handleMerge}, - {ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: handleCommitSquashDown}, - {ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit}, - {ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit}, - {ViewName: "commits", Key: 'f', Modifier: gocui.ModNone, Handler: handleCommitFixup}, - {ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply}, - {ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop}, - {ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop}, - {ViewName: "commitMessage", Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: handleCommitConfirm}, - {ViewName: "commitMessage", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleCommitClose}, - {ViewName: "commitMessage", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: handleNewlineCommitMessage}, + {ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: gui.quit}, + {ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: gui.quit}, + {ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: gui.scrollUpMain}, + {ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: gui.scrollDownMain}, + {ViewName: "", Key: gocui.KeyCtrlU, Modifier: gocui.ModNone, Handler: gui.scrollUpMain}, + {ViewName: "", Key: gocui.KeyCtrlD, Modifier: gocui.ModNone, Handler: gui.scrollDownMain}, + {ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: gui.pushFiles}, + {ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: gui.pullFiles}, + {ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: gui.handleRefresh}, + {ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: gui.handleCommitPress}, + {ViewName: "files", Key: 'C', Modifier: gocui.ModNone, Handler: gui.handleCommitEditorPress}, + {ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleFilePress}, + {ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleFileRemove}, + {ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleSwitchToMerge}, + {ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: gui.handleFileEdit}, + {ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: gui.handleFileOpen}, + {ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleSublimeFileOpen}, + {ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: gui.handleVsCodeFileOpen}, + {ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: gui.handleIgnoreFile}, + {ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRefreshFiles}, + {ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: gui.handleStashSave}, + {ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: gui.handleAbortMerge}, + {ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: gui.handleAddPatch}, + {ViewName: "files", Key: 'D', Modifier: gocui.ModNone, Handler: gui.handleResetHard}, + {ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.handleEscapeMerge}, + {ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handlePickHunk}, + {ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: gui.handlePickBothHunks}, + {ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.handleSelectPrevConflict}, + {ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.handleSelectNextConflict}, + {ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleSelectTop}, + {ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleSelectBottom}, + {ViewName: "main", Key: 'h', Modifier: gocui.ModNone, Handler: gui.handleSelectPrevConflict}, + {ViewName: "main", Key: 'l', Modifier: gocui.ModNone, Handler: gui.handleSelectNextConflict}, + {ViewName: "main", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleSelectTop}, + {ViewName: "main", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleSelectBottom}, + {ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: gui.handlePopFileSnapshot}, + {ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleBranchPress}, + {ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: gui.handleCheckoutByName}, + {ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: gui.handleForceCheckout}, + {ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: gui.handleNewBranch}, + {ViewName: "branches", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleDeleteBranch}, + {ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleMerge}, + {ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleCommitSquashDown}, + {ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRenameCommit}, + {ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: gui.handleResetToCommit}, + {ViewName: "commits", Key: 'f', Modifier: gocui.ModNone, Handler: gui.handleCommitFixup}, + {ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleStashApply}, + {ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: gui.handleStashPop}, + {ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleStashDrop}, + {ViewName: "commitMessage", Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: gui.handleCommitConfirm}, + {ViewName: "commitMessage", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.handleCommitClose}, + {ViewName: "commitMessage", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.handleNewlineCommitMessage}, } // Would make these keybindings global but that interferes with editing // input in the confirmation panel for _, viewName := range []string{"files", "branches", "commits", "stash"} { bindings = append(bindings, []Binding{ - {ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView}, - {ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView}, - {ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView}, - {ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp}, - {ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown}, - {ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: previousView}, - {ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: nextView}, - {ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: cursorUp}, - {ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: cursorDown}, + {ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView}, + {ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView}, + {ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView}, + {ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.cursorUp}, + {ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.cursorDown}, + {ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView}, + {ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: gui.nextView}, + {ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: gui.cursorUp}, + {ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: gui.cursorDown}, }...) } diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go new file mode 100644 index 000000000..81e37f593 --- /dev/null +++ b/pkg/gui/merge_panel.go @@ -0,0 +1,260 @@ +// though this panel is called the merge panel, it's really going to use the main panel. This may change in the future + +package gui + +import ( + "bufio" + "bytes" + "io/ioutil" + "math" + "os" + "strings" + + "github.com/fatih/color" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func (gui *Gui) findConflicts(content string) ([]commands.Conflict, error) { + conflicts := make([]commands.Conflict, 0) + var newConflict commands.Conflict + for i, line := range utils.SplitLines(content) { + if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" { + newConflict = commands.Conflict{Start: i} + } else if line == "=======" { + newConflict.Middle = i + } else if strings.HasPrefix(line, ">>>>>>> ") { + newConflict.End = i + conflicts = append(conflicts, newConflict) + } + } + return conflicts, nil +} + +func (gui *Gui) shiftConflict(conflicts []commands.Conflict) (commands.Conflict, []commands.Conflict) { + return conflicts[0], conflicts[1:] +} + +func (gui *Gui) shouldHighlightLine(index int, conflict commands.Conflict, top bool) bool { + return (index >= conflict.Start && index <= conflict.Middle && top) || (index >= conflict.Middle && index <= conflict.End && !top) +} + +func (gui *Gui) coloredConflictFile(content string, conflicts []commands.Conflict, conflictIndex int, conflictTop, hasFocus bool) (string, error) { + if len(conflicts) == 0 { + return content, nil + } + conflict, remainingConflicts := gui.shiftConflict(conflicts) + var outputBuffer bytes.Buffer + for i, line := range utils.SplitLines(content) { + colourAttr := color.FgWhite + if i == conflict.Start || i == conflict.Middle || i == conflict.End { + colourAttr = color.FgRed + } + colour := color.New(colourAttr) + if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && gui.shouldHighlightLine(i, conflict, conflictTop) { + colour.Add(color.Bold) + } + if i == conflict.End && len(remainingConflicts) > 0 { + conflict, remainingConflicts = gui.shiftConflict(remainingConflicts) + } + outputBuffer.WriteString(utils.ColoredStringDirect(line, colour) + "\n") + } + return outputBuffer.String(), nil +} + +func (gui *Gui) handleSelectTop(g *gocui.Gui, v *gocui.View) error { + gui.State.ConflictTop = true + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handleSelectBottom(g *gocui.Gui, v *gocui.View) error { + gui.State.ConflictTop = false + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error { + if gui.State.ConflictIndex >= len(gui.State.Conflicts)-1 { + return nil + } + gui.State.ConflictIndex++ + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error { + if gui.State.ConflictIndex <= 0 { + return nil + } + gui.State.ConflictIndex-- + return gui.refreshMergePanel(g) +} + +func (gui *Gui) isIndexToDelete(i int, conflict commands.Conflict, pick string) bool { + return i == conflict.Middle || + i == conflict.Start || + i == conflict.End || + pick != "both" && + (pick == "bottom" && i > conflict.Start && i < conflict.Middle) || + (pick == "top" && i > conflict.Middle && i < conflict.End) +} + +func (gui *Gui) resolveConflict(g *gocui.Gui, conflict commands.Conflict, pick string) error { + gitFile, err := gui.getSelectedFile(g) + if err != nil { + return err + } + file, err := os.Open(gitFile.Name) + if err != nil { + return err + } + defer file.Close() + + reader := bufio.NewReader(file) + output := "" + for i := 0; true; i++ { + line, err := reader.ReadString('\n') + if err != nil { + break + } + if !gui.isIndexToDelete(i, conflict, pick) { + output += line + } + } + gui.Log.Info(output) + return ioutil.WriteFile(gitFile.Name, []byte(output), 0644) +} + +func (gui *Gui) pushFileSnapshot(g *gocui.Gui) error { + gitFile, err := gui.getSelectedFile(g) + if err != nil { + return err + } + content, err := gui.GitCommand.CatFile(gitFile.Name) + if err != nil { + return err + } + gui.State.EditHistory.Push(content) + return nil +} + +func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error { + if gui.State.EditHistory.Len() == 0 { + return nil + } + prevContent := gui.State.EditHistory.Pop().(string) + gitFile, err := gui.getSelectedFile(g) + if err != nil { + return err + } + ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644) + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error { + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + gui.pushFileSnapshot(g) + pick := "bottom" + if gui.State.ConflictTop { + pick = "top" + } + err := gui.resolveConflict(g, conflict, pick) + if err != nil { + panic(err) + } + gui.refreshMergePanel(g) + return nil +} + +func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error { + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + gui.pushFileSnapshot(g) + err := gui.resolveConflict(g, conflict, "both") + if err != nil { + panic(err) + } + return gui.refreshMergePanel(g) +} + +func (gui *Gui) refreshMergePanel(g *gocui.Gui) error { + cat, err := gui.catSelectedFile(g) + if err != nil { + return err + } + gui.State.Conflicts, err = gui.findConflicts(cat) + if err != nil { + return err + } + + if len(gui.State.Conflicts) == 0 { + return gui.handleCompleteMerge(g) + } else if gui.State.ConflictIndex > len(gui.State.Conflicts)-1 { + gui.State.ConflictIndex = len(gui.State.Conflicts) - 1 + } + hasFocus := gui.currentViewName(g) == "main" + if hasFocus { + gui.renderMergeOptions(g) + } + content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus) + if err != nil { + return err + } + if err := gui.scrollToConflict(g); err != nil { + return err + } + return gui.renderString(g, "main", content) +} + +func (gui *Gui) scrollToConflict(g *gocui.Gui) error { + mainView, err := g.View("main") + if err != nil { + return err + } + if len(gui.State.Conflicts) == 0 { + return nil + } + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + ox, _ := mainView.Origin() + _, height := mainView.Size() + conflictMiddle := (conflict.End + conflict.Start) / 2 + newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2)))) + return mainView.SetOrigin(ox, newOriginY) +} + +func (gui *Gui) switchToMerging(g *gocui.Gui) error { + gui.State.ConflictIndex = 0 + gui.State.ConflictTop = true + _, err := g.SetCurrentView("main") + if err != nil { + return err + } + return gui.refreshMergePanel(g) +} + +func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { + return gui.renderOptionsMap(g, map[string]string{ + "↑ ↓": "select hunk", + "← →": "navigate conflicts", + "space": "pick hunk", + "b": "pick both hunks", + "z": "undo", + }) +} + +func (gui *Gui) handleEscapeMerge(g *gocui.Gui, v *gocui.View) error { + filesView, err := g.View("files") + if err != nil { + return err + } + gui.refreshFiles(g) + return gui.switchFocus(g, v, filesView) +} + +func (gui *Gui) handleCompleteMerge(g *gocui.Gui) error { + filesView, err := g.View("files") + if err != nil { + return err + } + gui.stageSelectedFile(g) + gui.refreshFiles(g) + return gui.switchFocus(g, nil, filesView) +} diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go new file mode 100644 index 000000000..b4fb11902 --- /dev/null +++ b/pkg/gui/stash_panel.go @@ -0,0 +1,97 @@ +package gui + +import ( + "fmt" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" +) + +func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { + g.Update(func(g *gocui.Gui) error { + v, err := g.View("stash") + if err != nil { + panic(err) + } + gui.State.StashEntries = gui.GitCommand.GetStashEntries() + v.Clear() + for _, stashEntry := range gui.State.StashEntries { + fmt.Fprintln(v, stashEntry.DisplayString) + } + return gui.resetOrigin(v) + }) + return nil +} + +func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { + if len(gui.State.StashEntries) == 0 { + return nil + } + lineNumber := gui.getItemPosition(v) + return &gui.State.StashEntries[lineNumber] +} + +func (gui *Gui) renderStashOptions(g *gocui.Gui) error { + return gui.renderOptionsMap(g, map[string]string{ + "space": "apply", + "g": "pop", + "d": "drop", + "← → ↑ ↓": "navigate", + }) +} + +func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { + if err := gui.renderStashOptions(g); err != nil { + return err + } + go func() { + stashEntry := gui.getSelectedStashEntry(v) + if stashEntry == nil { + gui.renderString(g, "main", "No stash entries") + return + } + diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index) + gui.renderString(g, "main", diff) + }() + return nil +} + +func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error { + return gui.stashDo(g, v, "apply") +} + +func (gui *Gui) handleStashPop(g *gocui.Gui, v *gocui.View) error { + return gui.stashDo(g, v, "pop") +} + +func (gui *Gui) handleStashDrop(g *gocui.Gui, v *gocui.View) error { + return gui.createConfirmationPanel(g, v, "Stash drop", "Are you sure you want to drop this stash entry?", func(g *gocui.Gui, v *gocui.View) error { + return gui.stashDo(g, v, "drop") + }, nil) +} + +func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { + stashEntry := gui.getSelectedStashEntry(v) + if stashEntry == nil { + return gui.createErrorPanel(g, "No stash to "+method) + } + if err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil { + gui.createErrorPanel(g, err.Error()) + } + gui.refreshStashEntries(g) + return gui.refreshFiles(g) +} + +func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error { + if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 { + return gui.createErrorPanel(g, "You have no tracked/staged files to stash") + } + gui.createPromptPanel(g, filesView, "Stash changes", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.StashSave(gui.trimmedContent(v)); err != nil { + gui.createErrorPanel(g, err.Error()) + } + gui.refreshStashEntries(g) + return gui.refreshFiles(g) + }) + return nil +} diff --git a/status_panel.go b/pkg/gui/status_panel.go similarity index 54% rename from status_panel.go rename to pkg/gui/status_panel.go index f3fcb8078..67f133738 100644 --- a/status_panel.go +++ b/pkg/gui/status_panel.go @@ -1,13 +1,14 @@ -package main +package gui import ( "fmt" "github.com/fatih/color" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func refreshStatus(g *gocui.Gui) error { +func (gui *Gui) refreshStatus(g *gocui.Gui) error { v, err := g.View("status") if err != nil { panic(err) @@ -17,22 +18,22 @@ func refreshStatus(g *gocui.Gui) error { // contents end up cleared g.Update(func(*gocui.Gui) error { v.Clear() - pushables, pullables := gitUpstreamDifferenceCount() + pushables, pullables := gui.GitCommand.UpstreamDifferenceCount() fmt.Fprint(v, "↑"+pushables+"↓"+pullables) - branches := state.Branches - if err := updateHasMergeConflictStatus(); err != nil { + branches := gui.State.Branches + if err := gui.updateHasMergeConflictStatus(); err != nil { return err } - if state.HasMergeConflicts { - fmt.Fprint(v, coloredString(" (merging)", color.FgYellow)) + if gui.State.HasMergeConflicts { + fmt.Fprint(v, utils.ColoredString(" (merging)", color.FgYellow)) } if len(branches) == 0 { return nil } branch := branches[0] - name := coloredString(branch.Name, branch.getColor()) - repo := getCurrentProject() + name := utils.ColoredString(branch.Name, branch.GetColor()) + repo := utils.GetCurrentRepoName() fmt.Fprint(v, " "+repo+" → "+name) return nil }) diff --git a/view_helpers.go b/pkg/gui/view_helpers.go similarity index 60% rename from view_helpers.go rename to pkg/gui/view_helpers.go index b28e84efb..331e27975 100644 --- a/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -1,4 +1,4 @@ -package main +package gui import ( "fmt" @@ -11,14 +11,14 @@ import ( var cyclableViews = []string{"files", "branches", "commits", "stash"} -func refreshSidePanels(g *gocui.Gui) error { - refreshBranches(g) - refreshFiles(g) - refreshCommits(g) +func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { + gui.refreshBranches(g) + gui.refreshFiles(g) + gui.refreshCommits(g) return nil } -func nextView(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { var focusedViewName string if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] { focusedViewName = cyclableViews[0] @@ -29,7 +29,7 @@ func nextView(g *gocui.Gui, v *gocui.View) error { break } if i == len(cyclableViews)-1 { - devLog(v.Name() + " is not in the list of views") + gui.Log.Info(v.Name() + " is not in the list of views") return nil } } @@ -38,10 +38,10 @@ func nextView(g *gocui.Gui, v *gocui.View) error { if err != nil { panic(err) } - return switchFocus(g, v, focusedView) + return gui.switchFocus(g, v, focusedView) } -func previousView(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { var focusedViewName string if v == nil || v.Name() == cyclableViews[0] { focusedViewName = cyclableViews[len(cyclableViews)-1] @@ -52,7 +52,7 @@ func previousView(g *gocui.Gui, v *gocui.View) error { break } if i == len(cyclableViews)-1 { - devLog(v.Name() + " is not in the list of views") + gui.Log.Info(v.Name() + " is not in the list of views") return nil } } @@ -61,69 +61,70 @@ func previousView(g *gocui.Gui, v *gocui.View) error { if err != nil { panic(err) } - return switchFocus(g, v, focusedView) + return gui.switchFocus(g, v, focusedView) } -func newLineFocused(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") mainView.SetOrigin(0, 0) switch v.Name() { case "files": - return handleFileSelect(g, v) + return gui.handleFileSelect(g, v) case "branches": - return handleBranchSelect(g, v) + return gui.handleBranchSelect(g, v) case "confirmation": return nil case "commitMessage": - return handleCommitFocused(g, v) + return gui.handleCommitFocused(g, v) case "main": // TODO: pull this out into a 'view focused' function - refreshMergePanel(g) + gui.refreshMergePanel(g) v.Highlight = false return nil case "commits": - return handleCommitSelect(g, v) + return gui.handleCommitSelect(g, v) case "stash": - return handleStashEntrySelect(g, v) + return gui.handleStashEntrySelect(g, v) default: panic("No view matching newLineFocused switch statement") } } -func returnFocus(g *gocui.Gui, v *gocui.View) error { - previousView, err := g.View(state.PreviousView) +func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error { + previousView, err := g.View(gui.State.PreviousView) if err != nil { panic(err) } - return switchFocus(g, v, previousView) + return gui.switchFocus(g, v, previousView) } // pass in oldView = nil if you don't want to be able to return to your old view -func switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { +func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { // we assume we'll never want to return focus to a confirmation panel i.e. // we should never stack confirmation panels if oldView != nil && oldView.Name() != "confirmation" { oldView.Highlight = false - devLog("setting previous view to:", oldView.Name()) - state.PreviousView = oldView.Name() + gui.Log.Info("setting previous view to:", oldView.Name()) + gui.State.PreviousView = oldView.Name() } newView.Highlight = true - devLog("new focused view is " + newView.Name()) + gui.Log.Info("new focused view is " + newView.Name()) if _, err := g.SetCurrentView(newView.Name()); err != nil { return err } g.Cursor = newView.Editable - return newLineFocused(g, newView) + return gui.newLineFocused(g, newView) } -func getItemPosition(v *gocui.View) int { +func (gui *Gui) getItemPosition(v *gocui.View) int { + gui.correctCursor(v) _, cy := v.Cursor() _, oy := v.Origin() return oy + cy } -func cursorUp(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error { // swallowing cursor movements in main // TODO: pull this out if v == nil || v.Name() == "main" { @@ -138,11 +139,11 @@ func cursorUp(g *gocui.Gui, v *gocui.View) error { } } - newLineFocused(g, v) + gui.newLineFocused(g, v) return nil } -func cursorDown(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error { // swallowing cursor movements in main // TODO: pull this out if v == nil || v.Name() == "main" { @@ -159,19 +160,19 @@ func cursorDown(g *gocui.Gui, v *gocui.View) error { } } - newLineFocused(g, v) + gui.newLineFocused(g, v) return nil } -func resetOrigin(v *gocui.View) error { +func (gui *Gui) resetOrigin(v *gocui.View) error { if err := v.SetCursor(0, 0); err != nil { return err } return v.SetOrigin(0, 0) } -// if the cursor down past the last item, move it up one -func correctCursor(v *gocui.View) error { +// if the cursor down past the last item, move it to the last line +func (gui *Gui) correctCursor(v *gocui.View) error { cx, cy := v.Cursor() _, oy := v.Origin() lineCount := len(v.BufferLines()) - 2 @@ -181,7 +182,7 @@ func correctCursor(v *gocui.View) error { return nil } -func renderString(g *gocui.Gui, viewName, s string) error { +func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error { g.Update(func(*gocui.Gui) error { v, err := g.View(viewName) // just in case the view disappeared as this function was called, we'll @@ -197,7 +198,7 @@ func renderString(g *gocui.Gui, viewName, s string) error { return nil } -func optionsMapToString(optionsMap map[string]string) string { +func (gui *Gui) optionsMapToString(optionsMap map[string]string) string { optionsArray := make([]string, 0) for key, description := range optionsMap { optionsArray = append(optionsArray, key+": "+description) @@ -206,11 +207,11 @@ func optionsMapToString(optionsMap map[string]string) string { return strings.Join(optionsArray, ", ") } -func renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error { - return renderString(g, "options", optionsMapToString(optionsMap)) +func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error { + return gui.renderString(g, "options", gui.optionsMapToString(optionsMap)) } -func loader() string { +func (gui *Gui) loader() string { characters := "|/-\\" now := time.Now() nanos := now.UnixNano() @@ -219,17 +220,26 @@ func loader() string { } // TODO: refactor properly -func getFilesView(g *gocui.Gui) *gocui.View { +func (gui *Gui) getFilesView(g *gocui.Gui) *gocui.View { v, _ := g.View("files") return v } -func getCommitsView(g *gocui.Gui) *gocui.View { +func (gui *Gui) getCommitsView(g *gocui.Gui) *gocui.View { v, _ := g.View("commits") return v } -func getCommitMessageView(g *gocui.Gui) *gocui.View { +func (gui *Gui) getCommitMessageView(g *gocui.Gui) *gocui.View { v, _ := g.View("commitMessage") return v } + +func (gui *Gui) trimmedContent(v *gocui.View) string { + return strings.TrimSpace(v.Buffer()) +} + +func (gui *Gui) currentViewName(g *gocui.Gui) string { + currentView := g.CurrentView() + return currentView.Name() +} diff --git a/i18n.go b/pkg/i18n/i18n.go similarity index 100% rename from i18n.go rename to pkg/i18n/i18n.go diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 000000000..68438246a --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,65 @@ +package utils + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "github.com/fatih/color" +) + +// SplitLines takes a multiline string and splits it on newlines +// currently we are also stripping \r's which may have adverse effects for +// windows users (but no issues have been raised yet) +func SplitLines(multilineString string) []string { + multilineString = strings.Replace(multilineString, "\r", "", -1) + if multilineString == "" || multilineString == "\n" { + return make([]string, 0) + } + lines := strings.Split(multilineString, "\n") + if lines[len(lines)-1] == "" { + return lines[:len(lines)-1] + } + return lines +} + +// WithPadding pads a string as much as you want +func WithPadding(str string, padding int) string { + if padding-len(str) < 0 { + return str + } + return str + strings.Repeat(" ", padding-len(str)) +} + +// ColoredString takes a string and a colour attribute and returns a colored +// string with that attribute +func ColoredString(str string, colorAttribute color.Attribute) string { + colour := color.New(colorAttribute) + return ColoredStringDirect(str, colour) +} + +// ColoredStringDirect used for aggregating a few color attributes rather than +// just sending a single one +func ColoredStringDirect(str string, colour *color.Color) string { + return colour.SprintFunc()(fmt.Sprint(str)) +} + +// GetCurrentRepoName gets the repo's base name +func GetCurrentRepoName() string { + pwd, err := os.Getwd() + if err != nil { + log.Fatalln(err.Error()) + } + return filepath.Base(pwd) +} + +// TrimTrailingNewline - Trims the trailing newline +// TODO: replace with `chomp` after refactor +func TrimTrailingNewline(str string) string { + if strings.HasSuffix(str, "\n") { + return str[:len(str)-1] + } + return str +} diff --git a/stash_panel.go b/stash_panel.go deleted file mode 100644 index 33c7e297b..000000000 --- a/stash_panel.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/jesseduffield/gocui" -) - -func refreshStashEntries(g *gocui.Gui) error { - g.Update(func(g *gocui.Gui) error { - v, err := g.View("stash") - if err != nil { - panic(err) - } - state.StashEntries = getGitStashEntries() - v.Clear() - for _, stashEntry := range state.StashEntries { - fmt.Fprintln(v, stashEntry.DisplayString) - } - return resetOrigin(v) - }) - return nil -} - -func getSelectedStashEntry(v *gocui.View) *StashEntry { - if len(state.StashEntries) == 0 { - return nil - } - lineNumber := getItemPosition(v) - return &state.StashEntries[lineNumber] -} - -func renderStashOptions(g *gocui.Gui) error { - return renderOptionsMap(g, map[string]string{ - "space": "apply", - "g": "pop", - "d": "drop", - "← → ↑ ↓": "navigate", - }) -} - -func handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { - if err := renderStashOptions(g); err != nil { - return err - } - go func() { - stashEntry := getSelectedStashEntry(v) - if stashEntry == nil { - renderString(g, "main", "No stash entries") - return - } - diff, _ := getStashEntryDiff(stashEntry.Index) - renderString(g, "main", diff) - }() - return nil -} - -func handleStashApply(g *gocui.Gui, v *gocui.View) error { - return stashDo(g, v, "apply") -} - -func handleStashPop(g *gocui.Gui, v *gocui.View) error { - return stashDo(g, v, "pop") -} - -func handleStashDrop(g *gocui.Gui, v *gocui.View) error { - return createConfirmationPanel(g, v, "Stash drop", "Are you sure you want to drop this stash entry?", func(g *gocui.Gui, v *gocui.View) error { - return stashDo(g, v, "drop") - }, nil) -} - -func stashDo(g *gocui.Gui, v *gocui.View, method string) error { - stashEntry := getSelectedStashEntry(v) - if stashEntry == nil { - return createErrorPanel(g, "No stash to "+method) - } - if output, err := gitStashDo(stashEntry.Index, method); err != nil { - createErrorPanel(g, output) - } - refreshStashEntries(g) - return refreshFiles(g) -} - -func handleStashSave(g *gocui.Gui, filesView *gocui.View) error { - createPromptPanel(g, filesView, "Stash changes", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitStashSave(trimmedContent(v)); err != nil { - createErrorPanel(g, output) - } - refreshStashEntries(g) - return refreshFiles(g) - }) - return nil -} diff --git a/test/repos/gpg.sh b/test/repos/gpg.sh new file mode 100755 index 000000000..94e0742e5 --- /dev/null +++ b/test/repos/gpg.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -ex; rm -rf repo; mkdir repo; cd repo + +git init + +git config gpg.program $(which gpg) +git config user.signingkey E304229F # test key +git config commit.gpgsign true + +touch foo +git add foo + +touch bar +git add bar \ No newline at end of file diff --git a/test/repos/pre_commit_hook.sh b/test/repos/pre_commit_hook.sh index 8857f4145..1c24bf19f 100755 --- a/test/repos/pre_commit_hook.sh +++ b/test/repos/pre_commit_hook.sh @@ -2,7 +2,7 @@ set -ex; rm -rf repo; mkdir repo; cd repo git init -cp ../pre-commit .git/hooks/pre-commit +cp ../extras/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit echo "file" > file diff --git a/utils.go b/utils.go deleted file mode 100644 index e2de46233..000000000 --- a/utils.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "path/filepath" - "strings" - - "github.com/fatih/color" - "github.com/jesseduffield/gocui" -) - -func splitLines(multilineString string) []string { - multilineString = strings.Replace(multilineString, "\r", "", -1) - if multilineString == "" || multilineString == "\n" { - return make([]string, 0) - } - lines := strings.Split(multilineString, "\n") - if lines[len(lines)-1] == "" { - return lines[:len(lines)-1] - } - return lines -} - -func trimmedContent(v *gocui.View) string { - return strings.TrimSpace(v.Buffer()) -} - -func withPadding(str string, padding int) string { - if padding-len(str) < 0 { - return str - } - return str + strings.Repeat(" ", padding-len(str)) -} - -func coloredString(str string, colorAttribute color.Attribute) string { - colour := color.New(colorAttribute) - return coloredStringDirect(str, colour) -} - -// used for aggregating a few color attributes rather than just sending a single one -func coloredStringDirect(str string, colour *color.Color) string { - return colour.SprintFunc()(fmt.Sprint(str)) -} - -// used to get the project name -func getCurrentProject() string { - pwd, err := os.Getwd() - if err != nil { - log.Fatalln(err.Error()) - } - return filepath.Base(pwd) -} diff --git a/vendor/github.com/Sirupsen/logrus/LICENSE b/vendor/github.com/Sirupsen/logrus/LICENSE new file mode 100644 index 000000000..f090cb42f --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Sirupsen/logrus/alt_exit.go b/vendor/github.com/Sirupsen/logrus/alt_exit.go new file mode 100644 index 000000000..8af90637a --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/alt_exit.go @@ -0,0 +1,64 @@ +package logrus + +// The following code was sourced and modified from the +// https://github.com/tebeka/atexit package governed by the following license: +// +// Copyright (c) 2012 Miki Tebeka . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import ( + "fmt" + "os" +) + +var handlers = []func(){} + +func runHandler(handler func()) { + defer func() { + if err := recover(); err != nil { + fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err) + } + }() + + handler() +} + +func runHandlers() { + for _, handler := range handlers { + runHandler(handler) + } +} + +// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code) +func Exit(code int) { + runHandlers() + os.Exit(code) +} + +// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke +// all handlers. The handlers will also be invoked when any Fatal log entry is +// made. +// +// This method is useful when a caller wishes to use logrus to log a fatal +// message but also needs to gracefully shutdown. An example usecase could be +// closing database connections, or sending a alert that the application is +// closing. +func RegisterExitHandler(handler func()) { + handlers = append(handlers, handler) +} diff --git a/vendor/github.com/Sirupsen/logrus/doc.go b/vendor/github.com/Sirupsen/logrus/doc.go new file mode 100644 index 000000000..da67aba06 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/doc.go @@ -0,0 +1,26 @@ +/* +Package logrus is a structured logger for Go, completely API compatible with the standard library logger. + + +The simplest way to use Logrus is simply the package-level exported logger: + + package main + + import ( + log "github.com/sirupsen/logrus" + ) + + func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "number": 1, + "size": 10, + }).Info("A walrus appears") + } + +Output: + time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10 + +For a full guide visit https://github.com/sirupsen/logrus +*/ +package logrus diff --git a/vendor/github.com/Sirupsen/logrus/entry.go b/vendor/github.com/Sirupsen/logrus/entry.go new file mode 100644 index 000000000..473bd1a0d --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/entry.go @@ -0,0 +1,300 @@ +package logrus + +import ( + "bytes" + "fmt" + "os" + "sync" + "time" +) + +var bufferPool *sync.Pool + +func init() { + bufferPool = &sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, + } +} + +// Defines the key when adding errors using WithError. +var ErrorKey = "error" + +// An entry is the final or intermediate Logrus logging entry. It contains all +// the fields passed with WithField{,s}. It's finally logged when Debug, Info, +// Warn, Error, Fatal or Panic is called on it. These objects can be reused and +// passed around as much as you wish to avoid field duplication. +type Entry struct { + Logger *Logger + + // Contains all the fields set by the user. + Data Fields + + // Time at which the log entry was created + Time time.Time + + // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic + // This field will be set on entry firing and the value will be equal to the one in Logger struct field. + Level Level + + // Message passed to Debug, Info, Warn, Error, Fatal or Panic + Message string + + // When formatter is called in entry.log(), an Buffer may be set to entry + Buffer *bytes.Buffer +} + +func NewEntry(logger *Logger) *Entry { + return &Entry{ + Logger: logger, + // Default is five fields, give a little extra room + Data: make(Fields, 5), + } +} + +// Returns the string representation from the reader and ultimately the +// formatter. +func (entry *Entry) String() (string, error) { + serialized, err := entry.Logger.Formatter.Format(entry) + if err != nil { + return "", err + } + str := string(serialized) + return str, nil +} + +// Add an error as single field (using the key defined in ErrorKey) to the Entry. +func (entry *Entry) WithError(err error) *Entry { + return entry.WithField(ErrorKey, err) +} + +// Add a single field to the Entry. +func (entry *Entry) WithField(key string, value interface{}) *Entry { + return entry.WithFields(Fields{key: value}) +} + +// Add a map of fields to the Entry. +func (entry *Entry) WithFields(fields Fields) *Entry { + data := make(Fields, len(entry.Data)+len(fields)) + for k, v := range entry.Data { + data[k] = v + } + for k, v := range fields { + data[k] = v + } + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time} +} + +// Overrides the time of the Entry. +func (entry *Entry) WithTime(t time.Time) *Entry { + return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t} +} + +// This function is not declared with a pointer value because otherwise +// race conditions will occur when using multiple goroutines +func (entry Entry) log(level Level, msg string) { + var buffer *bytes.Buffer + + // Default to now, but allow users to override if they want. + // + // We don't have to worry about polluting future calls to Entry#log() + // with this assignment because this function is declared with a + // non-pointer receiver. + if entry.Time.IsZero() { + entry.Time = time.Now() + } + + entry.Level = level + entry.Message = msg + + entry.fireHooks() + + buffer = bufferPool.Get().(*bytes.Buffer) + buffer.Reset() + defer bufferPool.Put(buffer) + entry.Buffer = buffer + + entry.write() + + entry.Buffer = nil + + // To avoid Entry#log() returning a value that only would make sense for + // panic() to use in Entry#Panic(), we avoid the allocation by checking + // directly here. + if level <= PanicLevel { + panic(&entry) + } +} + +func (entry *Entry) fireHooks() { + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + err := entry.Logger.Hooks.Fire(entry.Level, entry) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) + } +} + +func (entry *Entry) write() { + serialized, err := entry.Logger.Formatter.Format(entry) + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) + } else { + _, err = entry.Logger.Out.Write(serialized) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) + } + } +} + +func (entry *Entry) Debug(args ...interface{}) { + if entry.Logger.level() >= DebugLevel { + entry.log(DebugLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Print(args ...interface{}) { + entry.Info(args...) +} + +func (entry *Entry) Info(args ...interface{}) { + if entry.Logger.level() >= InfoLevel { + entry.log(InfoLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Warn(args ...interface{}) { + if entry.Logger.level() >= WarnLevel { + entry.log(WarnLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Warning(args ...interface{}) { + entry.Warn(args...) +} + +func (entry *Entry) Error(args ...interface{}) { + if entry.Logger.level() >= ErrorLevel { + entry.log(ErrorLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Fatal(args ...interface{}) { + if entry.Logger.level() >= FatalLevel { + entry.log(FatalLevel, fmt.Sprint(args...)) + } + Exit(1) +} + +func (entry *Entry) Panic(args ...interface{}) { + if entry.Logger.level() >= PanicLevel { + entry.log(PanicLevel, fmt.Sprint(args...)) + } + panic(fmt.Sprint(args...)) +} + +// Entry Printf family functions + +func (entry *Entry) Debugf(format string, args ...interface{}) { + if entry.Logger.level() >= DebugLevel { + entry.Debug(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Infof(format string, args ...interface{}) { + if entry.Logger.level() >= InfoLevel { + entry.Info(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Printf(format string, args ...interface{}) { + entry.Infof(format, args...) +} + +func (entry *Entry) Warnf(format string, args ...interface{}) { + if entry.Logger.level() >= WarnLevel { + entry.Warn(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Warningf(format string, args ...interface{}) { + entry.Warnf(format, args...) +} + +func (entry *Entry) Errorf(format string, args ...interface{}) { + if entry.Logger.level() >= ErrorLevel { + entry.Error(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Fatalf(format string, args ...interface{}) { + if entry.Logger.level() >= FatalLevel { + entry.Fatal(fmt.Sprintf(format, args...)) + } + Exit(1) +} + +func (entry *Entry) Panicf(format string, args ...interface{}) { + if entry.Logger.level() >= PanicLevel { + entry.Panic(fmt.Sprintf(format, args...)) + } +} + +// Entry Println family functions + +func (entry *Entry) Debugln(args ...interface{}) { + if entry.Logger.level() >= DebugLevel { + entry.Debug(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Infoln(args ...interface{}) { + if entry.Logger.level() >= InfoLevel { + entry.Info(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Println(args ...interface{}) { + entry.Infoln(args...) +} + +func (entry *Entry) Warnln(args ...interface{}) { + if entry.Logger.level() >= WarnLevel { + entry.Warn(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Warningln(args ...interface{}) { + entry.Warnln(args...) +} + +func (entry *Entry) Errorln(args ...interface{}) { + if entry.Logger.level() >= ErrorLevel { + entry.Error(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Fatalln(args ...interface{}) { + if entry.Logger.level() >= FatalLevel { + entry.Fatal(entry.sprintlnn(args...)) + } + Exit(1) +} + +func (entry *Entry) Panicln(args ...interface{}) { + if entry.Logger.level() >= PanicLevel { + entry.Panic(entry.sprintlnn(args...)) + } +} + +// Sprintlnn => Sprint no newline. This is to get the behavior of how +// fmt.Sprintln where spaces are always added between operands, regardless of +// their type. Instead of vendoring the Sprintln implementation to spare a +// string allocation, we do the simplest thing. +func (entry *Entry) sprintlnn(args ...interface{}) string { + msg := fmt.Sprintln(args...) + return msg[:len(msg)-1] +} diff --git a/vendor/github.com/Sirupsen/logrus/exported.go b/vendor/github.com/Sirupsen/logrus/exported.go new file mode 100644 index 000000000..eb612a6f3 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/exported.go @@ -0,0 +1,201 @@ +package logrus + +import ( + "io" + "time" +) + +var ( + // std is the name of the standard logger in stdlib `log` + std = New() +) + +func StandardLogger() *Logger { + return std +} + +// SetOutput sets the standard logger output. +func SetOutput(out io.Writer) { + std.SetOutput(out) +} + +// SetFormatter sets the standard logger formatter. +func SetFormatter(formatter Formatter) { + std.mu.Lock() + defer std.mu.Unlock() + std.Formatter = formatter +} + +// SetLevel sets the standard logger level. +func SetLevel(level Level) { + std.mu.Lock() + defer std.mu.Unlock() + std.SetLevel(level) +} + +// GetLevel returns the standard logger level. +func GetLevel() Level { + std.mu.Lock() + defer std.mu.Unlock() + return std.level() +} + +// AddHook adds a hook to the standard logger hooks. +func AddHook(hook Hook) { + std.mu.Lock() + defer std.mu.Unlock() + std.Hooks.Add(hook) +} + +// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key. +func WithError(err error) *Entry { + return std.WithField(ErrorKey, err) +} + +// WithField creates an entry from the standard logger and adds a field to +// it. If you want multiple fields, use `WithFields`. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithField(key string, value interface{}) *Entry { + return std.WithField(key, value) +} + +// WithFields creates an entry from the standard logger and adds multiple +// fields to it. This is simply a helper for `WithField`, invoking it +// once for each field. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithFields(fields Fields) *Entry { + return std.WithFields(fields) +} + +// WithTime creats an entry from the standard logger and overrides the time of +// logs generated with it. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithTime(t time.Time) *Entry { + return std.WithTime(t) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(args ...interface{}) { + std.Debug(args...) +} + +// Print logs a message at level Info on the standard logger. +func Print(args ...interface{}) { + std.Print(args...) +} + +// Info logs a message at level Info on the standard logger. +func Info(args ...interface{}) { + std.Info(args...) +} + +// Warn logs a message at level Warn on the standard logger. +func Warn(args ...interface{}) { + std.Warn(args...) +} + +// Warning logs a message at level Warn on the standard logger. +func Warning(args ...interface{}) { + std.Warning(args...) +} + +// Error logs a message at level Error on the standard logger. +func Error(args ...interface{}) { + std.Error(args...) +} + +// Panic logs a message at level Panic on the standard logger. +func Panic(args ...interface{}) { + std.Panic(args...) +} + +// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatal(args ...interface{}) { + std.Fatal(args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, args ...interface{}) { + std.Debugf(format, args...) +} + +// Printf logs a message at level Info on the standard logger. +func Printf(format string, args ...interface{}) { + std.Printf(format, args...) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, args ...interface{}) { + std.Infof(format, args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func Warnf(format string, args ...interface{}) { + std.Warnf(format, args...) +} + +// Warningf logs a message at level Warn on the standard logger. +func Warningf(format string, args ...interface{}) { + std.Warningf(format, args...) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, args ...interface{}) { + std.Errorf(format, args...) +} + +// Panicf logs a message at level Panic on the standard logger. +func Panicf(format string, args ...interface{}) { + std.Panicf(format, args...) +} + +// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatalf(format string, args ...interface{}) { + std.Fatalf(format, args...) +} + +// Debugln logs a message at level Debug on the standard logger. +func Debugln(args ...interface{}) { + std.Debugln(args...) +} + +// Println logs a message at level Info on the standard logger. +func Println(args ...interface{}) { + std.Println(args...) +} + +// Infoln logs a message at level Info on the standard logger. +func Infoln(args ...interface{}) { + std.Infoln(args...) +} + +// Warnln logs a message at level Warn on the standard logger. +func Warnln(args ...interface{}) { + std.Warnln(args...) +} + +// Warningln logs a message at level Warn on the standard logger. +func Warningln(args ...interface{}) { + std.Warningln(args...) +} + +// Errorln logs a message at level Error on the standard logger. +func Errorln(args ...interface{}) { + std.Errorln(args...) +} + +// Panicln logs a message at level Panic on the standard logger. +func Panicln(args ...interface{}) { + std.Panicln(args...) +} + +// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatalln(args ...interface{}) { + std.Fatalln(args...) +} diff --git a/vendor/github.com/Sirupsen/logrus/formatter.go b/vendor/github.com/Sirupsen/logrus/formatter.go new file mode 100644 index 000000000..83c74947b --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/formatter.go @@ -0,0 +1,51 @@ +package logrus + +import "time" + +const defaultTimestampFormat = time.RFC3339 + +// The Formatter interface is used to implement a custom Formatter. It takes an +// `Entry`. It exposes all the fields, including the default ones: +// +// * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. +// * `entry.Data["time"]`. The timestamp. +// * `entry.Data["level"]. The level the entry was logged at. +// +// Any additional fields added with `WithField` or `WithFields` are also in +// `entry.Data`. Format is expected to return an array of bytes which are then +// logged to `logger.Out`. +type Formatter interface { + Format(*Entry) ([]byte, error) +} + +// This is to not silently overwrite `time`, `msg` and `level` fields when +// dumping it. If this code wasn't there doing: +// +// logrus.WithField("level", 1).Info("hello") +// +// Would just silently drop the user provided level. Instead with this code +// it'll logged as: +// +// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} +// +// It's not exported because it's still using Data in an opinionated way. It's to +// avoid code duplication between the two default formatters. +func prefixFieldClashes(data Fields, fieldMap FieldMap) { + timeKey := fieldMap.resolve(FieldKeyTime) + if t, ok := data[timeKey]; ok { + data["fields."+timeKey] = t + delete(data, timeKey) + } + + msgKey := fieldMap.resolve(FieldKeyMsg) + if m, ok := data[msgKey]; ok { + data["fields."+msgKey] = m + delete(data, msgKey) + } + + levelKey := fieldMap.resolve(FieldKeyLevel) + if l, ok := data[levelKey]; ok { + data["fields."+levelKey] = l + delete(data, levelKey) + } +} diff --git a/vendor/github.com/Sirupsen/logrus/hooks.go b/vendor/github.com/Sirupsen/logrus/hooks.go new file mode 100644 index 000000000..3f151cdc3 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/hooks.go @@ -0,0 +1,34 @@ +package logrus + +// A hook to be fired when logging on the logging levels returned from +// `Levels()` on your implementation of the interface. Note that this is not +// fired in a goroutine or a channel with workers, you should handle such +// functionality yourself if your call is non-blocking and you don't wish for +// the logging calls for levels returned from `Levels()` to block. +type Hook interface { + Levels() []Level + Fire(*Entry) error +} + +// Internal type for storing the hooks on a logger instance. +type LevelHooks map[Level][]Hook + +// Add a hook to an instance of logger. This is called with +// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. +func (hooks LevelHooks) Add(hook Hook) { + for _, level := range hook.Levels() { + hooks[level] = append(hooks[level], hook) + } +} + +// Fire all the hooks for the passed level. Used by `entry.log` to fire +// appropriate hooks for a log entry. +func (hooks LevelHooks) Fire(level Level, entry *Entry) error { + for _, hook := range hooks[level] { + if err := hook.Fire(entry); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/Sirupsen/logrus/json_formatter.go b/vendor/github.com/Sirupsen/logrus/json_formatter.go new file mode 100644 index 000000000..dab17610f --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/json_formatter.go @@ -0,0 +1,89 @@ +package logrus + +import ( + "encoding/json" + "fmt" +) + +type fieldKey string + +// FieldMap allows customization of the key names for default fields. +type FieldMap map[fieldKey]string + +// Default key names for the default fields +const ( + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" +) + +func (f FieldMap) resolve(key fieldKey) string { + if k, ok := f[key]; ok { + return k + } + + return string(key) +} + +// JSONFormatter formats logs into parsable json +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + TimestampFormat string + + // DisableTimestamp allows disabling automatic timestamps in output + DisableTimestamp bool + + // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key. + DataKey string + + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &JSONFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message", + // }, + // } + FieldMap FieldMap +} + +// Format renders a single log entry +func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + data := make(Fields, len(entry.Data)+3) + for k, v := range entry.Data { + switch v := v.(type) { + case error: + // Otherwise errors are ignored by `encoding/json` + // https://github.com/sirupsen/logrus/issues/137 + data[k] = v.Error() + default: + data[k] = v + } + } + + if f.DataKey != "" { + newData := make(Fields, 4) + newData[f.DataKey] = data + data = newData + } + + prefixFieldClashes(data, f.FieldMap) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = defaultTimestampFormat + } + + if !f.DisableTimestamp { + data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) + } + data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message + data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() + + serialized, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} diff --git a/vendor/github.com/Sirupsen/logrus/logger.go b/vendor/github.com/Sirupsen/logrus/logger.go new file mode 100644 index 000000000..342f7977d --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/logger.go @@ -0,0 +1,337 @@ +package logrus + +import ( + "io" + "os" + "sync" + "sync/atomic" + "time" +) + +type Logger struct { + // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a + // file, or leave it default which is `os.Stderr`. You can also set this to + // something more adventorous, such as logging to Kafka. + Out io.Writer + // Hooks for the logger instance. These allow firing events based on logging + // levels and log entries. For example, to send errors to an error tracking + // service, log to StatsD or dump the core on fatal errors. + Hooks LevelHooks + // All log entries pass through the formatter before logged to Out. The + // included formatters are `TextFormatter` and `JSONFormatter` for which + // TextFormatter is the default. In development (when a TTY is attached) it + // logs with colors, but to a file it wouldn't. You can easily implement your + // own that implements the `Formatter` interface, see the `README` or included + // formatters for examples. + Formatter Formatter + // The logging level the logger should log at. This is typically (and defaults + // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be + // logged. + Level Level + // Used to sync writing to the log. Locking is enabled by Default + mu MutexWrap + // Reusable empty entry + entryPool sync.Pool +} + +type MutexWrap struct { + lock sync.Mutex + disabled bool +} + +func (mw *MutexWrap) Lock() { + if !mw.disabled { + mw.lock.Lock() + } +} + +func (mw *MutexWrap) Unlock() { + if !mw.disabled { + mw.lock.Unlock() + } +} + +func (mw *MutexWrap) Disable() { + mw.disabled = true +} + +// Creates a new logger. Configuration should be set by changing `Formatter`, +// `Out` and `Hooks` directly on the default logger instance. You can also just +// instantiate your own: +// +// var log = &Logger{ +// Out: os.Stderr, +// Formatter: new(JSONFormatter), +// Hooks: make(LevelHooks), +// Level: logrus.DebugLevel, +// } +// +// It's recommended to make this a global instance called `log`. +func New() *Logger { + return &Logger{ + Out: os.Stderr, + Formatter: new(TextFormatter), + Hooks: make(LevelHooks), + Level: InfoLevel, + } +} + +func (logger *Logger) newEntry() *Entry { + entry, ok := logger.entryPool.Get().(*Entry) + if ok { + return entry + } + return NewEntry(logger) +} + +func (logger *Logger) releaseEntry(entry *Entry) { + logger.entryPool.Put(entry) +} + +// Adds a field to the log entry, note that it doesn't log until you call +// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry. +// If you want multiple fields, use `WithFields`. +func (logger *Logger) WithField(key string, value interface{}) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithField(key, value) +} + +// Adds a struct of fields to the log entry. All it does is call `WithField` for +// each `Field`. +func (logger *Logger) WithFields(fields Fields) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithFields(fields) +} + +// Add an error as single field to the log entry. All it does is call +// `WithError` for the given `error`. +func (logger *Logger) WithError(err error) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithError(err) +} + +// Overrides the time of the log entry. +func (logger *Logger) WithTime(t time.Time) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithTime(t) +} + +func (logger *Logger) Debugf(format string, args ...interface{}) { + if logger.level() >= DebugLevel { + entry := logger.newEntry() + entry.Debugf(format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Infof(format string, args ...interface{}) { + if logger.level() >= InfoLevel { + entry := logger.newEntry() + entry.Infof(format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Printf(format string, args ...interface{}) { + entry := logger.newEntry() + entry.Printf(format, args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warnf(format string, args ...interface{}) { + if logger.level() >= WarnLevel { + entry := logger.newEntry() + entry.Warnf(format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Warningf(format string, args ...interface{}) { + if logger.level() >= WarnLevel { + entry := logger.newEntry() + entry.Warnf(format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Errorf(format string, args ...interface{}) { + if logger.level() >= ErrorLevel { + entry := logger.newEntry() + entry.Errorf(format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Fatalf(format string, args ...interface{}) { + if logger.level() >= FatalLevel { + entry := logger.newEntry() + entry.Fatalf(format, args...) + logger.releaseEntry(entry) + } + Exit(1) +} + +func (logger *Logger) Panicf(format string, args ...interface{}) { + if logger.level() >= PanicLevel { + entry := logger.newEntry() + entry.Panicf(format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Debug(args ...interface{}) { + if logger.level() >= DebugLevel { + entry := logger.newEntry() + entry.Debug(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Info(args ...interface{}) { + if logger.level() >= InfoLevel { + entry := logger.newEntry() + entry.Info(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Print(args ...interface{}) { + entry := logger.newEntry() + entry.Info(args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warn(args ...interface{}) { + if logger.level() >= WarnLevel { + entry := logger.newEntry() + entry.Warn(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Warning(args ...interface{}) { + if logger.level() >= WarnLevel { + entry := logger.newEntry() + entry.Warn(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Error(args ...interface{}) { + if logger.level() >= ErrorLevel { + entry := logger.newEntry() + entry.Error(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Fatal(args ...interface{}) { + if logger.level() >= FatalLevel { + entry := logger.newEntry() + entry.Fatal(args...) + logger.releaseEntry(entry) + } + Exit(1) +} + +func (logger *Logger) Panic(args ...interface{}) { + if logger.level() >= PanicLevel { + entry := logger.newEntry() + entry.Panic(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Debugln(args ...interface{}) { + if logger.level() >= DebugLevel { + entry := logger.newEntry() + entry.Debugln(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Infoln(args ...interface{}) { + if logger.level() >= InfoLevel { + entry := logger.newEntry() + entry.Infoln(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Println(args ...interface{}) { + entry := logger.newEntry() + entry.Println(args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warnln(args ...interface{}) { + if logger.level() >= WarnLevel { + entry := logger.newEntry() + entry.Warnln(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Warningln(args ...interface{}) { + if logger.level() >= WarnLevel { + entry := logger.newEntry() + entry.Warnln(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Errorln(args ...interface{}) { + if logger.level() >= ErrorLevel { + entry := logger.newEntry() + entry.Errorln(args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Fatalln(args ...interface{}) { + if logger.level() >= FatalLevel { + entry := logger.newEntry() + entry.Fatalln(args...) + logger.releaseEntry(entry) + } + Exit(1) +} + +func (logger *Logger) Panicln(args ...interface{}) { + if logger.level() >= PanicLevel { + entry := logger.newEntry() + entry.Panicln(args...) + logger.releaseEntry(entry) + } +} + +//When file is opened with appending mode, it's safe to +//write concurrently to a file (within 4k message on Linux). +//In these cases user can choose to disable the lock. +func (logger *Logger) SetNoLock() { + logger.mu.Disable() +} + +func (logger *Logger) level() Level { + return Level(atomic.LoadUint32((*uint32)(&logger.Level))) +} + +func (logger *Logger) SetLevel(level Level) { + atomic.StoreUint32((*uint32)(&logger.Level), uint32(level)) +} + +func (logger *Logger) SetOutput(out io.Writer) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Out = out +} + +func (logger *Logger) AddHook(hook Hook) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Hooks.Add(hook) +} diff --git a/vendor/github.com/Sirupsen/logrus/logrus.go b/vendor/github.com/Sirupsen/logrus/logrus.go new file mode 100644 index 000000000..dd3899974 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/logrus.go @@ -0,0 +1,143 @@ +package logrus + +import ( + "fmt" + "log" + "strings" +) + +// Fields type, used to pass to `WithFields`. +type Fields map[string]interface{} + +// Level type +type Level uint32 + +// Convert the Level to a string. E.g. PanicLevel becomes "panic". +func (level Level) String() string { + switch level { + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warning" + case ErrorLevel: + return "error" + case FatalLevel: + return "fatal" + case PanicLevel: + return "panic" + } + + return "unknown" +} + +// ParseLevel takes a string level and returns the Logrus log level constant. +func ParseLevel(lvl string) (Level, error) { + switch strings.ToLower(lvl) { + case "panic": + return PanicLevel, nil + case "fatal": + return FatalLevel, nil + case "error": + return ErrorLevel, nil + case "warn", "warning": + return WarnLevel, nil + case "info": + return InfoLevel, nil + case "debug": + return DebugLevel, nil + } + + var l Level + return l, fmt.Errorf("not a valid logrus Level: %q", lvl) +} + +// A constant exposing all logging levels +var AllLevels = []Level{ + PanicLevel, + FatalLevel, + ErrorLevel, + WarnLevel, + InfoLevel, + DebugLevel, +} + +// These are the different logging levels. You can set the logging level to log +// on your instance of logger, obtained with `logrus.New()`. +const ( + // PanicLevel level, highest level of severity. Logs and then calls panic with the + // message passed to Debug, Info, ... + PanicLevel Level = iota + // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the + // logging level is set to Panic. + FatalLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // InfoLevel level. General operational entries about what's going on inside the + // application. + InfoLevel + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel +) + +// Won't compile if StdLogger can't be realized by a log.Logger +var ( + _ StdLogger = &log.Logger{} + _ StdLogger = &Entry{} + _ StdLogger = &Logger{} +) + +// StdLogger is what your logrus-enabled library should take, that way +// it'll accept a stdlib logger and a logrus logger. There's no standard +// interface, this is the closest we get, unfortunately. +type StdLogger interface { + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) + + Fatal(...interface{}) + Fatalf(string, ...interface{}) + Fatalln(...interface{}) + + Panic(...interface{}) + Panicf(string, ...interface{}) + Panicln(...interface{}) +} + +// The FieldLogger interface generalizes the Entry and Logger types +type FieldLogger interface { + WithField(key string, value interface{}) *Entry + WithFields(fields Fields) *Entry + WithError(err error) *Entry + + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Printf(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Warningf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) + Panicf(format string, args ...interface{}) + + Debug(args ...interface{}) + Info(args ...interface{}) + Print(args ...interface{}) + Warn(args ...interface{}) + Warning(args ...interface{}) + Error(args ...interface{}) + Fatal(args ...interface{}) + Panic(args ...interface{}) + + Debugln(args ...interface{}) + Infoln(args ...interface{}) + Println(args ...interface{}) + Warnln(args ...interface{}) + Warningln(args ...interface{}) + Errorln(args ...interface{}) + Fatalln(args ...interface{}) + Panicln(args ...interface{}) +} diff --git a/vendor/github.com/Sirupsen/logrus/terminal_bsd.go b/vendor/github.com/Sirupsen/logrus/terminal_bsd.go new file mode 100644 index 000000000..4880d13d2 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/terminal_bsd.go @@ -0,0 +1,10 @@ +// +build darwin freebsd openbsd netbsd dragonfly +// +build !appengine,!gopherjs + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA + +type Termios unix.Termios diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go new file mode 100644 index 000000000..3de08e802 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go @@ -0,0 +1,11 @@ +// +build appengine gopherjs + +package logrus + +import ( + "io" +) + +func checkIfTerminal(w io.Writer) bool { + return true +} diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go b/vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go new file mode 100644 index 000000000..067047a12 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go @@ -0,0 +1,19 @@ +// +build !appengine,!gopherjs + +package logrus + +import ( + "io" + "os" + + "golang.org/x/crypto/ssh/terminal" +) + +func checkIfTerminal(w io.Writer) bool { + switch v := w.(type) { + case *os.File: + return terminal.IsTerminal(int(v.Fd())) + default: + return false + } +} diff --git a/vendor/github.com/Sirupsen/logrus/terminal_linux.go b/vendor/github.com/Sirupsen/logrus/terminal_linux.go new file mode 100644 index 000000000..f29a0097c --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/terminal_linux.go @@ -0,0 +1,14 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !appengine,!gopherjs + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS + +type Termios unix.Termios diff --git a/vendor/github.com/Sirupsen/logrus/text_formatter.go b/vendor/github.com/Sirupsen/logrus/text_formatter.go new file mode 100644 index 000000000..3e5504030 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/text_formatter.go @@ -0,0 +1,195 @@ +package logrus + +import ( + "bytes" + "fmt" + "sort" + "strings" + "sync" + "time" +) + +const ( + nocolor = 0 + red = 31 + green = 32 + yellow = 33 + blue = 36 + gray = 37 +) + +var ( + baseTimestamp time.Time + emptyFieldMap FieldMap +) + +func init() { + baseTimestamp = time.Now() +} + +// TextFormatter formats logs into text +type TextFormatter struct { + // Set to true to bypass checking for a TTY before outputting colors. + ForceColors bool + + // Force disabling colors. + DisableColors bool + + // Disable timestamp logging. useful when output is redirected to logging + // system that already adds timestamps. + DisableTimestamp bool + + // Enable logging the full timestamp when a TTY is attached instead of just + // the time passed since beginning of execution. + FullTimestamp bool + + // TimestampFormat to use for display when a full timestamp is printed + TimestampFormat string + + // The fields are sorted by default for a consistent output. For applications + // that log extremely frequently and don't use the JSON formatter this may not + // be desired. + DisableSorting bool + + // Disables the truncation of the level text to 4 characters. + DisableLevelTruncation bool + + // QuoteEmptyFields will wrap empty fields in quotes if true + QuoteEmptyFields bool + + // Whether the logger's out is to a terminal + isTerminal bool + + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &TextFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message"}} + FieldMap FieldMap + + sync.Once +} + +func (f *TextFormatter) init(entry *Entry) { + if entry.Logger != nil { + f.isTerminal = checkIfTerminal(entry.Logger.Out) + } +} + +// Format renders a single log entry +func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { + prefixFieldClashes(entry.Data, f.FieldMap) + + keys := make([]string, 0, len(entry.Data)) + for k := range entry.Data { + keys = append(keys, k) + } + + if !f.DisableSorting { + sort.Strings(keys) + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + f.Do(func() { f.init(entry) }) + + isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = defaultTimestampFormat + } + if isColored { + f.printColored(b, entry, keys, timestampFormat) + } else { + if !f.DisableTimestamp { + f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyTime), entry.Time.Format(timestampFormat)) + } + f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyLevel), entry.Level.String()) + if entry.Message != "" { + f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyMsg), entry.Message) + } + for _, key := range keys { + f.appendKeyValue(b, key, entry.Data[key]) + } + } + + b.WriteByte('\n') + return b.Bytes(), nil +} + +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) { + var levelColor int + switch entry.Level { + case DebugLevel: + levelColor = gray + case WarnLevel: + levelColor = yellow + case ErrorLevel, FatalLevel, PanicLevel: + levelColor = red + default: + levelColor = blue + } + + levelText := strings.ToUpper(entry.Level.String()) + if !f.DisableLevelTruncation { + levelText = levelText[0:4] + } + + if f.DisableTimestamp { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message) + } else if !f.FullTimestamp { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message) + } else { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message) + } + for _, k := range keys { + v := entry.Data[k] + fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) + f.appendValue(b, v) + } +} + +func (f *TextFormatter) needsQuoting(text string) bool { + if f.QuoteEmptyFields && len(text) == 0 { + return true + } + for _, ch := range text { + if !((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') { + return true + } + } + return false +} + +func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { + if b.Len() > 0 { + b.WriteByte(' ') + } + b.WriteString(key) + b.WriteByte('=') + f.appendValue(b, value) +} + +func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { + stringVal, ok := value.(string) + if !ok { + stringVal = fmt.Sprint(value) + } + + if !f.needsQuoting(stringVal) { + b.WriteString(stringVal) + } else { + b.WriteString(fmt.Sprintf("%q", stringVal)) + } +} diff --git a/vendor/github.com/Sirupsen/logrus/writer.go b/vendor/github.com/Sirupsen/logrus/writer.go new file mode 100644 index 000000000..7bdebedc6 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/writer.go @@ -0,0 +1,62 @@ +package logrus + +import ( + "bufio" + "io" + "runtime" +) + +func (logger *Logger) Writer() *io.PipeWriter { + return logger.WriterLevel(InfoLevel) +} + +func (logger *Logger) WriterLevel(level Level) *io.PipeWriter { + return NewEntry(logger).WriterLevel(level) +} + +func (entry *Entry) Writer() *io.PipeWriter { + return entry.WriterLevel(InfoLevel) +} + +func (entry *Entry) WriterLevel(level Level) *io.PipeWriter { + reader, writer := io.Pipe() + + var printFunc func(args ...interface{}) + + switch level { + case DebugLevel: + printFunc = entry.Debug + case InfoLevel: + printFunc = entry.Info + case WarnLevel: + printFunc = entry.Warn + case ErrorLevel: + printFunc = entry.Error + case FatalLevel: + printFunc = entry.Fatal + case PanicLevel: + printFunc = entry.Panic + default: + printFunc = entry.Print + } + + go entry.writerScanner(reader, printFunc) + runtime.SetFinalizer(writer, writerFinalizer) + + return writer +} + +func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + printFunc(scanner.Text()) + } + if err := scanner.Err(); err != nil { + entry.Errorf("Error while reading from Writer: %s", err) + } + reader.Close() +} + +func writerFinalizer(writer *io.PipeWriter) { + writer.Close() +} diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index 5f090327b..28a52d1cd 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -364,6 +364,9 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) { // MainLoop runs the main loop until an error is returned. A successful // finish should return ErrQuit. func (g *Gui) MainLoop() error { + if err := g.flush(); err != nil { + return err + } go func() { for { g.tbEvents <- termbox.PollEvent() diff --git a/vendor/github.com/mgutz/str/LICENSE b/vendor/github.com/mgutz/str/LICENSE new file mode 100644 index 000000000..6045e6c75 --- /dev/null +++ b/vendor/github.com/mgutz/str/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2014 Mario L. Gutierrez + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mgutz/str/doc.go b/vendor/github.com/mgutz/str/doc.go new file mode 100644 index 000000000..f48742a1f --- /dev/null +++ b/vendor/github.com/mgutz/str/doc.go @@ -0,0 +1,19 @@ +// Package str is a comprehensive set of string functions to build more +// Go awesomeness. Str complements Go's standard packages and does not duplicate +// functionality found in `strings` or `strconv`. +// +// Str is based on plain functions instead of object-based methods, +// consistent with Go standard string packages. +// +// str.Between("foo", "", "") == "foo" +// +// Str supports pipelining instead of chaining +// +// s := str.Pipe("\nabcdef\n", Clean, BetweenF("a", "f"), ChompLeftF("bc")) +// +// User-defined filters can be added to the pipeline by inserting a function +// or closure that returns a function with this signature +// +// func(string) string +// +package str diff --git a/vendor/github.com/mgutz/str/funcsAO.go b/vendor/github.com/mgutz/str/funcsAO.go new file mode 100644 index 000000000..fd17c1c12 --- /dev/null +++ b/vendor/github.com/mgutz/str/funcsAO.go @@ -0,0 +1,337 @@ +package str + +import ( + "fmt" + "html" + //"log" + "regexp" + "strings" +) + +// Verbose flag enables console output for those functions that have +// counterparts in Go's excellent stadard packages. +var Verbose = false +var templateOpen = "{{" +var templateClose = "}}" + +var beginEndSpacesRe = regexp.MustCompile("^\\s+|\\s+$") +var camelizeRe = regexp.MustCompile(`(\-|_|\s)+(.)?`) +var camelizeRe2 = regexp.MustCompile(`(\-|_|\s)+`) +var capitalsRe = regexp.MustCompile("([A-Z])") +var dashSpaceRe = regexp.MustCompile(`[-\s]+`) +var dashesRe = regexp.MustCompile("-+") +var isAlphaNumericRe = regexp.MustCompile(`[^0-9a-z\xC0-\xFF]`) +var isAlphaRe = regexp.MustCompile(`[^a-z\xC0-\xFF]`) +var nWhitespaceRe = regexp.MustCompile(`\s+`) +var notDigitsRe = regexp.MustCompile(`[^0-9]`) +var slugifyRe = regexp.MustCompile(`[^\w\s\-]`) +var spaceUnderscoreRe = regexp.MustCompile("[_\\s]+") +var spacesRe = regexp.MustCompile("[\\s\\xA0]+") +var stripPuncRe = regexp.MustCompile(`[^\w\s]|_`) +var templateRe = regexp.MustCompile(`([\-\[\]()*\s])`) +var templateRe2 = regexp.MustCompile(`\$`) +var underscoreRe = regexp.MustCompile(`([a-z\d])([A-Z]+)`) +var whitespaceRe = regexp.MustCompile(`^[\s\xa0]*$`) + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +// Between extracts a string between left and right strings. +func Between(s, left, right string) string { + l := len(left) + startPos := strings.Index(s, left) + if startPos < 0 { + return "" + } + endPos := IndexOf(s, right, startPos+l) + //log.Printf("%s: left %s right %s start %d end %d", s, left, right, startPos+l, endPos) + if endPos < 0 { + return "" + } else if right == "" { + return s[endPos:] + } else { + return s[startPos+l : endPos] + } +} + +// BetweenF is the filter form for Between. +func BetweenF(left, right string) func(string) string { + return func(s string) string { + return Between(s, left, right) + } +} + +// Camelize return new string which removes any underscores or dashes and convert a string into camel casing. +func Camelize(s string) string { + return camelizeRe.ReplaceAllStringFunc(s, func(val string) string { + val = strings.ToUpper(val) + val = camelizeRe2.ReplaceAllString(val, "") + return val + }) +} + +// Capitalize uppercases the first char of s and lowercases the rest. +func Capitalize(s string) string { + return strings.ToUpper(s[0:1]) + strings.ToLower(s[1:]) +} + +// CharAt returns a string from the character at the specified position. +func CharAt(s string, index int) string { + l := len(s) + shortcut := index < 0 || index > l-1 || l == 0 + if shortcut { + return "" + } + return s[index : index+1] +} + +// CharAtF is the filter form of CharAt. +func CharAtF(index int) func(string) string { + return func(s string) string { + return CharAt(s, index) + } +} + +// ChompLeft removes prefix at the start of a string. +func ChompLeft(s, prefix string) string { + if strings.HasPrefix(s, prefix) { + return s[len(prefix):] + } + return s +} + +// ChompLeftF is the filter form of ChompLeft. +func ChompLeftF(prefix string) func(string) string { + return func(s string) string { + return ChompLeft(s, prefix) + } +} + +// ChompRight removes suffix from end of s. +func ChompRight(s, suffix string) string { + if strings.HasSuffix(s, suffix) { + return s[:len(s)-len(suffix)] + } + return s +} + +// ChompRightF is the filter form of ChompRight. +func ChompRightF(suffix string) func(string) string { + return func(s string) string { + return ChompRight(s, suffix) + } +} + +// Classify returns a camelized string with the first letter upper cased. +func Classify(s string) string { + return Camelize("-" + s) +} + +// ClassifyF is the filter form of Classify. +func ClassifyF(s string) func(string) string { + return func(s string) string { + return Classify(s) + } +} + +// Clean compresses all adjacent whitespace to a single space and trims s. +func Clean(s string) string { + s = spacesRe.ReplaceAllString(s, " ") + s = beginEndSpacesRe.ReplaceAllString(s, "") + return s +} + +// Dasherize converts a camel cased string into a string delimited by dashes. +func Dasherize(s string) string { + s = strings.TrimSpace(s) + s = spaceUnderscoreRe.ReplaceAllString(s, "-") + s = capitalsRe.ReplaceAllString(s, "-$1") + s = dashesRe.ReplaceAllString(s, "-") + s = strings.ToLower(s) + return s +} + +// EscapeHTML is alias for html.EscapeString. +func EscapeHTML(s string) string { + if Verbose { + fmt.Println("Use html.EscapeString instead of EscapeHTML") + } + return html.EscapeString(s) +} + +// DecodeHTMLEntities decodes HTML entities into their proper string representation. +// DecodeHTMLEntities is an alias for html.UnescapeString +func DecodeHTMLEntities(s string) string { + if Verbose { + fmt.Println("Use html.UnescapeString instead of DecodeHTMLEntities") + } + return html.UnescapeString(s) +} + +// EnsurePrefix ensures s starts with prefix. +func EnsurePrefix(s, prefix string) string { + if strings.HasPrefix(s, prefix) { + return s + } + return prefix + s +} + +// EnsurePrefixF is the filter form of EnsurePrefix. +func EnsurePrefixF(prefix string) func(string) string { + return func(s string) string { + return EnsurePrefix(s, prefix) + } +} + +// EnsureSuffix ensures s ends with suffix. +func EnsureSuffix(s, suffix string) string { + if strings.HasSuffix(s, suffix) { + return s + } + return s + suffix +} + +// EnsureSuffixF is the filter form of EnsureSuffix. +func EnsureSuffixF(suffix string) func(string) string { + return func(s string) string { + return EnsureSuffix(s, suffix) + } +} + +// Humanize transforms s into a human friendly form. +func Humanize(s string) string { + if s == "" { + return s + } + s = Underscore(s) + var humanizeRe = regexp.MustCompile(`_id$`) + s = humanizeRe.ReplaceAllString(s, "") + s = strings.Replace(s, "_", " ", -1) + s = strings.TrimSpace(s) + s = Capitalize(s) + return s +} + +// Iif is short for immediate if. If condition is true return truthy else falsey. +func Iif(condition bool, truthy string, falsey string) string { + if condition { + return truthy + } + return falsey +} + +// IndexOf finds the index of needle in s starting from start. +func IndexOf(s string, needle string, start int) int { + l := len(s) + if needle == "" { + if start < 0 { + return 0 + } else if start < l { + return start + } else { + return l + } + } + if start < 0 || start > l-1 { + return -1 + } + pos := strings.Index(s[start:], needle) + if pos == -1 { + return -1 + } + return start + pos +} + +// IsAlpha returns true if a string contains only letters from ASCII (a-z,A-Z). Other letters from other languages are not supported. +func IsAlpha(s string) bool { + return !isAlphaRe.MatchString(strings.ToLower(s)) +} + +// IsAlphaNumeric returns true if a string contains letters and digits. +func IsAlphaNumeric(s string) bool { + return !isAlphaNumericRe.MatchString(strings.ToLower(s)) +} + +// IsLower returns true if s comprised of all lower case characters. +func IsLower(s string) bool { + return IsAlpha(s) && s == strings.ToLower(s) +} + +// IsNumeric returns true if a string contains only digits from 0-9. Other digits not in Latin (such as Arabic) are not currently supported. +func IsNumeric(s string) bool { + return !notDigitsRe.MatchString(s) +} + +// IsUpper returns true if s contains all upper case chracters. +func IsUpper(s string) bool { + return IsAlpha(s) && s == strings.ToUpper(s) +} + +// IsEmpty returns true if the string is solely composed of whitespace. +func IsEmpty(s string) bool { + if s == "" { + return true + } + return whitespaceRe.MatchString(s) +} + +// Left returns the left substring of length n. +func Left(s string, n int) string { + if n < 0 { + return Right(s, -n) + } + return Substr(s, 0, n) +} + +// LeftF is the filter form of Left. +func LeftF(n int) func(string) string { + return func(s string) string { + return Left(s, n) + } +} + +// LeftOf returns the substring left of needle. +func LeftOf(s string, needle string) string { + return Between(s, "", needle) +} + +// Letters returns an array of runes as strings so it can be indexed into. +func Letters(s string) []string { + result := []string{} + for _, r := range s { + result = append(result, string(r)) + } + return result +} + +// Lines convert windows newlines to unix newlines then convert to an Array of lines. +func Lines(s string) []string { + s = strings.Replace(s, "\r\n", "\n", -1) + return strings.Split(s, "\n") +} + +// Map maps an array's iitem through an iterator. +func Map(arr []string, iterator func(string) string) []string { + r := []string{} + for _, item := range arr { + r = append(r, iterator(item)) + } + return r +} + +// Match returns true if patterns matches the string +func Match(s, pattern string) bool { + r := regexp.MustCompile(pattern) + return r.MatchString(s) +} diff --git a/vendor/github.com/mgutz/str/funcsPZ.go b/vendor/github.com/mgutz/str/funcsPZ.go new file mode 100644 index 000000000..e8fe43f21 --- /dev/null +++ b/vendor/github.com/mgutz/str/funcsPZ.go @@ -0,0 +1,534 @@ +package str + +import ( + "fmt" + "html" + //"log" + "math" + "regexp" + "runtime" + "strconv" + "strings" + "unicode/utf8" +) + +// Pad pads string s on both sides with c until it has length of n. +func Pad(s, c string, n int) string { + L := len(s) + if L >= n { + return s + } + n -= L + + left := strings.Repeat(c, int(math.Ceil(float64(n)/2))) + right := strings.Repeat(c, int(math.Floor(float64(n)/2))) + return left + s + right +} + +// PadF is the filter form of Pad. +func PadF(c string, n int) func(string) string { + return func(s string) string { + return Pad(s, c, n) + } +} + +// PadLeft pads s on left side with c until it has length of n. +func PadLeft(s, c string, n int) string { + L := len(s) + if L > n { + return s + } + return strings.Repeat(c, (n-L)) + s +} + +// PadLeftF is the filter form of PadLeft. +func PadLeftF(c string, n int) func(string) string { + return func(s string) string { + return PadLeft(s, c, n) + } +} + +// PadRight pads s on right side with c until it has length of n. +func PadRight(s, c string, n int) string { + L := len(s) + if L > n { + return s + } + return s + strings.Repeat(c, n-L) +} + +// PadRightF is the filter form of Padright +func PadRightF(c string, n int) func(string) string { + return func(s string) string { + return PadRight(s, c, n) + } +} + +// Pipe pipes s through one or more string filters. +func Pipe(s string, funcs ...func(string) string) string { + for _, fn := range funcs { + s = fn(s) + } + return s +} + +// QuoteItems quotes all items in array, mostly for debugging. +func QuoteItems(arr []string) []string { + return Map(arr, func(s string) string { + return strconv.Quote(s) + }) +} + +// ReplaceF is the filter form of strings.Replace. +func ReplaceF(old, new string, n int) func(string) string { + return func(s string) string { + return strings.Replace(s, old, new, n) + } +} + +// ReplacePattern replaces string with regexp string. +// ReplacePattern returns a copy of src, replacing matches of the Regexp with the replacement string repl. Inside repl, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch. +func ReplacePattern(s, pattern, repl string) string { + r := regexp.MustCompile(pattern) + return r.ReplaceAllString(s, repl) +} + +// ReplacePatternF is the filter form of ReplaceRegexp. +func ReplacePatternF(pattern, repl string) func(string) string { + return func(s string) string { + return ReplacePattern(s, pattern, repl) + } +} + +// Reverse a string +func Reverse(s string) string { + cs := make([]rune, utf8.RuneCountInString(s)) + i := len(cs) + for _, c := range s { + i-- + cs[i] = c + } + return string(cs) +} + +// Right returns the right substring of length n. +func Right(s string, n int) string { + if n < 0 { + return Left(s, -n) + } + return Substr(s, len(s)-n, n) +} + +// RightF is the Filter version of Right. +func RightF(n int) func(string) string { + return func(s string) string { + return Right(s, n) + } +} + +// RightOf returns the substring to the right of prefix. +func RightOf(s string, prefix string) string { + return Between(s, prefix, "") +} + +// SetTemplateDelimiters sets the delimiters for Template function. Defaults to "{{" and "}}" +func SetTemplateDelimiters(opening, closing string) { + templateOpen = opening + templateClose = closing +} + +// Slice slices a string. If end is negative then it is the from the end +// of the string. +func Slice(s string, start, end int) string { + if end > -1 { + return s[start:end] + } + L := len(s) + if L+end > 0 { + return s[start : L-end] + } + return s[start:] +} + +// SliceF is the filter for Slice. +func SliceF(start, end int) func(string) string { + return func(s string) string { + return Slice(s, start, end) + } +} + +// SliceContains determines whether val is an element in slice. +func SliceContains(slice []string, val string) bool { + if slice == nil { + return false + } + + for _, it := range slice { + if it == val { + return true + } + } + return false +} + +// SliceIndexOf gets the indx of val in slice. Returns -1 if not found. +func SliceIndexOf(slice []string, val string) int { + if slice == nil { + return -1 + } + + for i, it := range slice { + if it == val { + return i + } + } + return -1 +} + +// Slugify converts s into a dasherized string suitable for URL segment. +func Slugify(s string) string { + sl := slugifyRe.ReplaceAllString(s, "") + sl = strings.ToLower(sl) + sl = Dasherize(sl) + return sl +} + +// StripPunctuation strips puncation from string. +func StripPunctuation(s string) string { + s = stripPuncRe.ReplaceAllString(s, "") + s = nWhitespaceRe.ReplaceAllString(s, " ") + return s +} + +// StripTags strips all of the html tags or tags specified by the parameters +func StripTags(s string, tags ...string) string { + if len(tags) == 0 { + tags = append(tags, "") + } + for _, tag := range tags { + stripTagsRe := regexp.MustCompile(`(?i)<\/?` + tag + `[^<>]*>`) + s = stripTagsRe.ReplaceAllString(s, "") + } + return s +} + +// Substr returns a substring of s starting at index of length n. +func Substr(s string, index int, n int) string { + L := len(s) + if index < 0 || index >= L || s == "" { + return "" + } + end := index + n + if end >= L { + end = L + } + if end <= index { + return "" + } + return s[index:end] +} + +// SubstrF is the filter form of Substr. +func SubstrF(index, n int) func(string) string { + return func(s string) string { + return Substr(s, index, n) + } +} + +// Template is a string template which replaces template placeholders delimited +// by "{{" and "}}" with values from map. The global delimiters may be set with +// SetTemplateDelimiters. +func Template(s string, values map[string]interface{}) string { + return TemplateWithDelimiters(s, values, templateOpen, templateClose) +} + +// TemplateDelimiters is the getter for the opening and closing delimiters for Template. +func TemplateDelimiters() (opening string, closing string) { + return templateOpen, templateClose +} + +// TemplateWithDelimiters is string template with user-defineable opening and closing delimiters. +func TemplateWithDelimiters(s string, values map[string]interface{}, opening, closing string) string { + escapeDelimiter := func(delim string) string { + result := templateRe.ReplaceAllString(delim, "\\$1") + return templateRe2.ReplaceAllString(result, "\\$") + } + + openingDelim := escapeDelimiter(opening) + closingDelim := escapeDelimiter(closing) + r := regexp.MustCompile(openingDelim + `(.+?)` + closingDelim) + matches := r.FindAllStringSubmatch(s, -1) + for _, submatches := range matches { + match := submatches[0] + key := submatches[1] + //log.Printf("match %s key %s\n", match, key) + if values[key] != nil { + v := fmt.Sprintf("%v", values[key]) + s = strings.Replace(s, match, v, -1) + } + } + + return s +} + +// ToArgv converts string s into an argv for exec. +func ToArgv(s string) []string { + const ( + InArg = iota + InArgQuote + OutOfArg + ) + currentState := OutOfArg + currentQuoteChar := "\x00" // to distinguish between ' and " quotations + // this allows to use "foo'bar" + currentArg := "" + argv := []string{} + + isQuote := func(c string) bool { + return c == `"` || c == `'` + } + + isEscape := func(c string) bool { + return c == `\` + } + + isWhitespace := func(c string) bool { + return c == " " || c == "\t" + } + + L := len(s) + for i := 0; i < L; i++ { + c := s[i : i+1] + + //fmt.Printf("c %s state %v arg %s argv %v i %d\n", c, currentState, currentArg, args, i) + if isQuote(c) { + switch currentState { + case OutOfArg: + currentArg = "" + fallthrough + case InArg: + currentState = InArgQuote + currentQuoteChar = c + + case InArgQuote: + if c == currentQuoteChar { + currentState = InArg + } else { + currentArg += c + } + } + + } else if isWhitespace(c) { + switch currentState { + case InArg: + argv = append(argv, currentArg) + currentState = OutOfArg + case InArgQuote: + currentArg += c + case OutOfArg: + // nothing + } + + } else if isEscape(c) { + switch currentState { + case OutOfArg: + currentArg = "" + currentState = InArg + fallthrough + case InArg: + fallthrough + case InArgQuote: + if i == L-1 { + if runtime.GOOS == "windows" { + // just add \ to end for windows + currentArg += c + } else { + panic("Escape character at end string") + } + } else { + if runtime.GOOS == "windows" { + peek := s[i+1 : i+2] + if peek != `"` { + currentArg += c + } + } else { + i++ + c = s[i : i+1] + currentArg += c + } + } + } + } else { + switch currentState { + case InArg, InArgQuote: + currentArg += c + + case OutOfArg: + currentArg = "" + currentArg += c + currentState = InArg + } + } + } + + if currentState == InArg { + argv = append(argv, currentArg) + } else if currentState == InArgQuote { + panic("Starting quote has no ending quote.") + } + + return argv +} + +// ToBool fuzzily converts truthy values. +func ToBool(s string) bool { + s = strings.ToLower(s) + return s == "true" || s == "yes" || s == "on" || s == "1" +} + +// ToBoolOr parses s as a bool or returns defaultValue. +func ToBoolOr(s string, defaultValue bool) bool { + b, err := strconv.ParseBool(s) + if err != nil { + return defaultValue + } + return b +} + +// ToIntOr parses s as an int or returns defaultValue. +func ToIntOr(s string, defaultValue int) int { + n, err := strconv.Atoi(s) + if err != nil { + return defaultValue + } + return n +} + +// ToFloat32Or parses as a float32 or returns defaultValue on error. +func ToFloat32Or(s string, defaultValue float32) float32 { + f, err := strconv.ParseFloat(s, 32) + if err != nil { + return defaultValue + } + return float32(f) +} + +// ToFloat64Or parses s as a float64 or returns defaultValue. +func ToFloat64Or(s string, defaultValue float64) float64 { + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return defaultValue + } + return f +} + +// ToFloatOr parses as a float64 or returns defaultValue. +var ToFloatOr = ToFloat64Or + +// TODO This is not working yet. Go's regexp package does not have some +// of the niceities in JavaScript +// +// Truncate truncates the string, accounting for word placement and chars count +// adding a morestr (defaults to ellipsis) +// func Truncate(s, morestr string, n int) string { +// L := len(s) +// if L <= n { +// return s +// } +// +// if morestr == "" { +// morestr = "..." +// } +// +// tmpl := func(c string) string { +// if strings.ToUpper(c) != strings.ToLower(c) { +// return "A" +// } +// return " " +// } +// template := s[0 : n+1] +// var truncateRe = regexp.MustCompile(`.(?=\W*\w*$)`) +// truncateRe.ReplaceAllStringFunc(template, tmpl) // 'Hello, world' -> 'HellAA AAAAA' +// var wwRe = regexp.MustCompile(`\w\w`) +// var whitespaceRe2 = regexp.MustCompile(`\s*\S+$`) +// if wwRe.MatchString(template[len(template)-2:]) { +// template = whitespaceRe2.ReplaceAllString(template, "") +// } else { +// template = strings.TrimRight(template, " \t\n") +// } +// +// if len(template+morestr) > L { +// return s +// } +// return s[0:len(template)] + morestr +// } +// +// truncate: function(length, pruneStr) { //from underscore.string, author: github.com/rwz +// var str = this.s; +// +// length = ~~length; +// pruneStr = pruneStr || '...'; +// +// if (str.length <= length) return new this.constructor(str); +// +// var tmpl = function(c){ return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' '; }, +// template = str.slice(0, length+1).replace(/.(?=\W*\w*$)/g, tmpl); // 'Hello, world' -> 'HellAA AAAAA' +// +// if (template.slice(template.length-2).match(/\w\w/)) +// template = template.replace(/\s*\S+$/, ''); +// else +// template = new S(template.slice(0, template.length-1)).trimRight().s; +// +// return (template+pruneStr).length > str.length ? new S(str) : new S(str.slice(0, template.length)+pruneStr); +// }, + +// Underscore returns converted camel cased string into a string delimited by underscores. +func Underscore(s string) string { + if s == "" { + return "" + } + u := strings.TrimSpace(s) + + u = underscoreRe.ReplaceAllString(u, "${1}_$2") + u = dashSpaceRe.ReplaceAllString(u, "_") + u = strings.ToLower(u) + if IsUpper(s[0:1]) { + return "_" + u + } + return u +} + +// UnescapeHTML is an alias for html.UnescapeString. +func UnescapeHTML(s string) string { + if Verbose { + fmt.Println("Use html.UnescapeString instead of UnescapeHTML") + } + return html.UnescapeString(s) +} + +// WrapHTML wraps s within HTML tag having attributes attrs. Note, +// WrapHTML does not escape s value. +func WrapHTML(s string, tag string, attrs map[string]string) string { + escapeHTMLAttributeQuotes := func(v string) string { + v = strings.Replace(v, "<", "<", -1) + v = strings.Replace(v, "&", "&", -1) + v = strings.Replace(v, "\"", """, -1) + return v + } + if tag == "" { + tag = "div" + } + el := "<" + tag + for name, val := range attrs { + el += " " + name + "=\"" + escapeHTMLAttributeQuotes(val) + "\"" + } + el += ">" + s + "" + return el +} + +// WrapHTMLF is the filter form of WrapHTML. +func WrapHTMLF(tag string, attrs map[string]string) func(string) string { + return func(s string) string { + return WrapHTML(s, tag, attrs) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go new file mode 100644 index 000000000..9a887598f --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go @@ -0,0 +1,951 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import ( + "bytes" + "io" + "sync" + "unicode/utf8" +) + +// EscapeCodes contains escape sequences that can be written to the terminal in +// order to achieve different styles of text. +type EscapeCodes struct { + // Foreground colors + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte + + // Reset all attributes + Reset []byte +} + +var vt100EscapeCodes = EscapeCodes{ + Black: []byte{keyEscape, '[', '3', '0', 'm'}, + Red: []byte{keyEscape, '[', '3', '1', 'm'}, + Green: []byte{keyEscape, '[', '3', '2', 'm'}, + Yellow: []byte{keyEscape, '[', '3', '3', 'm'}, + Blue: []byte{keyEscape, '[', '3', '4', 'm'}, + Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, + Cyan: []byte{keyEscape, '[', '3', '6', 'm'}, + White: []byte{keyEscape, '[', '3', '7', 'm'}, + + Reset: []byte{keyEscape, '[', '0', 'm'}, +} + +// Terminal contains the state for running a VT100 terminal that is capable of +// reading lines of input. +type Terminal struct { + // AutoCompleteCallback, if non-null, is called for each keypress with + // the full input line and the current position of the cursor (in + // bytes, as an index into |line|). If it returns ok=false, the key + // press is processed normally. Otherwise it returns a replacement line + // and the new cursor position. + AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) + + // Escape contains a pointer to the escape codes for this terminal. + // It's always a valid pointer, although the escape codes themselves + // may be empty if the terminal doesn't support them. + Escape *EscapeCodes + + // lock protects the terminal and the state in this object from + // concurrent processing of a key press and a Write() call. + lock sync.Mutex + + c io.ReadWriter + prompt []rune + + // line is the current line being entered. + line []rune + // pos is the logical position of the cursor in line + pos int + // echo is true if local echo is enabled + echo bool + // pasteActive is true iff there is a bracketed paste operation in + // progress. + pasteActive bool + + // cursorX contains the current X value of the cursor where the left + // edge is 0. cursorY contains the row number where the first row of + // the current line is 0. + cursorX, cursorY int + // maxLine is the greatest value of cursorY so far. + maxLine int + + termWidth, termHeight int + + // outBuf contains the terminal data to be sent. + outBuf []byte + // remainder contains the remainder of any partial key sequences after + // a read. It aliases into inBuf. + remainder []byte + inBuf [256]byte + + // history contains previously entered commands so that they can be + // accessed with the up and down keys. + history stRingBuffer + // historyIndex stores the currently accessed history entry, where zero + // means the immediately previous entry. + historyIndex int + // When navigating up and down the history it's possible to return to + // the incomplete, initial line. That value is stored in + // historyPending. + historyPending string +} + +// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is +// a local terminal, that terminal must first have been put into raw mode. +// prompt is a string that is written at the start of each input line (i.e. +// "> "). +func NewTerminal(c io.ReadWriter, prompt string) *Terminal { + return &Terminal{ + Escape: &vt100EscapeCodes, + c: c, + prompt: []rune(prompt), + termWidth: 80, + termHeight: 24, + echo: true, + historyIndex: -1, + } +} + +const ( + keyCtrlD = 4 + keyCtrlU = 21 + keyEnter = '\r' + keyEscape = 27 + keyBackspace = 127 + keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota + keyUp + keyDown + keyLeft + keyRight + keyAltLeft + keyAltRight + keyHome + keyEnd + keyDeleteWord + keyDeleteLine + keyClearScreen + keyPasteStart + keyPasteEnd +) + +var ( + crlf = []byte{'\r', '\n'} + pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} + pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'} +) + +// bytesToKey tries to parse a key sequence from b. If successful, it returns +// the key and the remainder of the input. Otherwise it returns utf8.RuneError. +func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { + if len(b) == 0 { + return utf8.RuneError, nil + } + + if !pasteActive { + switch b[0] { + case 1: // ^A + return keyHome, b[1:] + case 5: // ^E + return keyEnd, b[1:] + case 8: // ^H + return keyBackspace, b[1:] + case 11: // ^K + return keyDeleteLine, b[1:] + case 12: // ^L + return keyClearScreen, b[1:] + case 23: // ^W + return keyDeleteWord, b[1:] + } + } + + if b[0] != keyEscape { + if !utf8.FullRune(b) { + return utf8.RuneError, b + } + r, l := utf8.DecodeRune(b) + return r, b[l:] + } + + if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { + switch b[2] { + case 'A': + return keyUp, b[3:] + case 'B': + return keyDown, b[3:] + case 'C': + return keyRight, b[3:] + case 'D': + return keyLeft, b[3:] + case 'H': + return keyHome, b[3:] + case 'F': + return keyEnd, b[3:] + } + } + + if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { + switch b[5] { + case 'C': + return keyAltRight, b[6:] + case 'D': + return keyAltLeft, b[6:] + } + } + + if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { + return keyPasteStart, b[6:] + } + + if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { + return keyPasteEnd, b[6:] + } + + // If we get here then we have a key that we don't recognise, or a + // partial sequence. It's not clear how one should find the end of a + // sequence without knowing them all, but it seems that [a-zA-Z~] only + // appears at the end of a sequence. + for i, c := range b[0:] { + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { + return keyUnknown, b[i+1:] + } + } + + return utf8.RuneError, b +} + +// queue appends data to the end of t.outBuf +func (t *Terminal) queue(data []rune) { + t.outBuf = append(t.outBuf, []byte(string(data))...) +} + +var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'} +var space = []rune{' '} + +func isPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// moveCursorToPos appends data to t.outBuf which will move the cursor to the +// given, logical position in the text. +func (t *Terminal) moveCursorToPos(pos int) { + if !t.echo { + return + } + + x := visualLength(t.prompt) + pos + y := x / t.termWidth + x = x % t.termWidth + + up := 0 + if y < t.cursorY { + up = t.cursorY - y + } + + down := 0 + if y > t.cursorY { + down = y - t.cursorY + } + + left := 0 + if x < t.cursorX { + left = t.cursorX - x + } + + right := 0 + if x > t.cursorX { + right = x - t.cursorX + } + + t.cursorX = x + t.cursorY = y + t.move(up, down, left, right) +} + +func (t *Terminal) move(up, down, left, right int) { + movement := make([]rune, 3*(up+down+left+right)) + m := movement + for i := 0; i < up; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'A' + m = m[3:] + } + for i := 0; i < down; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'B' + m = m[3:] + } + for i := 0; i < left; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'D' + m = m[3:] + } + for i := 0; i < right; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'C' + m = m[3:] + } + + t.queue(movement) +} + +func (t *Terminal) clearLineToRight() { + op := []rune{keyEscape, '[', 'K'} + t.queue(op) +} + +const maxLineLength = 4096 + +func (t *Terminal) setLine(newLine []rune, newPos int) { + if t.echo { + t.moveCursorToPos(0) + t.writeLine(newLine) + for i := len(newLine); i < len(t.line); i++ { + t.writeLine(space) + } + t.moveCursorToPos(newPos) + } + t.line = newLine + t.pos = newPos +} + +func (t *Terminal) advanceCursor(places int) { + t.cursorX += places + t.cursorY += t.cursorX / t.termWidth + if t.cursorY > t.maxLine { + t.maxLine = t.cursorY + } + t.cursorX = t.cursorX % t.termWidth + + if places > 0 && t.cursorX == 0 { + // Normally terminals will advance the current position + // when writing a character. But that doesn't happen + // for the last character in a line. However, when + // writing a character (except a new line) that causes + // a line wrap, the position will be advanced two + // places. + // + // So, if we are stopping at the end of a line, we + // need to write a newline so that our cursor can be + // advanced to the next line. + t.outBuf = append(t.outBuf, '\r', '\n') + } +} + +func (t *Terminal) eraseNPreviousChars(n int) { + if n == 0 { + return + } + + if t.pos < n { + n = t.pos + } + t.pos -= n + t.moveCursorToPos(t.pos) + + copy(t.line[t.pos:], t.line[n+t.pos:]) + t.line = t.line[:len(t.line)-n] + if t.echo { + t.writeLine(t.line[t.pos:]) + for i := 0; i < n; i++ { + t.queue(space) + } + t.advanceCursor(n) + t.moveCursorToPos(t.pos) + } +} + +// countToLeftWord returns then number of characters from the cursor to the +// start of the previous word. +func (t *Terminal) countToLeftWord() int { + if t.pos == 0 { + return 0 + } + + pos := t.pos - 1 + for pos > 0 { + if t.line[pos] != ' ' { + break + } + pos-- + } + for pos > 0 { + if t.line[pos] == ' ' { + pos++ + break + } + pos-- + } + + return t.pos - pos +} + +// countToRightWord returns then number of characters from the cursor to the +// start of the next word. +func (t *Terminal) countToRightWord() int { + pos := t.pos + for pos < len(t.line) { + if t.line[pos] == ' ' { + break + } + pos++ + } + for pos < len(t.line) { + if t.line[pos] != ' ' { + break + } + pos++ + } + return pos - t.pos +} + +// visualLength returns the number of visible glyphs in s. +func visualLength(runes []rune) int { + inEscapeSeq := false + length := 0 + + for _, r := range runes { + switch { + case inEscapeSeq: + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { + inEscapeSeq = false + } + case r == '\x1b': + inEscapeSeq = true + default: + length++ + } + } + + return length +} + +// handleKey processes the given key and, optionally, returns a line of text +// that the user has entered. +func (t *Terminal) handleKey(key rune) (line string, ok bool) { + if t.pasteActive && key != keyEnter { + t.addKeyToLine(key) + return + } + + switch key { + case keyBackspace: + if t.pos == 0 { + return + } + t.eraseNPreviousChars(1) + case keyAltLeft: + // move left by a word. + t.pos -= t.countToLeftWord() + t.moveCursorToPos(t.pos) + case keyAltRight: + // move right by a word. + t.pos += t.countToRightWord() + t.moveCursorToPos(t.pos) + case keyLeft: + if t.pos == 0 { + return + } + t.pos-- + t.moveCursorToPos(t.pos) + case keyRight: + if t.pos == len(t.line) { + return + } + t.pos++ + t.moveCursorToPos(t.pos) + case keyHome: + if t.pos == 0 { + return + } + t.pos = 0 + t.moveCursorToPos(t.pos) + case keyEnd: + if t.pos == len(t.line) { + return + } + t.pos = len(t.line) + t.moveCursorToPos(t.pos) + case keyUp: + entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) + if !ok { + return "", false + } + if t.historyIndex == -1 { + t.historyPending = string(t.line) + } + t.historyIndex++ + runes := []rune(entry) + t.setLine(runes, len(runes)) + case keyDown: + switch t.historyIndex { + case -1: + return + case 0: + runes := []rune(t.historyPending) + t.setLine(runes, len(runes)) + t.historyIndex-- + default: + entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) + if ok { + t.historyIndex-- + runes := []rune(entry) + t.setLine(runes, len(runes)) + } + } + case keyEnter: + t.moveCursorToPos(len(t.line)) + t.queue([]rune("\r\n")) + line = string(t.line) + ok = true + t.line = t.line[:0] + t.pos = 0 + t.cursorX = 0 + t.cursorY = 0 + t.maxLine = 0 + case keyDeleteWord: + // Delete zero or more spaces and then one or more characters. + t.eraseNPreviousChars(t.countToLeftWord()) + case keyDeleteLine: + // Delete everything from the current cursor position to the + // end of line. + for i := t.pos; i < len(t.line); i++ { + t.queue(space) + t.advanceCursor(1) + } + t.line = t.line[:t.pos] + t.moveCursorToPos(t.pos) + case keyCtrlD: + // Erase the character under the current position. + // The EOF case when the line is empty is handled in + // readLine(). + if t.pos < len(t.line) { + t.pos++ + t.eraseNPreviousChars(1) + } + case keyCtrlU: + t.eraseNPreviousChars(t.pos) + case keyClearScreen: + // Erases the screen and moves the cursor to the home position. + t.queue([]rune("\x1b[2J\x1b[H")) + t.queue(t.prompt) + t.cursorX, t.cursorY = 0, 0 + t.advanceCursor(visualLength(t.prompt)) + t.setLine(t.line, t.pos) + default: + if t.AutoCompleteCallback != nil { + prefix := string(t.line[:t.pos]) + suffix := string(t.line[t.pos:]) + + t.lock.Unlock() + newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key) + t.lock.Lock() + + if completeOk { + t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos])) + return + } + } + if !isPrintable(key) { + return + } + if len(t.line) == maxLineLength { + return + } + t.addKeyToLine(key) + } + return +} + +// addKeyToLine inserts the given key at the current position in the current +// line. +func (t *Terminal) addKeyToLine(key rune) { + if len(t.line) == cap(t.line) { + newLine := make([]rune, len(t.line), 2*(1+len(t.line))) + copy(newLine, t.line) + t.line = newLine + } + t.line = t.line[:len(t.line)+1] + copy(t.line[t.pos+1:], t.line[t.pos:]) + t.line[t.pos] = key + if t.echo { + t.writeLine(t.line[t.pos:]) + } + t.pos++ + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) writeLine(line []rune) { + for len(line) != 0 { + remainingOnLine := t.termWidth - t.cursorX + todo := len(line) + if todo > remainingOnLine { + todo = remainingOnLine + } + t.queue(line[:todo]) + t.advanceCursor(visualLength(line[:todo])) + line = line[todo:] + } +} + +// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n. +func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) { + for len(buf) > 0 { + i := bytes.IndexByte(buf, '\n') + todo := len(buf) + if i >= 0 { + todo = i + } + + var nn int + nn, err = w.Write(buf[:todo]) + n += nn + if err != nil { + return n, err + } + buf = buf[todo:] + + if i >= 0 { + if _, err = w.Write(crlf); err != nil { + return n, err + } + n++ + buf = buf[1:] + } + } + + return n, nil +} + +func (t *Terminal) Write(buf []byte) (n int, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + if t.cursorX == 0 && t.cursorY == 0 { + // This is the easy case: there's nothing on the screen that we + // have to move out of the way. + return writeWithCRLF(t.c, buf) + } + + // We have a prompt and possibly user input on the screen. We + // have to clear it first. + t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */) + t.cursorX = 0 + t.clearLineToRight() + + for t.cursorY > 0 { + t.move(1 /* up */, 0, 0, 0) + t.cursorY-- + t.clearLineToRight() + } + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + + if n, err = writeWithCRLF(t.c, buf); err != nil { + return + } + + t.writeLine(t.prompt) + if t.echo { + t.writeLine(t.line) + } + + t.moveCursorToPos(t.pos) + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + return +} + +// ReadPassword temporarily changes the prompt and reads a password, without +// echo, from the terminal. +func (t *Terminal) ReadPassword(prompt string) (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + oldPrompt := t.prompt + t.prompt = []rune(prompt) + t.echo = false + + line, err = t.readLine() + + t.prompt = oldPrompt + t.echo = true + + return +} + +// ReadLine returns a line of input from the terminal. +func (t *Terminal) ReadLine() (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + return t.readLine() +} + +func (t *Terminal) readLine() (line string, err error) { + // t.lock must be held at this point + + if t.cursorX == 0 && t.cursorY == 0 { + t.writeLine(t.prompt) + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + } + + lineIsPasted := t.pasteActive + + for { + rest := t.remainder + lineOk := false + for !lineOk { + var key rune + key, rest = bytesToKey(rest, t.pasteActive) + if key == utf8.RuneError { + break + } + if !t.pasteActive { + if key == keyCtrlD { + if len(t.line) == 0 { + return "", io.EOF + } + } + if key == keyPasteStart { + t.pasteActive = true + if len(t.line) == 0 { + lineIsPasted = true + } + continue + } + } else if key == keyPasteEnd { + t.pasteActive = false + continue + } + if !t.pasteActive { + lineIsPasted = false + } + line, lineOk = t.handleKey(key) + } + if len(rest) > 0 { + n := copy(t.inBuf[:], rest) + t.remainder = t.inBuf[:n] + } else { + t.remainder = nil + } + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + if lineOk { + if t.echo { + t.historyIndex = -1 + t.history.Add(line) + } + if lineIsPasted { + err = ErrPasteIndicator + } + return + } + + // t.remainder is a slice at the beginning of t.inBuf + // containing a partial key sequence + readBuf := t.inBuf[len(t.remainder):] + var n int + + t.lock.Unlock() + n, err = t.c.Read(readBuf) + t.lock.Lock() + + if err != nil { + return + } + + t.remainder = t.inBuf[:n+len(t.remainder)] + } +} + +// SetPrompt sets the prompt to be used when reading subsequent lines. +func (t *Terminal) SetPrompt(prompt string) { + t.lock.Lock() + defer t.lock.Unlock() + + t.prompt = []rune(prompt) +} + +func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { + // Move cursor to column zero at the start of the line. + t.move(t.cursorY, 0, t.cursorX, 0) + t.cursorX, t.cursorY = 0, 0 + t.clearLineToRight() + for t.cursorY < numPrevLines { + // Move down a line + t.move(0, 1, 0, 0) + t.cursorY++ + t.clearLineToRight() + } + // Move back to beginning. + t.move(t.cursorY, 0, 0, 0) + t.cursorX, t.cursorY = 0, 0 + + t.queue(t.prompt) + t.advanceCursor(visualLength(t.prompt)) + t.writeLine(t.line) + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) SetSize(width, height int) error { + t.lock.Lock() + defer t.lock.Unlock() + + if width == 0 { + width = 1 + } + + oldWidth := t.termWidth + t.termWidth, t.termHeight = width, height + + switch { + case width == oldWidth: + // If the width didn't change then nothing else needs to be + // done. + return nil + case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: + // If there is nothing on current line and no prompt printed, + // just do nothing + return nil + case width < oldWidth: + // Some terminals (e.g. xterm) will truncate lines that were + // too long when shinking. Others, (e.g. gnome-terminal) will + // attempt to wrap them. For the former, repainting t.maxLine + // works great, but that behaviour goes badly wrong in the case + // of the latter because they have doubled every full line. + + // We assume that we are working on a terminal that wraps lines + // and adjust the cursor position based on every previous line + // wrapping and turning into two. This causes the prompt on + // xterms to move upwards, which isn't great, but it avoids a + // huge mess with gnome-terminal. + if t.cursorX >= t.termWidth { + t.cursorX = t.termWidth - 1 + } + t.cursorY *= 2 + t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) + case width > oldWidth: + // If the terminal expands then our position calculations will + // be wrong in the future because we think the cursor is + // |t.pos| chars into the string, but there will be a gap at + // the end of any wrapped line. + // + // But the position will actually be correct until we move, so + // we can move back to the beginning and repaint everything. + t.clearAndRepaintLinePlusNPrevious(t.maxLine) + } + + _, err := t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + return err +} + +type pasteIndicatorError struct{} + +func (pasteIndicatorError) Error() string { + return "terminal: ErrPasteIndicator not correctly handled" +} + +// ErrPasteIndicator may be returned from ReadLine as the error, in addition +// to valid line data. It indicates that bracketed paste mode is enabled and +// that the returned line consists only of pasted data. Programs may wish to +// interpret pasted data more literally than typed data. +var ErrPasteIndicator = pasteIndicatorError{} + +// SetBracketedPasteMode requests that the terminal bracket paste operations +// with markers. Not all terminals support this but, if it is supported, then +// enabling this mode will stop any autocomplete callback from running due to +// pastes. Additionally, any lines that are completely pasted will be returned +// from ReadLine with the error set to ErrPasteIndicator. +func (t *Terminal) SetBracketedPasteMode(on bool) { + if on { + io.WriteString(t.c, "\x1b[?2004h") + } else { + io.WriteString(t.c, "\x1b[?2004l") + } +} + +// stRingBuffer is a ring buffer of strings. +type stRingBuffer struct { + // entries contains max elements. + entries []string + max int + // head contains the index of the element most recently added to the ring. + head int + // size contains the number of elements in the ring. + size int +} + +func (s *stRingBuffer) Add(a string) { + if s.entries == nil { + const defaultNumEntries = 100 + s.entries = make([]string, defaultNumEntries) + s.max = defaultNumEntries + } + + s.head = (s.head + 1) % s.max + s.entries[s.head] = a + if s.size < s.max { + s.size++ + } +} + +// NthPreviousEntry returns the value passed to the nth previous call to Add. +// If n is zero then the immediately prior value is returned, if one, then the +// next most recent, and so on. If such an element doesn't exist then ok is +// false. +func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { + if n >= s.size { + return "", false + } + index := s.head - n + if index < 0 { + index += s.max + } + return s.entries[index], true +} + +// readPasswordLine reads from reader until it finds \n or io.EOF. +// The slice returned does not include the \n. +// readPasswordLine also ignores any \r it finds. +func readPasswordLine(reader io.Reader) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := reader.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '\n': + return ret, nil + case '\r': + // remove \r from passwords on Windows + default: + ret = append(ret, buf[0]) + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return ret, err + } + } +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util.go b/vendor/golang.org/x/crypto/ssh/terminal/util.go new file mode 100644 index 000000000..731c89a28 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util.go @@ -0,0 +1,114 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal // import "golang.org/x/crypto/ssh/terminal" + +import ( + "golang.org/x/sys/unix" +) + +// State contains the state of a terminal. +type State struct { + termios unix.Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + oldState := State{termios: *termios} + + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + termios.Oflag &^= unix.OPOST + termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + termios.Cflag &^= unix.CSIZE | unix.PARENB + termios.Cflag |= unix.CS8 + termios.Cc[unix.VMIN] = 1 + termios.Cc[unix.VTIME] = 0 + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil { + return nil, err + } + + return &oldState, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return -1, -1, err + } + return int(ws.Col), int(ws.Row), nil +} + +// passwordReader is an io.Reader that reads from a specific file descriptor. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return unix.Read(int(r), buf) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + newState := *termios + newState.Lflag &^= unix.ECHO + newState.Lflag |= unix.ICANON | unix.ISIG + newState.Iflag |= unix.ICRNL + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil { + return nil, err + } + + defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) + + return readPasswordLine(passwordReader(fd)) +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go b/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go new file mode 100644 index 000000000..cb23a5904 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go @@ -0,0 +1,12 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package terminal + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA +const ioctlWriteTermios = unix.TIOCSETA diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go b/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go new file mode 100644 index 000000000..5fadfe8a1 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go @@ -0,0 +1,10 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go b/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go new file mode 100644 index 000000000..799f049f0 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go @@ -0,0 +1,58 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "fmt" + "runtime" +) + +type State struct{} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + return false +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go new file mode 100644 index 000000000..9e41b9f43 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go @@ -0,0 +1,124 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package terminal // import "golang.org/x/crypto/ssh/terminal" + +import ( + "golang.org/x/sys/unix" + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + termios unix.Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) + return err == nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + // see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c + val, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + oldState := *val + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState) + if err != nil { + return nil, err + } + + defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState) + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} + +// MakeRaw puts the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +// see http://cr.illumos.org/~webrev/andy_js/1060/ +func MakeRaw(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + + oldState := State{termios: *termios} + + termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + termios.Oflag &^= unix.OPOST + termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + termios.Cflag &^= unix.CSIZE | unix.PARENB + termios.Cflag |= unix.CS8 + termios.Cc[unix.VMIN] = 1 + termios.Cc[unix.VTIME] = 0 + + if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil { + return nil, err + } + + return &oldState, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, oldState *State) error { + return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go new file mode 100644 index 000000000..8618955df --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go @@ -0,0 +1,103 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "os" + + "golang.org/x/sys/windows" +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + err := windows.GetConsoleMode(windows.Handle(fd), &st) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil { + return nil, err + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return windows.SetConsoleMode(windows.Handle(fd), state.mode) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info windows.ConsoleScreenBufferInfo + if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { + return 0, 0, err + } + return int(info.Size.X), int(info.Size.Y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + old := st + + st &^= (windows.ENABLE_ECHO_INPUT) + st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { + return nil, err + } + + defer windows.SetConsoleMode(windows.Handle(fd), old) + + var h windows.Handle + p, _ := windows.GetCurrentProcess() + if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil { + return nil, err + } + + f := os.NewFile(uintptr(h), "stdin") + defer f.Close() + return readPasswordLine(f) +} From 5ad97add08ae4d4ec8c9ee348c413d40d64d996a Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 11:27:46 +0200 Subject: [PATCH 06/45] Added the translation to some words again --- pkg/gui/gui.go | 17 +++++----- pkg/i18n/dutch.go | 76 +++++++++++++++++++++++++++++++++++++++++++ pkg/i18n/i18n.go | 83 +++++------------------------------------------ 3 files changed, 94 insertions(+), 82 deletions(-) create mode 100644 pkg/i18n/dutch.go diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 7fd396f05..afe17ff79 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -19,6 +19,7 @@ import ( "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/i18n" ) // OverlappingEdges determines if panel edges overlap @@ -133,7 +134,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Not enough space to render panels" + v.Title = lang.SLocalize("NotEnoughSpace", "Not enough space to render panels") v.Wrap = true } return nil @@ -152,7 +153,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Diff" + v.Title = lang.SLocalize("DiffTitle", "Diff") v.Wrap = true v.FgColor = gocui.ColorWhite } @@ -161,7 +162,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Status" + v.Title = lang.SLocalize("StatusTitle", "Status") v.FgColor = gocui.ColorWhite } @@ -171,7 +172,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } filesView.Highlight = true - filesView.Title = "Files" + filesView.Title = lang.SLocalize("FilesTitle", "Files") v.FgColor = gocui.ColorWhite } @@ -179,7 +180,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Branches" + v.Title = lang.SLocalize("BranchesTitle", "Branches") v.FgColor = gocui.ColorWhite } @@ -187,7 +188,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Commits" + v.Title = lang.SLocalize("CommitsTitle", "Commits") v.FgColor = gocui.ColorWhite } @@ -195,7 +196,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Stash" + v.Title = lang.SLocalize("StashTitle", "Stash") v.FgColor = gocui.ColorWhite } @@ -214,7 +215,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } g.SetViewOnBottom("commitMessage") - commitMessageView.Title = "Commit message" + commitMessageView.Title = lang.SLocalize("CommitMessage", "Commit message") commitMessageView.FgColor = gocui.ColorWhite commitMessageView.Editable = true } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go new file mode 100644 index 000000000..9a847b573 --- /dev/null +++ b/pkg/i18n/dutch.go @@ -0,0 +1,76 @@ +package lang + +import ( + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" +) + +func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { + i18nObject.AddMessages(language.English, + &i18n.Message{ + ID: "NotEnoughSpace", + Other: "Niet genoeg ruimte om de panelen te renderen", + }, &i18n.Message{ + ID: "DiffTitle", + Other: "Diff", + }, &i18n.Message{ + ID: "FilesTitle", + Other: "Bestanden", + }, &i18n.Message{ + ID: "BranchesTitle", + Other: "Branches", + }, &i18n.Message{ + ID: "CommitsTitle", + Other: "Commits", + }, &i18n.Message{ + ID: "StashTitle", + Other: "Stash", + }, &i18n.Message{ + ID: "CommitMessage", + Other: "Commit Bericht", + }, &i18n.Message{ + ID: "CommitChanges", + Other: "Commit Veranderingen", + }, &i18n.Message{ + ID: "StatusTitle", + Other: "Status", + }, &i18n.Message{ + ID: "navigate", + Other: "navigeer", + }, &i18n.Message{ + ID: "stashFiles", + Other: "stash-bestanden", + }, &i18n.Message{ + ID: "open", + Other: "open", + }, &i18n.Message{ + ID: "ignore", + Other: "negeren", + }, &i18n.Message{ + ID: "delete", + Other: "verwijderen", + }, &i18n.Message{ + ID: "toggleStaged", + Other: "toggle staged", + }, &i18n.Message{ + ID: "refresh", + Other: "verversen", + }, &i18n.Message{ + ID: "addPatch", + Other: "verandering toevoegen", + }, &i18n.Message{ + ID: "edit", + Other: "veranderen", + }, &i18n.Message{ + ID: "scroll", + Other: "scroll", + }, &i18n.Message{ + ID: "abortMerge", + Other: "samenvoegen afbreken", + }, &i18n.Message{ + ID: "resolveMergeConflicts", + Other: "verhelp samenvoegen fouten", + }, + ) + return i18nObject +} diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index 346b370fa..c743218f8 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -1,4 +1,4 @@ -package main +package lang import ( "github.com/BurntSushi/toml" @@ -11,76 +11,11 @@ func getlocalizer() *i18n.Localizer { // TODO: currently the system language issn't detected // I'm not sure how to detect it - var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} + var i18nObject = &i18n.Bundle{DefaultLanguage: language.Dutch} i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) - // To add more translations do: - // AddMessages(tag language.Tag, messages ...*Message) - // https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n#Bundle.AddMessages - - // Dutch translation for some words - i18nObject.AddMessages(language.Dutch, - &i18n.Message{ - ID: "FilesTitle", - Other: "Bestanden", - }, &i18n.Message{ - ID: "BranchesTitle", - Other: "Branches", - }, &i18n.Message{ - ID: "CommitsTitle", - Other: "Commits", - }, &i18n.Message{ - ID: "StashTitle", - Other: "Stash", - }, &i18n.Message{ - ID: "CommitMessage", - Other: "Commit Bericht", - }, &i18n.Message{ - ID: "CommitChanges", - Other: "Commit Veranderingen", - }, &i18n.Message{ - ID: "StatusTitle", - Other: "Status", - }, &i18n.Message{ - ID: "navigate", - Other: "navigeer", - }, &i18n.Message{ - ID: "stashFiles", - Other: "stash-bestanden", - }, &i18n.Message{ - ID: "open", - Other: "open", - }, &i18n.Message{ - ID: "ignore", - Other: "negeren", - }, &i18n.Message{ - ID: "delete", - Other: "verwijderen", - }, &i18n.Message{ - ID: "toggleStaged", - Other: "toggle staged", - }, &i18n.Message{ - ID: "refresh", - Other: "verversen", - }, &i18n.Message{ - ID: "addPatch", - Other: "verandering toevoegen", - }, &i18n.Message{ - ID: "edit", - Other: "veranderen", - }, &i18n.Message{ - ID: "scroll", - Other: "scroll", - }, &i18n.Message{ - ID: "abortMerge", - Other: "samenvoegen afbreken", - }, &i18n.Message{ - ID: "resolveMergeConflicts", - Other: "verhelp samenvoegen fouten", - }, - - // - ) + // add translation file(s) + i18nObject = addDutch(i18nObject) return i18n.NewLocalizer(i18nObject) } @@ -88,18 +23,18 @@ func getlocalizer() *i18n.Localizer { // setup the localizer for later use var localizer = getlocalizer() -// MustLocalize handels the translations +// Localize handels the translations // expects i18n.LocalizeConfig as input: https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n#Localizer.MustLocalize // output: translated string -func MustLocalize(config *i18n.LocalizeConfig) string { +func Localize(config *i18n.LocalizeConfig) string { return localizer.MustLocalize(config) } -// ShortLocalize is for 1 line localizations +// SLocalize (short localize) is for 1 line localizations // ID: The id that is used in the .toml translation files // Other: the default message it needs to return if there is no translation found or the system is english -func ShortLocalize(ID string, Other string) string { - return MustLocalize(&i18n.LocalizeConfig{ +func SLocalize(ID string, Other string) string { + return Localize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ ID: ID, Other: Other, From dd7e93ac8d5c169959fbeade89fcd59bfae49fe8 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 11:35:39 +0200 Subject: [PATCH 07/45] Added all the missing translations from dutch.go --- pkg/gui/files_panel.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index a7e9683d2..ad8ee6fbc 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -1,6 +1,7 @@ package gui import ( + "github.com/jesseduffield/lazygit/pkg/i18n" // "io" // "io/ioutil" @@ -147,21 +148,21 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error { optionsMap := map[string]string{ - "← → ↑ ↓": "navigate", - "S": "stash files", - "c": "commit changes", - "o": "open", - "i": "ignore", - "d": "delete", - "space": "toggle staged", - "R": "refresh", - "t": "add patch", - "e": "edit", - "PgUp/PgDn": "scroll", + "← → ↑ ↓": lang.SLocalize("navigate", "navigate"), + "S": lang.SLocalize("stashFiles", "stash files"), + "c": lang.SLocalize("CommitChanges", "commit changes"), + "o": lang.SLocalize("open", "open"), + "i": lang.SLocalize("ignore", "ignore"), + "d": lang.SLocalize("delete", "delete"), + "space": lang.SLocalize("toggleStaged", "toggle staged"), + "R": lang.SLocalize("refresh", "refresh"), + "t": lang.SLocalize("addPatch", "add patch"), + "e": lang.SLocalize("edit", "edit"), + "PgUp/PgDn": lang.SLocalize("scroll", "scroll"), } if gui.State.HasMergeConflicts { - optionsMap["a"] = "abort merge" - optionsMap["m"] = "resolve merge conflicts" + optionsMap["a"] = lang.SLocalize("abortMerge", "abort merge") + optionsMap["m"] = lang.SLocalize("resolveMergeConflicts", "resolve merge conflicts") } if file == nil { return gui.renderOptionsMap(g, optionsMap) From 0c39347224335998fd38cdad2feb23f55d3a5516 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 12:52:26 +0200 Subject: [PATCH 08/45] Added auto detection for the system language --- pkg/gui/files_panel.go | 2 +- pkg/i18n/dutch.go | 5 ++++- pkg/i18n/i18n.go | 10 ++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index ad8ee6fbc..ba60a3278 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -168,7 +168,7 @@ func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error { return gui.renderOptionsMap(g, optionsMap) } if file.Tracked { - optionsMap["d"] = "checkout" + optionsMap["d"] = lang.SLocalize("checkout", "checkout") } return gui.renderOptionsMap(g, optionsMap) } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 9a847b573..10d01c6df 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -6,7 +6,7 @@ import ( ) func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { - i18nObject.AddMessages(language.English, + i18nObject.AddMessages(language.Dutch, &i18n.Message{ ID: "NotEnoughSpace", Other: "Niet genoeg ruimte om de panelen te renderen", @@ -70,6 +70,9 @@ func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { }, &i18n.Message{ ID: "resolveMergeConflicts", Other: "verhelp samenvoegen fouten", + }, &i18n.Message{ + ID: "checkout", + Other: "uitchecken", }, ) return i18nObject diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index c743218f8..ffd77fde6 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -1,7 +1,7 @@ package lang import ( - "github.com/BurntSushi/toml" + "github.com/cloudfoundry/jibber_jabber" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" ) @@ -9,15 +9,13 @@ import ( // the function to setup the localizer func getlocalizer() *i18n.Localizer { - // TODO: currently the system language issn't detected - // I'm not sure how to detect it - var i18nObject = &i18n.Bundle{DefaultLanguage: language.Dutch} - i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) + userLang, _ := jibber_jabber.DetectLanguage() + var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} // add translation file(s) i18nObject = addDutch(i18nObject) - return i18n.NewLocalizer(i18nObject) + return i18n.NewLocalizer(i18nObject, userLang) } // setup the localizer for later use From 6e518142b48f70fc63b0c72d00754733accd23f2 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 12:56:11 +0200 Subject: [PATCH 09/45] added some commands --- pkg/i18n/dutch.go | 5 +++++ pkg/i18n/i18n.go | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 10d01c6df..12cbc8f43 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -5,7 +5,10 @@ import ( "golang.org/x/text/language" ) +// addDutch will add all the translations func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { + + // add the translations i18nObject.AddMessages(language.Dutch, &i18n.Message{ ID: "NotEnoughSpace", @@ -75,5 +78,7 @@ func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { Other: "uitchecken", }, ) + + // return the new i18nObject return i18nObject } diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index ffd77fde6..a62f50c1e 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -9,12 +9,16 @@ import ( // the function to setup the localizer func getlocalizer() *i18n.Localizer { + // detect the user's language userLang, _ := jibber_jabber.DetectLanguage() + + // create a i18n bundle that can be used to add translations and other things var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} // add translation file(s) i18nObject = addDutch(i18nObject) + // return the new localizer that can be used to translate text return i18n.NewLocalizer(i18nObject, userLang) } From 0568b32f0bed36093e773226f985b7e444112248 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 13:31:23 +0200 Subject: [PATCH 10/45] Added more translations --- pkg/gui/files_panel.go | 10 +++++----- pkg/i18n/dutch.go | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index ba60a3278..58069ef94 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -18,8 +18,8 @@ import ( ) var ( - errNoFiles = errors.New("No changed files") - errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`) + errNoFiles = errors.New(lang.SLocalize("NoChangedFiles", "No changed files")) + errNoUsername = errors.New(lang.SLocalize("NoUsernameSetErr", `No username set. Please do: git config --global user.name "Your Name"`)) ) func (gui *Gui) stagedFiles() []commands.File { @@ -87,10 +87,10 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { return err } if !file.HasUnstagedChanges { - return gui.createErrorPanel(g, "File has no unstaged changes to add") + return gui.createErrorPanel(g, lang.SLocalize("FileHasNoUnstagedChanges", "File has no unstaged changes to add")) } if !file.Tracked { - return gui.createErrorPanel(g, "Cannot git add --patch untracked files") + return gui.createErrorPanel(g, lang.SLocalize("CannotGitAdd", "Cannot git add --patch untracked files")) } sub, err := gui.GitCommand.AddPatch(file.Name) if err != nil { @@ -140,7 +140,7 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { return gui.createErrorPanel(g, err.Error()) } if file.Tracked { - return gui.createErrorPanel(g, "Cannot ignore tracked files") + return gui.createErrorPanel(g, lang.SLocalize("CantIgnoreTrackFiles", "Cannot ignore tracked files")) } gui.GitCommand.Ignore(file.Name) return gui.refreshFiles(g) diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 12cbc8f43..b0bd473c3 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -5,7 +5,7 @@ import ( "golang.org/x/text/language" ) -// addDutch will add all the translations +// addDutch will add all dutch translations func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { // add the translations @@ -76,6 +76,21 @@ func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { }, &i18n.Message{ ID: "checkout", Other: "uitchecken", + }, &i18n.Message{ + ID: "NoChangedFiles", + Other: "Geen Bestanden verandert", + }, &i18n.Message{ + ID: "NoUsernameSetErr", + Other: `Geen gebruikersnaam ingesteld. Doe: git config --global user.name "Jou Naam"`, + }, &i18n.Message{ + ID: "FileHasNoUnstagedChanges", + Other: "Het bestand heeft geen unstaged veranderingen om toe te voegen", + }, &i18n.Message{ + ID: "CannotGitAdd", + Other: "Kan commando niet uitvoeren git add --path untracked files", + }, &i18n.Message{ + ID: "CantIgnoreTrackFiles", + Other: "Kan gevolgde bestanden niet negeren", }, ) From 5cbacb0c67a10f08d38634cface70f35ca0e45e4 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 14 Aug 2018 22:12:07 +1000 Subject: [PATCH 11/45] make local i18n package confirm to project structure --- pkg/app/app.go | 10 ++++++- pkg/gui/commit_message_panel.go | 4 ++- pkg/gui/files_panel.go | 29 +++++++++---------- pkg/gui/gui.go | 22 +++++++------- pkg/i18n/dutch.go | 2 +- pkg/i18n/i18n.go | 51 ++++++++++++++++++++++++--------- 6 files changed, 76 insertions(+), 42 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index d558ed250..f07757f98 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -9,6 +9,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui" + "github.com/mjarkk/lazygit/pkg/i18n" ) // App struct @@ -20,6 +21,7 @@ type App struct { OSCommand *commands.OSCommand GitCommand *commands.GitCommand Gui *gui.Gui + Tr *i18n.Localizer } func newLogger(config config.AppConfigurer) *logrus.Logger { @@ -48,11 +50,17 @@ func NewApp(config config.AppConfigurer) (*App, error) { if err != nil { return nil, err } + + app.Tr, err = i18n.NewLocalizer() + if err != nil { + return nil, err + } + app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand) if err != nil { return nil, err } - app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, config.GetVersion()) + app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config.GetVersion()) if err != nil { return nil, err } diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index f765ab308..854732817 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -1,6 +1,8 @@ package gui -import "github.com/jesseduffield/gocui" +import ( + "github.com/jesseduffield/gocui" +) func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { message := gui.trimmedContent(v) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index ba60a3278..b99de1606 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -1,7 +1,6 @@ package gui import ( - "github.com/jesseduffield/lazygit/pkg/i18n" // "io" // "io/ioutil" @@ -148,27 +147,27 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error { optionsMap := map[string]string{ - "← → ↑ ↓": lang.SLocalize("navigate", "navigate"), - "S": lang.SLocalize("stashFiles", "stash files"), - "c": lang.SLocalize("CommitChanges", "commit changes"), - "o": lang.SLocalize("open", "open"), - "i": lang.SLocalize("ignore", "ignore"), - "d": lang.SLocalize("delete", "delete"), - "space": lang.SLocalize("toggleStaged", "toggle staged"), - "R": lang.SLocalize("refresh", "refresh"), - "t": lang.SLocalize("addPatch", "add patch"), - "e": lang.SLocalize("edit", "edit"), - "PgUp/PgDn": lang.SLocalize("scroll", "scroll"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), + "S": gui.Tr.SLocalize("stashFiles", "stash files"), + "c": gui.Tr.SLocalize("CommitChanges", "commit changes"), + "o": gui.Tr.SLocalize("open", "open"), + "i": gui.Tr.SLocalize("ignore", "ignore"), + "d": gui.Tr.SLocalize("delete", "delete"), + "space": gui.Tr.SLocalize("toggleStaged", "toggle staged"), + "R": gui.Tr.SLocalize("refresh", "refresh"), + "t": gui.Tr.SLocalize("addPatch", "add patch"), + "e": gui.Tr.SLocalize("edit", "edit"), + "PgUp/PgDn": gui.Tr.SLocalize("scroll", "scroll"), } if gui.State.HasMergeConflicts { - optionsMap["a"] = lang.SLocalize("abortMerge", "abort merge") - optionsMap["m"] = lang.SLocalize("resolveMergeConflicts", "resolve merge conflicts") + optionsMap["a"] = gui.Tr.SLocalize("abortMerge", "abort merge") + optionsMap["m"] = gui.Tr.SLocalize("resolveMergeConflicts", "resolve merge conflicts") } if file == nil { return gui.renderOptionsMap(g, optionsMap) } if file.Tracked { - optionsMap["d"] = lang.SLocalize("checkout", "checkout") + optionsMap["d"] = gui.Tr.SLocalize("checkout", "checkout") } return gui.renderOptionsMap(g, optionsMap) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index afe17ff79..bf8360f72 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -19,7 +19,7 @@ import ( "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/mjarkk/lazygit/pkg/i18n" ) // OverlappingEdges determines if panel edges overlap @@ -40,6 +40,7 @@ type Gui struct { Version string SubProcess *exec.Cmd State guiState + Tr *i18n.Localizer } type guiState struct { @@ -58,7 +59,7 @@ type guiState struct { } // NewGui builds a new gui handler -func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, version string) (*Gui, error) { +func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *lang.Localizer, version string) (*Gui, error) { initialState := guiState{ Files: make([]commands.File, 0), PreviousView: "files", @@ -78,6 +79,7 @@ func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *comm OSCommand: oSCommand, Version: version, State: initialState, + Tr: tr, }, nil } @@ -134,7 +136,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = lang.SLocalize("NotEnoughSpace", "Not enough space to render panels") + v.Title = gui.Tr.SLocalize("NotEnoughSpace", "Not enough space to render panels") v.Wrap = true } return nil @@ -153,7 +155,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = lang.SLocalize("DiffTitle", "Diff") + v.Title = gui.Tr.SLocalize("DiffTitle", "Diff") v.Wrap = true v.FgColor = gocui.ColorWhite } @@ -162,7 +164,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = lang.SLocalize("StatusTitle", "Status") + v.Title = gui.Tr.SLocalize("StatusTitle", "Status") v.FgColor = gocui.ColorWhite } @@ -172,7 +174,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } filesView.Highlight = true - filesView.Title = lang.SLocalize("FilesTitle", "Files") + filesView.Title = gui.Tr.SLocalize("FilesTitle", "Files") v.FgColor = gocui.ColorWhite } @@ -180,7 +182,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = lang.SLocalize("BranchesTitle", "Branches") + v.Title = gui.Tr.SLocalize("BranchesTitle", "Branches") v.FgColor = gocui.ColorWhite } @@ -188,7 +190,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = lang.SLocalize("CommitsTitle", "Commits") + v.Title = gui.Tr.SLocalize("CommitsTitle", "Commits") v.FgColor = gocui.ColorWhite } @@ -196,7 +198,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = lang.SLocalize("StashTitle", "Stash") + v.Title = gui.Tr.SLocalize("StashTitle", "Stash") v.FgColor = gocui.ColorWhite } @@ -215,7 +217,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } g.SetViewOnBottom("commitMessage") - commitMessageView.Title = lang.SLocalize("CommitMessage", "Commit message") + commitMessageView.Title = gui.Tr.SLocalize("CommitMessage", "Commit message") commitMessageView.FgColor = gocui.ColorWhite commitMessageView.Editable = true } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 12cbc8f43..199b238b3 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -1,4 +1,4 @@ -package lang +package i18n import ( "github.com/nicksnyder/go-i18n/v2/i18n" diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index a62f50c1e..e58c05aec 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -1,45 +1,68 @@ -package lang +package i18n import ( + "github.com/Sirupsen/logrus" "github.com/cloudfoundry/jibber_jabber" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" ) -// the function to setup the localizer -func getlocalizer() *i18n.Localizer { +// Localizer will translate a message into the user's language +type Localizer struct { + i18nLocalizer *i18n.Localizer + language string + Log *logrus.Logger +} + +// NewLocalizer creates a new Localizer +func NewLocalizer(log *logrus.Logger) (*Localizer, error) { // detect the user's language userLang, _ := jibber_jabber.DetectLanguage() + log.Info("language: " + userLang) // create a i18n bundle that can be used to add translations and other things - var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} + i18nBundle := &i18n.Bundle{DefaultLanguage: language.English} - // add translation file(s) - i18nObject = addDutch(i18nObject) + addBundles(i18nBundle) // return the new localizer that can be used to translate text - return i18n.NewLocalizer(i18nObject, userLang) -} + i18nLocalizer := i18n.NewLocalizer(i18nBundle, userLang) -// setup the localizer for later use -var localizer = getlocalizer() + localizer := &Localizer{ + i18nLocalizer: i18nLocalizer, + language: userLang, + Log: log, + } + + return localizer, nil +} // Localize handels the translations // expects i18n.LocalizeConfig as input: https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n#Localizer.MustLocalize // output: translated string -func Localize(config *i18n.LocalizeConfig) string { - return localizer.MustLocalize(config) +func (l *Localizer) Localize(config *i18n.LocalizeConfig) string { + return l.i18nLocalizer.MustLocalize(config) } // SLocalize (short localize) is for 1 line localizations // ID: The id that is used in the .toml translation files // Other: the default message it needs to return if there is no translation found or the system is english -func SLocalize(ID string, Other string) string { - return Localize(&i18n.LocalizeConfig{ +func (l *Localizer) SLocalize(ID string, Other string) string { + return l.Localize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ ID: ID, Other: Other, }, }) } + +// GetLanguage returns the currently selected language, e.g 'en' +func (l *Localizer) GetLanguage() string { + return l.language +} + +// add translation file(s) +func addBundles(i18nBundle *i18n.Bundle) { + addDutch(i18nBundle) +} From 73a168254066278b6f5d38667564877a071b4376 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 15:26:25 +0200 Subject: [PATCH 12/45] fixed package naming and added tr object to file_panel.go --- pkg/app/app.go | 2 +- pkg/gui/files_panel.go | 13 ++++++++----- pkg/gui/gui.go | 4 ++-- pkg/i18n/i18n.go | 5 ++++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index f07757f98..f4311bb03 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -9,7 +9,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui" - "github.com/mjarkk/lazygit/pkg/i18n" + "github.com/jesseduffield/lazygit/pkg/i18n" ) // App struct diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 8c4c5cb6f..2bf8e754b 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -14,11 +14,14 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/i18n" ) +var tr *i18n.Localizer + var ( - errNoFiles = errors.New(lang.SLocalize("NoChangedFiles", "No changed files")) - errNoUsername = errors.New(lang.SLocalize("NoUsernameSetErr", `No username set. Please do: git config --global user.name "Your Name"`)) + errNoFiles = errors.New(tr.SLocalize("NoChangedFiles", "No changed files")) + errNoUsername = errors.New(tr.SLocalize("NoUsernameSetErr", `No username set. Please do: git config --global user.name "Your Name"`)) ) func (gui *Gui) stagedFiles() []commands.File { @@ -86,10 +89,10 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { return err } if !file.HasUnstagedChanges { - return gui.createErrorPanel(g, lang.SLocalize("FileHasNoUnstagedChanges", "File has no unstaged changes to add")) + return gui.createErrorPanel(g, tr.SLocalize("FileHasNoUnstagedChanges", "File has no unstaged changes to add")) } if !file.Tracked { - return gui.createErrorPanel(g, lang.SLocalize("CannotGitAdd", "Cannot git add --patch untracked files")) + return gui.createErrorPanel(g, tr.SLocalize("CannotGitAdd", "Cannot git add --patch untracked files")) } sub, err := gui.GitCommand.AddPatch(file.Name) if err != nil { @@ -139,7 +142,7 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { return gui.createErrorPanel(g, err.Error()) } if file.Tracked { - return gui.createErrorPanel(g, lang.SLocalize("CantIgnoreTrackFiles", "Cannot ignore tracked files")) + return gui.createErrorPanel(g, tr.SLocalize("CantIgnoreTrackFiles", "Cannot ignore tracked files")) } gui.GitCommand.Ignore(file.Name) return gui.refreshFiles(g) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 237f78d31..f30e60137 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -19,7 +19,7 @@ import ( "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/mjarkk/lazygit/pkg/i18n" + "github.com/jesseduffield/lazygit/pkg/i18n" ) // OverlappingEdges determines if panel edges overlap @@ -59,7 +59,7 @@ type guiState struct { } // NewGui builds a new gui handler -func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *lang.Localizer, version string) (*Gui, error) { +func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, version string) (*Gui, error) { initialState := guiState{ Files: make([]commands.File, 0), PreviousView: "files", diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index e58c05aec..f54cb9d80 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -18,7 +18,10 @@ type Localizer struct { func NewLocalizer(log *logrus.Logger) (*Localizer, error) { // detect the user's language - userLang, _ := jibber_jabber.DetectLanguage() + userLang, err := jibber_jabber.DetectLanguage() + if err != nil { + return nil, err + } log.Info("language: " + userLang) // create a i18n bundle that can be used to add translations and other things From ba2b6fbf1fa13ca5a72c18fba996bdd561d4899c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 14 Aug 2018 23:47:14 +1000 Subject: [PATCH 13/45] pull errors out of package scope and store sentinel errors on the gui struct --- pkg/commands/os.go | 11 ++------- pkg/gui/commit_message_panel.go | 4 ++-- pkg/gui/commits_panel.go | 9 ++------ pkg/gui/files_panel.go | 28 +++++++++------------- pkg/gui/gui.go | 41 +++++++++++++++++++++++++-------- 5 files changed, 49 insertions(+), 44 deletions(-) diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 9f9819a5a..d4ea75e74 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -14,13 +14,6 @@ import ( gitconfig "github.com/tcnksm/go-gitconfig" ) -var ( - // ErrNoOpenCommand : When we don't know which command to use to open a file - ErrNoOpenCommand = errors.New("Unsure what command to use to open this file") - // ErrNoEditorDefined : When we can't find an editor to edit a file - ErrNoEditorDefined = errors.New("No editor defined in $VISUAL, $EDITOR, or git config") -) - // Platform stores the os state type Platform struct { os string @@ -113,7 +106,7 @@ func (c *OSCommand) GetOpenCommand() (string, string, error) { return name, trail, nil } } - return "", "", ErrNoOpenCommand + return "", "", errors.New("Unsure what command to use to open this file") } // VsCodeOpenFile opens the file in code, with the -r flag to open in the @@ -157,7 +150,7 @@ func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) { } } if editor == "" { - return nil, ErrNoEditorDefined + return nil, errors.New("No editor defined in $VISUAL, $EDITOR, or git config") } return c.PrepareSubProcess(editor, filename) } diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 854732817..599c80186 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -12,13 +12,13 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { sub, err := gui.GitCommand.Commit(g, message) if err != nil { // TODO need to find a way to send through this error - if err != ErrSubProcess { + if err != gui.Errors.ErrSubProcess { return gui.createErrorPanel(g, err.Error()) } } if sub != nil { gui.SubProcess = sub - return ErrSubProcess + return gui.Errors.ErrSubProcess } gui.refreshFiles(g) v.Clear() diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 60ae1c315..4f5ae2afc 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -8,11 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands" ) -var ( - // ErrNoCommits : When no commits are found for the branch - ErrNoCommits = errors.New("No commits for this branch") -) - func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { gui.State.Commits = gui.GitCommand.GetCommits() @@ -79,7 +74,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { } commit, err := gui.getSelectedCommit(g) if err != nil { - if err != ErrNoCommits { + if err != errors.New("No commits for this branch") { return err } return gui.renderString(g, "main", "No commits for this branch") @@ -165,7 +160,7 @@ func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) { panic(err) } if len(gui.State.Commits) == 0 { - return commands.Commit{}, ErrNoCommits + return commands.Commit{}, errors.New("No commits for this branch") } lineNumber := gui.getItemPosition(v) if lineNumber > len(gui.State.Commits)-1 { diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 8c4c5cb6f..312c06554 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -7,7 +7,6 @@ import ( // "strings" - "errors" "os/exec" "strings" @@ -16,11 +15,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands" ) -var ( - errNoFiles = errors.New(lang.SLocalize("NoChangedFiles", "No changed files")) - errNoUsername = errors.New(lang.SLocalize("NoUsernameSetErr", `No username set. Please do: git config --global user.name "Your Name"`)) -) - func (gui *Gui) stagedFiles() []commands.File { files := gui.State.Files result := make([]commands.File, 0) @@ -54,7 +48,7 @@ func (gui *Gui) stageSelectedFile(g *gocui.Gui) error { func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error { file, err := gui.getSelectedFile(g) if err != nil { - if err == errNoFiles { + if err == gui.Errors.ErrNoFiles { return nil } return err @@ -80,16 +74,16 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { file, err := gui.getSelectedFile(g) if err != nil { - if err == errNoFiles { + if err == gui.Errors.ErrNoFiles { return nil } return err } if !file.HasUnstagedChanges { - return gui.createErrorPanel(g, lang.SLocalize("FileHasNoUnstagedChanges", "File has no unstaged changes to add")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("FileHasNoUnstagedChanges", "File has no unstaged changes to add")) } if !file.Tracked { - return gui.createErrorPanel(g, lang.SLocalize("CannotGitAdd", "Cannot git add --patch untracked files")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CannotGitAdd", "Cannot git add --patch untracked files")) } sub, err := gui.GitCommand.AddPatch(file.Name) if err != nil { @@ -101,7 +95,7 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) { if len(gui.State.Files) == 0 { - return commands.File{}, errNoFiles + return commands.File{}, gui.Errors.ErrNoFiles } filesView, err := g.View("files") if err != nil { @@ -114,7 +108,7 @@ func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) { func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { file, err := gui.getSelectedFile(g) if err != nil { - if err == errNoFiles { + if err == gui.Errors.ErrNoFiles { return nil } return err @@ -139,7 +133,7 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { return gui.createErrorPanel(g, err.Error()) } if file.Tracked { - return gui.createErrorPanel(g, lang.SLocalize("CantIgnoreTrackFiles", "Cannot ignore tracked files")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantIgnoreTrackFiles", "Cannot ignore tracked files")) } gui.GitCommand.Ignore(file.Name) return gui.refreshFiles(g) @@ -175,7 +169,7 @@ func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error { func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { file, err := gui.getSelectedFile(g) if err != nil { - if err != errNoFiles { + if err != gui.Errors.ErrNoFiles { return err } gui.renderString(g, "main", "No changed files") @@ -230,7 +224,7 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) error { func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) (*exec.Cmd, error)) error { file, err := gui.getSelectedFile(g) if err != nil { - if err != errNoFiles { + if err != gui.Errors.ErrNoFiles { return err } return nil @@ -303,7 +297,7 @@ func (gui *Gui) renderFile(file commands.File, filesView *gocui.View) { func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { item, err := gui.getSelectedFile(g) if err != nil { - if err != errNoFiles { + if err != gui.Errors.ErrNoFiles { return "", err } return "", gui.renderString(g, "main", "No file to display") @@ -369,7 +363,7 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { } file, err := gui.getSelectedFile(g) if err != nil { - if err != errNoFiles { + if err != gui.Errors.ErrNoFiles { return err } return nil diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index bf8360f72..4e8382eae 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -25,11 +25,29 @@ import ( // OverlappingEdges determines if panel edges overlap var OverlappingEdges = false -// ErrSubProcess tells us we're switching to a subprocess so we need to -// close the Gui until it is finished -var ( - ErrSubProcess = errors.New("running subprocess") -) +// SentinelErrors are the errors that have special meaning and need to be checked +// by calling functions. The less of these, the better +type SentinelErrors struct { + ErrSubProcess error + ErrNoFiles error +} + +// GenerateSentinelErrors makes the sentinel errors for the gui. We're defining it here +// because we can't do package-scoped errors with localization, and also because +// it seems like package-scoped variables are bad in general +// https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables +// In the future it would be good to implement some of the recommendations of +// that article. For now, if we don't need an error to be a sentinel, we will just +// define it inline. This has implications for error messages that pop up everywhere +// in that we'll be duplicating the default values. We may need to look at +// having a default localisation bundle defined, and just using keys-only when +// localising things in the code. +func (gui *Gui) GenerateSentinelErrors() { + gui.Errors = SentinelErrors{ + ErrSubProcess: errors.New("running subprocess"), + ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles", "No changed files")), + } +} // Gui wraps the gocui Gui object which handles rendering and events type Gui struct { @@ -41,6 +59,7 @@ type Gui struct { SubProcess *exec.Cmd State guiState Tr *i18n.Localizer + Errors SentinelErrors } type guiState struct { @@ -70,17 +89,21 @@ func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *comm Conflicts: make([]commands.Conflict, 0), EditHistory: stack.New(), Platform: *oSCommand.Platform, - Version: "test version", // TODO: send version in + Version: version, } - return &Gui{ + gui := &Gui{ Log: log, GitCommand: gitCommand, OSCommand: oSCommand, Version: version, State: initialState, Tr: tr, - }, nil + } + + gui.GenerateSentinelErrors() + + return gui, nil } func (gui *Gui) scrollUpMain(g *gocui.Gui, v *gocui.View) error { @@ -313,7 +336,7 @@ func (gui *Gui) RunWithSubprocesses() { if err := gui.Run(); err != nil { if err == gocui.ErrQuit { break - } else if err == ErrSubProcess { + } else if err == gui.Errors.ErrSubProcess { gui.SubProcess.Stdin = os.Stdin gui.SubProcess.Stdout = os.Stdout gui.SubProcess.Stderr = os.Stderr From 883f436b0f0ab6d118563da4840f57a74142e29b Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 16:12:21 +0200 Subject: [PATCH 14/45] can't go any further because of an error --- pkg/app/app.go | 2 +- pkg/gui/files_panel.go | 2 +- pkg/i18n/dutch.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index f4311bb03..3e227a395 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -51,7 +51,7 @@ func NewApp(config config.AppConfigurer) (*App, error) { return nil, err } - app.Tr, err = i18n.NewLocalizer() + app.Tr, err = i18n.NewLocalizer(app.Log) if err != nil { return nil, err } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 2bf8e754b..aaff46949 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -196,7 +196,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { - return gui.createErrorPanel(g, "There are no staged files to commit") + return gui.createErrorPanel(g, tr.SLocalize("NoStagedFilesCommit", "There are no staged files to commit")) } commitMessageView := gui.getCommitMessageView(g) g.Update(func(g *gocui.Gui) error { diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index da3406ee8..e6ef4ff7e 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -6,7 +6,7 @@ import ( ) // addDutch will add all dutch translations -func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { +func addDutch(i18nObject *i18n.Bundle) { // add the translations i18nObject.AddMessages(language.Dutch, @@ -91,9 +91,9 @@ func addDutch(i18nObject *i18n.Bundle) *i18n.Bundle { }, &i18n.Message{ ID: "CantIgnoreTrackFiles", Other: "Kan gevolgde bestanden niet negeren", + }, &i18n.Message{ + ID: "NoStagedFilesCommit", + Other: "Er zijn geen staged bestanden om te commiten", }, ) - - // return the new i18nObject - return i18nObject } From 38a1a00cf158a9c1fd9c70ec44f932491ab1f26f Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 16:38:25 +0200 Subject: [PATCH 15/45] Fixed comment from myself on issue: 137 --- pkg/gui/files_panel.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 6c62240a7..f65dde439 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -13,7 +13,6 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/i18n" ) func (gui *Gui) stagedFiles() []commands.File { @@ -91,7 +90,7 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { return err } gui.SubProcess = sub - return ErrSubProcess + return nil } func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) { @@ -188,7 +187,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { - return gui.createErrorPanel(g, tr.SLocalize("NoStagedFilesCommit", "There are no staged files to commit")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesCommit", "There are no staged files to commit")) } commitMessageView := gui.getCommitMessageView(g) g.Update(func(g *gocui.Gui) error { @@ -217,7 +216,7 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) error { } gui.SubProcess = sub g.Update(func(g *gocui.Gui) error { - return ErrSubProcess + return nil }) return nil } @@ -236,7 +235,7 @@ func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) ( } if sub != nil { gui.SubProcess = sub - return ErrSubProcess + return nil } return nil } From be3f5846e48c6f775496fd9f0a6827ae1356bba7 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 21:06:50 +0200 Subject: [PATCH 16/45] Added more translations --- pkg/gui/files_panel.go | 28 ++++++++++++++++++---------- pkg/i18n/dutch.go | 20 +++++++++++++++++++- pkg/i18n/i18n.go | 11 +++++++++++ 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index f65dde439..b5296900f 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -115,11 +115,19 @@ func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { } var deleteVerb string if file.Tracked { - deleteVerb = "checkout" + deleteVerb = gui.Tr.SLocalize("checkout", "checkout") } else { - deleteVerb = "delete" + deleteVerb = gui.Tr.SLocalize("delete", "delete") } - return gui.createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error { + message := gui.Tr.TemplateLocalize( + "SureTo", + "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?", + map[string]interface{}{ + "deleteVerb": deleteVerb, + "fileName": file.Name, + }, + ) + return gui.createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.RemoveFile(file); err != nil { panic(err) } @@ -187,7 +195,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { - return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesCommit", "There are no staged files to commit")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit", "There are no staged files to commit")) } commitMessageView := gui.getCommitMessageView(g) g.Update(func(g *gocui.Gui) error { @@ -202,7 +210,7 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { // their editor rather than via the popup panel func (gui *Gui) handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { - return gui.createErrorPanel(g, "There are no staged files to commit") + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit", "There are no staged files to commit")) } gui.PrepareSubProcess(g, "git", "commit") return nil @@ -300,7 +308,7 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { if err != gui.Errors.ErrNoFiles { return "", err } - return "", gui.renderString(g, "main", "No file to display") + return "", gui.renderString(g, "main", gui.Tr.SLocalize("NoFilesDisplay", "No file to display")) } cat, err := gui.GitCommand.CatFile(item.Name) if err != nil { @@ -327,7 +335,7 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { - gui.createMessagePanel(g, v, "", "Pulling...") + gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PullWait", "Pulling...")) go func() { if err := gui.GitCommand.Pull(); err != nil { gui.createErrorPanel(g, err.Error()) @@ -342,7 +350,7 @@ func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { - gui.createMessagePanel(g, v, "", "Pushing...") + gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PushWait", "Pushing...")) go func() { branchName := gui.State.Branches[0].Name if err := gui.GitCommand.Push(branchName); err != nil { @@ -369,7 +377,7 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { return nil } if !file.HasMergeConflicts { - return gui.createErrorPanel(g, "This file has no merge conflicts") + return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons", "This file has no merge conflicts")) } gui.switchFocus(g, v, mergeView) return gui.refreshMergePanel(g) @@ -385,7 +393,7 @@ func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleResetHard(g *gocui.Gui, v *gocui.View) error { - return gui.createConfirmationPanel(g, v, "Clear file panel", "Are you sure you want `reset --hard HEAD`? You may lose changes", func(g *gocui.Gui, v *gocui.View) error { + return gui.createConfirmationPanel(g, v, "Clear file panel", gui.Tr.SLocalize("SureResetHardHead", "Are you sure you want `reset --hard HEAD`? You may lose changes"), func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.ResetHard(); err != nil { gui.createErrorPanel(g, err.Error()) } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index e6ef4ff7e..1ad7b3b26 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -92,8 +92,26 @@ func addDutch(i18nObject *i18n.Bundle) { ID: "CantIgnoreTrackFiles", Other: "Kan gevolgde bestanden niet negeren", }, &i18n.Message{ - ID: "NoStagedFilesCommit", + ID: "NoStagedFilesToCommit", Other: "Er zijn geen staged bestanden om te commiten", + }, &i18n.Message{ + ID: "NoFilesDisplay", + Other: "Geen bestanden om te laten zien", + }, &i18n.Message{ + ID: "PullWait", + Other: "Pulling...", + }, &i18n.Message{ + ID: "PushWait", + Other: "Pushing...", + }, &i18n.Message{ + ID: "FileNoMergeCons", + Other: "Dit bestand heeft geen merge conflicten", + }, &i18n.Message{ + ID: "SureResetHardHead", + Other: "Weet je het zeker dat je `reset --hard HEAD` wil uitvoeren? het kan dat je hierdoor bestanden verliest", + }, &i18n.Message{ + ID: "SureTo", + Other: "Weet je het zeker dat je {{.fileName}} wilt {{.deleteVerb}} (je veranderingen zullen worden verwijdert)", }, ) } diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index f54cb9d80..5a09d6d66 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -60,6 +60,17 @@ func (l *Localizer) SLocalize(ID string, Other string) string { }) } +// TemplateLocalize allows the Other input to be dynamic +func (l *Localizer) TemplateLocalize(ID string, Other string, TemplateData map[string]interface{}) string { + return l.Localize(&i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: ID, + Other: Other, + }, + TemplateData: TemplateData, + }) +} + // GetLanguage returns the currently selected language, e.g 'en' func (l *Localizer) GetLanguage() string { return l.language From 3dba246029957ef20828e7ad95a9dbd8f0318b22 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 22:29:17 +0200 Subject: [PATCH 17/45] Added translations for files_panel.go and fixed some typos --- pkg/gui/branches_panel.go | 49 ++++++++++++++++++++++++++------------- pkg/i18n/dutch.go | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index c4786d39f..05334894b 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -12,7 +12,7 @@ import ( func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { index := gui.getItemPosition(v) if index == 0 { - return gui.createErrorPanel(g, "You have already checked out this branch") + return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch", "You have already checked out this branch")) } branch := gui.getSelectedBranch(v) if err := gui.GitCommand.Checkout(branch.Name, false); err != nil { @@ -23,7 +23,9 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error { branch := gui.getSelectedBranch(v) - return gui.createConfirmationPanel(g, v, "Force Checkout Branch", "Are you sure you want force checkout? You will lose all local changes", func(g *gocui.Gui, v *gocui.View) error { + message := gui.Tr.SLocalize("SureForceCheckout", "Are you sure you want force checkout? You will lose all local changes") + title := gui.Tr.SLocalize("ForceCheckoutBranch", "Force Checkout Branch") + return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.Checkout(branch.Name, true); err != nil { gui.createErrorPanel(g, err.Error()) } @@ -32,7 +34,7 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error { - gui.createPromptPanel(g, v, "Branch Name:", func(g *gocui.Gui, v *gocui.View) error { + gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName", "Branch Name")+":", func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.Checkout(gui.trimmedContent(v), false); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -43,7 +45,14 @@ func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error { branch := gui.State.Branches[0] - gui.createPromptPanel(g, v, "New Branch Name (Branch is off of "+branch.Name+")", func(g *gocui.Gui, v *gocui.View) error { + message := gui.Tr.TemplateLocalize( + "NewBranchNameBranchOff", + "New Branch Name (Branch is off of {{.branchName}})", + map[string]interface{}{ + "branchName": branch.Name, + }, + ) + gui.createPromptPanel(g, v, message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.NewBranch(gui.trimmedContent(v)); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -57,9 +66,17 @@ func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { checkedOutBranch := gui.State.Branches[0] selectedBranch := gui.getSelectedBranch(v) if checkedOutBranch.Name == selectedBranch.Name { - return gui.createErrorPanel(g, "You cannot delete the checked out branch!") + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch", "You cannot delete the checked out branch!")) } - return gui.createConfirmationPanel(g, v, "Delete Branch", "Are you sure you want delete the branch "+selectedBranch.Name+" ?", func(g *gocui.Gui, v *gocui.View) error { + message := gui.Tr.TemplateLocalize( + "DeleteBranchMessage", + "Are you sure you want delete the branch {{.selectedBranchName}} ?", + map[string]interface{}{ + "selectedBranchName": selectedBranch.Name, + }, + ) + title := gui.Tr.SLocalize("DeleteBranch", "Delete Branch") + return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.DeleteBranch(selectedBranch.Name); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -72,7 +89,7 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { selectedBranch := gui.getSelectedBranch(v) defer gui.refreshSidePanels(g) if checkedOutBranch.Name == selectedBranch.Name { - return gui.createErrorPanel(g, "You cannot merge a branch into itself") + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself", "You cannot merge a branch into itself")) } if err := gui.GitCommand.Merge(selectedBranch.Name); err != nil { return gui.createErrorPanel(g, err.Error()) @@ -87,13 +104,13 @@ func (gui *Gui) getSelectedBranch(v *gocui.View) commands.Branch { func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "space": "checkout", - "f": "force checkout", - "m": "merge", - "c": "checkout by name", - "n": "new branch", - "d": "delete branch", - "← → ↑ ↓": "navigate", + "space": gui.Tr.SLocalize("checkout", "checkout"), + "f": gui.Tr.SLocalize("forceCheckout", "force checkout"), + "m": gui.Tr.SLocalize("merge", "merge"), + "c": gui.Tr.SLocalize("checkoutByName", "checkout by name"), + "n": gui.Tr.SLocalize("newBranch", "new branch"), + "d": gui.Tr.SLocalize("deleteBranch", "delete branch"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), }) } @@ -104,13 +121,13 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { } // This really shouldn't happen: there should always be a master branch if len(gui.State.Branches) == 0 { - return gui.renderString(g, "main", "No branches for this repo") + return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo", "No branches for this repo")) } go func() { branch := gui.getSelectedBranch(v) diff, err := gui.GitCommand.GetBranchGraph(branch.Name) if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { - diff = "There is no tracking for this branch" + diff = gui.Tr.SLocalize("NoTrackingThisRepo", "There is no tracking for this branch") } gui.renderString(g, "main", diff) }() diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 1ad7b3b26..f383b783b 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -112,6 +112,54 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "SureTo", Other: "Weet je het zeker dat je {{.fileName}} wilt {{.deleteVerb}} (je veranderingen zullen worden verwijdert)", + }, &i18n.Message{ + ID: "AlreadyCheckedOutBranch", + Other: "Je hebt uitgecheckt op deze branch", + }, &i18n.Message{ + ID: "SureForceCheckout", + Other: "Weet je zeker dat je het uitchecken wil forceren? al je locale verandering zullen worden verwijdert", + }, &i18n.Message{ + ID: "ForceCheckoutBranch", + Other: "Forceer uitchecken op deze branch", + }, &i18n.Message{ + ID: "BranchName", + Other: "Branch naam", + }, &i18n.Message{ + ID: "NewBranchNameBranchOff", + Other: "Nieuw branch naam (Branch is afgeleid van {{.branchName}})", + }, &i18n.Message{ + ID: "CantDeleteCheckOutBranch", + Other: "Je kan een uitgecheckte branch niet verwijderen!", + }, &i18n.Message{ + ID: "DeleteBranch", + Other: "Verwijder branch", + }, &i18n.Message{ + ID: "DeleteBranchMessage", + Other: "Weet je zeker dat je {{.selectedBranchName}} branch wil verwijderen?", + }, &i18n.Message{ + ID: "CantMergeBranchIntoItself", + Other: "Je kan niet een branch in zichzelf mergen", + }, &i18n.Message{ + ID: "forceCheckout", + Other: "forceren checkout", + }, &i18n.Message{ + ID: "merge", + Other: "merge", + }, &i18n.Message{ + ID: "checkoutByName", + Other: "uitchecken bij naam", + }, &i18n.Message{ + ID: "newBranch", + Other: "nieuwe branch", + }, &i18n.Message{ + ID: "deleteBranch", + Other: "verwijder branch", + }, &i18n.Message{ + ID: "NoBranchesThisRepo", + Other: "Geen branches voor deze repo", + }, &i18n.Message{ + ID: "NoTrackingThisRepo", + Other: "deze branch wordt niet gevolgd", }, ) } From 8418fa17a536749dc4b9cb8d49862c7e04368e5f Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Wed, 15 Aug 2018 09:15:31 +0200 Subject: [PATCH 18/45] Fully translated pkg/gui/commit_message_panel.go --- pkg/gui/commit_message_panel.go | 12 ++++++++++-- pkg/i18n/dutch.go | 6 ++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 599c80186..086d4f287 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -7,7 +7,7 @@ import ( func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { message := gui.trimmedContent(v) if message == "" { - return gui.createErrorPanel(g, "You cannot commit without a commit message") + return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr", "You cannot commit without a commit message")) } sub, err := gui.GitCommand.Commit(g, message) if err != nil { @@ -48,5 +48,13 @@ func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { - return gui.renderString(g, "options", "esc: close, enter: confirm") + message := gui.Tr.TemplateLocalize( + "CloseConfirm", + "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm", + map[string]interface{}{ + "keyBindClose": "esc", + "keyBindConfirm": "enter", + }, + ) + return gui.renderString(g, "options", message) } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index f383b783b..e44a85268 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -160,6 +160,12 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "NoTrackingThisRepo", Other: "deze branch wordt niet gevolgd", + }, &i18n.Message{ + ID: "CommitWithoutMessageErr", + Other: "Je kan geen commit maken zonder commit bericht", + }, &i18n.Message{ + ID: "CloseConfirm", + Other: "{{.keyBindClose}}: Sluiten, {{.keyBindConfirm}}: Bevestigen", }, ) } From d12cc5a74e770ed99f4cc57bf3f4f9d34c499181 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Wed, 15 Aug 2018 10:30:29 +0200 Subject: [PATCH 19/45] Fully translated pkg/gui/commits_panel.go --- pkg/gui/commits_panel.go | 35 +++++++++++++++-------------- pkg/i18n/dutch.go | 48 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 4f5ae2afc..be1dc033a 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -39,7 +39,7 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { } func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { - return gui.createConfirmationPanel(g, commitView, "Reset To Commit", "Are you sure you want to reset to this commit?", func(g *gocui.Gui, v *gocui.View) error { + return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit", "Reset To Commit"), gui.Tr.SLocalize("SureResetThisCommit", "Are you sure you want to reset to this commit?"), func(g *gocui.Gui, v *gocui.View) error { commit, err := gui.getSelectedCommit(g) if err != nil { panic(err) @@ -60,11 +60,11 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "s": "squash down", - "r": "rename", - "g": "reset to this commit", - "f": "fixup commit", - "← → ↑ ↓": "navigate", + "s": gui.Tr.SLocalize("squashDown", "squash down"), + "r": gui.Tr.SLocalize("rename", "rename"), + "g": gui.Tr.SLocalize("resetToThisCommit", "reset to this commit"), + "f": gui.Tr.SLocalize("FixupCommit", "fixup commit"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), }) } @@ -74,10 +74,10 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { } commit, err := gui.getSelectedCommit(g) if err != nil { - if err != errors.New("No commits for this branch") { + if err != errors.New(gui.Tr.SLocalize("NoCommitsThisBranch", "No commits for this branch")) { return err } - return gui.renderString(g, "main", "No commits for this branch") + return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch", "No commits for this branch")) } commitText := gui.GitCommand.Show(commit.Sha) return gui.renderString(g, "main", commitText) @@ -85,10 +85,10 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { if gui.getItemPosition(v) != 0 { - return gui.createErrorPanel(g, "Can only squash topmost commit") + return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit", "Can only squash topmost commit")) } if len(gui.State.Commits) == 1 { - return gui.createErrorPanel(g, "You have no commits to squash with") + return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash", "You have no commits to squash with")) } commit, err := gui.getSelectedCommit(g) if err != nil { @@ -116,17 +116,18 @@ func (gui *Gui) anyUnStagedChanges(files []commands.File) bool { func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { if len(gui.State.Commits) == 1 { - return gui.createErrorPanel(g, "You have no commits to squash with") + return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash", "You have no commits to squash with")) } if gui.anyUnStagedChanges(gui.State.Files) { - return gui.createErrorPanel(g, "Can't fixup while there are unstaged changes") + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges", "Can't fixup while there are unstaged changes")) } branch := gui.State.Branches[0] commit, err := gui.getSelectedCommit(g) if err != nil { return err } - gui.createConfirmationPanel(g, v, "Fixup", "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one", func(g *gocui.Gui, v *gocui.View) error { + message := gui.Tr.SLocalize("SureFixupThisCommit", "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one") + gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup", "Fixup"), message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.SquashFixupCommit(branch.Name, commit.Sha); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -140,9 +141,9 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error { if gui.getItemPosition(v) != 0 { - return gui.createErrorPanel(g, "Can only rename topmost commit") + return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit", "Can only rename topmost commit")) } - gui.createPromptPanel(g, v, "Rename Commit", func(g *gocui.Gui, v *gocui.View) error { + gui.createPromptPanel(g, v, gui.Tr.SLocalize("RenameCommit", "Rename Commit"), func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.RenameCommit(v.Buffer()); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -160,11 +161,11 @@ func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) { panic(err) } if len(gui.State.Commits) == 0 { - return commands.Commit{}, errors.New("No commits for this branch") + return commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch", "No commits for this branch")) } lineNumber := gui.getItemPosition(v) if lineNumber > len(gui.State.Commits)-1 { - gui.Log.Info("potential error in getSelected Commit (mismatched ui and state)", gui.State.Commits, lineNumber) + gui.Log.Info(gui.Tr.SLocalize("PotentialErrInGetselectedCommit", "potential error in getSelected Commit (mismatched ui and state)"), gui.State.Commits, lineNumber) return gui.State.Commits[len(gui.State.Commits)-1], nil } return gui.State.Commits[lineNumber], nil diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index e44a85268..ccc285386 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -166,6 +166,54 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "CloseConfirm", Other: "{{.keyBindClose}}: Sluiten, {{.keyBindConfirm}}: Bevestigen", + }, &i18n.Message{ + ID: "SureResetThisCommit", + Other: "Weet je het zeker dat je wil resetten naar deze commit?", + }, &i18n.Message{ + ID: "ResetToCommit", + Other: "Reset Naar Commit", + }, &i18n.Message{ + ID: "squashDown", + Other: "squash beneden", + }, &i18n.Message{ + ID: "rename", + Other: "hernoem", + }, &i18n.Message{ + ID: "resetToThisCommit", + Other: "reset naar deze commit", + }, &i18n.Message{ + ID: "FixupCommit", + Other: "Fixup commit", + }, &i18n.Message{ + ID: "NoCommitsThisBranch", + Other: "Er zijn geen commits voor deze branch", + }, &i18n.Message{ + ID: "OnlySquashTopmostCommit", + Other: "Kan alleen bovenste commit squashen", + }, &i18n.Message{ + ID: "YouNoCommitsToSquash", + Other: "Je hebt geen commits om mee te squashen", + }, &i18n.Message{ + ID: "CantFixupWhileUnstagedChanges", + Other: "Kan geen Fixup uitvoeren op unstaged veranderingen", + }, &i18n.Message{ + ID: "Fixup", + Other: "Fixup", + }, &i18n.Message{ + ID: "SureFixupThisCommit", + Other: "Weet je zeker dat je fixup wil uitvoeren op deze commit? De commit hieronder zol worden squashed in deze", + }, &i18n.Message{ + ID: "OnlyRenameTopCommit", + Other: "Je kan alleen de bovenste commit hernoemen", + }, &i18n.Message{ + ID: "RenameCommit", + Other: "Hernoem Commit", + }, &i18n.Message{ + ID: "PotentialErrInGetselectedCommit", + Other: "Er is mogelijk een error in getSelected Commit (geen match tussen ui en state)", + }, &i18n.Message{ + ID: "NoCommitsThisBranch", + Other: "Geen commits voor deze branch", }, ) } From 7e926cf41da55461fbd02d2a4ce4e3c2318db0a8 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Wed, 15 Aug 2018 10:53:05 +0200 Subject: [PATCH 20/45] Added translation for pkg/gui/ confirmation_panel.go gui.go merge_panel.go --- pkg/gui/confirmation_panel.go | 14 +++++++++++--- pkg/gui/gui.go | 2 +- pkg/gui/merge_panel.go | 10 +++++----- pkg/i18n/dutch.go | 26 +++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 3bec18419..0a1cd0c5b 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -113,7 +113,15 @@ func (gui *Gui) handleNewline(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { - gui.renderString(g, "options", "esc: close, enter: confirm") + actions := gui.Tr.TemplateLocalize( + "CloseConfirm", + "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm", + map[string]interface{}{ + "keyBindClose": "esc", + "keyBindConfirm": "enter", + }, + ) + gui.renderString(g, "options", actions) if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm)); err != nil { return err } @@ -131,7 +139,7 @@ func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error { currentView := g.CurrentView() colorFunction := color.New(color.FgRed).SprintFunc() coloredMessage := colorFunction(strings.TrimSpace(message)) - return gui.createConfirmationPanel(g, currentView, "Error", coloredMessage, nil, nil) + return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error", "Error"), coloredMessage, nil, nil) } func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { @@ -143,7 +151,7 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { return nil } - gui.Log.Info("resizing popup panel") + gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel", "resizing popup panel")) _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) return err } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 11c6cae74..5aabca8a8 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -44,7 +44,7 @@ type SentinelErrors struct { // localising things in the code. func (gui *Gui) GenerateSentinelErrors() { gui.Errors = SentinelErrors{ - ErrSubProcess: errors.New("running subprocess"), + ErrSubProcess: errors.New(gui.Tr.SLocalize("RunningSubprocess", "running subprocess")), ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles", "No changed files")), } } diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 81e37f593..213bac739 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -232,11 +232,11 @@ func (gui *Gui) switchToMerging(g *gocui.Gui) error { func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "↑ ↓": "select hunk", - "← →": "navigate conflicts", - "space": "pick hunk", - "b": "pick both hunks", - "z": "undo", + "↑ ↓": gui.Tr.SLocalize("selectHunk", "select hunk"), + "← →": gui.Tr.SLocalize("navigateConflicts", "navigate conflicts"), + "space": gui.Tr.SLocalize("pickHunk", "pick hunk"), + "b": gui.Tr.SLocalize("pickBothHunks", "pick both hunks"), + "z": gui.Tr.SLocalize("undo", "undo"), }) } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index ccc285386..1f59529b0 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -141,7 +141,7 @@ func addDutch(i18nObject *i18n.Bundle) { Other: "Je kan niet een branch in zichzelf mergen", }, &i18n.Message{ ID: "forceCheckout", - Other: "forceren checkout", + Other: "forceer checkout", }, &i18n.Message{ ID: "merge", Other: "merge", @@ -214,6 +214,30 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "NoCommitsThisBranch", Other: "Geen commits voor deze branch", + }, &i18n.Message{ + ID: "Error", + Other: "Error", + }, &i18n.Message{ + ID: "resizingPopupPanel", + Other: "resizen popup paneel", + }, &i18n.Message{ + ID: "RunningSubprocess", + Other: "subprocess lopend", + }, &i18n.Message{ + ID: "selectHunk", + Other: "selecteer Hunk", + }, &i18n.Message{ + ID: "navigateConflicts", + Other: "navigeer conflicts", + }, &i18n.Message{ + ID: "pickHunk", + Other: "kies Hunk", + }, &i18n.Message{ + ID: "pickBothHunks", + Other: "kies bijde hunks", + }, &i18n.Message{ + ID: "undo", + Other: "ongedaan maken", }, ) } From 295093a432f991869abc52bc7cb00addd0032bf1 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Wed, 15 Aug 2018 11:12:46 +0200 Subject: [PATCH 21/45] Translated pkg/gui/stash_panel.go --- pkg/gui/stash_panel.go | 27 ++++++++++++++++++--------- pkg/i18n/dutch.go | 27 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index b4fb11902..96d7baba7 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -33,10 +33,10 @@ func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { func (gui *Gui) renderStashOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "space": "apply", - "g": "pop", - "d": "drop", - "← → ↑ ↓": "navigate", + "space": gui.Tr.SLocalize("apply", "apply"), + "g": gui.Tr.SLocalize("pop", "pop"), + "d": gui.Tr.SLocalize("drop", "drop"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), }) } @@ -47,7 +47,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { go func() { stashEntry := gui.getSelectedStashEntry(v) if stashEntry == nil { - gui.renderString(g, "main", "No stash entries") + gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries", "No stash entries")) return } diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index) @@ -65,7 +65,9 @@ func (gui *Gui) handleStashPop(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleStashDrop(g *gocui.Gui, v *gocui.View) error { - return gui.createConfirmationPanel(g, v, "Stash drop", "Are you sure you want to drop this stash entry?", func(g *gocui.Gui, v *gocui.View) error { + title := gui.Tr.SLocalize("StashDrop", "Stash drop") + message := gui.Tr.SLocalize("SureDropStashEntry", "Are you sure you want to drop this stash entry?") + return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { return gui.stashDo(g, v, "drop") }, nil) } @@ -73,7 +75,14 @@ func (gui *Gui) handleStashDrop(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { stashEntry := gui.getSelectedStashEntry(v) if stashEntry == nil { - return gui.createErrorPanel(g, "No stash to "+method) + errorMessage := gui.Tr.TemplateLocalize( + "NoStashTo", + "No stash to {{.method}}", + map[string]interface{}{ + "method": method, + }, + ) + return gui.createErrorPanel(g, errorMessage) } if err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil { gui.createErrorPanel(g, err.Error()) @@ -84,9 +93,9 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error { if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 { - return gui.createErrorPanel(g, "You have no tracked/staged files to stash") + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoTrackedStagedFilesStash", "You have no tracked/staged files to stash")) } - gui.createPromptPanel(g, filesView, "Stash changes", func(g *gocui.Gui, v *gocui.View) error { + gui.createPromptPanel(g, filesView, gui.Tr.SLocalize("StashChanges", "Stash changes"), func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.StashSave(gui.trimmedContent(v)); err != nil { gui.createErrorPanel(g, err.Error()) } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 1f59529b0..e970a0a2d 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -238,6 +238,33 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "undo", Other: "ongedaan maken", + }, &i18n.Message{ + ID: "pop", + Other: "pop", + }, &i18n.Message{ + ID: "drop", + Other: "drop", + }, &i18n.Message{ + ID: "apply", + Other: "toepassen", + }, &i18n.Message{ + ID: "NoStashEntries", + Other: "Geen stash items", + }, &i18n.Message{ + ID: "StashDrop", + Other: "Stash drop", + }, &i18n.Message{ + ID: "SureDropStashEntry", + Other: "Weet je het zeker dat je deze stash entry wil laten vallen?", + }, &i18n.Message{ + ID: "NoStashTo", + Other: "Geen stash voor {{.method}}", + }, &i18n.Message{ + ID: "NoTrackedStagedFilesStash", + Other: "Je hebt geen tracked/staged bestanden om te laten stashen", + }, &i18n.Message{ + ID: "StashChanges", + Other: "Stash veranderingen", }, ) } From 50b41bfcccf1f56076da487cdbe7994decbb9e6b Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Wed, 15 Aug 2018 11:49:43 +0200 Subject: [PATCH 22/45] Translated pkg/gui/view_helpers.go --- pkg/gui/view_helpers.go | 38 +++++++++++++++++++++++++++++++++----- pkg/i18n/dutch.go | 12 ++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 331e27975..48ad73962 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -29,7 +29,14 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { break } if i == len(cyclableViews)-1 { - gui.Log.Info(v.Name() + " is not in the list of views") + message := gui.Tr.TemplateLocalize( + "IssntListOfViews", + "{{.name}} is not in the list of views", + map[string]interface{}{ + "name": v.Name(), + }, + ) + gui.Log.Info(message) return nil } } @@ -52,7 +59,14 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { break } if i == len(cyclableViews)-1 { - gui.Log.Info(v.Name() + " is not in the list of views") + message := gui.Tr.TemplateLocalize( + "IssntListOfViews", + "{{.name}} is not in the list of views", + map[string]interface{}{ + "name": v.Name(), + }, + ) + gui.Log.Info(message) return nil } } @@ -87,7 +101,7 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { case "stash": return gui.handleStashEntrySelect(g, v) default: - panic("No view matching newLineFocused switch statement") + panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement", "No view matching newLineFocused switch statement")) } } @@ -105,11 +119,25 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { // we should never stack confirmation panels if oldView != nil && oldView.Name() != "confirmation" { oldView.Highlight = false - gui.Log.Info("setting previous view to:", oldView.Name()) + message := gui.Tr.TemplateLocalize( + "settingPreviewsViewTo", + "setting previous view to: {{.oldViewName}}", + map[string]interface{}{ + "oldViewName": oldView.Name(), + }, + ) + gui.Log.Info(message) gui.State.PreviousView = oldView.Name() } newView.Highlight = true - gui.Log.Info("new focused view is " + newView.Name()) + message := gui.Tr.TemplateLocalize( + "newFocusedViewIs", + "new focused view is {{.newFocusedView}}", + map[string]interface{}{ + "newFocusedView": newView.Name(), + }, + ) + gui.Log.Info(message) if _, err := g.SetCurrentView(newView.Name()); err != nil { return err } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index e970a0a2d..85a7a72d6 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -265,6 +265,18 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "StashChanges", Other: "Stash veranderingen", + }, &i18n.Message{ + ID: "IssntListOfViews", + Other: "{{.name}} is niet in de lijst van weergaves", + }, &i18n.Message{ + ID: "NoViewMachingNewLineFocusedSwitchStatement", + Other: "Er machen geen weergave met de newLineFocused switch declaratie", + }, &i18n.Message{ + ID: "settingPreviewsViewTo", + Other: "vorige weergave instellen op: {{.oldViewName}}", + }, &i18n.Message{ + ID: "newFocusedViewIs", + Other: "nieuw gefocussed weergave is {{.newFocusedView}}", }, ) } From 7c33c0293082161aa0e5e79115e30289175a26fd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 15 Aug 2018 21:43:31 +1000 Subject: [PATCH 23/45] dont panic if unable to close confirmation prompt --- pkg/gui/confirmation_panel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 3bec18419..2dd721c81 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -79,7 +79,7 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t // delete the existing confirmation panel if it exists if view, _ := g.View("confirmation"); view != nil { if err := gui.closeConfirmationPrompt(g); err != nil { - panic(err) + gui.Log.Error("Could not close confirmation prompt: ", err.Error()) } } x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, prompt) From 8d99b400fd9b5ba050e1c8f87b5427482bd50c7b Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 15 Aug 2018 21:49:38 +1000 Subject: [PATCH 24/45] factor out code for new popup panels --- pkg/gui/confirmation_panel.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 2dd721c81..5018d0a9f 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -57,7 +57,7 @@ func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int } func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error { - g.SetViewOnBottom("commitMessage") + gui.onNewPopupPanel() // only need to fit one line x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, "") if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { @@ -73,8 +73,12 @@ func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title s return nil } +func (gui *Gui) onNewPopupPanel() { + gui.g.SetViewOnBottom("commitMessage") +} + func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { - g.SetViewOnBottom("commitMessage") + gui.onNewPopupPanel() g.Update(func(g *gocui.Gui) error { // delete the existing confirmation panel if it exists if view, _ := g.View("confirmation"); view != nil { From d00c46a7125180cdac01c5ff643d40fd2b06a293 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Wed, 15 Aug 2018 14:57:20 +0200 Subject: [PATCH 25/45] Added all english translations to a file and fixed some typos --- pkg/gui/branches_panel.go | 2 +- pkg/gui/commits_panel.go | 2 +- pkg/i18n/dutch.go | 9 +- pkg/i18n/english.go | 289 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 294 insertions(+), 8 deletions(-) create mode 100644 pkg/i18n/english.go diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 05334894b..b11036d48 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -127,7 +127,7 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { branch := gui.getSelectedBranch(v) diff, err := gui.GitCommand.GetBranchGraph(branch.Name) if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { - diff = gui.Tr.SLocalize("NoTrackingThisRepo", "There is no tracking for this branch") + diff = gui.Tr.SLocalize("NoTrackingThisBranch", "There is no tracking for this branch") } gui.renderString(g, "main", diff) }() diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index be1dc033a..bc12aff90 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -63,7 +63,7 @@ func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error { "s": gui.Tr.SLocalize("squashDown", "squash down"), "r": gui.Tr.SLocalize("rename", "rename"), "g": gui.Tr.SLocalize("resetToThisCommit", "reset to this commit"), - "f": gui.Tr.SLocalize("FixupCommit", "fixup commit"), + "f": gui.Tr.SLocalize("fixupCommit", "fixup commit"), "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), }) } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 85a7a72d6..7d8ae4b36 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -79,9 +79,6 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "NoChangedFiles", Other: "Geen Bestanden verandert", - }, &i18n.Message{ - ID: "NoUsernameSetErr", - Other: `Geen gebruikersnaam ingesteld. Doe: git config --global user.name "Jou Naam"`, }, &i18n.Message{ ID: "FileHasNoUnstagedChanges", Other: "Het bestand heeft geen unstaged veranderingen om toe te voegen", @@ -158,7 +155,7 @@ func addDutch(i18nObject *i18n.Bundle) { ID: "NoBranchesThisRepo", Other: "Geen branches voor deze repo", }, &i18n.Message{ - ID: "NoTrackingThisRepo", + ID: "NoTrackingThisBranch", Other: "deze branch wordt niet gevolgd", }, &i18n.Message{ ID: "CommitWithoutMessageErr", @@ -182,7 +179,7 @@ func addDutch(i18nObject *i18n.Bundle) { ID: "resetToThisCommit", Other: "reset naar deze commit", }, &i18n.Message{ - ID: "FixupCommit", + ID: "fixupCommit", Other: "Fixup commit", }, &i18n.Message{ ID: "NoCommitsThisBranch", @@ -216,7 +213,7 @@ func addDutch(i18nObject *i18n.Bundle) { Other: "Geen commits voor deze branch", }, &i18n.Message{ ID: "Error", - Other: "Error", + Other: "Fout", }, &i18n.Message{ ID: "resizingPopupPanel", Other: "resizen popup paneel", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go new file mode 100644 index 000000000..d208b13d1 --- /dev/null +++ b/pkg/i18n/english.go @@ -0,0 +1,289 @@ +/* + +About: this pakcage contains just the contents to gets started with a new translations + +Todo list when making a new translation +- Copy this file and rename it to the language you want to translate to like someLanguage.go +- Change the addEnglish() name to the language you want to translate to like addSomeLanguage() +- change the first function argument of i18nObject.AddMessages( to the language you want to translate to like language.SomeLanguage +- Remove this todo and the about section + +*/ + +package i18n + +import ( + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" +) + +func addEnglish(i18nObject *i18n.Bundle) { + + i18nObject.AddMessages(language.English, + &i18n.Message{ + ID: "NotEnoughSpace", + Other: "Not enough space to render panels", + }, &i18n.Message{ + ID: "DiffTitle", + Other: "Diff", + }, &i18n.Message{ + ID: "FilesTitle", + Other: "Files", + }, &i18n.Message{ + ID: "BranchesTitle", + Other: "Branches", + }, &i18n.Message{ + ID: "CommitsTitle", + Other: "Commits", + }, &i18n.Message{ + ID: "StashTitle", + Other: "Stash", + }, &i18n.Message{ + ID: "CommitMessage", + Other: "Commit message", + }, &i18n.Message{ + ID: "CommitChanges", + Other: "commit changes", + }, &i18n.Message{ + ID: "StatusTitle", + Other: "Status", + }, &i18n.Message{ + ID: "navigate", + Other: "navigate", + }, &i18n.Message{ + ID: "stashFiles", + Other: "stash files", + }, &i18n.Message{ + ID: "open", + Other: "open", + }, &i18n.Message{ + ID: "ignore", + Other: "ignore", + }, &i18n.Message{ + ID: "delete", + Other: "delete", + }, &i18n.Message{ + ID: "toggleStaged", + Other: "toggle staged", + }, &i18n.Message{ + ID: "refresh", + Other: "refresh", + }, &i18n.Message{ + ID: "addPatch", + Other: "add path", + }, &i18n.Message{ + ID: "edit", + Other: "edit", + }, &i18n.Message{ + ID: "scroll", + Other: "scroll", + }, &i18n.Message{ + ID: "abortMerge", + Other: "abort merge", + }, &i18n.Message{ + ID: "resolveMergeConflicts", + Other: "resolve merge conflicts", + }, &i18n.Message{ + ID: "checkout", + Other: "checkout", + }, &i18n.Message{ + ID: "NoChangedFiles", + Other: "No changed files", + }, &i18n.Message{ + ID: "FileHasNoUnstagedChanges", + Other: "File has no unstaged changes to add", + }, &i18n.Message{ + ID: "CannotGitAdd", + Other: "Cannot git add --patch untracked files", + }, &i18n.Message{ + ID: "CantIgnoreTrackFiles", + Other: "Cannot ignore tracked files", + }, &i18n.Message{ + ID: "NoStagedFilesToCommit", + Other: "There are no staged files to commit", + }, &i18n.Message{ + ID: "NoFilesDisplay", + Other: "No file to display", + }, &i18n.Message{ + ID: "PullWait", + Other: "Pulling...", + }, &i18n.Message{ + ID: "PushWait", + Other: "Pushing...", + }, &i18n.Message{ + ID: "FileNoMergeCons", + Other: "This file has no merge conflicts", + }, &i18n.Message{ + ID: "SureResetHardHead", + Other: "Are you sure you want `reset --hard HEAD`? You may lose changes", + }, &i18n.Message{ + ID: "SureTo", + Other: "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?", + }, &i18n.Message{ + ID: "AlreadyCheckedOutBranch", + Other: "You have already checked out this branch", + }, &i18n.Message{ + ID: "SureForceCheckout", + Other: "Are you sure you want force checkout? You will lose all local changes", + }, &i18n.Message{ + ID: "ForceCheckoutBranch", + Other: "Force Checkout Branch", + }, &i18n.Message{ + ID: "BranchName", + Other: "Branch name", + }, &i18n.Message{ + ID: "NewBranchNameBranchOff", + Other: "New Branch Name (Branch is off of {{.branchName}})", + }, &i18n.Message{ + ID: "CantDeleteCheckOutBranch", + Other: "You cannot delete the checked out branch!", + }, &i18n.Message{ + ID: "DeleteBranch", + Other: "Delete Branch", + }, &i18n.Message{ + ID: "DeleteBranchMessage", + Other: "Are you sure you want delete the branch {{.selectedBranchName}} ?", + }, &i18n.Message{ + ID: "CantMergeBranchIntoItself", + Other: "You cannot merge a branch into itself", + }, &i18n.Message{ + ID: "forceCheckout", + Other: "force checkout", + }, &i18n.Message{ + ID: "merge", + Other: "merge", + }, &i18n.Message{ + ID: "checkoutByName", + Other: "checkout by name", + }, &i18n.Message{ + ID: "newBranch", + Other: "new branch", + }, &i18n.Message{ + ID: "deleteBranch", + Other: "delete branch", + }, &i18n.Message{ + ID: "NoBranchesThisRepo", + Other: "No branches for this repo", + }, &i18n.Message{ + ID: "NoTrackingThisBranch", + Other: "There is no tracking for this branch", + }, &i18n.Message{ + ID: "CommitWithoutMessageErr", + Other: "You cannot commit without a commit message", + }, &i18n.Message{ + ID: "CloseConfirm", + Other: "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm", + }, &i18n.Message{ + ID: "SureResetThisCommit", + Other: "Are you sure you want to reset to this commit?", + }, &i18n.Message{ + ID: "ResetToCommit", + Other: "Reset To Commit", + }, &i18n.Message{ + ID: "squashDown", + Other: "squash down", + }, &i18n.Message{ + ID: "rename", + Other: "rename", + }, &i18n.Message{ + ID: "resetToThisCommit", + Other: "reset to this commit", + }, &i18n.Message{ + ID: "fixupCommit", + Other: "fixup commit", + }, &i18n.Message{ + ID: "NoCommitsThisBranch", + Other: "No commits for this branch", + }, &i18n.Message{ + ID: "OnlySquashTopmostCommit", + Other: "Can only squash topmost commit", + }, &i18n.Message{ + ID: "YouNoCommitsToSquash", + Other: "You have no commits to squash with", + }, &i18n.Message{ + ID: "CantFixupWhileUnstagedChanges", + Other: "Can't fixup while there are unstaged changes", + }, &i18n.Message{ + ID: "Fixup", + Other: "Fixup", + }, &i18n.Message{ + ID: "SureFixupThisCommit", + Other: "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one", + }, &i18n.Message{ + ID: "OnlyRenameTopCommit", + Other: "Can only rename topmost commit", + }, &i18n.Message{ + ID: "RenameCommit", + Other: "Rename Commit", + }, &i18n.Message{ + ID: "PotentialErrInGetselectedCommit", + Other: "potential error in getSelected Commit (mismatched ui and state)", + }, &i18n.Message{ + ID: "NoCommitsThisBranch", + Other: "No commits for this branch", + }, &i18n.Message{ + ID: "Error", + Other: "Error", + }, &i18n.Message{ + ID: "resizingPopupPanel", + Other: "resizing popup panel", + }, &i18n.Message{ + ID: "RunningSubprocess", + Other: "running subprocess", + }, &i18n.Message{ + ID: "selectHunk", + Other: "select hunk", + }, &i18n.Message{ + ID: "navigateConflicts", + Other: "navigate conflicts", + }, &i18n.Message{ + ID: "pickHunk", + Other: "pick hunk", + }, &i18n.Message{ + ID: "pickBothHunks", + Other: "pick both hunks", + }, &i18n.Message{ + ID: "undo", + Other: "undo", + }, &i18n.Message{ + ID: "pop", + Other: "pop", + }, &i18n.Message{ + ID: "drop", + Other: "drop", + }, &i18n.Message{ + ID: "apply", + Other: "apply", + }, &i18n.Message{ + ID: "NoStashEntries", + Other: "No stash entries", + }, &i18n.Message{ + ID: "StashDrop", + Other: "Stash drop", + }, &i18n.Message{ + ID: "SureDropStashEntry", + Other: "Are you sure you want to drop this stash entry?", + }, &i18n.Message{ + ID: "NoStashTo", + Other: "No stash to {{.method}}", + }, &i18n.Message{ + ID: "NoTrackedStagedFilesStash", + Other: "You have no tracked/staged files to stash", + }, &i18n.Message{ + ID: "StashChanges", + Other: "Stash changes", + }, &i18n.Message{ + ID: "IssntListOfViews", + Other: "{{.name}} is not in the list of views", + }, &i18n.Message{ + ID: "NoViewMachingNewLineFocusedSwitchStatement", + Other: "No view matching newLineFocused switch statement", + }, &i18n.Message{ + ID: "settingPreviewsViewTo", + Other: "setting previous view to: {{.oldViewName}}", + }, &i18n.Message{ + ID: "newFocusedViewIs", + Other: "new focused view is {{.newFocusedView}}", + }, + ) +} From 9abbfe5a43ea7b2a9758f910dd51d41ae43cf45b Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Wed, 15 Aug 2018 15:12:55 +0200 Subject: [PATCH 26/45] Fully translated pkg/gui/confirmation_panel.go --- pkg/gui/confirmation_panel.go | 9 ++++++++- pkg/i18n/dutch.go | 3 +++ pkg/i18n/english.go | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index b34200e16..9f76bc1ea 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -83,7 +83,14 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t // delete the existing confirmation panel if it exists if view, _ := g.View("confirmation"); view != nil { if err := gui.closeConfirmationPrompt(g); err != nil { - gui.Log.Error("Could not close confirmation prompt: ", err.Error()) + errMessage := gui.Tr.TemplateLocalize( + "CantCloseConfirmationPrompt", + "Could not close confirmation prompt: {{.error}}", + map[string]interface{}{ + "error": err.Error(), + }, + ) + gui.Log.Error(errMessage) } } x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, prompt) diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 7d8ae4b36..9bf542fad 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -274,6 +274,9 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "newFocusedViewIs", Other: "nieuw gefocussed weergave is {{.newFocusedView}}", + }, &i18n.Message{ + ID: "CantCloseConfirmationPrompt", + Other: "Kon de bevestiging prompt niet sluiten: {{.error}}", }, ) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index d208b13d1..d4c31ae53 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -284,6 +284,9 @@ func addEnglish(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "newFocusedViewIs", Other: "new focused view is {{.newFocusedView}}", + }, &i18n.Message{ + ID: "CantCloseConfirmationPrompt", + Other: "Could not close confirmation prompt: {{.error}}", }, ) } From adc906bb87a0d4858386d22bf0dfa9f69a68dfa0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 16 Aug 2018 10:32:52 +1000 Subject: [PATCH 27/45] add slack image file --- files/slack_rgb.png | Bin 0 -> 5804 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 files/slack_rgb.png diff --git a/files/slack_rgb.png b/files/slack_rgb.png new file mode 100644 index 0000000000000000000000000000000000000000..e400779c057d92e57f5a0c40255f2c09c7463992 GIT binary patch literal 5804 zcmZu#WmFW}+8$u&9#T4_8-^Y_hekk9Lb_q-ZYfDAX#^x>Xi1Tf?k?#D=?B;HI#|d+>;^Gk&7UtsS<>KY#ctUWvdONzAcyTzo z(*GUgUvXqDT+Li;oZM_+jx>MbnwY}e-NfkV{v`T$`=?F^r++7Mbp40b6F)956DKYn zPHwLM&&kck@;~MM53fHbf4lv~>hENbCuu}vTr5o7U@qD)n7z2vAMI$M4klI>FDy*V zy&+ux8}Xk?A%8-OsJqx$JaPTQNSp`4^?z*t;2~Up=>9kQe{1`f^+{ZDjHgWhx+rlB z7S3TY0D#a3Dl4UJjePjp*GpUOLuFHCz-BgHXb^*I4H*B-4U|)f1fc=X)I{zZ8*Br2 zJUr0~(8rzc%_LagT(>kF2^Yghj+@~kF^1M8U?(Xg5-{F0z-s8zYOr|YV>@~5yXr$> zzvGQS7-C_lOLKi9ss48$aFE(ShXe)EfPzLtGlAWBuabLTbr?a#HM94izMb1mw!3hF zPm>kVQj}-jMUz(&a=wR>IDWp=kT9qI`bFenxo}NXz`JiBSLe8hzPjvuxP+0Akdzu# z&h8bL9V=Om(t2JsXBN~RZN#vFP76$xca!i)kq=;T%!Hk{(^1MwQ+EzjvEB!bcIOws zytT_A->|}f9;AcGjmr4uY_ty?=O>^OBIOdFf`e?N?aQjVwB9*RYY>iPEX)ne>vFA#tP_u}{ggCX#M7V}k*htvZBiQ1mu?4m za}K3?2E1{(Wkc=s2$zQ-`7WiP8ZVa=u5sv8BaUxyV!>)P2<|PBdZRYJH0ptEO#bf#X z+~s3X$Sts+UW_7;7q+1_n>lo+UIDj30wv87l?84WlMBJlKR^ok>Zk1Oj(=osi?<32 zJAUt);fz?v3>d}j6o-(go%7S{rU{ZPgaJCJ+ohJItaXN+m1>^yNdIBMwwv zZidzxD}UooB-r+-uU*1oDJ5aoo0ps5Y`HhS)vZ*8TVEkF9yusYwOXIQ`z_@ETTV^D z5dMyRY@Bri)gXD!8@`7BezXCBP8|}VAV+Lr%7Zn)y`G840*)KBwUhfu9fesvptOe} zm`LP+FCl_gZ(aJu1)C)ifqg0AWu_^Rzferdq152pjMX4Ilh^tlPG`cY+-8edM8V9A z`8qam{k7-enFm5{Rj_Pt_vOWNHBWhjfgHh6hb)(YGndNp8Z_^=AQ2|Tji_EImw@F+HQJqY$T`h@@B3?ggXufR1 z9?~EtlBL4eVMWgpsBsbr*@-PHaSbu|AvCruZtRN#J zqd=G??KHo2kq;eewci?>4^buiuk=K%;KwP<>0qd%=HCyqQoASRyd0QqW+|uL3(7 zRP#8z9Jg(LjE3)dZn&mYgn73MBD*#kYF=V%-0-d=ceG?h&U`WH3aaYloGmL*{UZO7?PFD6tRAPGuuBJsaoXa61)jo_%Wf zhT9h=uZLzANj1{HUgi zm4AU(HcHEJRpuzftHFb;vE1P;BfdDBnU_nAj5g_p^ujC3wz|HCB=Yxwk(Q)pxKuhO z*ig4Gh4!uZaFLD2dzHZg?a&$u9e+Z}CF$z~gZ4z!Uq%90y`!Ji^@tlC9%zQ&=9I_= zt>;+=Bw8Q95oU9$qi3Y$&B@=vW81y*`qJ1|gWrJOxIvnZOE0BC7SFMm4g8lL*LGZW zx~sX(QO@E5Thb3K5k*?V2S)w81z z(hjJ!fQ{L)KiU2h}vG^VPKN6)7*3y{~&1@|AsP*Z=ZU6U>C znv$Jq%DaT$6tsMB1;Vx0R#(83Yi4|vyo;w$_OTg1ILDN>K#&cQphZ<66~!!ACC!$5 zc{o(s9Yi7Od&y~P4-y8J{HS+?nNz-5 zZKC{MRuC0i?aofSru;0?c}X5i^4sx-wre_(sIDrviRDCz|ZEkm}gU22>6{qw(&@R6WC?=sC^g@07|bBl_B&{vmOV)M=`F-gpgV z)*_WkTMayOKviONm9=+$?DvqNj6<-31T%uPZ#i*@6IHQOeJ?0JseA-_S}3bh%C^(??os1&L(i(`Jnym9shBg%<#RI9jC%^1 zm=*kduiJ#3N9>*p`p8aEMXr?D&X}g~iJ?WiHQk``v25A)%+FG`+G>i67Q~MkC?Qw+ z?rNy#;&nPCEgQC03wXX9H`7~4())~1Du8!>9Ma*q^T7z|d;MC_fyLNyp0k~;Euqy} z?xgX8fJ3jZMDQo4UobR99_|?ob*m$kG>Nj{^L=b>OQ_w zd$@u2!R5SvxOS?07DUt3!`*+^&Mu&{q-R)_;&YoYyDN9Jvdi~&jv_Ys_N-Vg>>>oV z4-%y6vt0=v+g2Fv`HzI;2&5RM-08YrD@XX^)O;IE5ouxXc4{^a>x$cJk7i0@f)X#k zdt0YJE;^v3oA$w*p7=GoMLU^r6tba=ANAY415Vl6rAfd z5=elQe85*7&AQ-KEhr(Vjaq|YH#`8n5uAZ=N?%Lo-ajd?{IXcdmy2+YG-7K~&;{D{ zS?xOPm@vo`ba5h!*w>Y{^a8(fB*!pIR=CnC4U+lGVALuE$O>?f{;vB@T?kD{7*h#UbWtT=CupSr<=l>)c{Jq=`S$Eos+AwzBv*$w zfITjp8Kt?R4!ZL0$f&fFiPx?R*^YwYQ^W*f@`Uvn9k`8!_ZS>?U;aMC}q3_r=lb&5E?E7!rmKx~@F#zO)s1Hos8gb?WZjezd zl((Z1-hJ9>SYeHha(B zy8^3lI&)0zPpP+hvo*5Dqa?gPTiZAot9v`cT2h0Px4x#<^F1RLc@Vml#Rl$!i$~Hs zq?NJ=7PMO4qm)wBn0p+)XJDfsp}eWu*Zou+c>cNj?)W>eYVkyZ`=aec=3C}qcGB|9 zm*LzVAvYg*Mqgppu~99QT3bE6I{|#4qqdjH<(Vk=pU_N(iNWZ(C(PT4SU(4fa777m zBM3NM^JtQLo$y&wb?ptWxpn+R2WJ9gGof--sR(h|`!)issc}&bNjOqJ1da24jSns` z*NzADQlSt7-uFeb>(2pUwPD9@qyUDP!Za8yjUOgeS!h+DBx&Qb+fQ%syaT+wRaz9} zvi-%Fc)SUTdv~qE9R@4%(uQXj?MW`MUz6yJ6ci)p#>`j_R=)r3nhk3{&bZgV8IwubZGI}#+4Yz;C0h8_)dypfY3 zp}_wg98Xs-wK$Wluk?l{y-54GMmkUCr+{^Q=(EaPEi6Q#0lRqroXSZaArx9-73H4~ zEELy0dz9`DcG#PP@p0DgzGZgxi?9{?-<%|Ga)#BV1;1zg+K)BtRFA8%fXh!WrI=?$ zjzPY(dhIWE0=<+ezBaw;5GNaXJJti`vDRyDNgvMUxyE3>5gz6Sldf_|ZHq>lt^sQLsU`^e4k!b31jBfEzuQN7YwZ18=l=66BH(*P3)D<^`oAFvqr#MlVi-H0y49^YkA zsJZ4^nuhp^He^Bq=?X0CPYq+{#b?u&sgjDhAn#*v z>Wp>0wU1-%z#Uu`4URp_KB*|b0DS?ol#vvVG|^TzAB@J{mtXt_Lgkb7_)g-YSG93x zpEX-=)AuR_^u$%Q?Me6dxm3q9vbexI*JUrcPjA}0Q8*Fls z4U5n*3`9KVp;ViVH_zSIsUp(+5n3Xx0TX+_XkDWaC4SL-RqMiRJKgPbcOPD2$%y0p zIn^-7c1F!fX{v^OE4d#I&mcPgT&SeuQ7eL|Kd)aVYJi+aK7l#iQ~U0Prj{HxgOqMa zota}z?+LM)p_Ll@YYrWIHbYDcH9^&ViQHT+lS9Q0#=}>i%Pb~9!$Qt_^wVQ2NpyW= zCjF|L3P5&FRdCNp`M$(^>2%bG7BoXIIUG!dDwm)jk3( z5#va{Uk3TehKs*(2{tf?kwcy&;%-d9)B(gFikU=qft>h$KE5&Fff)7=_=mOgw2Xw zB5*k<*EF`yS6i%zCBbdZi&Yc+)`G?m-R?=8`&U&q&U|6=?AVE?vFOcWw3= z$tf&xEij8Hrl=mj<^#J|~Y@WFwypV7$hDwb*!)Bid91 zxvgrh-+Z?kll{m!7dIU*yNl~fJSPF2dUU=hH$1k0KF+hV2*e~@R>u1zTNgyQ55}Ec zG`e6Ah~=0qPpq4@qIu5u%u;96nE&vbB3U~O5RZ znecPXoU4GCf5YUKAMMl{rm79M$nfJ5Fe@YDhr)U4aGd>v|fFD bhw?z;VRQ@ Date: Thu, 16 Aug 2018 10:37:23 +1000 Subject: [PATCH 28/45] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ba1190ce8..0990db695 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,9 @@ whichever rc file you're using). ## Contributing We love your input! Please check out the [contributing guide](CONTRIBUTING.md). +For contributor discussion about things not better discussed here in the repo, join the slack channel + +[![Slack](/files/slack_rgb.png)](https://join.slack.com/t/lazygit/shared_invite/enQtNDE3MjIwNTYyMDA0LTM3Yjk3NzdiYzhhNTA1YjM4Y2M4MWNmNDBkOTI0YTE4YjQ1ZmI2YWRhZTgwNjg2YzhhYjg3NDBlMmQyMTI5N2M) ## Work in progress This is still a work in progress so there's still bugs to iron out and as this From ee4660af97a6da7fcb0a17474a9a6d70d0d7df0b Mon Sep 17 00:00:00 2001 From: Tommy Nguyen Date: Wed, 15 Aug 2018 23:55:55 -0400 Subject: [PATCH 29/45] #158: escapes backticks, which is a problem in shells like Bash --- pkg/commands/os.go | 3 +++ pkg/commands/os_test.go | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 pkg/commands/os_test.go diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 9f9819a5a..9ccdebc56 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "runtime" + "regexp" "github.com/davecgh/go-spew/spew" @@ -170,5 +171,7 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) (*e // Quote wraps a message in platform-specific quotation marks func (c *OSCommand) Quote(message string) string { + r := regexp.MustCompile("`") + message = r.ReplaceAllString(message, "\\`") return c.Platform.escapedQuote + message + c.Platform.escapedQuote } diff --git a/pkg/commands/os_test.go b/pkg/commands/os_test.go new file mode 100644 index 000000000..a6bdbc1b1 --- /dev/null +++ b/pkg/commands/os_test.go @@ -0,0 +1,16 @@ +package commands + +import "testing" + +func TestQuote(t *testing.T) { + osCommand := &OSCommand { + Log: nil, + Platform: getPlatform(), + } + test := "hello `test`" + expected := osCommand.Platform.escapedQuote + "hello \\`test\\`" + osCommand.Platform.escapedQuote + test = osCommand.Quote(test) + if test != expected { + t.Error("Expected " + expected + ", got " + test) + } +} From db94dde11475a45a5bceafd7564c5fe3b8eacb18 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen Date: Wed, 15 Aug 2018 23:58:44 -0400 Subject: [PATCH 30/45] fix formatting --- pkg/commands/os.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 9ccdebc56..c23b48c82 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -171,7 +171,7 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) (*e // Quote wraps a message in platform-specific quotation marks func (c *OSCommand) Quote(message string) string { - r := regexp.MustCompile("`") - message = r.ReplaceAllString(message, "\\`") + r := regexp.MustCompile("`") + message = r.ReplaceAllString(message, "\\`") return c.Platform.escapedQuote + message + c.Platform.escapedQuote } From 88e1a815fe5c4ddf21779ca4a93f029b7b9b9239 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Thu, 16 Aug 2018 07:16:32 +0200 Subject: [PATCH 31/45] Fixed comment on issue #137 from @jesseduffield --- pkg/gui/branches_panel.go | 34 +++++++++----------- pkg/gui/commit_message_panel.go | 3 +- pkg/gui/commits_panel.go | 36 ++++++++++----------- pkg/gui/confirmation_panel.go | 6 ++-- pkg/gui/files_panel.go | 57 ++++++++++++++++----------------- pkg/gui/gui.go | 20 ++++++------ pkg/gui/merge_panel.go | 10 +++--- pkg/gui/stash_panel.go | 19 ++++++----- pkg/gui/view_helpers.go | 6 +--- pkg/i18n/dutch.go | 9 ++++++ pkg/i18n/english.go | 11 +++++-- pkg/i18n/i18n.go | 11 +++---- 12 files changed, 113 insertions(+), 109 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index b11036d48..2a08eb8b1 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -12,7 +12,7 @@ import ( func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { index := gui.getItemPosition(v) if index == 0 { - return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch", "You have already checked out this branch")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch")) } branch := gui.getSelectedBranch(v) if err := gui.GitCommand.Checkout(branch.Name, false); err != nil { @@ -23,8 +23,8 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error { branch := gui.getSelectedBranch(v) - message := gui.Tr.SLocalize("SureForceCheckout", "Are you sure you want force checkout? You will lose all local changes") - title := gui.Tr.SLocalize("ForceCheckoutBranch", "Force Checkout Branch") + message := gui.Tr.SLocalize("SureForceCheckout") + title := gui.Tr.SLocalize("ForceCheckoutBranch") return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.Checkout(branch.Name, true); err != nil { gui.createErrorPanel(g, err.Error()) @@ -34,7 +34,7 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error { - gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName", "Branch Name")+":", func(g *gocui.Gui, v *gocui.View) error { + gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.Checkout(gui.trimmedContent(v), false); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -47,7 +47,6 @@ func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error { branch := gui.State.Branches[0] message := gui.Tr.TemplateLocalize( "NewBranchNameBranchOff", - "New Branch Name (Branch is off of {{.branchName}})", map[string]interface{}{ "branchName": branch.Name, }, @@ -66,16 +65,15 @@ func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { checkedOutBranch := gui.State.Branches[0] selectedBranch := gui.getSelectedBranch(v) if checkedOutBranch.Name == selectedBranch.Name { - return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch", "You cannot delete the checked out branch!")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch")) } message := gui.Tr.TemplateLocalize( "DeleteBranchMessage", - "Are you sure you want delete the branch {{.selectedBranchName}} ?", map[string]interface{}{ "selectedBranchName": selectedBranch.Name, }, ) - title := gui.Tr.SLocalize("DeleteBranch", "Delete Branch") + title := gui.Tr.SLocalize("DeleteBranch") return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.DeleteBranch(selectedBranch.Name); err != nil { return gui.createErrorPanel(g, err.Error()) @@ -89,7 +87,7 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { selectedBranch := gui.getSelectedBranch(v) defer gui.refreshSidePanels(g) if checkedOutBranch.Name == selectedBranch.Name { - return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself", "You cannot merge a branch into itself")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself")) } if err := gui.GitCommand.Merge(selectedBranch.Name); err != nil { return gui.createErrorPanel(g, err.Error()) @@ -104,13 +102,13 @@ func (gui *Gui) getSelectedBranch(v *gocui.View) commands.Branch { func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "space": gui.Tr.SLocalize("checkout", "checkout"), - "f": gui.Tr.SLocalize("forceCheckout", "force checkout"), - "m": gui.Tr.SLocalize("merge", "merge"), - "c": gui.Tr.SLocalize("checkoutByName", "checkout by name"), - "n": gui.Tr.SLocalize("newBranch", "new branch"), - "d": gui.Tr.SLocalize("deleteBranch", "delete branch"), - "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), + "space": gui.Tr.SLocalize("checkout"), + "f": gui.Tr.SLocalize("forceCheckout"), + "m": gui.Tr.SLocalize("merge"), + "c": gui.Tr.SLocalize("checkoutByName"), + "n": gui.Tr.SLocalize("newBranch"), + "d": gui.Tr.SLocalize("deleteBranch"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate"), }) } @@ -121,13 +119,13 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { } // This really shouldn't happen: there should always be a master branch if len(gui.State.Branches) == 0 { - return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo", "No branches for this repo")) + return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo")) } go func() { branch := gui.getSelectedBranch(v) diff, err := gui.GitCommand.GetBranchGraph(branch.Name) if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { - diff = gui.Tr.SLocalize("NoTrackingThisBranch", "There is no tracking for this branch") + diff = gui.Tr.SLocalize("NoTrackingThisBranch") } gui.renderString(g, "main", diff) }() diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 086d4f287..697fbc024 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -7,7 +7,7 @@ import ( func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { message := gui.trimmedContent(v) if message == "" { - return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr", "You cannot commit without a commit message")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr")) } sub, err := gui.GitCommand.Commit(g, message) if err != nil { @@ -50,7 +50,6 @@ func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { message := gui.Tr.TemplateLocalize( "CloseConfirm", - "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm", map[string]interface{}{ "keyBindClose": "esc", "keyBindConfirm": "enter", diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index bc12aff90..417b947a2 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -39,7 +39,7 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { } func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { - return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit", "Reset To Commit"), gui.Tr.SLocalize("SureResetThisCommit", "Are you sure you want to reset to this commit?"), func(g *gocui.Gui, v *gocui.View) error { + return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error { commit, err := gui.getSelectedCommit(g) if err != nil { panic(err) @@ -60,11 +60,11 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "s": gui.Tr.SLocalize("squashDown", "squash down"), - "r": gui.Tr.SLocalize("rename", "rename"), - "g": gui.Tr.SLocalize("resetToThisCommit", "reset to this commit"), - "f": gui.Tr.SLocalize("fixupCommit", "fixup commit"), - "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), + "s": gui.Tr.SLocalize("squashDown"), + "r": gui.Tr.SLocalize("rename"), + "g": gui.Tr.SLocalize("resetToThisCommit"), + "f": gui.Tr.SLocalize("fixupCommit"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate"), }) } @@ -74,10 +74,10 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { } commit, err := gui.getSelectedCommit(g) if err != nil { - if err != errors.New(gui.Tr.SLocalize("NoCommitsThisBranch", "No commits for this branch")) { + if err != errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) { return err } - return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch", "No commits for this branch")) + return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch")) } commitText := gui.GitCommand.Show(commit.Sha) return gui.renderString(g, "main", commitText) @@ -85,10 +85,10 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { if gui.getItemPosition(v) != 0 { - return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit", "Can only squash topmost commit")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit")) } if len(gui.State.Commits) == 1 { - return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash", "You have no commits to squash with")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash")) } commit, err := gui.getSelectedCommit(g) if err != nil { @@ -116,18 +116,18 @@ func (gui *Gui) anyUnStagedChanges(files []commands.File) bool { func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { if len(gui.State.Commits) == 1 { - return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash", "You have no commits to squash with")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash")) } if gui.anyUnStagedChanges(gui.State.Files) { - return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges", "Can't fixup while there are unstaged changes")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges")) } branch := gui.State.Branches[0] commit, err := gui.getSelectedCommit(g) if err != nil { return err } - message := gui.Tr.SLocalize("SureFixupThisCommit", "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one") - gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup", "Fixup"), message, func(g *gocui.Gui, v *gocui.View) error { + message := gui.Tr.SLocalize("SureFixupThisCommit") + gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup"), message, func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.SquashFixupCommit(branch.Name, commit.Sha); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -141,9 +141,9 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error { if gui.getItemPosition(v) != 0 { - return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit", "Can only rename topmost commit")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit")) } - gui.createPromptPanel(g, v, gui.Tr.SLocalize("RenameCommit", "Rename Commit"), func(g *gocui.Gui, v *gocui.View) error { + gui.createPromptPanel(g, v, gui.Tr.SLocalize("RenameCommit"), func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.RenameCommit(v.Buffer()); err != nil { return gui.createErrorPanel(g, err.Error()) } @@ -161,11 +161,11 @@ func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) { panic(err) } if len(gui.State.Commits) == 0 { - return commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch", "No commits for this branch")) + return commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) } lineNumber := gui.getItemPosition(v) if lineNumber > len(gui.State.Commits)-1 { - gui.Log.Info(gui.Tr.SLocalize("PotentialErrInGetselectedCommit", "potential error in getSelected Commit (mismatched ui and state)"), gui.State.Commits, lineNumber) + gui.Log.Info(gui.Tr.SLocalize("PotentialErrInGetselectedCommit"), gui.State.Commits, lineNumber) return gui.State.Commits[len(gui.State.Commits)-1], nil } return gui.State.Commits[lineNumber], nil diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 9f76bc1ea..aec190b7f 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -85,7 +85,6 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t if err := gui.closeConfirmationPrompt(g); err != nil { errMessage := gui.Tr.TemplateLocalize( "CantCloseConfirmationPrompt", - "Could not close confirmation prompt: {{.error}}", map[string]interface{}{ "error": err.Error(), }, @@ -126,7 +125,6 @@ func (gui *Gui) handleNewline(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { actions := gui.Tr.TemplateLocalize( "CloseConfirm", - "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm", map[string]interface{}{ "keyBindClose": "esc", "keyBindConfirm": "enter", @@ -150,7 +148,7 @@ func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error { currentView := g.CurrentView() colorFunction := color.New(color.FgRed).SprintFunc() coloredMessage := colorFunction(strings.TrimSpace(message)) - return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error", "Error"), coloredMessage, nil, nil) + return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil) } func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { @@ -162,7 +160,7 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { return nil } - gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel", "resizing popup panel")) + gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel")) _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) return err } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index b5296900f..72a7d20b5 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -80,10 +80,10 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { return err } if !file.HasUnstagedChanges { - return gui.createErrorPanel(g, gui.Tr.SLocalize("FileHasNoUnstagedChanges", "File has no unstaged changes to add")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("FileHasNoUnstagedChanges")) } if !file.Tracked { - return gui.createErrorPanel(g, gui.Tr.SLocalize("CannotGitAdd", "Cannot git add --patch untracked files")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CannotGitAdd")) } sub, err := gui.GitCommand.AddPatch(file.Name) if err != nil { @@ -115,13 +115,12 @@ func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { } var deleteVerb string if file.Tracked { - deleteVerb = gui.Tr.SLocalize("checkout", "checkout") + deleteVerb = gui.Tr.SLocalize("checkout") } else { - deleteVerb = gui.Tr.SLocalize("delete", "delete") + deleteVerb = gui.Tr.SLocalize("delete") } message := gui.Tr.TemplateLocalize( "SureTo", - "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?", map[string]interface{}{ "deleteVerb": deleteVerb, "fileName": file.Name, @@ -141,7 +140,7 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { return gui.createErrorPanel(g, err.Error()) } if file.Tracked { - return gui.createErrorPanel(g, gui.Tr.SLocalize("CantIgnoreTrackFiles", "Cannot ignore tracked files")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("CantIgnoreTrackFiles")) } gui.GitCommand.Ignore(file.Name) return gui.refreshFiles(g) @@ -149,27 +148,27 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error { optionsMap := map[string]string{ - "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), - "S": gui.Tr.SLocalize("stashFiles", "stash files"), - "c": gui.Tr.SLocalize("CommitChanges", "commit changes"), - "o": gui.Tr.SLocalize("open", "open"), - "i": gui.Tr.SLocalize("ignore", "ignore"), - "d": gui.Tr.SLocalize("delete", "delete"), - "space": gui.Tr.SLocalize("toggleStaged", "toggle staged"), - "R": gui.Tr.SLocalize("refresh", "refresh"), - "t": gui.Tr.SLocalize("addPatch", "add patch"), - "e": gui.Tr.SLocalize("edit", "edit"), - "PgUp/PgDn": gui.Tr.SLocalize("scroll", "scroll"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate"), + "S": gui.Tr.SLocalize("stashFiles"), + "c": gui.Tr.SLocalize("CommitChanges"), + "o": gui.Tr.SLocalize("open"), + "i": gui.Tr.SLocalize("ignore"), + "d": gui.Tr.SLocalize("delete"), + "space": gui.Tr.SLocalize("toggleStaged"), + "R": gui.Tr.SLocalize("refresh"), + "t": gui.Tr.SLocalize("addPatch"), + "e": gui.Tr.SLocalize("edit"), + "PgUp/PgDn": gui.Tr.SLocalize("scroll"), } if gui.State.HasMergeConflicts { - optionsMap["a"] = gui.Tr.SLocalize("abortMerge", "abort merge") - optionsMap["m"] = gui.Tr.SLocalize("resolveMergeConflicts", "resolve merge conflicts") + optionsMap["a"] = gui.Tr.SLocalize("abortMerge") + optionsMap["m"] = gui.Tr.SLocalize("resolveMergeConflicts") } if file == nil { return gui.renderOptionsMap(g, optionsMap) } if file.Tracked { - optionsMap["d"] = gui.Tr.SLocalize("checkout", "checkout") + optionsMap["d"] = gui.Tr.SLocalize("checkout") } return gui.renderOptionsMap(g, optionsMap) } @@ -180,7 +179,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { if err != gui.Errors.ErrNoFiles { return err } - gui.renderString(g, "main", "No changed files") + gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles")) return gui.renderfilesOptions(g, nil) } gui.renderfilesOptions(g, &file) @@ -195,7 +194,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { - return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit", "There are no staged files to commit")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) } commitMessageView := gui.getCommitMessageView(g) g.Update(func(g *gocui.Gui) error { @@ -210,7 +209,7 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { // their editor rather than via the popup panel func (gui *Gui) handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { - return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit", "There are no staged files to commit")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) } gui.PrepareSubProcess(g, "git", "commit") return nil @@ -308,7 +307,7 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { if err != gui.Errors.ErrNoFiles { return "", err } - return "", gui.renderString(g, "main", gui.Tr.SLocalize("NoFilesDisplay", "No file to display")) + return "", gui.renderString(g, "main", gui.Tr.SLocalize("NoFilesDisplay")) } cat, err := gui.GitCommand.CatFile(item.Name) if err != nil { @@ -335,7 +334,7 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { - gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PullWait", "Pulling...")) + gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PullWait")) go func() { if err := gui.GitCommand.Pull(); err != nil { gui.createErrorPanel(g, err.Error()) @@ -350,7 +349,7 @@ func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { - gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PushWait", "Pushing...")) + gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PushWait")) go func() { branchName := gui.State.Branches[0].Name if err := gui.GitCommand.Push(branchName); err != nil { @@ -377,7 +376,7 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { return nil } if !file.HasMergeConflicts { - return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons", "This file has no merge conflicts")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons")) } gui.switchFocus(g, v, mergeView) return gui.refreshMergePanel(g) @@ -387,13 +386,13 @@ func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.AbortMerge(); err != nil { return gui.createErrorPanel(g, err.Error()) } - gui.createMessagePanel(g, v, "", "Merge aborted") + gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("MergeAborted")) gui.refreshStatus(g) return gui.refreshFiles(g) } func (gui *Gui) handleResetHard(g *gocui.Gui, v *gocui.View) error { - return gui.createConfirmationPanel(g, v, "Clear file panel", gui.Tr.SLocalize("SureResetHardHead", "Are you sure you want `reset --hard HEAD`? You may lose changes"), func(g *gocui.Gui, v *gocui.View) error { + return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("ClearFilePanel"), gui.Tr.SLocalize("SureResetHardHead"), func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.ResetHard(); err != nil { gui.createErrorPanel(g, err.Error()) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 5aabca8a8..a6341f755 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -44,8 +44,8 @@ type SentinelErrors struct { // localising things in the code. func (gui *Gui) GenerateSentinelErrors() { gui.Errors = SentinelErrors{ - ErrSubProcess: errors.New(gui.Tr.SLocalize("RunningSubprocess", "running subprocess")), - ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles", "No changed files")), + ErrSubProcess: errors.New(gui.Tr.SLocalize("RunningSubprocess")), + ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles")), } } @@ -159,7 +159,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = gui.Tr.SLocalize("NotEnoughSpace", "Not enough space to render panels") + v.Title = gui.Tr.SLocalize("NotEnoughSpace") v.Wrap = true } return nil @@ -178,7 +178,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = gui.Tr.SLocalize("DiffTitle", "Diff") + v.Title = gui.Tr.SLocalize("DiffTitle") v.Wrap = true v.FgColor = gocui.ColorWhite } @@ -187,7 +187,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = gui.Tr.SLocalize("StatusTitle", "Status") + v.Title = gui.Tr.SLocalize("StatusTitle") v.FgColor = gocui.ColorWhite } @@ -197,7 +197,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } filesView.Highlight = true - filesView.Title = gui.Tr.SLocalize("FilesTitle", "Files") + filesView.Title = gui.Tr.SLocalize("FilesTitle") v.FgColor = gocui.ColorWhite } @@ -205,7 +205,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = gui.Tr.SLocalize("BranchesTitle", "Branches") + v.Title = gui.Tr.SLocalize("BranchesTitle") v.FgColor = gocui.ColorWhite } @@ -213,7 +213,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = gui.Tr.SLocalize("CommitsTitle", "Commits") + v.Title = gui.Tr.SLocalize("CommitsTitle") v.FgColor = gocui.ColorWhite } @@ -221,7 +221,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = gui.Tr.SLocalize("StashTitle", "Stash") + v.Title = gui.Tr.SLocalize("StashTitle") v.FgColor = gocui.ColorWhite } @@ -240,7 +240,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } g.SetViewOnBottom("commitMessage") - commitMessageView.Title = gui.Tr.SLocalize("CommitMessage", "Commit message") + commitMessageView.Title = gui.Tr.SLocalize("CommitMessage") commitMessageView.FgColor = gocui.ColorWhite commitMessageView.Editable = true } diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 213bac739..1efea16da 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -232,11 +232,11 @@ func (gui *Gui) switchToMerging(g *gocui.Gui) error { func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "↑ ↓": gui.Tr.SLocalize("selectHunk", "select hunk"), - "← →": gui.Tr.SLocalize("navigateConflicts", "navigate conflicts"), - "space": gui.Tr.SLocalize("pickHunk", "pick hunk"), - "b": gui.Tr.SLocalize("pickBothHunks", "pick both hunks"), - "z": gui.Tr.SLocalize("undo", "undo"), + "↑ ↓": gui.Tr.SLocalize("selectHunk"), + "← →": gui.Tr.SLocalize("navigateConflicts"), + "space": gui.Tr.SLocalize("pickHunk"), + "b": gui.Tr.SLocalize("pickBothHunks"), + "z": gui.Tr.SLocalize("undo"), }) } diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 96d7baba7..bc157f1be 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -33,10 +33,10 @@ func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { func (gui *Gui) renderStashOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ - "space": gui.Tr.SLocalize("apply", "apply"), - "g": gui.Tr.SLocalize("pop", "pop"), - "d": gui.Tr.SLocalize("drop", "drop"), - "← → ↑ ↓": gui.Tr.SLocalize("navigate", "navigate"), + "space": gui.Tr.SLocalize("apply"), + "g": gui.Tr.SLocalize("pop"), + "d": gui.Tr.SLocalize("drop"), + "← → ↑ ↓": gui.Tr.SLocalize("navigate"), }) } @@ -47,7 +47,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { go func() { stashEntry := gui.getSelectedStashEntry(v) if stashEntry == nil { - gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries", "No stash entries")) + gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries")) return } diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index) @@ -65,8 +65,8 @@ func (gui *Gui) handleStashPop(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleStashDrop(g *gocui.Gui, v *gocui.View) error { - title := gui.Tr.SLocalize("StashDrop", "Stash drop") - message := gui.Tr.SLocalize("SureDropStashEntry", "Are you sure you want to drop this stash entry?") + title := gui.Tr.SLocalize("StashDrop") + message := gui.Tr.SLocalize("SureDropStashEntry") return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { return gui.stashDo(g, v, "drop") }, nil) @@ -77,7 +77,6 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { if stashEntry == nil { errorMessage := gui.Tr.TemplateLocalize( "NoStashTo", - "No stash to {{.method}}", map[string]interface{}{ "method": method, }, @@ -93,9 +92,9 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error { if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 { - return gui.createErrorPanel(g, gui.Tr.SLocalize("NoTrackedStagedFilesStash", "You have no tracked/staged files to stash")) + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoTrackedStagedFilesStash")) } - gui.createPromptPanel(g, filesView, gui.Tr.SLocalize("StashChanges", "Stash changes"), func(g *gocui.Gui, v *gocui.View) error { + gui.createPromptPanel(g, filesView, gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.StashSave(gui.trimmedContent(v)); err != nil { gui.createErrorPanel(g, err.Error()) } diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 48ad73962..0d215a337 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -31,7 +31,6 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { if i == len(cyclableViews)-1 { message := gui.Tr.TemplateLocalize( "IssntListOfViews", - "{{.name}} is not in the list of views", map[string]interface{}{ "name": v.Name(), }, @@ -61,7 +60,6 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { if i == len(cyclableViews)-1 { message := gui.Tr.TemplateLocalize( "IssntListOfViews", - "{{.name}} is not in the list of views", map[string]interface{}{ "name": v.Name(), }, @@ -101,7 +99,7 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { case "stash": return gui.handleStashEntrySelect(g, v) default: - panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement", "No view matching newLineFocused switch statement")) + panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement")) } } @@ -121,7 +119,6 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { oldView.Highlight = false message := gui.Tr.TemplateLocalize( "settingPreviewsViewTo", - "setting previous view to: {{.oldViewName}}", map[string]interface{}{ "oldViewName": oldView.Name(), }, @@ -132,7 +129,6 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { newView.Highlight = true message := gui.Tr.TemplateLocalize( "newFocusedViewIs", - "new focused view is {{.newFocusedView}}", map[string]interface{}{ "newFocusedView": newView.Name(), }, diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 9bf542fad..0ba562a22 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -277,6 +277,15 @@ func addDutch(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "CantCloseConfirmationPrompt", Other: "Kon de bevestiging prompt niet sluiten: {{.error}}", + }, &i18n.Message{ + ID: "NoChangedFiles", + Other: "Geen veranderde files", + }, &i18n.Message{ + ID: "ClearFilePanel", + Other: "maak bestandsvenster leeg", + }, &i18n.Message{ + ID: "MergeAborted", + Other: "Merge afgebroken", }, ) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index d4c31ae53..6e2af8e22 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -1,7 +1,5 @@ /* -About: this pakcage contains just the contents to gets started with a new translations - Todo list when making a new translation - Copy this file and rename it to the language you want to translate to like someLanguage.go - Change the addEnglish() name to the language you want to translate to like addSomeLanguage() @@ -287,6 +285,15 @@ func addEnglish(i18nObject *i18n.Bundle) { }, &i18n.Message{ ID: "CantCloseConfirmationPrompt", Other: "Could not close confirmation prompt: {{.error}}", + }, &i18n.Message{ + ID: "NoChangedFiles", + Other: "No changed files", + }, &i18n.Message{ + ID: "ClearFilePanel", + Other: "Clear file panel", + }, &i18n.Message{ + ID: "MergeAborted", + Other: "Merge aborted", }, ) } diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index 5a09d6d66..2e445391d 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -51,21 +51,19 @@ func (l *Localizer) Localize(config *i18n.LocalizeConfig) string { // SLocalize (short localize) is for 1 line localizations // ID: The id that is used in the .toml translation files // Other: the default message it needs to return if there is no translation found or the system is english -func (l *Localizer) SLocalize(ID string, Other string) string { +func (l *Localizer) SLocalize(ID string) string { return l.Localize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ - ID: ID, - Other: Other, + ID: ID, }, }) } // TemplateLocalize allows the Other input to be dynamic -func (l *Localizer) TemplateLocalize(ID string, Other string, TemplateData map[string]interface{}) string { +func (l *Localizer) TemplateLocalize(ID string, TemplateData map[string]interface{}) string { return l.Localize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ - ID: ID, - Other: Other, + ID: ID, }, TemplateData: TemplateData, }) @@ -79,4 +77,5 @@ func (l *Localizer) GetLanguage() string { // add translation file(s) func addBundles(i18nBundle *i18n.Bundle) { addDutch(i18nBundle) + addEnglish(i18nBundle) } From 90746502df432ce88ed1b62e36414c2142322ff7 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Thu, 16 Aug 2018 11:31:03 +0200 Subject: [PATCH 32/45] Fixed comments from jesseduffield on issue #137 --- pkg/gui/branches_panel.go | 4 ++-- pkg/gui/commit_message_panel.go | 2 +- pkg/gui/confirmation_panel.go | 4 ++-- pkg/gui/files_panel.go | 8 ++++---- pkg/gui/gui.go | 3 +++ 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 2a08eb8b1..67e0ceb07 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -47,7 +47,7 @@ func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error { branch := gui.State.Branches[0] message := gui.Tr.TemplateLocalize( "NewBranchNameBranchOff", - map[string]interface{}{ + Teml{ "branchName": branch.Name, }, ) @@ -69,7 +69,7 @@ func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { } message := gui.Tr.TemplateLocalize( "DeleteBranchMessage", - map[string]interface{}{ + Teml{ "selectedBranchName": selectedBranch.Name, }, ) diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 697fbc024..26db703f0 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -50,7 +50,7 @@ func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { message := gui.Tr.TemplateLocalize( "CloseConfirm", - map[string]interface{}{ + Teml{ "keyBindClose": "esc", "keyBindConfirm": "enter", }, diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index aec190b7f..9c3d2b263 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -85,7 +85,7 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t if err := gui.closeConfirmationPrompt(g); err != nil { errMessage := gui.Tr.TemplateLocalize( "CantCloseConfirmationPrompt", - map[string]interface{}{ + Teml{ "error": err.Error(), }, ) @@ -125,7 +125,7 @@ func (gui *Gui) handleNewline(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { actions := gui.Tr.TemplateLocalize( "CloseConfirm", - map[string]interface{}{ + Teml{ "keyBindClose": "esc", "keyBindConfirm": "enter", }, diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 72a7d20b5..27589bc19 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -90,7 +90,7 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { return err } gui.SubProcess = sub - return nil + return gui.Errors.ErrSubProcess } func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) { @@ -121,7 +121,7 @@ func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { } message := gui.Tr.TemplateLocalize( "SureTo", - map[string]interface{}{ + Teml{ "deleteVerb": deleteVerb, "fileName": file.Name, }, @@ -223,7 +223,7 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) error { } gui.SubProcess = sub g.Update(func(g *gocui.Gui) error { - return nil + return gui.Errors.ErrSubProcess }) return nil } @@ -242,7 +242,7 @@ func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) ( } if sub != nil { gui.SubProcess = sub - return nil + return gui.Errors.ErrSubProcess } return nil } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index a6341f755..1a2ec44ba 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -49,6 +49,9 @@ func (gui *Gui) GenerateSentinelErrors() { } } +// Teml is short for template used to make the required map[string]interface{} shorter when using gui.Tr.SLocalize and gui.Tr.TemplateLocalize +type Teml map[string]interface{} + // Gui wraps the gocui Gui object which handles rendering and events type Gui struct { g *gocui.Gui From faf218f4650edbd0a711a3304cc5549180e9dfdc Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Thu, 16 Aug 2018 11:31:50 +0200 Subject: [PATCH 33/45] Fixed comments from jesseduffield on issue #137 --- pkg/gui/stash_panel.go | 2 +- pkg/gui/view_helpers.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index bc157f1be..0c323e254 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -77,7 +77,7 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { if stashEntry == nil { errorMessage := gui.Tr.TemplateLocalize( "NoStashTo", - map[string]interface{}{ + Teml{ "method": method, }, ) diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 0d215a337..95324ecde 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -31,7 +31,7 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { if i == len(cyclableViews)-1 { message := gui.Tr.TemplateLocalize( "IssntListOfViews", - map[string]interface{}{ + Teml{ "name": v.Name(), }, ) @@ -60,7 +60,7 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { if i == len(cyclableViews)-1 { message := gui.Tr.TemplateLocalize( "IssntListOfViews", - map[string]interface{}{ + Teml{ "name": v.Name(), }, ) @@ -119,7 +119,7 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { oldView.Highlight = false message := gui.Tr.TemplateLocalize( "settingPreviewsViewTo", - map[string]interface{}{ + Teml{ "oldViewName": oldView.Name(), }, ) @@ -129,7 +129,7 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { newView.Highlight = true message := gui.Tr.TemplateLocalize( "newFocusedViewIs", - map[string]interface{}{ + Teml{ "newFocusedView": newView.Name(), }, ) From a7755ab184031cca783052155d1ba4c8275b51dc Mon Sep 17 00:00:00 2001 From: Tommy Nguyen Date: Thu, 16 Aug 2018 07:00:13 -0400 Subject: [PATCH 34/45] reformat --- pkg/commands/os_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/os_test.go b/pkg/commands/os_test.go index a6bdbc1b1..29540aff6 100644 --- a/pkg/commands/os_test.go +++ b/pkg/commands/os_test.go @@ -3,8 +3,8 @@ package commands import "testing" func TestQuote(t *testing.T) { - osCommand := &OSCommand { - Log: nil, + osCommand := &OSCommand{ + Log: nil, Platform: getPlatform(), } test := "hello `test`" From fcf616bd62a71e3ae3fac9ef93d02e20e8355c0b Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Thu, 16 Aug 2018 13:35:04 +0200 Subject: [PATCH 35/45] Fixed it --- pkg/gui/gui.go | 2 +- pkg/i18n/i18n.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 1a2ec44ba..a5bdb9e07 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -50,7 +50,7 @@ func (gui *Gui) GenerateSentinelErrors() { } // Teml is short for template used to make the required map[string]interface{} shorter when using gui.Tr.SLocalize and gui.Tr.TemplateLocalize -type Teml map[string]interface{} +type Teml i18n.Teml // Gui wraps the gocui Gui object which handles rendering and events type Gui struct { diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index 2e445391d..9102ff0b3 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -7,6 +7,9 @@ import ( "golang.org/x/text/language" ) +// Teml is short for template used to make the required map[string]interface{} shorter when using gui.Tr.SLocalize and gui.Tr.TemplateLocalize +type Teml map[string]interface{} + // Localizer will translate a message into the user's language type Localizer struct { i18nLocalizer *i18n.Localizer From 563efc41c749af43b4aba5286fd5b5d2e4a51c7a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 16 Aug 2018 21:48:09 +1000 Subject: [PATCH 36/45] bump dependencies for i18n --- Gopkg.lock | 27 +- .../cloudfoundry/jibber_jabber/LICENSE | 201 + .../jibber_jabber/jibber_jabber.go | 22 + .../jibber_jabber/jibber_jabber_unix.go | 57 + .../jibber_jabber/jibber_jabber_windows.go | 114 + vendor/github.com/nicksnyder/go-i18n/LICENSE | 19 + .../nicksnyder/go-i18n/v2/i18n/bundle.go | 129 + .../nicksnyder/go-i18n/v2/i18n/doc.go | 21 + .../nicksnyder/go-i18n/v2/i18n/localizer.go | 198 + .../nicksnyder/go-i18n/v2/i18n/message.go | 6 + .../nicksnyder/go-i18n/v2/internal/message.go | 164 + .../go-i18n/v2/internal/message_template.go | 55 + .../nicksnyder/go-i18n/v2/internal/parse.go | 112 + .../go-i18n/v2/internal/plural/doc.go | 3 + .../go-i18n/v2/internal/plural/form.go | 16 + .../go-i18n/v2/internal/plural/operands.go | 120 + .../go-i18n/v2/internal/plural/rule.go | 44 + .../go-i18n/v2/internal/plural/rule_gen.go | 561 +++ .../go-i18n/v2/internal/plural/rules.go | 24 + .../go-i18n/v2/internal/template.go | 26 + vendor/golang.org/x/text/internal/tag/tag.go | 100 + vendor/golang.org/x/text/language/common.go | 16 + vendor/golang.org/x/text/language/coverage.go | 197 + vendor/golang.org/x/text/language/doc.go | 102 + vendor/golang.org/x/text/language/gen.go | 1712 ++++++++ .../golang.org/x/text/language/gen_common.go | 20 + .../golang.org/x/text/language/gen_index.go | 162 + vendor/golang.org/x/text/language/go1_1.go | 38 + vendor/golang.org/x/text/language/go1_2.go | 11 + vendor/golang.org/x/text/language/index.go | 783 ++++ vendor/golang.org/x/text/language/language.go | 907 ++++ vendor/golang.org/x/text/language/lookup.go | 396 ++ vendor/golang.org/x/text/language/match.go | 933 +++++ vendor/golang.org/x/text/language/parse.go | 859 ++++ vendor/golang.org/x/text/language/tables.go | 3686 +++++++++++++++++ vendor/golang.org/x/text/language/tags.go | 143 + 36 files changed, 11983 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/cloudfoundry/jibber_jabber/LICENSE create mode 100644 vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go create mode 100644 vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go create mode 100644 vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/LICENSE create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/i18n/bundle.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/i18n/doc.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/i18n/message.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/message.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/message_template.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/parse.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/doc.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/form.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/operands.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule_gen.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rules.go create mode 100644 vendor/github.com/nicksnyder/go-i18n/v2/internal/template.go create mode 100644 vendor/golang.org/x/text/internal/tag/tag.go create mode 100644 vendor/golang.org/x/text/language/common.go create mode 100644 vendor/golang.org/x/text/language/coverage.go create mode 100644 vendor/golang.org/x/text/language/doc.go create mode 100644 vendor/golang.org/x/text/language/gen.go create mode 100644 vendor/golang.org/x/text/language/gen_common.go create mode 100644 vendor/golang.org/x/text/language/gen_index.go create mode 100644 vendor/golang.org/x/text/language/go1_1.go create mode 100644 vendor/golang.org/x/text/language/go1_2.go create mode 100644 vendor/golang.org/x/text/language/index.go create mode 100644 vendor/golang.org/x/text/language/language.go create mode 100644 vendor/golang.org/x/text/language/lookup.go create mode 100644 vendor/golang.org/x/text/language/match.go create mode 100644 vendor/golang.org/x/text/language/parse.go create mode 100644 vendor/golang.org/x/text/language/tables.go create mode 100644 vendor/golang.org/x/text/language/tags.go diff --git a/Gopkg.lock b/Gopkg.lock index 7b167c7d8..930872ae3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -9,6 +9,14 @@ revision = "3e01752db0189b9157070a0e1668a620f9a85da2" version = "v1.0.6" +[[projects]] + branch = "master" + digest = "1:cd7ba2b29e93e2a8384e813dfc80ebb0f85d9214762e6ca89bb55a58092eab87" + name = "github.com/cloudfoundry/jibber_jabber" + packages = ["."] + pruneopts = "NUT" + revision = "bcc4c8345a21301bf47c032ff42dd1aae2fe3027" + [[projects]] digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" @@ -112,6 +120,18 @@ pruneopts = "NUT" revision = "58046073cbffe2f25d425fe1331102f55cf719de" +[[projects]] + digest = "1:2c34c77bf3ec848da26e48af58fc511ed52750961fa848399d122882b8890928" + name = "github.com/nicksnyder/go-i18n" + packages = [ + "v2/i18n", + "v2/internal", + "v2/internal/plural", + ] + pruneopts = "NUT" + revision = "a16b91a3ba80db3a2301c70d1d302d42251c9079" + version = "v2.0.0-beta.5" + [[projects]] branch = "master" digest = "1:34d9354c2c5d916c05864327553047df59fc10e86ff1f408e4136eba0a25a5ec" @@ -211,12 +231,14 @@ revision = "98c5dad5d1a0e8a73845ecc8897d0bd56586511d" [[projects]] - digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295" + digest = "1:a95288ef1ef4dfad6cba7fe30843e1683f71bc28c912ca1ba3f6a539d44db739" name = "golang.org/x/text" packages = [ "internal/gen", + "internal/tag", "internal/triegen", "internal/ucd", + "language", "transform", "unicode/cldr", "unicode/norm", @@ -300,12 +322,15 @@ analyzer-version = 1 input-imports = [ "github.com/Sirupsen/logrus", + "github.com/cloudfoundry/jibber_jabber", "github.com/davecgh/go-spew/spew", "github.com/fatih/color", "github.com/golang-collections/collections/stack", "github.com/jesseduffield/gocui", "github.com/mgutz/str", + "github.com/nicksnyder/go-i18n/v2/i18n", "github.com/tcnksm/go-gitconfig", + "golang.org/x/text/language", "gopkg.in/src-d/go-git.v4", "gopkg.in/src-d/go-git.v4/plumbing", ] diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE b/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE new file mode 100644 index 000000000..915b20892 --- /dev/null +++ b/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2014 Pivotal + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go new file mode 100644 index 000000000..45d288ea8 --- /dev/null +++ b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go @@ -0,0 +1,22 @@ +package jibber_jabber + +import ( + "strings" +) + +const ( + COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE = "Could not detect Language" +) + +func splitLocale(locale string) (string, string) { + formattedLocale := strings.Split(locale, ".")[0] + formattedLocale = strings.Replace(formattedLocale, "-", "_", -1) + + pieces := strings.Split(formattedLocale, "_") + language := pieces[0] + territory := "" + if len(pieces) > 1 { + territory = strings.Split(formattedLocale, "_")[1] + } + return language, territory +} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go new file mode 100644 index 000000000..374d76176 --- /dev/null +++ b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go @@ -0,0 +1,57 @@ +// +build darwin freebsd linux netbsd openbsd + +package jibber_jabber + +import ( + "errors" + "os" + "strings" +) + +func getLangFromEnv() (locale string) { + locale = os.Getenv("LC_ALL") + if locale == "" { + locale = os.Getenv("LANG") + } + return +} + +func getUnixLocale() (unix_locale string, err error) { + unix_locale = getLangFromEnv() + if unix_locale == "" { + err = errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE) + } + + return +} + +func DetectIETF() (locale string, err error) { + unix_locale, err := getUnixLocale() + if err == nil { + language, territory := splitLocale(unix_locale) + locale = language + if territory != "" { + locale = strings.Join([]string{language, territory}, "-") + } + } + + return +} + +func DetectLanguage() (language string, err error) { + unix_locale, err := getUnixLocale() + if err == nil { + language, _ = splitLocale(unix_locale) + } + + return +} + +func DetectTerritory() (territory string, err error) { + unix_locale, err := getUnixLocale() + if err == nil { + _, territory = splitLocale(unix_locale) + } + + return +} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go new file mode 100644 index 000000000..1acd96c38 --- /dev/null +++ b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go @@ -0,0 +1,114 @@ +// +build windows + +package jibber_jabber + +import ( + "errors" + "syscall" + "unsafe" +) + +const LOCALE_NAME_MAX_LENGTH uint32 = 85 + +var SUPPORTED_LOCALES = map[uintptr]string{ + 0x0407: "de-DE", + 0x0409: "en-US", + 0x0c0a: "es-ES", //or is it 0x040a + 0x040c: "fr-FR", + 0x0410: "it-IT", + 0x0411: "ja-JA", + 0x0412: "ko_KR", + 0x0416: "pt-BR", + //0x0419: "ru_RU", - Will add support for Russian when nicksnyder/go-i18n supports Russian + 0x0804: "zh-CN", + 0x0c04: "zh-HK", + 0x0404: "zh-TW", +} + +func getWindowsLocaleFrom(sysCall string) (locale string, err error) { + buffer := make([]uint16, LOCALE_NAME_MAX_LENGTH) + + dll := syscall.MustLoadDLL("kernel32") + proc := dll.MustFindProc(sysCall) + r, _, dllError := proc.Call(uintptr(unsafe.Pointer(&buffer[0])), uintptr(LOCALE_NAME_MAX_LENGTH)) + if r == 0 { + err = errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE + ":\n" + dllError.Error()) + return + } + + locale = syscall.UTF16ToString(buffer) + + return +} + +func getAllWindowsLocaleFrom(sysCall string) (string, error) { + dll, err := syscall.LoadDLL("kernel32") + if err != nil { + return "", errors.New("Could not find kernel32 dll") + } + + proc, err := dll.FindProc(sysCall) + if err != nil { + return "", err + } + + locale, _, dllError := proc.Call() + if locale == 0 { + return "", errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE + ":\n" + dllError.Error()) + } + + return SUPPORTED_LOCALES[locale], nil +} + +func getWindowsLocale() (locale string, err error) { + dll, err := syscall.LoadDLL("kernel32") + if err != nil { + return "", errors.New("Could not find kernel32 dll") + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", err + } + + v, _, _ := proc.Call() + windowsVersion := byte(v) + isVistaOrGreater := (windowsVersion >= 6) + + if isVistaOrGreater { + locale, err = getWindowsLocaleFrom("GetUserDefaultLocaleName") + if err != nil { + locale, err = getWindowsLocaleFrom("GetSystemDefaultLocaleName") + } + } else if !isVistaOrGreater { + locale, err = getAllWindowsLocaleFrom("GetUserDefaultLCID") + if err != nil { + locale, err = getAllWindowsLocaleFrom("GetSystemDefaultLCID") + } + } else { + panic(v) + } + return +} +func DetectIETF() (locale string, err error) { + locale, err = getWindowsLocale() + return +} + +func DetectLanguage() (language string, err error) { + windows_locale, err := getWindowsLocale() + if err == nil { + language, _ = splitLocale(windows_locale) + } + + return +} + +func DetectTerritory() (territory string, err error) { + windows_locale, err := getWindowsLocale() + if err == nil { + _, territory = splitLocale(windows_locale) + } + + return +} diff --git a/vendor/github.com/nicksnyder/go-i18n/LICENSE b/vendor/github.com/nicksnyder/go-i18n/LICENSE new file mode 100644 index 000000000..609cce797 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Nick Snyder https://github.com/nicksnyder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/i18n/bundle.go b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/bundle.go new file mode 100644 index 000000000..2a8faaf23 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/bundle.go @@ -0,0 +1,129 @@ +package i18n + +import ( + "fmt" + "io/ioutil" + + "github.com/nicksnyder/go-i18n/v2/internal" + "github.com/nicksnyder/go-i18n/v2/internal/plural" + + "golang.org/x/text/language" +) + +// UnmarshalFunc unmarshals data into v. +type UnmarshalFunc = internal.UnmarshalFunc + +// Bundle stores a set of messages and pluralization rules. +// Most applications only need a single bundle +// that is initialized early in the application's lifecycle. +type Bundle struct { + // DefaultLanguage is the default language of the bundle. + DefaultLanguage language.Tag + + // UnmarshalFuncs is a map of file extensions to UnmarshalFuncs. + UnmarshalFuncs map[string]UnmarshalFunc + + messageTemplates map[language.Tag]map[string]*internal.MessageTemplate + pluralRules plural.Rules + tags []language.Tag + matcher language.Matcher +} + +func (b *Bundle) init() { + if b.pluralRules == nil { + b.pluralRules = plural.DefaultRules() + } + b.addTag(b.DefaultLanguage) +} + +// RegisterUnmarshalFunc registers an UnmarshalFunc for format. +func (b *Bundle) RegisterUnmarshalFunc(format string, unmarshalFunc UnmarshalFunc) { + if b.UnmarshalFuncs == nil { + b.UnmarshalFuncs = make(map[string]UnmarshalFunc) + } + b.UnmarshalFuncs[format] = unmarshalFunc +} + +// LoadMessageFile loads the bytes from path +// and then calls ParseMessageFileBytes. +func (b *Bundle) LoadMessageFile(path string) (*MessageFile, error) { + buf, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return b.ParseMessageFileBytes(buf, path) +} + +// MustLoadMessageFile is similar to LoadTranslationFile +// except it panics if an error happens. +func (b *Bundle) MustLoadMessageFile(path string) { + if _, err := b.LoadMessageFile(path); err != nil { + panic(err) + } +} + +// MessageFile represents a parsed message file. +type MessageFile = internal.MessageFile + +// ParseMessageFileBytes parses the bytes in buf to add translations to the bundle. +// +// The format of the file is everything after the last ".". +// +// The language tag of the file is everything after the second to last "." or after the last path separator, but before the format. +func (b *Bundle) ParseMessageFileBytes(buf []byte, path string) (*MessageFile, error) { + messageFile, err := internal.ParseMessageFileBytes(buf, path, b.UnmarshalFuncs) + if err != nil { + return nil, err + } + if err := b.AddMessages(messageFile.Tag, messageFile.Messages...); err != nil { + return nil, err + } + return messageFile, nil +} + +// MustParseMessageFileBytes is similar to ParseMessageFileBytes +// except it panics if an error happens. +func (b *Bundle) MustParseMessageFileBytes(buf []byte, path string) { + if _, err := b.ParseMessageFileBytes(buf, path); err != nil { + panic(err) + } +} + +// AddMessages adds messages for a language. +// It is useful if your messages are in a format not supported by ParseMessageFileBytes. +func (b *Bundle) AddMessages(tag language.Tag, messages ...*Message) error { + b.init() + pluralRule := b.pluralRules.Rule(tag) + if pluralRule == nil { + return fmt.Errorf("no plural rule registered for %s", tag) + } + if b.messageTemplates == nil { + b.messageTemplates = map[language.Tag]map[string]*internal.MessageTemplate{} + } + if b.messageTemplates[tag] == nil { + b.messageTemplates[tag] = map[string]*internal.MessageTemplate{} + b.addTag(tag) + } + for _, m := range messages { + b.messageTemplates[tag][m.ID] = internal.NewMessageTemplate(m) + } + return nil +} + +// MustAddMessages is similar to AddMessages except it panics if an error happens. +func (b *Bundle) MustAddMessages(tag language.Tag, messages ...*Message) { + if err := b.AddMessages(tag, messages...); err != nil { + panic(err) + } +} + +func (b *Bundle) addTag(tag language.Tag) { + for _, t := range b.tags { + if t == tag { + // Tag already exists + return + } + } + b.tags = append(b.tags, tag) + b.matcher = language.NewMatcher(b.tags) +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/i18n/doc.go b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/doc.go new file mode 100644 index 000000000..7b56a7172 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/doc.go @@ -0,0 +1,21 @@ +// Package i18n provides support for looking up messages +// according to a set of locale preferences. +// +// Create a Bundle to use for the lifetime of your application. +// bundle := &i18n.Bundle{DefaultLanguage: language.English} +// +// Create a Localizer to use for a set of language preferences. +// func(w http.ResponseWriter, r *http.Request) { +// lang := r.FormValue("lang") +// accept := r.Header.Get("Accept-Language") +// localizer := i18n.NewLocalizer(bundle, lang, accept) +// } +// +// Use the Localizer to lookup messages. +// localizer.MustLocalize(&i18n.LocalizeConfig{ +// DefaultMessage: &i18n.Message{ +// ID: "HelloWorld", +// Other: "Hello World!", +// }, +// }) +package i18n diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go new file mode 100644 index 000000000..f9d516fa6 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go @@ -0,0 +1,198 @@ +package i18n + +import ( + "fmt" + + "text/template" + + "github.com/nicksnyder/go-i18n/v2/internal" + "github.com/nicksnyder/go-i18n/v2/internal/plural" + "golang.org/x/text/language" +) + +// Localizer provides Localize and MustLocalize methods that return localized messages. +type Localizer struct { + // bundle contains the messages that can be returned by the Localizer. + bundle *Bundle + + // tags is the list of language tags that the Localizer checks + // in order when localizing a message. + tags []language.Tag +} + +// NewLocalizer returns a new Localizer that looks up messages +// in the bundle according to the language preferences in langs. +// It can parse Accept-Language headers as defined in http://www.ietf.org/rfc/rfc2616.txt. +func NewLocalizer(bundle *Bundle, langs ...string) *Localizer { + bundle.init() + return &Localizer{ + bundle: bundle, + tags: parseTags(langs), + } +} + +func parseTags(langs []string) []language.Tag { + tags := []language.Tag{} + for _, lang := range langs { + t, _, err := language.ParseAcceptLanguage(lang) + if err != nil { + continue + } + tags = append(tags, t...) + } + return tags +} + +// LocalizeConfig configures a call to the Localize method on Localizer. +type LocalizeConfig struct { + // MessageID is the id of the message to lookup. + // This field is ignored if DefaultMessage is set. + MessageID string + + // TemplateData is the data passed when executing the message's template. + // If TemplateData is nil and PluralCount is not nil, then the message template + // will be executed with data that contains the plural count. + TemplateData interface{} + + // PluralCount determines which plural form of the message is used. + PluralCount interface{} + + // DefaultMessage is used if the message is not found in any message files. + DefaultMessage *Message + + // Funcs is used to extend the Go template engines built in functions + Funcs template.FuncMap +} + +type invalidPluralCountErr struct { + messageID string + pluralCount interface{} + err error +} + +func (e *invalidPluralCountErr) Error() string { + return fmt.Sprintf("invalid plural count %#v for message id %q: %s", e.pluralCount, e.messageID, e.err) +} + +type messageNotFoundErr struct { + messageID string +} + +func (e *messageNotFoundErr) Error() string { + return fmt.Sprintf("message %q not found", e.messageID) +} + +type pluralizeErr struct { + messageID string + tag language.Tag +} + +func (e *pluralizeErr) Error() string { + return fmt.Sprintf("unable to pluralize %q because there no plural rule for %q", e.messageID, e.tag) +} + +// Localize returns a localized message. +func (l *Localizer) Localize(lc *LocalizeConfig) (string, error) { + messageID := lc.MessageID + if lc.DefaultMessage != nil { + messageID = lc.DefaultMessage.ID + } + + var operands *plural.Operands + templateData := lc.TemplateData + if lc.PluralCount != nil { + var err error + operands, err = plural.NewOperands(lc.PluralCount) + if err != nil { + return "", &invalidPluralCountErr{messageID: messageID, pluralCount: lc.PluralCount, err: err} + } + if templateData == nil { + templateData = map[string]interface{}{ + "PluralCount": lc.PluralCount, + } + } + } + tag, template := l.getTemplate(messageID, lc.DefaultMessage) + if template == nil { + return "", &messageNotFoundErr{messageID: messageID} + } + pluralForm := l.pluralForm(tag, operands) + if pluralForm == plural.Invalid { + return "", &pluralizeErr{messageID: messageID, tag: tag} + } + return template.Execute(pluralForm, templateData, lc.Funcs) +} + +func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Tag, *internal.MessageTemplate) { + // Fast path. + // Optimistically assume this message id is defined in each language. + fastTag, template := l.matchTemplate(id, l.bundle.matcher, l.bundle.tags) + if template != nil { + return fastTag, template + } + if fastTag == l.bundle.DefaultLanguage { + if defaultMessage == nil { + return fastTag, nil + } + return fastTag, internal.NewMessageTemplate(defaultMessage) + } + if len(l.bundle.tags) > 1 { + // Slow path. + // We didn't find a translation for the tag suggested by the default matcher + // so we need to create a new matcher that contains only the tags in the bundle + // that have this message. + foundTags := make([]language.Tag, 0, len(l.bundle.messageTemplates)) + if l.bundle.DefaultLanguage != fastTag { + foundTags = append(foundTags, l.bundle.DefaultLanguage) + } + for t, templates := range l.bundle.messageTemplates { + if t == fastTag { + // We already tried this tag in the fast path + continue + } + template := templates[id] + if template == nil || template.Other == "" { + continue + } + foundTags = append(foundTags, t) + } + tag, template := l.matchTemplate(id, language.NewMatcher(foundTags), foundTags) + if template != nil { + return tag, template + } + } + if defaultMessage == nil { + return l.bundle.DefaultLanguage, nil + } + return l.bundle.DefaultLanguage, internal.NewMessageTemplate(defaultMessage) +} + +func (l *Localizer) matchTemplate(id string, matcher language.Matcher, tags []language.Tag) (language.Tag, *internal.MessageTemplate) { + _, i, _ := matcher.Match(l.tags...) + tag := tags[i] + templates := l.bundle.messageTemplates[tag] + if templates != nil && templates[id] != nil { + return tag, templates[id] + } + return tag, nil +} + +func (l *Localizer) pluralForm(tag language.Tag, operands *plural.Operands) plural.Form { + if operands == nil { + return plural.Other + } + pluralRule := l.bundle.pluralRules.Rule(tag) + if pluralRule == nil { + return plural.Invalid + } + return pluralRule.PluralFormFunc(operands) +} + +// MustLocalize is similar to Localize, except it panics if an error happens. +func (l *Localizer) MustLocalize(lc *LocalizeConfig) string { + localized, err := l.Localize(lc) + if err != nil { + panic(err) + } + return localized +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/i18n/message.go b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/message.go new file mode 100644 index 000000000..7c881a6b4 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/message.go @@ -0,0 +1,6 @@ +package i18n + +import "github.com/nicksnyder/go-i18n/v2/internal" + +// Message is a string that can be localized. +type Message = internal.Message diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/message.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/message.go new file mode 100644 index 000000000..592ec363e --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/message.go @@ -0,0 +1,164 @@ +package internal + +import ( + "fmt" + "strings" +) + +// Message is a string that can be localized. +type Message struct { + // ID uniquely identifies the message. + ID string + + // Hash uniquely identifies the content of the message + // that this message was translated from. + Hash string + + // Description describes the message to give additional + // context to translators that may be relevant for translation. + Description string + + // LeftDelim is the left Go template delimiter. + LeftDelim string + + // RightDelim is the right Go template delimiter.`` + RightDelim string + + // Zero is the content of the message for the CLDR plural form "zero". + Zero string + + // One is the content of the message for the CLDR plural form "one". + One string + + // Two is the content of the message for the CLDR plural form "two". + Two string + + // Few is the content of the message for the CLDR plural form "few". + Few string + + // Many is the content of the message for the CLDR plural form "many". + Many string + + // Other is the content of the message for the CLDR plural form "other". + Other string +} + +// NewMessage parses data and returns a new message. +func NewMessage(data interface{}) (*Message, error) { + m := &Message{} + if err := m.unmarshalInterface(data); err != nil { + return nil, err + } + return m, nil +} + +// MustNewMessage is similar to NewMessage except it panics if an error happens. +func MustNewMessage(data interface{}) *Message { + m, err := NewMessage(data) + if err != nil { + panic(err) + } + return m +} + +// unmarshalInterface unmarshals a message from data. +func (m *Message) unmarshalInterface(v interface{}) error { + strdata, err := stringMap(v) + if err != nil { + return err + } + for k, v := range strdata { + switch strings.ToLower(k) { + case "id": + m.ID = v + case "description": + m.Description = v + case "hash": + m.Hash = v + case "leftDelim": + m.LeftDelim = v + case "rightDelim": + m.RightDelim = v + case "zero": + m.Zero = v + case "one": + m.One = v + case "two": + m.Two = v + case "few": + m.Few = v + case "many": + m.Many = v + case "other": + m.Other = v + } + } + return nil +} + +func stringMap(v interface{}) (map[string]string, error) { + switch value := v.(type) { + case string: + return map[string]string{ + "other": value, + }, nil + case map[string]string: + return value, nil + case map[string]interface{}: + strdata := map[string]string{} + for k, v := range value { + if k == "translation" { + switch vt := v.(type) { + case string: + strdata["other"] = vt + default: + v1Message, err := stringMap(v) + if err != nil { + return nil, err + } + for kk, vv := range v1Message { + strdata[kk] = vv + } + } + continue + } + vstr, ok := v.(string) + if !ok { + return nil, fmt.Errorf("expected value for key %q be a string but got %#v", k, v) + } + strdata[k] = vstr + } + return strdata, nil + case map[interface{}]interface{}: + strdata := map[string]string{} + for k, v := range value { + kstr, ok := k.(string) + if !ok { + return nil, fmt.Errorf("expected key to be a string but got %#v", k) + } + if kstr == "translation" { + switch vt := v.(type) { + case string: + strdata["other"] = vt + default: + v1Message, err := stringMap(v) + if err != nil { + return nil, err + } + for kk, vv := range v1Message { + strdata[kk] = vv + } + } + continue + } + vstr, ok := v.(string) + if !ok { + return nil, fmt.Errorf("expected value for key %q be a string but got %#v", k, v) + } + strdata[kstr] = vstr + } + return strdata, nil + default: + return nil, fmt.Errorf("unsupported type %#v", value) + } +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/message_template.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/message_template.go new file mode 100644 index 000000000..03aed6f39 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/message_template.go @@ -0,0 +1,55 @@ +package internal + +import ( + "bytes" + + "text/template" + + "github.com/nicksnyder/go-i18n/v2/internal/plural" +) + +// MessageTemplate is an executable template for a message. +type MessageTemplate struct { + *Message + PluralTemplates map[plural.Form]*Template +} + +// NewMessageTemplate returns a new message template. +func NewMessageTemplate(m *Message) *MessageTemplate { + pluralTemplates := map[plural.Form]*Template{} + setPluralTemplate(pluralTemplates, plural.Zero, m.Zero) + setPluralTemplate(pluralTemplates, plural.One, m.One) + setPluralTemplate(pluralTemplates, plural.Two, m.Two) + setPluralTemplate(pluralTemplates, plural.Few, m.Few) + setPluralTemplate(pluralTemplates, plural.Many, m.Many) + setPluralTemplate(pluralTemplates, plural.Other, m.Other) + if len(pluralTemplates) == 0 { + return nil + } + return &MessageTemplate{ + Message: m, + PluralTemplates: pluralTemplates, + } +} + +func setPluralTemplate(pluralTemplates map[plural.Form]*Template, pluralForm plural.Form, src string) { + if src != "" { + pluralTemplates[pluralForm] = &Template{Src: src} + } +} + +// Execute executes the template for the plural form and template data. +func (mt *MessageTemplate) Execute(pluralForm plural.Form, data interface{}, funcs template.FuncMap) (string, error) { + t := mt.PluralTemplates[pluralForm] + if err := t.parse(mt.LeftDelim, mt.RightDelim, funcs); err != nil { + return "", err + } + if t.Template == nil { + return t.Src, nil + } + var buf bytes.Buffer + if err := t.Template.Execute(&buf, data); err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/parse.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/parse.go new file mode 100644 index 000000000..a5cee87c6 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/parse.go @@ -0,0 +1,112 @@ +package internal + +import ( + "encoding/json" + "fmt" + "os" + + "golang.org/x/text/language" +) + +// UnmarshalFunc unmarshals data into v. +type UnmarshalFunc func(data []byte, v interface{}) error + +// MessageFile represents a parsed message file. +type MessageFile struct { + Path string + Tag language.Tag + Format string + Messages []*Message +} + +// ParseMessageFileBytes returns the messages parsed from file. +func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]UnmarshalFunc) (*MessageFile, error) { + lang, format := parsePath(path) + tag := language.Make(lang) + messageFile := &MessageFile{ + Path: path, + Tag: tag, + Format: format, + } + if len(buf) == 0 { + return messageFile, nil + } + unmarshalFunc := unmarshalFuncs[messageFile.Format] + if unmarshalFunc == nil { + if messageFile.Format == "json" { + unmarshalFunc = json.Unmarshal + } else { + return nil, fmt.Errorf("no unmarshaler registered for %s", messageFile.Format) + } + } + var raw interface{} + if err := unmarshalFunc(buf, &raw); err != nil { + return nil, err + } + switch data := raw.(type) { + case map[string]interface{}: + messageFile.Messages = make([]*Message, 0, len(data)) + for id, data := range data { + m, err := NewMessage(data) + if err != nil { + return nil, err + } + m.ID = id + messageFile.Messages = append(messageFile.Messages, m) + } + case map[interface{}]interface{}: + messageFile.Messages = make([]*Message, 0, len(data)) + for id, data := range data { + strid, ok := id.(string) + if !ok { + return nil, fmt.Errorf("expected key to be string but got %#v", id) + } + m, err := NewMessage(data) + if err != nil { + return nil, err + } + m.ID = strid + messageFile.Messages = append(messageFile.Messages, m) + } + case []interface{}: + // Backward compatibility for v1 file format. + messageFile.Messages = make([]*Message, 0, len(data)) + for _, data := range data { + m, err := NewMessage(data) + if err != nil { + return nil, err + } + messageFile.Messages = append(messageFile.Messages, m) + } + default: + return nil, fmt.Errorf("unsupported file format %T", raw) + } + return messageFile, nil +} + +func parsePath(path string) (langTag, format string) { + formatStartIdx := -1 + for i := len(path) - 1; i >= 0; i-- { + c := path[i] + if os.IsPathSeparator(c) { + if formatStartIdx != -1 { + langTag = path[i+1 : formatStartIdx] + } + return + } + if path[i] == '.' { + if formatStartIdx != -1 { + langTag = path[i+1 : formatStartIdx] + return + } + if formatStartIdx == -1 { + format = path[i+1:] + formatStartIdx = i + } + } + } + if formatStartIdx != -1 { + langTag = path[:formatStartIdx] + } + return +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/doc.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/doc.go new file mode 100644 index 000000000..c2a71d53e --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/doc.go @@ -0,0 +1,3 @@ +// Package plural provides support for pluralizing messages +// according to CLDR rules http://cldr.unicode.org/index/cldr-spec/plural-rules +package plural diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/form.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/form.go new file mode 100644 index 000000000..287a87f22 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/form.go @@ -0,0 +1,16 @@ +package plural + +// Form represents a language pluralization form as defined here: +// http://cldr.unicode.org/index/cldr-spec/plural-rules +type Form string + +// All defined plural forms. +const ( + Invalid Form = "" + Zero Form = "zero" + One Form = "one" + Two Form = "two" + Few Form = "few" + Many Form = "many" + Other Form = "other" +) diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/operands.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/operands.go new file mode 100644 index 000000000..4d2972659 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/operands.go @@ -0,0 +1,120 @@ +package plural + +import ( + "fmt" + "strconv" + "strings" +) + +// Operands is a representation of http://unicode.org/reports/tr35/tr35-numbers.html#Operands +type Operands struct { + N float64 // absolute value of the source number (integer and decimals) + I int64 // integer digits of n + V int64 // number of visible fraction digits in n, with trailing zeros + W int64 // number of visible fraction digits in n, without trailing zeros + F int64 // visible fractional digits in n, with trailing zeros + T int64 // visible fractional digits in n, without trailing zeros +} + +// NEqualsAny returns true if o represents an integer equal to any of the arguments. +func (o *Operands) NEqualsAny(any ...int64) bool { + for _, i := range any { + if o.I == i && o.T == 0 { + return true + } + } + return false +} + +// NModEqualsAny returns true if o represents an integer equal to any of the arguments modulo mod. +func (o *Operands) NModEqualsAny(mod int64, any ...int64) bool { + modI := o.I % mod + for _, i := range any { + if modI == i && o.T == 0 { + return true + } + } + return false +} + +// NInRange returns true if o represents an integer in the closed interval [from, to]. +func (o *Operands) NInRange(from, to int64) bool { + return o.T == 0 && from <= o.I && o.I <= to +} + +// NModInRange returns true if o represents an integer in the closed interval [from, to] modulo mod. +func (o *Operands) NModInRange(mod, from, to int64) bool { + modI := o.I % mod + return o.T == 0 && from <= modI && modI <= to +} + +// NewOperands returns the operands for number. +func NewOperands(number interface{}) (*Operands, error) { + switch number := number.(type) { + case int: + return newOperandsInt64(int64(number)), nil + case int8: + return newOperandsInt64(int64(number)), nil + case int16: + return newOperandsInt64(int64(number)), nil + case int32: + return newOperandsInt64(int64(number)), nil + case int64: + return newOperandsInt64(number), nil + case string: + return newOperandsString(number) + case float32, float64: + return nil, fmt.Errorf("floats should be formatted into a string") + default: + return nil, fmt.Errorf("invalid type %T; expected integer or string", number) + } +} + +func newOperandsInt64(i int64) *Operands { + if i < 0 { + i = -i + } + return &Operands{float64(i), i, 0, 0, 0, 0} +} + +func newOperandsString(s string) (*Operands, error) { + if s[0] == '-' { + s = s[1:] + } + n, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, err + } + ops := &Operands{N: n} + parts := strings.SplitN(s, ".", 2) + ops.I, err = strconv.ParseInt(parts[0], 10, 64) + if err != nil { + return nil, err + } + if len(parts) == 1 { + return ops, nil + } + fraction := parts[1] + ops.V = int64(len(fraction)) + for i := ops.V - 1; i >= 0; i-- { + if fraction[i] != '0' { + ops.W = i + 1 + break + } + } + if ops.V > 0 { + f, err := strconv.ParseInt(fraction, 10, 0) + if err != nil { + return nil, err + } + ops.F = f + } + if ops.W > 0 { + t, err := strconv.ParseInt(fraction[:ops.W], 10, 0) + if err != nil { + return nil, err + } + ops.T = t + } + return ops, nil +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule.go new file mode 100644 index 000000000..0869c84ff --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule.go @@ -0,0 +1,44 @@ +package plural + +import ( + "golang.org/x/text/language" +) + +// Rule defines the CLDR plural rules for a language. +// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html +// http://unicode.org/reports/tr35/tr35-numbers.html#Operands +type Rule struct { + PluralForms map[Form]struct{} + PluralFormFunc func(*Operands) Form +} + +func addPluralRules(rules Rules, ids []string, ps *Rule) { + for _, id := range ids { + if id == "root" { + continue + } + tag := language.MustParse(id) + rules[tag] = ps + } +} + +func newPluralFormSet(pluralForms ...Form) map[Form]struct{} { + set := make(map[Form]struct{}, len(pluralForms)) + for _, plural := range pluralForms { + set[plural] = struct{}{} + } + return set +} + +func intInRange(i, from, to int64) bool { + return from <= i && i <= to +} + +func intEqualsAny(i int64, any ...int64) bool { + for _, a := range any { + if i == a { + return true + } + } + return false +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule_gen.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule_gen.go new file mode 100644 index 000000000..7cc6fc7ba --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rule_gen.go @@ -0,0 +1,561 @@ +// This file is generated by i18n/plural/codegen/generate.sh; DO NOT EDIT + +package plural + +// DefaultRules returns a map of Rules generated from CLDR language data. +func DefaultRules() Rules { + rules := Rules{} + + addPluralRules(rules, []string{"bm", "bo", "dz", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "root", "sah", "ses", "sg", "th", "to", "vi", "wo", "yo", "yue", "zh"}, &Rule{ + PluralForms: newPluralFormSet(Other), + PluralFormFunc: func(ops *Operands) Form { + return Other + }, + }) + addPluralRules(rules, []string{"am", "as", "bn", "fa", "gu", "hi", "kn", "mr", "zu"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 0 or n = 1 + if intEqualsAny(ops.I, 0) || + ops.NEqualsAny(1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"ff", "fr", "hy", "kab"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 0,1 + if intEqualsAny(ops.I, 0, 1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"pt"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 0..1 + if intInRange(ops.I, 0, 1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "io", "it", "ji", "nl", "pt_PT", "scn", "sv", "sw", "ur", "yi"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 1 and v = 0 + if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"si"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 0,1 or i = 0 and f = 1 + if ops.NEqualsAny(0, 1) || + intEqualsAny(ops.I, 0) && intEqualsAny(ops.F, 1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"ak", "bh", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 0..1 + if ops.NInRange(0, 1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"tzm"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 0..1 or n = 11..99 + if ops.NInRange(0, 1) || + ops.NInRange(11, 99) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"af", "asa", "az", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "es", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sd", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 1 + if ops.NEqualsAny(1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"da"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 1 or t != 0 and i = 0,1 + if ops.NEqualsAny(1) || + !intEqualsAny(ops.T, 0) && intEqualsAny(ops.I, 0, 1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"is"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 + if intEqualsAny(ops.T, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) || + !intEqualsAny(ops.T, 0) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"mk"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) || + intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"fil", "tl"}, &Rule{ + PluralForms: newPluralFormSet(One, Other), + PluralFormFunc: func(ops *Operands) Form { + // v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I, 1, 2, 3) || + intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I%10, 4, 6, 9) || + !intEqualsAny(ops.V, 0) && !intEqualsAny(ops.F%10, 4, 6, 9) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"lv", "prg"}, &Rule{ + PluralForms: newPluralFormSet(Zero, One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 + if ops.NModEqualsAny(10, 0) || + ops.NModInRange(100, 11, 19) || + intEqualsAny(ops.V, 2) && intInRange(ops.F%100, 11, 19) { + return Zero + } + // n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 + if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11) || + intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) || + !intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"lag"}, &Rule{ + PluralForms: newPluralFormSet(Zero, One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 0 + if ops.NEqualsAny(0) { + return Zero + } + // i = 0,1 and n != 0 + if intEqualsAny(ops.I, 0, 1) && !ops.NEqualsAny(0) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"ksh"}, &Rule{ + PluralForms: newPluralFormSet(Zero, One, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 0 + if ops.NEqualsAny(0) { + return Zero + } + // n = 1 + if ops.NEqualsAny(1) { + return One + } + return Other + }, + }) + addPluralRules(rules, []string{"iu", "kw", "naq", "se", "sma", "smi", "smj", "smn", "sms"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 1 + if ops.NEqualsAny(1) { + return One + } + // n = 2 + if ops.NEqualsAny(2) { + return Two + } + return Other + }, + }) + addPluralRules(rules, []string{"shi"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 0 or n = 1 + if intEqualsAny(ops.I, 0) || + ops.NEqualsAny(1) { + return One + } + // n = 2..10 + if ops.NInRange(2, 10) { + return Few + } + return Other + }, + }) + addPluralRules(rules, []string{"mo", "ro"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 1 and v = 0 + if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { + return One + } + // v != 0 or n = 0 or n != 1 and n % 100 = 1..19 + if !intEqualsAny(ops.V, 0) || + ops.NEqualsAny(0) || + !ops.NEqualsAny(1) && ops.NModInRange(100, 1, 19) { + return Few + } + return Other + }, + }) + addPluralRules(rules, []string{"bs", "hr", "sh", "sr"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Other), + PluralFormFunc: func(ops *Operands) Form { + // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) || + intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) { + return One + } + // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 + if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) || + intInRange(ops.F%10, 2, 4) && !intInRange(ops.F%100, 12, 14) { + return Few + } + return Other + }, + }) + addPluralRules(rules, []string{"gd"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Few, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 1,11 + if ops.NEqualsAny(1, 11) { + return One + } + // n = 2,12 + if ops.NEqualsAny(2, 12) { + return Two + } + // n = 3..10,13..19 + if ops.NInRange(3, 10) || ops.NInRange(13, 19) { + return Few + } + return Other + }, + }) + addPluralRules(rules, []string{"sl"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Few, Other), + PluralFormFunc: func(ops *Operands) Form { + // v = 0 and i % 100 = 1 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) { + return One + } + // v = 0 and i % 100 = 2 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) { + return Two + } + // v = 0 and i % 100 = 3..4 or v != 0 + if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) || + !intEqualsAny(ops.V, 0) { + return Few + } + return Other + }, + }) + addPluralRules(rules, []string{"dsb", "hsb"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Few, Other), + PluralFormFunc: func(ops *Operands) Form { + // v = 0 and i % 100 = 1 or f % 100 = 1 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) || + intEqualsAny(ops.F%100, 1) { + return One + } + // v = 0 and i % 100 = 2 or f % 100 = 2 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) || + intEqualsAny(ops.F%100, 2) { + return Two + } + // v = 0 and i % 100 = 3..4 or f % 100 = 3..4 + if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) || + intInRange(ops.F%100, 3, 4) { + return Few + } + return Other + }, + }) + addPluralRules(rules, []string{"he", "iw"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 1 and v = 0 + if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { + return One + } + // i = 2 and v = 0 + if intEqualsAny(ops.I, 2) && intEqualsAny(ops.V, 0) { + return Two + } + // v = 0 and n != 0..10 and n % 10 = 0 + if intEqualsAny(ops.V, 0) && !ops.NInRange(0, 10) && ops.NModEqualsAny(10, 0) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"cs", "sk"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 1 and v = 0 + if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { + return One + } + // i = 2..4 and v = 0 + if intInRange(ops.I, 2, 4) && intEqualsAny(ops.V, 0) { + return Few + } + // v != 0 + if !intEqualsAny(ops.V, 0) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"pl"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // i = 1 and v = 0 + if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { + return One + } + // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 + if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) { + return Few + } + // v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 + if intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I, 1) && intInRange(ops.I%10, 0, 1) || + intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) || + intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 12, 14) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"be"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // n % 10 = 1 and n % 100 != 11 + if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11) { + return One + } + // n % 10 = 2..4 and n % 100 != 12..14 + if ops.NModInRange(10, 2, 4) && !ops.NModInRange(100, 12, 14) { + return Few + } + // n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 + if ops.NModEqualsAny(10, 0) || + ops.NModInRange(10, 5, 9) || + ops.NModInRange(100, 11, 14) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"lt"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // n % 10 = 1 and n % 100 != 11..19 + if ops.NModEqualsAny(10, 1) && !ops.NModInRange(100, 11, 19) { + return One + } + // n % 10 = 2..9 and n % 100 != 11..19 + if ops.NModInRange(10, 2, 9) && !ops.NModInRange(100, 11, 19) { + return Few + } + // f != 0 + if !intEqualsAny(ops.F, 0) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"mt"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 1 + if ops.NEqualsAny(1) { + return One + } + // n = 0 or n % 100 = 2..10 + if ops.NEqualsAny(0) || + ops.NModInRange(100, 2, 10) { + return Few + } + // n % 100 = 11..19 + if ops.NModInRange(100, 11, 19) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"ru", "uk"}, &Rule{ + PluralForms: newPluralFormSet(One, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // v = 0 and i % 10 = 1 and i % 100 != 11 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) { + return One + } + // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 + if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) { + return Few + } + // v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 0) || + intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) || + intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 11, 14) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"br"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // n % 10 = 1 and n % 100 != 11,71,91 + if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11, 71, 91) { + return One + } + // n % 10 = 2 and n % 100 != 12,72,92 + if ops.NModEqualsAny(10, 2) && !ops.NModEqualsAny(100, 12, 72, 92) { + return Two + } + // n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 + if (ops.NModInRange(10, 3, 4) || ops.NModEqualsAny(10, 9)) && !(ops.NModInRange(100, 10, 19) || ops.NModInRange(100, 70, 79) || ops.NModInRange(100, 90, 99)) { + return Few + } + // n != 0 and n % 1000000 = 0 + if !ops.NEqualsAny(0) && ops.NModEqualsAny(1000000, 0) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"ga"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 1 + if ops.NEqualsAny(1) { + return One + } + // n = 2 + if ops.NEqualsAny(2) { + return Two + } + // n = 3..6 + if ops.NInRange(3, 6) { + return Few + } + // n = 7..10 + if ops.NInRange(7, 10) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"gv"}, &Rule{ + PluralForms: newPluralFormSet(One, Two, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // v = 0 and i % 10 = 1 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) { + return One + } + // v = 0 and i % 10 = 2 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 2) { + return Two + } + // v = 0 and i % 100 = 0,20,40,60,80 + if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 0, 20, 40, 60, 80) { + return Few + } + // v != 0 + if !intEqualsAny(ops.V, 0) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"ar", "ars"}, &Rule{ + PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 0 + if ops.NEqualsAny(0) { + return Zero + } + // n = 1 + if ops.NEqualsAny(1) { + return One + } + // n = 2 + if ops.NEqualsAny(2) { + return Two + } + // n % 100 = 3..10 + if ops.NModInRange(100, 3, 10) { + return Few + } + // n % 100 = 11..99 + if ops.NModInRange(100, 11, 99) { + return Many + } + return Other + }, + }) + addPluralRules(rules, []string{"cy"}, &Rule{ + PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other), + PluralFormFunc: func(ops *Operands) Form { + // n = 0 + if ops.NEqualsAny(0) { + return Zero + } + // n = 1 + if ops.NEqualsAny(1) { + return One + } + // n = 2 + if ops.NEqualsAny(2) { + return Two + } + // n = 3 + if ops.NEqualsAny(3) { + return Few + } + // n = 6 + if ops.NEqualsAny(6) { + return Many + } + return Other + }, + }) + + return rules +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rules.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rules.go new file mode 100644 index 000000000..87eb8369d --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/plural/rules.go @@ -0,0 +1,24 @@ +package plural + +import "golang.org/x/text/language" + +// Rules is a set of plural rules by language tag. +type Rules map[language.Tag]*Rule + +// Rule returns the closest matching plural rule for the language tag +// or nil if no rule could be found. +func (r Rules) Rule(tag language.Tag) *Rule { + t := tag + for { + if rule := r[t]; rule != nil { + return rule + } + t = t.Parent() + if t.IsRoot() { + break + } + } + base, _ := tag.Base() + baseTag, _ := language.Parse(base.String()) + return r[baseTag] +} diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/internal/template.go b/vendor/github.com/nicksnyder/go-i18n/v2/internal/template.go new file mode 100644 index 000000000..2ef1eeac2 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/internal/template.go @@ -0,0 +1,26 @@ +package internal + +import ( + "strings" + gotemplate "text/template" +) + +// Template stores the template for a string. +type Template struct { + Src string + Template *gotemplate.Template + ParseErr *error +} + +func (t *Template) parse(leftDelim, rightDelim string, funcs gotemplate.FuncMap) error { + if t.ParseErr == nil { + if strings.Contains(t.Src, leftDelim) { + gt, err := gotemplate.New("").Funcs(funcs).Delims(leftDelim, rightDelim).Parse(t.Src) + t.Template = gt + t.ParseErr = &err + } else { + t.ParseErr = new(error) + } + } + return *t.ParseErr +} diff --git a/vendor/golang.org/x/text/internal/tag/tag.go b/vendor/golang.org/x/text/internal/tag/tag.go new file mode 100644 index 000000000..b5d348891 --- /dev/null +++ b/vendor/golang.org/x/text/internal/tag/tag.go @@ -0,0 +1,100 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tag contains functionality handling tags and related data. +package tag // import "golang.org/x/text/internal/tag" + +import "sort" + +// An Index converts tags to a compact numeric value. +// +// All elements are of size 4. Tags may be up to 4 bytes long. Excess bytes can +// be used to store additional information about the tag. +type Index string + +// Elem returns the element data at the given index. +func (s Index) Elem(x int) string { + return string(s[x*4 : x*4+4]) +} + +// Index reports the index of the given key or -1 if it could not be found. +// Only the first len(key) bytes from the start of the 4-byte entries will be +// considered for the search and the first match in Index will be returned. +func (s Index) Index(key []byte) int { + n := len(key) + // search the index of the first entry with an equal or higher value than + // key in s. + index := sort.Search(len(s)/4, func(i int) bool { + return cmp(s[i*4:i*4+n], key) != -1 + }) + i := index * 4 + if cmp(s[i:i+len(key)], key) != 0 { + return -1 + } + return index +} + +// Next finds the next occurrence of key after index x, which must have been +// obtained from a call to Index using the same key. It returns x+1 or -1. +func (s Index) Next(key []byte, x int) int { + if x++; x*4 < len(s) && cmp(s[x*4:x*4+len(key)], key) == 0 { + return x + } + return -1 +} + +// cmp returns an integer comparing a and b lexicographically. +func cmp(a Index, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + for i, c := range b[:n] { + switch { + case a[i] > c: + return 1 + case a[i] < c: + return -1 + } + } + switch { + case len(a) < len(b): + return -1 + case len(a) > len(b): + return 1 + } + return 0 +} + +// Compare returns an integer comparing a and b lexicographically. +func Compare(a string, b []byte) int { + return cmp(Index(a), b) +} + +// FixCase reformats b to the same pattern of cases as form. +// If returns false if string b is malformed. +func FixCase(form string, b []byte) bool { + if len(form) != len(b) { + return false + } + for i, c := range b { + if form[i] <= 'Z' { + if c >= 'a' { + c -= 'z' - 'Z' + } + if c < 'A' || 'Z' < c { + return false + } + } else { + if c <= 'Z' { + c += 'z' - 'Z' + } + if c < 'a' || 'z' < c { + return false + } + } + b[i] = c + } + return true +} diff --git a/vendor/golang.org/x/text/language/common.go b/vendor/golang.org/x/text/language/common.go new file mode 100644 index 000000000..9d86e1855 --- /dev/null +++ b/vendor/golang.org/x/text/language/common.go @@ -0,0 +1,16 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +package language + +// This file contains code common to the maketables.go and the package code. + +// langAliasType is the type of an alias in langAliasMap. +type langAliasType int8 + +const ( + langDeprecated langAliasType = iota + langMacro + langLegacy + + langAliasTypeUnknown langAliasType = -1 +) diff --git a/vendor/golang.org/x/text/language/coverage.go b/vendor/golang.org/x/text/language/coverage.go new file mode 100644 index 000000000..101fd23c1 --- /dev/null +++ b/vendor/golang.org/x/text/language/coverage.go @@ -0,0 +1,197 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package language + +import ( + "fmt" + "sort" +) + +// The Coverage interface is used to define the level of coverage of an +// internationalization service. Note that not all types are supported by all +// services. As lists may be generated on the fly, it is recommended that users +// of a Coverage cache the results. +type Coverage interface { + // Tags returns the list of supported tags. + Tags() []Tag + + // BaseLanguages returns the list of supported base languages. + BaseLanguages() []Base + + // Scripts returns the list of supported scripts. + Scripts() []Script + + // Regions returns the list of supported regions. + Regions() []Region +} + +var ( + // Supported defines a Coverage that lists all supported subtags. Tags + // always returns nil. + Supported Coverage = allSubtags{} +) + +// TODO: +// - Support Variants, numbering systems. +// - CLDR coverage levels. +// - Set of common tags defined in this package. + +type allSubtags struct{} + +// Regions returns the list of supported regions. As all regions are in a +// consecutive range, it simply returns a slice of numbers in increasing order. +// The "undefined" region is not returned. +func (s allSubtags) Regions() []Region { + reg := make([]Region, numRegions) + for i := range reg { + reg[i] = Region{regionID(i + 1)} + } + return reg +} + +// Scripts returns the list of supported scripts. As all scripts are in a +// consecutive range, it simply returns a slice of numbers in increasing order. +// The "undefined" script is not returned. +func (s allSubtags) Scripts() []Script { + scr := make([]Script, numScripts) + for i := range scr { + scr[i] = Script{scriptID(i + 1)} + } + return scr +} + +// BaseLanguages returns the list of all supported base languages. It generates +// the list by traversing the internal structures. +func (s allSubtags) BaseLanguages() []Base { + base := make([]Base, 0, numLanguages) + for i := 0; i < langNoIndexOffset; i++ { + // We included "und" already for the value 0. + if i != nonCanonicalUnd { + base = append(base, Base{langID(i)}) + } + } + i := langNoIndexOffset + for _, v := range langNoIndex { + for k := 0; k < 8; k++ { + if v&1 == 1 { + base = append(base, Base{langID(i)}) + } + v >>= 1 + i++ + } + } + return base +} + +// Tags always returns nil. +func (s allSubtags) Tags() []Tag { + return nil +} + +// coverage is used used by NewCoverage which is used as a convenient way for +// creating Coverage implementations for partially defined data. Very often a +// package will only need to define a subset of slices. coverage provides a +// convenient way to do this. Moreover, packages using NewCoverage, instead of +// their own implementation, will not break if later new slice types are added. +type coverage struct { + tags func() []Tag + bases func() []Base + scripts func() []Script + regions func() []Region +} + +func (s *coverage) Tags() []Tag { + if s.tags == nil { + return nil + } + return s.tags() +} + +// bases implements sort.Interface and is used to sort base languages. +type bases []Base + +func (b bases) Len() int { + return len(b) +} + +func (b bases) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b bases) Less(i, j int) bool { + return b[i].langID < b[j].langID +} + +// BaseLanguages returns the result from calling s.bases if it is specified or +// otherwise derives the set of supported base languages from tags. +func (s *coverage) BaseLanguages() []Base { + if s.bases == nil { + tags := s.Tags() + if len(tags) == 0 { + return nil + } + a := make([]Base, len(tags)) + for i, t := range tags { + a[i] = Base{langID(t.lang)} + } + sort.Sort(bases(a)) + k := 0 + for i := 1; i < len(a); i++ { + if a[k] != a[i] { + k++ + a[k] = a[i] + } + } + return a[:k+1] + } + return s.bases() +} + +func (s *coverage) Scripts() []Script { + if s.scripts == nil { + return nil + } + return s.scripts() +} + +func (s *coverage) Regions() []Region { + if s.regions == nil { + return nil + } + return s.regions() +} + +// NewCoverage returns a Coverage for the given lists. It is typically used by +// packages providing internationalization services to define their level of +// coverage. A list may be of type []T or func() []T, where T is either Tag, +// Base, Script or Region. The returned Coverage derives the value for Bases +// from Tags if no func or slice for []Base is specified. For other unspecified +// types the returned Coverage will return nil for the respective methods. +func NewCoverage(list ...interface{}) Coverage { + s := &coverage{} + for _, x := range list { + switch v := x.(type) { + case func() []Base: + s.bases = v + case func() []Script: + s.scripts = v + case func() []Region: + s.regions = v + case func() []Tag: + s.tags = v + case []Base: + s.bases = func() []Base { return v } + case []Script: + s.scripts = func() []Script { return v } + case []Region: + s.regions = func() []Region { return v } + case []Tag: + s.tags = func() []Tag { return v } + default: + panic(fmt.Sprintf("language: unsupported set type %T", v)) + } + } + return s +} diff --git a/vendor/golang.org/x/text/language/doc.go b/vendor/golang.org/x/text/language/doc.go new file mode 100644 index 000000000..8afecd50e --- /dev/null +++ b/vendor/golang.org/x/text/language/doc.go @@ -0,0 +1,102 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package language implements BCP 47 language tags and related functionality. +// +// The most important function of package language is to match a list of +// user-preferred languages to a list of supported languages. +// It alleviates the developer of dealing with the complexity of this process +// and provides the user with the best experience +// (see https://blog.golang.org/matchlang). +// +// +// Matching preferred against supported languages +// +// A Matcher for an application that supports English, Australian English, +// Danish, and standard Mandarin can be created as follows: +// +// var matcher = language.NewMatcher([]language.Tag{ +// language.English, // The first language is used as fallback. +// language.MustParse("en-AU"), +// language.Danish, +// language.Chinese, +// }) +// +// This list of supported languages is typically implied by the languages for +// which there exists translations of the user interface. +// +// User-preferred languages usually come as a comma-separated list of BCP 47 +// language tags. +// The MatchString finds best matches for such strings: +// +// handler(w http.ResponseWriter, r *http.Request) { +// lang, _ := r.Cookie("lang") +// accept := r.Header.Get("Accept-Language") +// tag, _ := language.MatchStrings(matcher, lang.String(), accept) +// +// // tag should now be used for the initialization of any +// // locale-specific service. +// } +// +// The Matcher's Match method can be used to match Tags directly. +// +// Matchers are aware of the intricacies of equivalence between languages, such +// as deprecated subtags, legacy tags, macro languages, mutual +// intelligibility between scripts and languages, and transparently passing +// BCP 47 user configuration. +// For instance, it will know that a reader of Bokmål Danish can read Norwegian +// and will know that Cantonese ("yue") is a good match for "zh-HK". +// +// +// Using match results +// +// To guarantee a consistent user experience to the user it is important to +// use the same language tag for the selection of any locale-specific services. +// For example, it is utterly confusing to substitute spelled-out numbers +// or dates in one language in text of another language. +// More subtly confusing is using the wrong sorting order or casing +// algorithm for a certain language. +// +// All the packages in x/text that provide locale-specific services +// (e.g. collate, cases) should be initialized with the tag that was +// obtained at the start of an interaction with the user. +// +// Note that Tag that is returned by Match and MatchString may differ from any +// of the supported languages, as it may contain carried over settings from +// the user tags. +// This may be inconvenient when your application has some additional +// locale-specific data for your supported languages. +// Match and MatchString both return the index of the matched supported tag +// to simplify associating such data with the matched tag. +// +// +// Canonicalization +// +// If one uses the Matcher to compare languages one does not need to +// worry about canonicalization. +// +// The meaning of a Tag varies per application. The language package +// therefore delays canonicalization and preserves information as much +// as possible. The Matcher, however, will always take into account that +// two different tags may represent the same language. +// +// By default, only legacy and deprecated tags are converted into their +// canonical equivalent. All other information is preserved. This approach makes +// the confidence scores more accurate and allows matchers to distinguish +// between variants that are otherwise lost. +// +// As a consequence, two tags that should be treated as identical according to +// BCP 47 or CLDR, like "en-Latn" and "en", will be represented differently. The +// Matcher handles such distinctions, though, and is aware of the +// equivalence relations. The CanonType type can be used to alter the +// canonicalization form. +// +// References +// +// BCP 47 - Tags for Identifying Languages http://tools.ietf.org/html/bcp47 +// +package language // import "golang.org/x/text/language" + +// TODO: explanation on how to match languages for your own locale-specific +// service. diff --git a/vendor/golang.org/x/text/language/gen.go b/vendor/golang.org/x/text/language/gen.go new file mode 100644 index 000000000..302f1940a --- /dev/null +++ b/vendor/golang.org/x/text/language/gen.go @@ -0,0 +1,1712 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +// Language tag table generator. +// Data read from the web. + +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "math" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + + "golang.org/x/text/internal/gen" + "golang.org/x/text/internal/tag" + "golang.org/x/text/unicode/cldr" +) + +var ( + test = flag.Bool("test", + false, + "test existing tables; can be used to compare web data with package data.") + outputFile = flag.String("output", + "tables.go", + "output file for generated tables") +) + +var comment = []string{ + ` +lang holds an alphabetically sorted list of ISO-639 language identifiers. +All entries are 4 bytes. The index of the identifier (divided by 4) is the language tag. +For 2-byte language identifiers, the two successive bytes have the following meaning: + - if the first letter of the 2- and 3-letter ISO codes are the same: + the second and third letter of the 3-letter ISO code. + - otherwise: a 0 and a by 2 bits right-shifted index into altLangISO3. +For 3-byte language identifiers the 4th byte is 0.`, + ` +langNoIndex is a bit vector of all 3-letter language codes that are not used as an index +in lookup tables. The language ids for these language codes are derived directly +from the letters and are not consecutive.`, + ` +altLangISO3 holds an alphabetically sorted list of 3-letter language code alternatives +to 2-letter language codes that cannot be derived using the method described above. +Each 3-letter code is followed by its 1-byte langID.`, + ` +altLangIndex is used to convert indexes in altLangISO3 to langIDs.`, + ` +langAliasMap maps langIDs to their suggested replacements.`, + ` +script is an alphabetically sorted list of ISO 15924 codes. The index +of the script in the string, divided by 4, is the internal scriptID.`, + ` +isoRegionOffset needs to be added to the index of regionISO to obtain the regionID +for 2-letter ISO codes. (The first isoRegionOffset regionIDs are reserved for +the UN.M49 codes used for groups.)`, + ` +regionISO holds a list of alphabetically sorted 2-letter ISO region codes. +Each 2-letter codes is followed by two bytes with the following meaning: + - [A-Z}{2}: the first letter of the 2-letter code plus these two + letters form the 3-letter ISO code. + - 0, n: index into altRegionISO3.`, + ` +regionTypes defines the status of a region for various standards.`, + ` +m49 maps regionIDs to UN.M49 codes. The first isoRegionOffset entries are +codes indicating collections of regions.`, + ` +m49Index gives indexes into fromM49 based on the three most significant bits +of a 10-bit UN.M49 code. To search an UN.M49 code in fromM49, search in + fromM49[m49Index[msb39(code)]:m49Index[msb3(code)+1]] +for an entry where the first 7 bits match the 7 lsb of the UN.M49 code. +The region code is stored in the 9 lsb of the indexed value.`, + ` +fromM49 contains entries to map UN.M49 codes to regions. See m49Index for details.`, + ` +altRegionISO3 holds a list of 3-letter region codes that cannot be +mapped to 2-letter codes using the default algorithm. This is a short list.`, + ` +altRegionIDs holds a list of regionIDs the positions of which match those +of the 3-letter ISO codes in altRegionISO3.`, + ` +variantNumSpecialized is the number of specialized variants in variants.`, + ` +suppressScript is an index from langID to the dominant script for that language, +if it exists. If a script is given, it should be suppressed from the language tag.`, + ` +likelyLang is a lookup table, indexed by langID, for the most likely +scripts and regions given incomplete information. If more entries exist for a +given language, region and script are the index and size respectively +of the list in likelyLangList.`, + ` +likelyLangList holds lists info associated with likelyLang.`, + ` +likelyRegion is a lookup table, indexed by regionID, for the most likely +languages and scripts given incomplete information. If more entries exist +for a given regionID, lang and script are the index and size respectively +of the list in likelyRegionList. +TODO: exclude containers and user-definable regions from the list.`, + ` +likelyRegionList holds lists info associated with likelyRegion.`, + ` +likelyScript is a lookup table, indexed by scriptID, for the most likely +languages and regions given a script.`, + ` +matchLang holds pairs of langIDs of base languages that are typically +mutually intelligible. Each pair is associated with a confidence and +whether the intelligibility goes one or both ways.`, + ` +matchScript holds pairs of scriptIDs where readers of one script +can typically also read the other. Each is associated with a confidence.`, + ` +nRegionGroups is the number of region groups.`, + ` +regionInclusion maps region identifiers to sets of regions in regionInclusionBits, +where each set holds all groupings that are directly connected in a region +containment graph.`, + ` +regionInclusionBits is an array of bit vectors where every vector represents +a set of region groupings. These sets are used to compute the distance +between two regions for the purpose of language matching.`, + ` +regionInclusionNext marks, for each entry in regionInclusionBits, the set of +all groups that are reachable from the groups set in the respective entry.`, +} + +// TODO: consider changing some of these structures to tries. This can reduce +// memory, but may increase the need for memory allocations. This could be +// mitigated if we can piggyback on language tags for common cases. + +func failOnError(e error) { + if e != nil { + log.Panic(e) + } +} + +type setType int + +const ( + Indexed setType = 1 + iota // all elements must be of same size + Linear +) + +type stringSet struct { + s []string + sorted, frozen bool + + // We often need to update values after the creation of an index is completed. + // We include a convenience map for keeping track of this. + update map[string]string + typ setType // used for checking. +} + +func (ss *stringSet) clone() stringSet { + c := *ss + c.s = append([]string(nil), c.s...) + return c +} + +func (ss *stringSet) setType(t setType) { + if ss.typ != t && ss.typ != 0 { + log.Panicf("type %d cannot be assigned as it was already %d", t, ss.typ) + } +} + +// parse parses a whitespace-separated string and initializes ss with its +// components. +func (ss *stringSet) parse(s string) { + scan := bufio.NewScanner(strings.NewReader(s)) + scan.Split(bufio.ScanWords) + for scan.Scan() { + ss.add(scan.Text()) + } +} + +func (ss *stringSet) assertChangeable() { + if ss.frozen { + log.Panic("attempt to modify a frozen stringSet") + } +} + +func (ss *stringSet) add(s string) { + ss.assertChangeable() + ss.s = append(ss.s, s) + ss.sorted = ss.frozen +} + +func (ss *stringSet) freeze() { + ss.compact() + ss.frozen = true +} + +func (ss *stringSet) compact() { + if ss.sorted { + return + } + a := ss.s + sort.Strings(a) + k := 0 + for i := 1; i < len(a); i++ { + if a[k] != a[i] { + a[k+1] = a[i] + k++ + } + } + ss.s = a[:k+1] + ss.sorted = ss.frozen +} + +type funcSorter struct { + fn func(a, b string) bool + sort.StringSlice +} + +func (s funcSorter) Less(i, j int) bool { + return s.fn(s.StringSlice[i], s.StringSlice[j]) +} + +func (ss *stringSet) sortFunc(f func(a, b string) bool) { + ss.compact() + sort.Sort(funcSorter{f, sort.StringSlice(ss.s)}) +} + +func (ss *stringSet) remove(s string) { + ss.assertChangeable() + if i, ok := ss.find(s); ok { + copy(ss.s[i:], ss.s[i+1:]) + ss.s = ss.s[:len(ss.s)-1] + } +} + +func (ss *stringSet) replace(ol, nu string) { + ss.s[ss.index(ol)] = nu + ss.sorted = ss.frozen +} + +func (ss *stringSet) index(s string) int { + ss.setType(Indexed) + i, ok := ss.find(s) + if !ok { + if i < len(ss.s) { + log.Panicf("find: item %q is not in list. Closest match is %q.", s, ss.s[i]) + } + log.Panicf("find: item %q is not in list", s) + + } + return i +} + +func (ss *stringSet) find(s string) (int, bool) { + ss.compact() + i := sort.SearchStrings(ss.s, s) + return i, i != len(ss.s) && ss.s[i] == s +} + +func (ss *stringSet) slice() []string { + ss.compact() + return ss.s +} + +func (ss *stringSet) updateLater(v, key string) { + if ss.update == nil { + ss.update = map[string]string{} + } + ss.update[v] = key +} + +// join joins the string and ensures that all entries are of the same length. +func (ss *stringSet) join() string { + ss.setType(Indexed) + n := len(ss.s[0]) + for _, s := range ss.s { + if len(s) != n { + log.Panicf("join: not all entries are of the same length: %q", s) + } + } + ss.s = append(ss.s, strings.Repeat("\xff", n)) + return strings.Join(ss.s, "") +} + +// ianaEntry holds information for an entry in the IANA Language Subtag Repository. +// All types use the same entry. +// See http://tools.ietf.org/html/bcp47#section-5.1 for a description of the various +// fields. +type ianaEntry struct { + typ string + description []string + scope string + added string + preferred string + deprecated string + suppressScript string + macro string + prefix []string +} + +type builder struct { + w *gen.CodeWriter + hw io.Writer // MultiWriter for w and w.Hash + data *cldr.CLDR + supp *cldr.SupplementalData + + // indices + locale stringSet // common locales + lang stringSet // canonical language ids (2 or 3 letter ISO codes) with data + langNoIndex stringSet // 3-letter ISO codes with no associated data + script stringSet // 4-letter ISO codes + region stringSet // 2-letter ISO or 3-digit UN M49 codes + variant stringSet // 4-8-alphanumeric variant code. + + // Region codes that are groups with their corresponding group IDs. + groups map[int]index + + // langInfo + registry map[string]*ianaEntry +} + +type index uint + +func newBuilder(w *gen.CodeWriter) *builder { + r := gen.OpenCLDRCoreZip() + defer r.Close() + d := &cldr.Decoder{} + data, err := d.DecodeZip(r) + failOnError(err) + b := builder{ + w: w, + hw: io.MultiWriter(w, w.Hash), + data: data, + supp: data.Supplemental(), + } + b.parseRegistry() + return &b +} + +func (b *builder) parseRegistry() { + r := gen.OpenIANAFile("assignments/language-subtag-registry") + defer r.Close() + b.registry = make(map[string]*ianaEntry) + + scan := bufio.NewScanner(r) + scan.Split(bufio.ScanWords) + var record *ianaEntry + for more := scan.Scan(); more; { + key := scan.Text() + more = scan.Scan() + value := scan.Text() + switch key { + case "Type:": + record = &ianaEntry{typ: value} + case "Subtag:", "Tag:": + if s := strings.SplitN(value, "..", 2); len(s) > 1 { + for a := s[0]; a <= s[1]; a = inc(a) { + b.addToRegistry(a, record) + } + } else { + b.addToRegistry(value, record) + } + case "Suppress-Script:": + record.suppressScript = value + case "Added:": + record.added = value + case "Deprecated:": + record.deprecated = value + case "Macrolanguage:": + record.macro = value + case "Preferred-Value:": + record.preferred = value + case "Prefix:": + record.prefix = append(record.prefix, value) + case "Scope:": + record.scope = value + case "Description:": + buf := []byte(value) + for more = scan.Scan(); more; more = scan.Scan() { + b := scan.Bytes() + if b[0] == '%' || b[len(b)-1] == ':' { + break + } + buf = append(buf, ' ') + buf = append(buf, b...) + } + record.description = append(record.description, string(buf)) + continue + default: + continue + } + more = scan.Scan() + } + if scan.Err() != nil { + log.Panic(scan.Err()) + } +} + +func (b *builder) addToRegistry(key string, entry *ianaEntry) { + if info, ok := b.registry[key]; ok { + if info.typ != "language" || entry.typ != "extlang" { + log.Fatalf("parseRegistry: tag %q already exists", key) + } + } else { + b.registry[key] = entry + } +} + +var commentIndex = make(map[string]string) + +func init() { + for _, s := range comment { + key := strings.TrimSpace(strings.SplitN(s, " ", 2)[0]) + commentIndex[key] = s + } +} + +func (b *builder) comment(name string) { + if s := commentIndex[name]; len(s) > 0 { + b.w.WriteComment(s) + } else { + fmt.Fprintln(b.w) + } +} + +func (b *builder) pf(f string, x ...interface{}) { + fmt.Fprintf(b.hw, f, x...) + fmt.Fprint(b.hw, "\n") +} + +func (b *builder) p(x ...interface{}) { + fmt.Fprintln(b.hw, x...) +} + +func (b *builder) addSize(s int) { + b.w.Size += s + b.pf("// Size: %d bytes", s) +} + +func (b *builder) writeConst(name string, x interface{}) { + b.comment(name) + b.w.WriteConst(name, x) +} + +// writeConsts computes f(v) for all v in values and writes the results +// as constants named _v to a single constant block. +func (b *builder) writeConsts(f func(string) int, values ...string) { + b.pf("const (") + for _, v := range values { + b.pf("\t_%s = %v", v, f(v)) + } + b.pf(")") +} + +// writeType writes the type of the given value, which must be a struct. +func (b *builder) writeType(value interface{}) { + b.comment(reflect.TypeOf(value).Name()) + b.w.WriteType(value) +} + +func (b *builder) writeSlice(name string, ss interface{}) { + b.writeSliceAddSize(name, 0, ss) +} + +func (b *builder) writeSliceAddSize(name string, extraSize int, ss interface{}) { + b.comment(name) + b.w.Size += extraSize + v := reflect.ValueOf(ss) + t := v.Type().Elem() + b.pf("// Size: %d bytes, %d elements", v.Len()*int(t.Size())+extraSize, v.Len()) + + fmt.Fprintf(b.w, "var %s = ", name) + b.w.WriteArray(ss) + b.p() +} + +type fromTo struct { + from, to uint16 +} + +func (b *builder) writeSortedMap(name string, ss *stringSet, index func(s string) uint16) { + ss.sortFunc(func(a, b string) bool { + return index(a) < index(b) + }) + m := []fromTo{} + for _, s := range ss.s { + m = append(m, fromTo{index(s), index(ss.update[s])}) + } + b.writeSlice(name, m) +} + +const base = 'z' - 'a' + 1 + +func strToInt(s string) uint { + v := uint(0) + for i := 0; i < len(s); i++ { + v *= base + v += uint(s[i] - 'a') + } + return v +} + +// converts the given integer to the original ASCII string passed to strToInt. +// len(s) must match the number of characters obtained. +func intToStr(v uint, s []byte) { + for i := len(s) - 1; i >= 0; i-- { + s[i] = byte(v%base) + 'a' + v /= base + } +} + +func (b *builder) writeBitVector(name string, ss []string) { + vec := make([]uint8, int(math.Ceil(math.Pow(base, float64(len(ss[0])))/8))) + for _, s := range ss { + v := strToInt(s) + vec[v/8] |= 1 << (v % 8) + } + b.writeSlice(name, vec) +} + +// TODO: convert this type into a list or two-stage trie. +func (b *builder) writeMapFunc(name string, m map[string]string, f func(string) uint16) { + b.comment(name) + v := reflect.ValueOf(m) + sz := v.Len() * (2 + int(v.Type().Key().Size())) + for _, k := range m { + sz += len(k) + } + b.addSize(sz) + keys := []string{} + b.pf(`var %s = map[string]uint16{`, name) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + b.pf("\t%q: %v,", k, f(m[k])) + } + b.p("}") +} + +func (b *builder) writeMap(name string, m interface{}) { + b.comment(name) + v := reflect.ValueOf(m) + sz := v.Len() * (2 + int(v.Type().Key().Size()) + int(v.Type().Elem().Size())) + b.addSize(sz) + f := strings.FieldsFunc(fmt.Sprintf("%#v", m), func(r rune) bool { + return strings.IndexRune("{}, ", r) != -1 + }) + sort.Strings(f[1:]) + b.pf(`var %s = %s{`, name, f[0]) + for _, kv := range f[1:] { + b.pf("\t%s,", kv) + } + b.p("}") +} + +func (b *builder) langIndex(s string) uint16 { + if s == "und" { + return 0 + } + if i, ok := b.lang.find(s); ok { + return uint16(i) + } + return uint16(strToInt(s)) + uint16(len(b.lang.s)) +} + +// inc advances the string to its lexicographical successor. +func inc(s string) string { + const maxTagLength = 4 + var buf [maxTagLength]byte + intToStr(strToInt(strings.ToLower(s))+1, buf[:len(s)]) + for i := 0; i < len(s); i++ { + if s[i] <= 'Z' { + buf[i] -= 'a' - 'A' + } + } + return string(buf[:len(s)]) +} + +func (b *builder) parseIndices() { + meta := b.supp.Metadata + + for k, v := range b.registry { + var ss *stringSet + switch v.typ { + case "language": + if len(k) == 2 || v.suppressScript != "" || v.scope == "special" { + b.lang.add(k) + continue + } else { + ss = &b.langNoIndex + } + case "region": + ss = &b.region + case "script": + ss = &b.script + case "variant": + ss = &b.variant + default: + continue + } + ss.add(k) + } + // Include any language for which there is data. + for _, lang := range b.data.Locales() { + if x := b.data.RawLDML(lang); false || + x.LocaleDisplayNames != nil || + x.Characters != nil || + x.Delimiters != nil || + x.Measurement != nil || + x.Dates != nil || + x.Numbers != nil || + x.Units != nil || + x.ListPatterns != nil || + x.Collations != nil || + x.Segmentations != nil || + x.Rbnf != nil || + x.Annotations != nil || + x.Metadata != nil { + + from := strings.Split(lang, "_") + if lang := from[0]; lang != "root" { + b.lang.add(lang) + } + } + } + // Include locales for plural rules, which uses a different structure. + for _, plurals := range b.data.Supplemental().Plurals { + for _, rules := range plurals.PluralRules { + for _, lang := range strings.Split(rules.Locales, " ") { + if lang = strings.Split(lang, "_")[0]; lang != "root" { + b.lang.add(lang) + } + } + } + } + // Include languages in likely subtags. + for _, m := range b.supp.LikelySubtags.LikelySubtag { + from := strings.Split(m.From, "_") + b.lang.add(from[0]) + } + // Include ISO-639 alpha-3 bibliographic entries. + for _, a := range meta.Alias.LanguageAlias { + if a.Reason == "bibliographic" { + b.langNoIndex.add(a.Type) + } + } + // Include regions in territoryAlias (not all are in the IANA registry!) + for _, reg := range b.supp.Metadata.Alias.TerritoryAlias { + if len(reg.Type) == 2 { + b.region.add(reg.Type) + } + } + + for _, s := range b.lang.s { + if len(s) == 3 { + b.langNoIndex.remove(s) + } + } + b.writeConst("numLanguages", len(b.lang.slice())+len(b.langNoIndex.slice())) + b.writeConst("numScripts", len(b.script.slice())) + b.writeConst("numRegions", len(b.region.slice())) + + // Add dummy codes at the start of each list to represent "unspecified". + b.lang.add("---") + b.script.add("----") + b.region.add("---") + + // common locales + b.locale.parse(meta.DefaultContent.Locales) +} + +// TODO: region inclusion data will probably not be use used in future matchers. + +func (b *builder) computeRegionGroups() { + b.groups = make(map[int]index) + + // Create group indices. + for i := 1; b.region.s[i][0] < 'A'; i++ { // Base M49 indices on regionID. + b.groups[i] = index(len(b.groups)) + } + for _, g := range b.supp.TerritoryContainment.Group { + // Skip UN and EURO zone as they are flattening the containment + // relationship. + if g.Type == "EZ" || g.Type == "UN" { + continue + } + group := b.region.index(g.Type) + if _, ok := b.groups[group]; !ok { + b.groups[group] = index(len(b.groups)) + } + } + if len(b.groups) > 64 { + log.Fatalf("only 64 groups supported, found %d", len(b.groups)) + } + b.writeConst("nRegionGroups", len(b.groups)) +} + +var langConsts = []string{ + "af", "am", "ar", "az", "bg", "bn", "ca", "cs", "da", "de", "el", "en", "es", + "et", "fa", "fi", "fil", "fr", "gu", "he", "hi", "hr", "hu", "hy", "id", "is", + "it", "ja", "ka", "kk", "km", "kn", "ko", "ky", "lo", "lt", "lv", "mk", "ml", + "mn", "mo", "mr", "ms", "mul", "my", "nb", "ne", "nl", "no", "pa", "pl", "pt", + "ro", "ru", "sh", "si", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", + "tl", "tn", "tr", "uk", "ur", "uz", "vi", "zh", "zu", + + // constants for grandfathered tags (if not already defined) + "jbo", "ami", "bnn", "hak", "tlh", "lb", "nv", "pwn", "tao", "tay", "tsu", + "nn", "sfb", "vgt", "sgg", "cmn", "nan", "hsn", +} + +// writeLanguage generates all tables needed for language canonicalization. +func (b *builder) writeLanguage() { + meta := b.supp.Metadata + + b.writeConst("nonCanonicalUnd", b.lang.index("und")) + b.writeConsts(func(s string) int { return int(b.langIndex(s)) }, langConsts...) + b.writeConst("langPrivateStart", b.langIndex("qaa")) + b.writeConst("langPrivateEnd", b.langIndex("qtz")) + + // Get language codes that need to be mapped (overlong 3-letter codes, + // deprecated 2-letter codes, legacy and grandfathered tags.) + langAliasMap := stringSet{} + aliasTypeMap := map[string]langAliasType{} + + // altLangISO3 get the alternative ISO3 names that need to be mapped. + altLangISO3 := stringSet{} + // Add dummy start to avoid the use of index 0. + altLangISO3.add("---") + altLangISO3.updateLater("---", "aa") + + lang := b.lang.clone() + for _, a := range meta.Alias.LanguageAlias { + if a.Replacement == "" { + a.Replacement = "und" + } + // TODO: support mapping to tags + repl := strings.SplitN(a.Replacement, "_", 2)[0] + if a.Reason == "overlong" { + if len(a.Replacement) == 2 && len(a.Type) == 3 { + lang.updateLater(a.Replacement, a.Type) + } + } else if len(a.Type) <= 3 { + switch a.Reason { + case "macrolanguage": + aliasTypeMap[a.Type] = langMacro + case "deprecated": + // handled elsewhere + continue + case "bibliographic", "legacy": + if a.Type == "no" { + continue + } + aliasTypeMap[a.Type] = langLegacy + default: + log.Fatalf("new %s alias: %s", a.Reason, a.Type) + } + langAliasMap.add(a.Type) + langAliasMap.updateLater(a.Type, repl) + } + } + // Manually add the mapping of "nb" (Norwegian) to its macro language. + // This can be removed if CLDR adopts this change. + langAliasMap.add("nb") + langAliasMap.updateLater("nb", "no") + aliasTypeMap["nb"] = langMacro + + for k, v := range b.registry { + // Also add deprecated values for 3-letter ISO codes, which CLDR omits. + if v.typ == "language" && v.deprecated != "" && v.preferred != "" { + langAliasMap.add(k) + langAliasMap.updateLater(k, v.preferred) + aliasTypeMap[k] = langDeprecated + } + } + // Fix CLDR mappings. + lang.updateLater("tl", "tgl") + lang.updateLater("sh", "hbs") + lang.updateLater("mo", "mol") + lang.updateLater("no", "nor") + lang.updateLater("tw", "twi") + lang.updateLater("nb", "nob") + lang.updateLater("ak", "aka") + lang.updateLater("bh", "bih") + + // Ensure that each 2-letter code is matched with a 3-letter code. + for _, v := range lang.s[1:] { + s, ok := lang.update[v] + if !ok { + if s, ok = lang.update[langAliasMap.update[v]]; !ok { + continue + } + lang.update[v] = s + } + if v[0] != s[0] { + altLangISO3.add(s) + altLangISO3.updateLater(s, v) + } + } + + // Complete canonicalized language tags. + lang.freeze() + for i, v := range lang.s { + // We can avoid these manual entries by using the IANA registry directly. + // Seems easier to update the list manually, as changes are rare. + // The panic in this loop will trigger if we miss an entry. + add := "" + if s, ok := lang.update[v]; ok { + if s[0] == v[0] { + add = s[1:] + } else { + add = string([]byte{0, byte(altLangISO3.index(s))}) + } + } else if len(v) == 3 { + add = "\x00" + } else { + log.Panicf("no data for long form of %q", v) + } + lang.s[i] += add + } + b.writeConst("lang", tag.Index(lang.join())) + + b.writeConst("langNoIndexOffset", len(b.lang.s)) + + // space of all valid 3-letter language identifiers. + b.writeBitVector("langNoIndex", b.langNoIndex.slice()) + + altLangIndex := []uint16{} + for i, s := range altLangISO3.slice() { + altLangISO3.s[i] += string([]byte{byte(len(altLangIndex))}) + if i > 0 { + idx := b.lang.index(altLangISO3.update[s]) + altLangIndex = append(altLangIndex, uint16(idx)) + } + } + b.writeConst("altLangISO3", tag.Index(altLangISO3.join())) + b.writeSlice("altLangIndex", altLangIndex) + + b.writeSortedMap("langAliasMap", &langAliasMap, b.langIndex) + types := make([]langAliasType, len(langAliasMap.s)) + for i, s := range langAliasMap.s { + types[i] = aliasTypeMap[s] + } + b.writeSlice("langAliasTypes", types) +} + +var scriptConsts = []string{ + "Latn", "Hani", "Hans", "Hant", "Qaaa", "Qaai", "Qabx", "Zinh", "Zyyy", + "Zzzz", +} + +func (b *builder) writeScript() { + b.writeConsts(b.script.index, scriptConsts...) + b.writeConst("script", tag.Index(b.script.join())) + + supp := make([]uint8, len(b.lang.slice())) + for i, v := range b.lang.slice()[1:] { + if sc := b.registry[v].suppressScript; sc != "" { + supp[i+1] = uint8(b.script.index(sc)) + } + } + b.writeSlice("suppressScript", supp) + + // There is only one deprecated script in CLDR. This value is hard-coded. + // We check here if the code must be updated. + for _, a := range b.supp.Metadata.Alias.ScriptAlias { + if a.Type != "Qaai" { + log.Panicf("unexpected deprecated stript %q", a.Type) + } + } +} + +func parseM49(s string) int16 { + if len(s) == 0 { + return 0 + } + v, err := strconv.ParseUint(s, 10, 10) + failOnError(err) + return int16(v) +} + +var regionConsts = []string{ + "001", "419", "BR", "CA", "ES", "GB", "MD", "PT", "UK", "US", + "ZZ", "XA", "XC", "XK", // Unofficial tag for Kosovo. +} + +func (b *builder) writeRegion() { + b.writeConsts(b.region.index, regionConsts...) + + isoOffset := b.region.index("AA") + m49map := make([]int16, len(b.region.slice())) + fromM49map := make(map[int16]int) + altRegionISO3 := "" + altRegionIDs := []uint16{} + + b.writeConst("isoRegionOffset", isoOffset) + + // 2-letter region lookup and mapping to numeric codes. + regionISO := b.region.clone() + regionISO.s = regionISO.s[isoOffset:] + regionISO.sorted = false + + regionTypes := make([]byte, len(b.region.s)) + + // Is the region valid BCP 47? + for s, e := range b.registry { + if len(s) == 2 && s == strings.ToUpper(s) { + i := b.region.index(s) + for _, d := range e.description { + if strings.Contains(d, "Private use") { + regionTypes[i] = iso3166UserAssigned + } + } + regionTypes[i] |= bcp47Region + } + } + + // Is the region a valid ccTLD? + r := gen.OpenIANAFile("domains/root/db") + defer r.Close() + + buf, err := ioutil.ReadAll(r) + failOnError(err) + re := regexp.MustCompile(`"/domains/root/db/([a-z]{2}).html"`) + for _, m := range re.FindAllSubmatch(buf, -1) { + i := b.region.index(strings.ToUpper(string(m[1]))) + regionTypes[i] |= ccTLD + } + + b.writeSlice("regionTypes", regionTypes) + + iso3Set := make(map[string]int) + update := func(iso2, iso3 string) { + i := regionISO.index(iso2) + if j, ok := iso3Set[iso3]; !ok && iso3[0] == iso2[0] { + regionISO.s[i] += iso3[1:] + iso3Set[iso3] = -1 + } else { + if ok && j >= 0 { + regionISO.s[i] += string([]byte{0, byte(j)}) + } else { + iso3Set[iso3] = len(altRegionISO3) + regionISO.s[i] += string([]byte{0, byte(len(altRegionISO3))}) + altRegionISO3 += iso3 + altRegionIDs = append(altRegionIDs, uint16(isoOffset+i)) + } + } + } + for _, tc := range b.supp.CodeMappings.TerritoryCodes { + i := regionISO.index(tc.Type) + isoOffset + if d := m49map[i]; d != 0 { + log.Panicf("%s found as a duplicate UN.M49 code of %03d", tc.Numeric, d) + } + m49 := parseM49(tc.Numeric) + m49map[i] = m49 + if r := fromM49map[m49]; r == 0 { + fromM49map[m49] = i + } else if r != i { + dep := b.registry[regionISO.s[r-isoOffset]].deprecated + if t := b.registry[tc.Type]; t != nil && dep != "" && (t.deprecated == "" || t.deprecated > dep) { + fromM49map[m49] = i + } + } + } + for _, ta := range b.supp.Metadata.Alias.TerritoryAlias { + if len(ta.Type) == 3 && ta.Type[0] <= '9' && len(ta.Replacement) == 2 { + from := parseM49(ta.Type) + if r := fromM49map[from]; r == 0 { + fromM49map[from] = regionISO.index(ta.Replacement) + isoOffset + } + } + } + for _, tc := range b.supp.CodeMappings.TerritoryCodes { + if len(tc.Alpha3) == 3 { + update(tc.Type, tc.Alpha3) + } + } + // This entries are not included in territoryCodes. Mostly 3-letter variants + // of deleted codes and an entry for QU. + for _, m := range []struct{ iso2, iso3 string }{ + {"CT", "CTE"}, + {"DY", "DHY"}, + {"HV", "HVO"}, + {"JT", "JTN"}, + {"MI", "MID"}, + {"NH", "NHB"}, + {"NQ", "ATN"}, + {"PC", "PCI"}, + {"PU", "PUS"}, + {"PZ", "PCZ"}, + {"RH", "RHO"}, + {"VD", "VDR"}, + {"WK", "WAK"}, + // These three-letter codes are used for others as well. + {"FQ", "ATF"}, + } { + update(m.iso2, m.iso3) + } + for i, s := range regionISO.s { + if len(s) != 4 { + regionISO.s[i] = s + " " + } + } + b.writeConst("regionISO", tag.Index(regionISO.join())) + b.writeConst("altRegionISO3", altRegionISO3) + b.writeSlice("altRegionIDs", altRegionIDs) + + // Create list of deprecated regions. + // TODO: consider inserting SF -> FI. Not included by CLDR, but is the only + // Transitionally-reserved mapping not included. + regionOldMap := stringSet{} + // Include regions in territoryAlias (not all are in the IANA registry!) + for _, reg := range b.supp.Metadata.Alias.TerritoryAlias { + if len(reg.Type) == 2 && reg.Reason == "deprecated" && len(reg.Replacement) == 2 { + regionOldMap.add(reg.Type) + regionOldMap.updateLater(reg.Type, reg.Replacement) + i, _ := regionISO.find(reg.Type) + j, _ := regionISO.find(reg.Replacement) + if k := m49map[i+isoOffset]; k == 0 { + m49map[i+isoOffset] = m49map[j+isoOffset] + } + } + } + b.writeSortedMap("regionOldMap", ®ionOldMap, func(s string) uint16 { + return uint16(b.region.index(s)) + }) + // 3-digit region lookup, groupings. + for i := 1; i < isoOffset; i++ { + m := parseM49(b.region.s[i]) + m49map[i] = m + fromM49map[m] = i + } + b.writeSlice("m49", m49map) + + const ( + searchBits = 7 + regionBits = 9 + ) + if len(m49map) >= 1< %d", len(m49map), 1<>searchBits] = int16(len(fromM49)) + } + b.writeSlice("m49Index", m49Index) + b.writeSlice("fromM49", fromM49) +} + +const ( + // TODO: put these lists in regionTypes as user data? Could be used for + // various optimizations and refinements and could be exposed in the API. + iso3166Except = "AC CP DG EA EU FX IC SU TA UK" + iso3166Trans = "AN BU CS NT TP YU ZR" // SF is not in our set of Regions. + // DY and RH are actually not deleted, but indeterminately reserved. + iso3166DelCLDR = "CT DD DY FQ HV JT MI NH NQ PC PU PZ RH VD WK YD" +) + +const ( + iso3166UserAssigned = 1 << iota + ccTLD + bcp47Region +) + +func find(list []string, s string) int { + for i, t := range list { + if t == s { + return i + } + } + return -1 +} + +// writeVariants generates per-variant information and creates a map from variant +// name to index value. We assign index values such that sorting multiple +// variants by index value will result in the correct order. +// There are two types of variants: specialized and general. Specialized variants +// are only applicable to certain language or language-script pairs. Generalized +// variants apply to any language. Generalized variants always sort after +// specialized variants. We will therefore always assign a higher index value +// to a generalized variant than any other variant. Generalized variants are +// sorted alphabetically among themselves. +// Specialized variants may also sort after other specialized variants. Such +// variants will be ordered after any of the variants they may follow. +// We assume that if a variant x is followed by a variant y, then for any prefix +// p of x, p-x is a prefix of y. This allows us to order tags based on the +// maximum of the length of any of its prefixes. +// TODO: it is possible to define a set of Prefix values on variants such that +// a total order cannot be defined to the point that this algorithm breaks. +// In other words, we cannot guarantee the same order of variants for the +// future using the same algorithm or for non-compliant combinations of +// variants. For this reason, consider using simple alphabetic sorting +// of variants and ignore Prefix restrictions altogether. +func (b *builder) writeVariant() { + generalized := stringSet{} + specialized := stringSet{} + specializedExtend := stringSet{} + // Collate the variants by type and check assumptions. + for _, v := range b.variant.slice() { + e := b.registry[v] + if len(e.prefix) == 0 { + generalized.add(v) + continue + } + c := strings.Split(e.prefix[0], "-") + hasScriptOrRegion := false + if len(c) > 1 { + _, hasScriptOrRegion = b.script.find(c[1]) + if !hasScriptOrRegion { + _, hasScriptOrRegion = b.region.find(c[1]) + + } + } + if len(c) == 1 || len(c) == 2 && hasScriptOrRegion { + // Variant is preceded by a language. + specialized.add(v) + continue + } + // Variant is preceded by another variant. + specializedExtend.add(v) + prefix := c[0] + "-" + if hasScriptOrRegion { + prefix += c[1] + } + for _, p := range e.prefix { + // Verify that the prefix minus the last element is a prefix of the + // predecessor element. + i := strings.LastIndex(p, "-") + pred := b.registry[p[i+1:]] + if find(pred.prefix, p[:i]) < 0 { + log.Fatalf("prefix %q for variant %q not consistent with predecessor spec", p, v) + } + // The sorting used below does not work in the general case. It works + // if we assume that variants that may be followed by others only have + // prefixes of the same length. Verify this. + count := strings.Count(p[:i], "-") + for _, q := range pred.prefix { + if c := strings.Count(q, "-"); c != count { + log.Fatalf("variant %q preceding %q has a prefix %q of size %d; want %d", p[i+1:], v, q, c, count) + } + } + if !strings.HasPrefix(p, prefix) { + log.Fatalf("prefix %q of variant %q should start with %q", p, v, prefix) + } + } + } + + // Sort extended variants. + a := specializedExtend.s + less := func(v, w string) bool { + // Sort by the maximum number of elements. + maxCount := func(s string) (max int) { + for _, p := range b.registry[s].prefix { + if c := strings.Count(p, "-"); c > max { + max = c + } + } + return + } + if cv, cw := maxCount(v), maxCount(w); cv != cw { + return cv < cw + } + // Sort by name as tie breaker. + return v < w + } + sort.Sort(funcSorter{less, sort.StringSlice(a)}) + specializedExtend.frozen = true + + // Create index from variant name to index. + variantIndex := make(map[string]uint8) + add := func(s []string) { + for _, v := range s { + variantIndex[v] = uint8(len(variantIndex)) + } + } + add(specialized.slice()) + add(specializedExtend.s) + numSpecialized := len(variantIndex) + add(generalized.slice()) + if n := len(variantIndex); n > 255 { + log.Fatalf("maximum number of variants exceeded: was %d; want <= 255", n) + } + b.writeMap("variantIndex", variantIndex) + b.writeConst("variantNumSpecialized", numSpecialized) +} + +func (b *builder) writeLanguageInfo() { +} + +// writeLikelyData writes tables that are used both for finding parent relations and for +// language matching. Each entry contains additional bits to indicate the status of the +// data to know when it cannot be used for parent relations. +func (b *builder) writeLikelyData() { + const ( + isList = 1 << iota + scriptInFrom + regionInFrom + ) + type ( // generated types + likelyScriptRegion struct { + region uint16 + script uint8 + flags uint8 + } + likelyLangScript struct { + lang uint16 + script uint8 + flags uint8 + } + likelyLangRegion struct { + lang uint16 + region uint16 + } + // likelyTag is used for getting likely tags for group regions, where + // the likely region might be a region contained in the group. + likelyTag struct { + lang uint16 + region uint16 + script uint8 + } + ) + var ( // generated variables + likelyRegionGroup = make([]likelyTag, len(b.groups)) + likelyLang = make([]likelyScriptRegion, len(b.lang.s)) + likelyRegion = make([]likelyLangScript, len(b.region.s)) + likelyScript = make([]likelyLangRegion, len(b.script.s)) + likelyLangList = []likelyScriptRegion{} + likelyRegionList = []likelyLangScript{} + ) + type fromTo struct { + from, to []string + } + langToOther := map[int][]fromTo{} + regionToOther := map[int][]fromTo{} + for _, m := range b.supp.LikelySubtags.LikelySubtag { + from := strings.Split(m.From, "_") + to := strings.Split(m.To, "_") + if len(to) != 3 { + log.Fatalf("invalid number of subtags in %q: found %d, want 3", m.To, len(to)) + } + if len(from) > 3 { + log.Fatalf("invalid number of subtags: found %d, want 1-3", len(from)) + } + if from[0] != to[0] && from[0] != "und" { + log.Fatalf("unexpected language change in expansion: %s -> %s", from, to) + } + if len(from) == 3 { + if from[2] != to[2] { + log.Fatalf("unexpected region change in expansion: %s -> %s", from, to) + } + if from[0] != "und" { + log.Fatalf("unexpected fully specified from tag: %s -> %s", from, to) + } + } + if len(from) == 1 || from[0] != "und" { + id := 0 + if from[0] != "und" { + id = b.lang.index(from[0]) + } + langToOther[id] = append(langToOther[id], fromTo{from, to}) + } else if len(from) == 2 && len(from[1]) == 4 { + sid := b.script.index(from[1]) + likelyScript[sid].lang = uint16(b.langIndex(to[0])) + likelyScript[sid].region = uint16(b.region.index(to[2])) + } else { + r := b.region.index(from[len(from)-1]) + if id, ok := b.groups[r]; ok { + if from[0] != "und" { + log.Fatalf("region changed unexpectedly: %s -> %s", from, to) + } + likelyRegionGroup[id].lang = uint16(b.langIndex(to[0])) + likelyRegionGroup[id].script = uint8(b.script.index(to[1])) + likelyRegionGroup[id].region = uint16(b.region.index(to[2])) + } else { + regionToOther[r] = append(regionToOther[r], fromTo{from, to}) + } + } + } + b.writeType(likelyLangRegion{}) + b.writeSlice("likelyScript", likelyScript) + + for id := range b.lang.s { + list := langToOther[id] + if len(list) == 1 { + likelyLang[id].region = uint16(b.region.index(list[0].to[2])) + likelyLang[id].script = uint8(b.script.index(list[0].to[1])) + } else if len(list) > 1 { + likelyLang[id].flags = isList + likelyLang[id].region = uint16(len(likelyLangList)) + likelyLang[id].script = uint8(len(list)) + for _, x := range list { + flags := uint8(0) + if len(x.from) > 1 { + if x.from[1] == x.to[2] { + flags = regionInFrom + } else { + flags = scriptInFrom + } + } + likelyLangList = append(likelyLangList, likelyScriptRegion{ + region: uint16(b.region.index(x.to[2])), + script: uint8(b.script.index(x.to[1])), + flags: flags, + }) + } + } + } + // TODO: merge suppressScript data with this table. + b.writeType(likelyScriptRegion{}) + b.writeSlice("likelyLang", likelyLang) + b.writeSlice("likelyLangList", likelyLangList) + + for id := range b.region.s { + list := regionToOther[id] + if len(list) == 1 { + likelyRegion[id].lang = uint16(b.langIndex(list[0].to[0])) + likelyRegion[id].script = uint8(b.script.index(list[0].to[1])) + if len(list[0].from) > 2 { + likelyRegion[id].flags = scriptInFrom + } + } else if len(list) > 1 { + likelyRegion[id].flags = isList + likelyRegion[id].lang = uint16(len(likelyRegionList)) + likelyRegion[id].script = uint8(len(list)) + for i, x := range list { + if len(x.from) == 2 && i != 0 || i > 0 && len(x.from) != 3 { + log.Fatalf("unspecified script must be first in list: %v at %d", x.from, i) + } + x := likelyLangScript{ + lang: uint16(b.langIndex(x.to[0])), + script: uint8(b.script.index(x.to[1])), + } + if len(list[0].from) > 2 { + x.flags = scriptInFrom + } + likelyRegionList = append(likelyRegionList, x) + } + } + } + b.writeType(likelyLangScript{}) + b.writeSlice("likelyRegion", likelyRegion) + b.writeSlice("likelyRegionList", likelyRegionList) + + b.writeType(likelyTag{}) + b.writeSlice("likelyRegionGroup", likelyRegionGroup) +} + +type mutualIntelligibility struct { + want, have uint16 + distance uint8 + oneway bool +} + +type scriptIntelligibility struct { + wantLang, haveLang uint16 + wantScript, haveScript uint8 + distance uint8 + // Always oneway +} + +type regionIntelligibility struct { + lang uint16 // compact language id + script uint8 // 0 means any + group uint8 // 0 means any; if bit 7 is set it means inverse + distance uint8 + // Always twoway. +} + +// writeMatchData writes tables with languages and scripts for which there is +// mutual intelligibility. The data is based on CLDR's languageMatching data. +// Note that we use a different algorithm than the one defined by CLDR and that +// we slightly modify the data. For example, we convert scores to confidence levels. +// We also drop all region-related data as we use a different algorithm to +// determine region equivalence. +func (b *builder) writeMatchData() { + lm := b.supp.LanguageMatching.LanguageMatches + cldr.MakeSlice(&lm).SelectAnyOf("type", "written_new") + + regionHierarchy := map[string][]string{} + for _, g := range b.supp.TerritoryContainment.Group { + regions := strings.Split(g.Contains, " ") + regionHierarchy[g.Type] = append(regionHierarchy[g.Type], regions...) + } + regionToGroups := make([]uint8, len(b.region.s)) + + idToIndex := map[string]uint8{} + for i, mv := range lm[0].MatchVariable { + if i > 6 { + log.Fatalf("Too many groups: %d", i) + } + idToIndex[mv.Id] = uint8(i + 1) + // TODO: also handle '-' + for _, r := range strings.Split(mv.Value, "+") { + todo := []string{r} + for k := 0; k < len(todo); k++ { + r := todo[k] + regionToGroups[b.region.index(r)] |= 1 << uint8(i) + todo = append(todo, regionHierarchy[r]...) + } + } + } + b.writeSlice("regionToGroups", regionToGroups) + + // maps language id to in- and out-of-group region. + paradigmLocales := [][3]uint16{} + locales := strings.Split(lm[0].ParadigmLocales[0].Locales, " ") + for i := 0; i < len(locales); i += 2 { + x := [3]uint16{} + for j := 0; j < 2; j++ { + pc := strings.SplitN(locales[i+j], "-", 2) + x[0] = b.langIndex(pc[0]) + if len(pc) == 2 { + x[1+j] = uint16(b.region.index(pc[1])) + } + } + paradigmLocales = append(paradigmLocales, x) + } + b.writeSlice("paradigmLocales", paradigmLocales) + + b.writeType(mutualIntelligibility{}) + b.writeType(scriptIntelligibility{}) + b.writeType(regionIntelligibility{}) + + matchLang := []mutualIntelligibility{} + matchScript := []scriptIntelligibility{} + matchRegion := []regionIntelligibility{} + // Convert the languageMatch entries in lists keyed by desired language. + for _, m := range lm[0].LanguageMatch { + // Different versions of CLDR use different separators. + desired := strings.Replace(m.Desired, "-", "_", -1) + supported := strings.Replace(m.Supported, "-", "_", -1) + d := strings.Split(desired, "_") + s := strings.Split(supported, "_") + if len(d) != len(s) { + log.Fatalf("not supported: desired=%q; supported=%q", desired, supported) + continue + } + distance, _ := strconv.ParseInt(m.Distance, 10, 8) + switch len(d) { + case 2: + if desired == supported && desired == "*_*" { + continue + } + // language-script pair. + matchScript = append(matchScript, scriptIntelligibility{ + wantLang: uint16(b.langIndex(d[0])), + haveLang: uint16(b.langIndex(s[0])), + wantScript: uint8(b.script.index(d[1])), + haveScript: uint8(b.script.index(s[1])), + distance: uint8(distance), + }) + if m.Oneway != "true" { + matchScript = append(matchScript, scriptIntelligibility{ + wantLang: uint16(b.langIndex(s[0])), + haveLang: uint16(b.langIndex(d[0])), + wantScript: uint8(b.script.index(s[1])), + haveScript: uint8(b.script.index(d[1])), + distance: uint8(distance), + }) + } + case 1: + if desired == supported && desired == "*" { + continue + } + if distance == 1 { + // nb == no is already handled by macro mapping. Check there + // really is only this case. + if d[0] != "no" || s[0] != "nb" { + log.Fatalf("unhandled equivalence %s == %s", s[0], d[0]) + } + continue + } + // TODO: consider dropping oneway field and just doubling the entry. + matchLang = append(matchLang, mutualIntelligibility{ + want: uint16(b.langIndex(d[0])), + have: uint16(b.langIndex(s[0])), + distance: uint8(distance), + oneway: m.Oneway == "true", + }) + case 3: + if desired == supported && desired == "*_*_*" { + continue + } + if desired != supported { + // This is now supported by CLDR, but only one case, which + // should already be covered by paradigm locales. For instance, + // test case "und, en, en-GU, en-IN, en-GB ; en-ZA ; en-GB" in + // testdata/CLDRLocaleMatcherTest.txt tests this. + if supported != "en_*_GB" { + log.Fatalf("not supported: desired=%q; supported=%q", desired, supported) + } + continue + } + ri := regionIntelligibility{ + lang: b.langIndex(d[0]), + distance: uint8(distance), + } + if d[1] != "*" { + ri.script = uint8(b.script.index(d[1])) + } + switch { + case d[2] == "*": + ri.group = 0x80 // not contained in anything + case strings.HasPrefix(d[2], "$!"): + ri.group = 0x80 + d[2] = "$" + d[2][len("$!"):] + fallthrough + case strings.HasPrefix(d[2], "$"): + ri.group |= idToIndex[d[2]] + } + matchRegion = append(matchRegion, ri) + default: + log.Fatalf("not supported: desired=%q; supported=%q", desired, supported) + } + } + sort.SliceStable(matchLang, func(i, j int) bool { + return matchLang[i].distance < matchLang[j].distance + }) + b.writeSlice("matchLang", matchLang) + + sort.SliceStable(matchScript, func(i, j int) bool { + return matchScript[i].distance < matchScript[j].distance + }) + b.writeSlice("matchScript", matchScript) + + sort.SliceStable(matchRegion, func(i, j int) bool { + return matchRegion[i].distance < matchRegion[j].distance + }) + b.writeSlice("matchRegion", matchRegion) +} + +func (b *builder) writeRegionInclusionData() { + var ( + // mm holds for each group the set of groups with a distance of 1. + mm = make(map[int][]index) + + // containment holds for each group the transitive closure of + // containment of other groups. + containment = make(map[index][]index) + ) + for _, g := range b.supp.TerritoryContainment.Group { + // Skip UN and EURO zone as they are flattening the containment + // relationship. + if g.Type == "EZ" || g.Type == "UN" { + continue + } + group := b.region.index(g.Type) + groupIdx := b.groups[group] + for _, mem := range strings.Split(g.Contains, " ") { + r := b.region.index(mem) + mm[r] = append(mm[r], groupIdx) + if g, ok := b.groups[r]; ok { + mm[group] = append(mm[group], g) + containment[groupIdx] = append(containment[groupIdx], g) + } + } + } + + regionContainment := make([]uint64, len(b.groups)) + for _, g := range b.groups { + l := containment[g] + + // Compute the transitive closure of containment. + for i := 0; i < len(l); i++ { + l = append(l, containment[l[i]]...) + } + + // Compute the bitmask. + regionContainment[g] = 1 << g + for _, v := range l { + regionContainment[g] |= 1 << v + } + } + b.writeSlice("regionContainment", regionContainment) + + regionInclusion := make([]uint8, len(b.region.s)) + bvs := make(map[uint64]index) + // Make the first bitvector positions correspond with the groups. + for r, i := range b.groups { + bv := uint64(1 << i) + for _, g := range mm[r] { + bv |= 1 << g + } + bvs[bv] = i + regionInclusion[r] = uint8(bvs[bv]) + } + for r := 1; r < len(b.region.s); r++ { + if _, ok := b.groups[r]; !ok { + bv := uint64(0) + for _, g := range mm[r] { + bv |= 1 << g + } + if bv == 0 { + // Pick the world for unspecified regions. + bv = 1 << b.groups[b.region.index("001")] + } + if _, ok := bvs[bv]; !ok { + bvs[bv] = index(len(bvs)) + } + regionInclusion[r] = uint8(bvs[bv]) + } + } + b.writeSlice("regionInclusion", regionInclusion) + regionInclusionBits := make([]uint64, len(bvs)) + for k, v := range bvs { + regionInclusionBits[v] = uint64(k) + } + // Add bit vectors for increasingly large distances until a fixed point is reached. + regionInclusionNext := []uint8{} + for i := 0; i < len(regionInclusionBits); i++ { + bits := regionInclusionBits[i] + next := bits + for i := uint(0); i < uint(len(b.groups)); i++ { + if bits&(1< b'. Using + // bytes.Replace will do. + out := bytes.Replace(buf.Bytes(), []byte("language."), nil, -1) + if err := ioutil.WriteFile("index.go", out, 0600); err != nil { + log.Fatalf("Could not create file index.go: %v", err) + } + }() + + m := map[language.Tag]bool{} + for _, lang := range data.Locales() { + // We include all locales unconditionally to be consistent with en_US. + // We want en_US, even though it has no data associated with it. + + // TODO: put any of the languages for which no data exists at the end + // of the index. This allows all components based on ICU to use that + // as the cutoff point. + // if x := data.RawLDML(lang); false || + // x.LocaleDisplayNames != nil || + // x.Characters != nil || + // x.Delimiters != nil || + // x.Measurement != nil || + // x.Dates != nil || + // x.Numbers != nil || + // x.Units != nil || + // x.ListPatterns != nil || + // x.Collations != nil || + // x.Segmentations != nil || + // x.Rbnf != nil || + // x.Annotations != nil || + // x.Metadata != nil { + + // TODO: support POSIX natively, albeit non-standard. + tag := language.Make(strings.Replace(lang, "_POSIX", "-u-va-posix", 1)) + m[tag] = true + // } + } + // Include locales for plural rules, which uses a different structure. + for _, plurals := range data.Supplemental().Plurals { + for _, rules := range plurals.PluralRules { + for _, lang := range strings.Split(rules.Locales, " ") { + m[language.Make(lang)] = true + } + } + } + + var core, special []language.Tag + + for t := range m { + if x := t.Extensions(); len(x) != 0 && fmt.Sprint(x) != "[u-va-posix]" { + log.Fatalf("Unexpected extension %v in %v", x, t) + } + if len(t.Variants()) == 0 && len(t.Extensions()) == 0 { + core = append(core, t) + } else { + special = append(special, t) + } + } + + w.WriteComment(` + NumCompactTags is the number of common tags. The maximum tag is + NumCompactTags-1.`) + w.WriteConst("NumCompactTags", len(core)+len(special)) + + sort.Sort(byAlpha(special)) + w.WriteVar("specialTags", special) + + // TODO: order by frequency? + sort.Sort(byAlpha(core)) + + // Size computations are just an estimate. + w.Size += int(reflect.TypeOf(map[uint32]uint16{}).Size()) + w.Size += len(core) * 6 // size of uint32 and uint16 + + fmt.Fprintln(w) + fmt.Fprintln(w, "var coreTags = map[uint32]uint16{") + fmt.Fprintln(w, "0x0: 0, // und") + i := len(special) + 1 // Und and special tags already written. + for _, t := range core { + if t == language.Und { + continue + } + fmt.Fprint(w.Hash, t, i) + b, s, r := t.Raw() + fmt.Fprintf(w, "0x%s%s%s: %d, // %s\n", + getIndex(b, 3), // 3 is enough as it is guaranteed to be a compact number + getIndex(s, 2), + getIndex(r, 3), + i, t) + i++ + } + fmt.Fprintln(w, "}") +} + +// getIndex prints the subtag type and extracts its index of size nibble. +// If the index is less than n nibbles, the result is prefixed with 0s. +func getIndex(x interface{}, n int) string { + s := fmt.Sprintf("%#v", x) // s is of form Type{typeID: 0x00} + s = s[strings.Index(s, "0x")+2 : len(s)-1] + return strings.Repeat("0", n-len(s)) + s +} + +type byAlpha []language.Tag + +func (a byAlpha) Len() int { return len(a) } +func (a byAlpha) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byAlpha) Less(i, j int) bool { return a[i].String() < a[j].String() } diff --git a/vendor/golang.org/x/text/language/go1_1.go b/vendor/golang.org/x/text/language/go1_1.go new file mode 100644 index 000000000..380f4c09f --- /dev/null +++ b/vendor/golang.org/x/text/language/go1_1.go @@ -0,0 +1,38 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.2 + +package language + +import "sort" + +func sortStable(s sort.Interface) { + ss := stableSort{ + s: s, + pos: make([]int, s.Len()), + } + for i := range ss.pos { + ss.pos[i] = i + } + sort.Sort(&ss) +} + +type stableSort struct { + s sort.Interface + pos []int +} + +func (s *stableSort) Len() int { + return len(s.pos) +} + +func (s *stableSort) Less(i, j int) bool { + return s.s.Less(i, j) || !s.s.Less(j, i) && s.pos[i] < s.pos[j] +} + +func (s *stableSort) Swap(i, j int) { + s.s.Swap(i, j) + s.pos[i], s.pos[j] = s.pos[j], s.pos[i] +} diff --git a/vendor/golang.org/x/text/language/go1_2.go b/vendor/golang.org/x/text/language/go1_2.go new file mode 100644 index 000000000..38268c57a --- /dev/null +++ b/vendor/golang.org/x/text/language/go1_2.go @@ -0,0 +1,11 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.2 + +package language + +import "sort" + +var sortStable = sort.Stable diff --git a/vendor/golang.org/x/text/language/index.go b/vendor/golang.org/x/text/language/index.go new file mode 100644 index 000000000..5311e5cbe --- /dev/null +++ b/vendor/golang.org/x/text/language/index.go @@ -0,0 +1,783 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +package language + +// NumCompactTags is the number of common tags. The maximum tag is +// NumCompactTags-1. +const NumCompactTags = 768 + +var specialTags = []Tag{ // 2 elements + 0: {lang: 0xd7, region: 0x6e, script: 0x0, pVariant: 0x5, pExt: 0xe, str: "ca-ES-valencia"}, + 1: {lang: 0x139, region: 0x135, script: 0x0, pVariant: 0x5, pExt: 0x5, str: "en-US-u-va-posix"}, +} // Size: 72 bytes + +var coreTags = map[uint32]uint16{ + 0x0: 0, // und + 0x01600000: 3, // af + 0x016000d2: 4, // af-NA + 0x01600161: 5, // af-ZA + 0x01c00000: 6, // agq + 0x01c00052: 7, // agq-CM + 0x02100000: 8, // ak + 0x02100080: 9, // ak-GH + 0x02700000: 10, // am + 0x0270006f: 11, // am-ET + 0x03a00000: 12, // ar + 0x03a00001: 13, // ar-001 + 0x03a00023: 14, // ar-AE + 0x03a00039: 15, // ar-BH + 0x03a00062: 16, // ar-DJ + 0x03a00067: 17, // ar-DZ + 0x03a0006b: 18, // ar-EG + 0x03a0006c: 19, // ar-EH + 0x03a0006d: 20, // ar-ER + 0x03a00097: 21, // ar-IL + 0x03a0009b: 22, // ar-IQ + 0x03a000a1: 23, // ar-JO + 0x03a000a8: 24, // ar-KM + 0x03a000ac: 25, // ar-KW + 0x03a000b0: 26, // ar-LB + 0x03a000b9: 27, // ar-LY + 0x03a000ba: 28, // ar-MA + 0x03a000c9: 29, // ar-MR + 0x03a000e1: 30, // ar-OM + 0x03a000ed: 31, // ar-PS + 0x03a000f3: 32, // ar-QA + 0x03a00108: 33, // ar-SA + 0x03a0010b: 34, // ar-SD + 0x03a00115: 35, // ar-SO + 0x03a00117: 36, // ar-SS + 0x03a0011c: 37, // ar-SY + 0x03a00120: 38, // ar-TD + 0x03a00128: 39, // ar-TN + 0x03a0015e: 40, // ar-YE + 0x04000000: 41, // ars + 0x04300000: 42, // as + 0x04300099: 43, // as-IN + 0x04400000: 44, // asa + 0x0440012f: 45, // asa-TZ + 0x04800000: 46, // ast + 0x0480006e: 47, // ast-ES + 0x05800000: 48, // az + 0x0581f000: 49, // az-Cyrl + 0x0581f032: 50, // az-Cyrl-AZ + 0x05857000: 51, // az-Latn + 0x05857032: 52, // az-Latn-AZ + 0x05e00000: 53, // bas + 0x05e00052: 54, // bas-CM + 0x07100000: 55, // be + 0x07100047: 56, // be-BY + 0x07500000: 57, // bem + 0x07500162: 58, // bem-ZM + 0x07900000: 59, // bez + 0x0790012f: 60, // bez-TZ + 0x07e00000: 61, // bg + 0x07e00038: 62, // bg-BG + 0x08200000: 63, // bh + 0x0a000000: 64, // bm + 0x0a0000c3: 65, // bm-ML + 0x0a500000: 66, // bn + 0x0a500035: 67, // bn-BD + 0x0a500099: 68, // bn-IN + 0x0a900000: 69, // bo + 0x0a900053: 70, // bo-CN + 0x0a900099: 71, // bo-IN + 0x0b200000: 72, // br + 0x0b200078: 73, // br-FR + 0x0b500000: 74, // brx + 0x0b500099: 75, // brx-IN + 0x0b700000: 76, // bs + 0x0b71f000: 77, // bs-Cyrl + 0x0b71f033: 78, // bs-Cyrl-BA + 0x0b757000: 79, // bs-Latn + 0x0b757033: 80, // bs-Latn-BA + 0x0d700000: 81, // ca + 0x0d700022: 82, // ca-AD + 0x0d70006e: 83, // ca-ES + 0x0d700078: 84, // ca-FR + 0x0d70009e: 85, // ca-IT + 0x0db00000: 86, // ccp + 0x0db00035: 87, // ccp-BD + 0x0db00099: 88, // ccp-IN + 0x0dc00000: 89, // ce + 0x0dc00106: 90, // ce-RU + 0x0df00000: 91, // cgg + 0x0df00131: 92, // cgg-UG + 0x0e500000: 93, // chr + 0x0e500135: 94, // chr-US + 0x0e900000: 95, // ckb + 0x0e90009b: 96, // ckb-IQ + 0x0e90009c: 97, // ckb-IR + 0x0fa00000: 98, // cs + 0x0fa0005e: 99, // cs-CZ + 0x0fe00000: 100, // cu + 0x0fe00106: 101, // cu-RU + 0x10000000: 102, // cy + 0x1000007b: 103, // cy-GB + 0x10100000: 104, // da + 0x10100063: 105, // da-DK + 0x10100082: 106, // da-GL + 0x10800000: 107, // dav + 0x108000a4: 108, // dav-KE + 0x10d00000: 109, // de + 0x10d0002e: 110, // de-AT + 0x10d00036: 111, // de-BE + 0x10d0004e: 112, // de-CH + 0x10d00060: 113, // de-DE + 0x10d0009e: 114, // de-IT + 0x10d000b2: 115, // de-LI + 0x10d000b7: 116, // de-LU + 0x11700000: 117, // dje + 0x117000d4: 118, // dje-NE + 0x11f00000: 119, // dsb + 0x11f00060: 120, // dsb-DE + 0x12400000: 121, // dua + 0x12400052: 122, // dua-CM + 0x12800000: 123, // dv + 0x12b00000: 124, // dyo + 0x12b00114: 125, // dyo-SN + 0x12d00000: 126, // dz + 0x12d00043: 127, // dz-BT + 0x12f00000: 128, // ebu + 0x12f000a4: 129, // ebu-KE + 0x13000000: 130, // ee + 0x13000080: 131, // ee-GH + 0x13000122: 132, // ee-TG + 0x13600000: 133, // el + 0x1360005d: 134, // el-CY + 0x13600087: 135, // el-GR + 0x13900000: 136, // en + 0x13900001: 137, // en-001 + 0x1390001a: 138, // en-150 + 0x13900025: 139, // en-AG + 0x13900026: 140, // en-AI + 0x1390002d: 141, // en-AS + 0x1390002e: 142, // en-AT + 0x1390002f: 143, // en-AU + 0x13900034: 144, // en-BB + 0x13900036: 145, // en-BE + 0x1390003a: 146, // en-BI + 0x1390003d: 147, // en-BM + 0x13900042: 148, // en-BS + 0x13900046: 149, // en-BW + 0x13900048: 150, // en-BZ + 0x13900049: 151, // en-CA + 0x1390004a: 152, // en-CC + 0x1390004e: 153, // en-CH + 0x13900050: 154, // en-CK + 0x13900052: 155, // en-CM + 0x1390005c: 156, // en-CX + 0x1390005d: 157, // en-CY + 0x13900060: 158, // en-DE + 0x13900061: 159, // en-DG + 0x13900063: 160, // en-DK + 0x13900064: 161, // en-DM + 0x1390006d: 162, // en-ER + 0x13900072: 163, // en-FI + 0x13900073: 164, // en-FJ + 0x13900074: 165, // en-FK + 0x13900075: 166, // en-FM + 0x1390007b: 167, // en-GB + 0x1390007c: 168, // en-GD + 0x1390007f: 169, // en-GG + 0x13900080: 170, // en-GH + 0x13900081: 171, // en-GI + 0x13900083: 172, // en-GM + 0x1390008a: 173, // en-GU + 0x1390008c: 174, // en-GY + 0x1390008d: 175, // en-HK + 0x13900096: 176, // en-IE + 0x13900097: 177, // en-IL + 0x13900098: 178, // en-IM + 0x13900099: 179, // en-IN + 0x1390009a: 180, // en-IO + 0x1390009f: 181, // en-JE + 0x139000a0: 182, // en-JM + 0x139000a4: 183, // en-KE + 0x139000a7: 184, // en-KI + 0x139000a9: 185, // en-KN + 0x139000ad: 186, // en-KY + 0x139000b1: 187, // en-LC + 0x139000b4: 188, // en-LR + 0x139000b5: 189, // en-LS + 0x139000bf: 190, // en-MG + 0x139000c0: 191, // en-MH + 0x139000c6: 192, // en-MO + 0x139000c7: 193, // en-MP + 0x139000ca: 194, // en-MS + 0x139000cb: 195, // en-MT + 0x139000cc: 196, // en-MU + 0x139000ce: 197, // en-MW + 0x139000d0: 198, // en-MY + 0x139000d2: 199, // en-NA + 0x139000d5: 200, // en-NF + 0x139000d6: 201, // en-NG + 0x139000d9: 202, // en-NL + 0x139000dd: 203, // en-NR + 0x139000df: 204, // en-NU + 0x139000e0: 205, // en-NZ + 0x139000e6: 206, // en-PG + 0x139000e7: 207, // en-PH + 0x139000e8: 208, // en-PK + 0x139000eb: 209, // en-PN + 0x139000ec: 210, // en-PR + 0x139000f0: 211, // en-PW + 0x13900107: 212, // en-RW + 0x13900109: 213, // en-SB + 0x1390010a: 214, // en-SC + 0x1390010b: 215, // en-SD + 0x1390010c: 216, // en-SE + 0x1390010d: 217, // en-SG + 0x1390010e: 218, // en-SH + 0x1390010f: 219, // en-SI + 0x13900112: 220, // en-SL + 0x13900117: 221, // en-SS + 0x1390011b: 222, // en-SX + 0x1390011d: 223, // en-SZ + 0x1390011f: 224, // en-TC + 0x13900125: 225, // en-TK + 0x13900129: 226, // en-TO + 0x1390012c: 227, // en-TT + 0x1390012d: 228, // en-TV + 0x1390012f: 229, // en-TZ + 0x13900131: 230, // en-UG + 0x13900133: 231, // en-UM + 0x13900135: 232, // en-US + 0x13900139: 233, // en-VC + 0x1390013c: 234, // en-VG + 0x1390013d: 235, // en-VI + 0x1390013f: 236, // en-VU + 0x13900142: 237, // en-WS + 0x13900161: 238, // en-ZA + 0x13900162: 239, // en-ZM + 0x13900164: 240, // en-ZW + 0x13c00000: 241, // eo + 0x13c00001: 242, // eo-001 + 0x13e00000: 243, // es + 0x13e0001f: 244, // es-419 + 0x13e0002c: 245, // es-AR + 0x13e0003f: 246, // es-BO + 0x13e00041: 247, // es-BR + 0x13e00048: 248, // es-BZ + 0x13e00051: 249, // es-CL + 0x13e00054: 250, // es-CO + 0x13e00056: 251, // es-CR + 0x13e00059: 252, // es-CU + 0x13e00065: 253, // es-DO + 0x13e00068: 254, // es-EA + 0x13e00069: 255, // es-EC + 0x13e0006e: 256, // es-ES + 0x13e00086: 257, // es-GQ + 0x13e00089: 258, // es-GT + 0x13e0008f: 259, // es-HN + 0x13e00094: 260, // es-IC + 0x13e000cf: 261, // es-MX + 0x13e000d8: 262, // es-NI + 0x13e000e2: 263, // es-PA + 0x13e000e4: 264, // es-PE + 0x13e000e7: 265, // es-PH + 0x13e000ec: 266, // es-PR + 0x13e000f1: 267, // es-PY + 0x13e0011a: 268, // es-SV + 0x13e00135: 269, // es-US + 0x13e00136: 270, // es-UY + 0x13e0013b: 271, // es-VE + 0x14000000: 272, // et + 0x1400006a: 273, // et-EE + 0x14500000: 274, // eu + 0x1450006e: 275, // eu-ES + 0x14600000: 276, // ewo + 0x14600052: 277, // ewo-CM + 0x14800000: 278, // fa + 0x14800024: 279, // fa-AF + 0x1480009c: 280, // fa-IR + 0x14e00000: 281, // ff + 0x14e00052: 282, // ff-CM + 0x14e00084: 283, // ff-GN + 0x14e000c9: 284, // ff-MR + 0x14e00114: 285, // ff-SN + 0x15100000: 286, // fi + 0x15100072: 287, // fi-FI + 0x15300000: 288, // fil + 0x153000e7: 289, // fil-PH + 0x15800000: 290, // fo + 0x15800063: 291, // fo-DK + 0x15800076: 292, // fo-FO + 0x15e00000: 293, // fr + 0x15e00036: 294, // fr-BE + 0x15e00037: 295, // fr-BF + 0x15e0003a: 296, // fr-BI + 0x15e0003b: 297, // fr-BJ + 0x15e0003c: 298, // fr-BL + 0x15e00049: 299, // fr-CA + 0x15e0004b: 300, // fr-CD + 0x15e0004c: 301, // fr-CF + 0x15e0004d: 302, // fr-CG + 0x15e0004e: 303, // fr-CH + 0x15e0004f: 304, // fr-CI + 0x15e00052: 305, // fr-CM + 0x15e00062: 306, // fr-DJ + 0x15e00067: 307, // fr-DZ + 0x15e00078: 308, // fr-FR + 0x15e0007a: 309, // fr-GA + 0x15e0007e: 310, // fr-GF + 0x15e00084: 311, // fr-GN + 0x15e00085: 312, // fr-GP + 0x15e00086: 313, // fr-GQ + 0x15e00091: 314, // fr-HT + 0x15e000a8: 315, // fr-KM + 0x15e000b7: 316, // fr-LU + 0x15e000ba: 317, // fr-MA + 0x15e000bb: 318, // fr-MC + 0x15e000be: 319, // fr-MF + 0x15e000bf: 320, // fr-MG + 0x15e000c3: 321, // fr-ML + 0x15e000c8: 322, // fr-MQ + 0x15e000c9: 323, // fr-MR + 0x15e000cc: 324, // fr-MU + 0x15e000d3: 325, // fr-NC + 0x15e000d4: 326, // fr-NE + 0x15e000e5: 327, // fr-PF + 0x15e000ea: 328, // fr-PM + 0x15e00102: 329, // fr-RE + 0x15e00107: 330, // fr-RW + 0x15e0010a: 331, // fr-SC + 0x15e00114: 332, // fr-SN + 0x15e0011c: 333, // fr-SY + 0x15e00120: 334, // fr-TD + 0x15e00122: 335, // fr-TG + 0x15e00128: 336, // fr-TN + 0x15e0013f: 337, // fr-VU + 0x15e00140: 338, // fr-WF + 0x15e0015f: 339, // fr-YT + 0x16900000: 340, // fur + 0x1690009e: 341, // fur-IT + 0x16d00000: 342, // fy + 0x16d000d9: 343, // fy-NL + 0x16e00000: 344, // ga + 0x16e00096: 345, // ga-IE + 0x17e00000: 346, // gd + 0x17e0007b: 347, // gd-GB + 0x19000000: 348, // gl + 0x1900006e: 349, // gl-ES + 0x1a300000: 350, // gsw + 0x1a30004e: 351, // gsw-CH + 0x1a300078: 352, // gsw-FR + 0x1a3000b2: 353, // gsw-LI + 0x1a400000: 354, // gu + 0x1a400099: 355, // gu-IN + 0x1a900000: 356, // guw + 0x1ab00000: 357, // guz + 0x1ab000a4: 358, // guz-KE + 0x1ac00000: 359, // gv + 0x1ac00098: 360, // gv-IM + 0x1b400000: 361, // ha + 0x1b400080: 362, // ha-GH + 0x1b4000d4: 363, // ha-NE + 0x1b4000d6: 364, // ha-NG + 0x1b800000: 365, // haw + 0x1b800135: 366, // haw-US + 0x1bc00000: 367, // he + 0x1bc00097: 368, // he-IL + 0x1be00000: 369, // hi + 0x1be00099: 370, // hi-IN + 0x1d100000: 371, // hr + 0x1d100033: 372, // hr-BA + 0x1d100090: 373, // hr-HR + 0x1d200000: 374, // hsb + 0x1d200060: 375, // hsb-DE + 0x1d500000: 376, // hu + 0x1d500092: 377, // hu-HU + 0x1d700000: 378, // hy + 0x1d700028: 379, // hy-AM + 0x1e100000: 380, // id + 0x1e100095: 381, // id-ID + 0x1e700000: 382, // ig + 0x1e7000d6: 383, // ig-NG + 0x1ea00000: 384, // ii + 0x1ea00053: 385, // ii-CN + 0x1f500000: 386, // io + 0x1f800000: 387, // is + 0x1f80009d: 388, // is-IS + 0x1f900000: 389, // it + 0x1f90004e: 390, // it-CH + 0x1f90009e: 391, // it-IT + 0x1f900113: 392, // it-SM + 0x1f900138: 393, // it-VA + 0x1fa00000: 394, // iu + 0x20000000: 395, // ja + 0x200000a2: 396, // ja-JP + 0x20300000: 397, // jbo + 0x20700000: 398, // jgo + 0x20700052: 399, // jgo-CM + 0x20a00000: 400, // jmc + 0x20a0012f: 401, // jmc-TZ + 0x20e00000: 402, // jv + 0x21000000: 403, // ka + 0x2100007d: 404, // ka-GE + 0x21200000: 405, // kab + 0x21200067: 406, // kab-DZ + 0x21600000: 407, // kaj + 0x21700000: 408, // kam + 0x217000a4: 409, // kam-KE + 0x21f00000: 410, // kcg + 0x22300000: 411, // kde + 0x2230012f: 412, // kde-TZ + 0x22700000: 413, // kea + 0x2270005a: 414, // kea-CV + 0x23400000: 415, // khq + 0x234000c3: 416, // khq-ML + 0x23900000: 417, // ki + 0x239000a4: 418, // ki-KE + 0x24200000: 419, // kk + 0x242000ae: 420, // kk-KZ + 0x24400000: 421, // kkj + 0x24400052: 422, // kkj-CM + 0x24500000: 423, // kl + 0x24500082: 424, // kl-GL + 0x24600000: 425, // kln + 0x246000a4: 426, // kln-KE + 0x24a00000: 427, // km + 0x24a000a6: 428, // km-KH + 0x25100000: 429, // kn + 0x25100099: 430, // kn-IN + 0x25400000: 431, // ko + 0x254000aa: 432, // ko-KP + 0x254000ab: 433, // ko-KR + 0x25600000: 434, // kok + 0x25600099: 435, // kok-IN + 0x26a00000: 436, // ks + 0x26a00099: 437, // ks-IN + 0x26b00000: 438, // ksb + 0x26b0012f: 439, // ksb-TZ + 0x26d00000: 440, // ksf + 0x26d00052: 441, // ksf-CM + 0x26e00000: 442, // ksh + 0x26e00060: 443, // ksh-DE + 0x27400000: 444, // ku + 0x28100000: 445, // kw + 0x2810007b: 446, // kw-GB + 0x28a00000: 447, // ky + 0x28a000a5: 448, // ky-KG + 0x29100000: 449, // lag + 0x2910012f: 450, // lag-TZ + 0x29500000: 451, // lb + 0x295000b7: 452, // lb-LU + 0x2a300000: 453, // lg + 0x2a300131: 454, // lg-UG + 0x2af00000: 455, // lkt + 0x2af00135: 456, // lkt-US + 0x2b500000: 457, // ln + 0x2b50002a: 458, // ln-AO + 0x2b50004b: 459, // ln-CD + 0x2b50004c: 460, // ln-CF + 0x2b50004d: 461, // ln-CG + 0x2b800000: 462, // lo + 0x2b8000af: 463, // lo-LA + 0x2bf00000: 464, // lrc + 0x2bf0009b: 465, // lrc-IQ + 0x2bf0009c: 466, // lrc-IR + 0x2c000000: 467, // lt + 0x2c0000b6: 468, // lt-LT + 0x2c200000: 469, // lu + 0x2c20004b: 470, // lu-CD + 0x2c400000: 471, // luo + 0x2c4000a4: 472, // luo-KE + 0x2c500000: 473, // luy + 0x2c5000a4: 474, // luy-KE + 0x2c700000: 475, // lv + 0x2c7000b8: 476, // lv-LV + 0x2d100000: 477, // mas + 0x2d1000a4: 478, // mas-KE + 0x2d10012f: 479, // mas-TZ + 0x2e900000: 480, // mer + 0x2e9000a4: 481, // mer-KE + 0x2ed00000: 482, // mfe + 0x2ed000cc: 483, // mfe-MU + 0x2f100000: 484, // mg + 0x2f1000bf: 485, // mg-MG + 0x2f200000: 486, // mgh + 0x2f2000d1: 487, // mgh-MZ + 0x2f400000: 488, // mgo + 0x2f400052: 489, // mgo-CM + 0x2ff00000: 490, // mk + 0x2ff000c2: 491, // mk-MK + 0x30400000: 492, // ml + 0x30400099: 493, // ml-IN + 0x30b00000: 494, // mn + 0x30b000c5: 495, // mn-MN + 0x31b00000: 496, // mr + 0x31b00099: 497, // mr-IN + 0x31f00000: 498, // ms + 0x31f0003e: 499, // ms-BN + 0x31f000d0: 500, // ms-MY + 0x31f0010d: 501, // ms-SG + 0x32000000: 502, // mt + 0x320000cb: 503, // mt-MT + 0x32500000: 504, // mua + 0x32500052: 505, // mua-CM + 0x33100000: 506, // my + 0x331000c4: 507, // my-MM + 0x33a00000: 508, // mzn + 0x33a0009c: 509, // mzn-IR + 0x34100000: 510, // nah + 0x34500000: 511, // naq + 0x345000d2: 512, // naq-NA + 0x34700000: 513, // nb + 0x347000da: 514, // nb-NO + 0x34700110: 515, // nb-SJ + 0x34e00000: 516, // nd + 0x34e00164: 517, // nd-ZW + 0x35000000: 518, // nds + 0x35000060: 519, // nds-DE + 0x350000d9: 520, // nds-NL + 0x35100000: 521, // ne + 0x35100099: 522, // ne-IN + 0x351000db: 523, // ne-NP + 0x36700000: 524, // nl + 0x36700030: 525, // nl-AW + 0x36700036: 526, // nl-BE + 0x36700040: 527, // nl-BQ + 0x3670005b: 528, // nl-CW + 0x367000d9: 529, // nl-NL + 0x36700116: 530, // nl-SR + 0x3670011b: 531, // nl-SX + 0x36800000: 532, // nmg + 0x36800052: 533, // nmg-CM + 0x36a00000: 534, // nn + 0x36a000da: 535, // nn-NO + 0x36c00000: 536, // nnh + 0x36c00052: 537, // nnh-CM + 0x36f00000: 538, // no + 0x37500000: 539, // nqo + 0x37600000: 540, // nr + 0x37a00000: 541, // nso + 0x38000000: 542, // nus + 0x38000117: 543, // nus-SS + 0x38700000: 544, // ny + 0x38900000: 545, // nyn + 0x38900131: 546, // nyn-UG + 0x39000000: 547, // om + 0x3900006f: 548, // om-ET + 0x390000a4: 549, // om-KE + 0x39500000: 550, // or + 0x39500099: 551, // or-IN + 0x39800000: 552, // os + 0x3980007d: 553, // os-GE + 0x39800106: 554, // os-RU + 0x39d00000: 555, // pa + 0x39d05000: 556, // pa-Arab + 0x39d050e8: 557, // pa-Arab-PK + 0x39d33000: 558, // pa-Guru + 0x39d33099: 559, // pa-Guru-IN + 0x3a100000: 560, // pap + 0x3b300000: 561, // pl + 0x3b3000e9: 562, // pl-PL + 0x3bd00000: 563, // prg + 0x3bd00001: 564, // prg-001 + 0x3be00000: 565, // ps + 0x3be00024: 566, // ps-AF + 0x3c000000: 567, // pt + 0x3c00002a: 568, // pt-AO + 0x3c000041: 569, // pt-BR + 0x3c00004e: 570, // pt-CH + 0x3c00005a: 571, // pt-CV + 0x3c000086: 572, // pt-GQ + 0x3c00008b: 573, // pt-GW + 0x3c0000b7: 574, // pt-LU + 0x3c0000c6: 575, // pt-MO + 0x3c0000d1: 576, // pt-MZ + 0x3c0000ee: 577, // pt-PT + 0x3c000118: 578, // pt-ST + 0x3c000126: 579, // pt-TL + 0x3c400000: 580, // qu + 0x3c40003f: 581, // qu-BO + 0x3c400069: 582, // qu-EC + 0x3c4000e4: 583, // qu-PE + 0x3d400000: 584, // rm + 0x3d40004e: 585, // rm-CH + 0x3d900000: 586, // rn + 0x3d90003a: 587, // rn-BI + 0x3dc00000: 588, // ro + 0x3dc000bc: 589, // ro-MD + 0x3dc00104: 590, // ro-RO + 0x3de00000: 591, // rof + 0x3de0012f: 592, // rof-TZ + 0x3e200000: 593, // ru + 0x3e200047: 594, // ru-BY + 0x3e2000a5: 595, // ru-KG + 0x3e2000ae: 596, // ru-KZ + 0x3e2000bc: 597, // ru-MD + 0x3e200106: 598, // ru-RU + 0x3e200130: 599, // ru-UA + 0x3e500000: 600, // rw + 0x3e500107: 601, // rw-RW + 0x3e600000: 602, // rwk + 0x3e60012f: 603, // rwk-TZ + 0x3eb00000: 604, // sah + 0x3eb00106: 605, // sah-RU + 0x3ec00000: 606, // saq + 0x3ec000a4: 607, // saq-KE + 0x3f300000: 608, // sbp + 0x3f30012f: 609, // sbp-TZ + 0x3fa00000: 610, // sd + 0x3fa000e8: 611, // sd-PK + 0x3fc00000: 612, // sdh + 0x3fd00000: 613, // se + 0x3fd00072: 614, // se-FI + 0x3fd000da: 615, // se-NO + 0x3fd0010c: 616, // se-SE + 0x3ff00000: 617, // seh + 0x3ff000d1: 618, // seh-MZ + 0x40100000: 619, // ses + 0x401000c3: 620, // ses-ML + 0x40200000: 621, // sg + 0x4020004c: 622, // sg-CF + 0x40800000: 623, // shi + 0x40857000: 624, // shi-Latn + 0x408570ba: 625, // shi-Latn-MA + 0x408dc000: 626, // shi-Tfng + 0x408dc0ba: 627, // shi-Tfng-MA + 0x40c00000: 628, // si + 0x40c000b3: 629, // si-LK + 0x41200000: 630, // sk + 0x41200111: 631, // sk-SK + 0x41600000: 632, // sl + 0x4160010f: 633, // sl-SI + 0x41c00000: 634, // sma + 0x41d00000: 635, // smi + 0x41e00000: 636, // smj + 0x41f00000: 637, // smn + 0x41f00072: 638, // smn-FI + 0x42200000: 639, // sms + 0x42300000: 640, // sn + 0x42300164: 641, // sn-ZW + 0x42900000: 642, // so + 0x42900062: 643, // so-DJ + 0x4290006f: 644, // so-ET + 0x429000a4: 645, // so-KE + 0x42900115: 646, // so-SO + 0x43100000: 647, // sq + 0x43100027: 648, // sq-AL + 0x431000c2: 649, // sq-MK + 0x4310014d: 650, // sq-XK + 0x43200000: 651, // sr + 0x4321f000: 652, // sr-Cyrl + 0x4321f033: 653, // sr-Cyrl-BA + 0x4321f0bd: 654, // sr-Cyrl-ME + 0x4321f105: 655, // sr-Cyrl-RS + 0x4321f14d: 656, // sr-Cyrl-XK + 0x43257000: 657, // sr-Latn + 0x43257033: 658, // sr-Latn-BA + 0x432570bd: 659, // sr-Latn-ME + 0x43257105: 660, // sr-Latn-RS + 0x4325714d: 661, // sr-Latn-XK + 0x43700000: 662, // ss + 0x43a00000: 663, // ssy + 0x43b00000: 664, // st + 0x44400000: 665, // sv + 0x44400031: 666, // sv-AX + 0x44400072: 667, // sv-FI + 0x4440010c: 668, // sv-SE + 0x44500000: 669, // sw + 0x4450004b: 670, // sw-CD + 0x445000a4: 671, // sw-KE + 0x4450012f: 672, // sw-TZ + 0x44500131: 673, // sw-UG + 0x44e00000: 674, // syr + 0x45000000: 675, // ta + 0x45000099: 676, // ta-IN + 0x450000b3: 677, // ta-LK + 0x450000d0: 678, // ta-MY + 0x4500010d: 679, // ta-SG + 0x46100000: 680, // te + 0x46100099: 681, // te-IN + 0x46400000: 682, // teo + 0x464000a4: 683, // teo-KE + 0x46400131: 684, // teo-UG + 0x46700000: 685, // tg + 0x46700124: 686, // tg-TJ + 0x46b00000: 687, // th + 0x46b00123: 688, // th-TH + 0x46f00000: 689, // ti + 0x46f0006d: 690, // ti-ER + 0x46f0006f: 691, // ti-ET + 0x47100000: 692, // tig + 0x47600000: 693, // tk + 0x47600127: 694, // tk-TM + 0x48000000: 695, // tn + 0x48200000: 696, // to + 0x48200129: 697, // to-TO + 0x48a00000: 698, // tr + 0x48a0005d: 699, // tr-CY + 0x48a0012b: 700, // tr-TR + 0x48e00000: 701, // ts + 0x49400000: 702, // tt + 0x49400106: 703, // tt-RU + 0x4a400000: 704, // twq + 0x4a4000d4: 705, // twq-NE + 0x4a900000: 706, // tzm + 0x4a9000ba: 707, // tzm-MA + 0x4ac00000: 708, // ug + 0x4ac00053: 709, // ug-CN + 0x4ae00000: 710, // uk + 0x4ae00130: 711, // uk-UA + 0x4b400000: 712, // ur + 0x4b400099: 713, // ur-IN + 0x4b4000e8: 714, // ur-PK + 0x4bc00000: 715, // uz + 0x4bc05000: 716, // uz-Arab + 0x4bc05024: 717, // uz-Arab-AF + 0x4bc1f000: 718, // uz-Cyrl + 0x4bc1f137: 719, // uz-Cyrl-UZ + 0x4bc57000: 720, // uz-Latn + 0x4bc57137: 721, // uz-Latn-UZ + 0x4be00000: 722, // vai + 0x4be57000: 723, // vai-Latn + 0x4be570b4: 724, // vai-Latn-LR + 0x4bee3000: 725, // vai-Vaii + 0x4bee30b4: 726, // vai-Vaii-LR + 0x4c000000: 727, // ve + 0x4c300000: 728, // vi + 0x4c30013e: 729, // vi-VN + 0x4c900000: 730, // vo + 0x4c900001: 731, // vo-001 + 0x4cc00000: 732, // vun + 0x4cc0012f: 733, // vun-TZ + 0x4ce00000: 734, // wa + 0x4cf00000: 735, // wae + 0x4cf0004e: 736, // wae-CH + 0x4e500000: 737, // wo + 0x4e500114: 738, // wo-SN + 0x4f200000: 739, // xh + 0x4fb00000: 740, // xog + 0x4fb00131: 741, // xog-UG + 0x50900000: 742, // yav + 0x50900052: 743, // yav-CM + 0x51200000: 744, // yi + 0x51200001: 745, // yi-001 + 0x51800000: 746, // yo + 0x5180003b: 747, // yo-BJ + 0x518000d6: 748, // yo-NG + 0x51f00000: 749, // yue + 0x51f38000: 750, // yue-Hans + 0x51f38053: 751, // yue-Hans-CN + 0x51f39000: 752, // yue-Hant + 0x51f3908d: 753, // yue-Hant-HK + 0x52800000: 754, // zgh + 0x528000ba: 755, // zgh-MA + 0x52900000: 756, // zh + 0x52938000: 757, // zh-Hans + 0x52938053: 758, // zh-Hans-CN + 0x5293808d: 759, // zh-Hans-HK + 0x529380c6: 760, // zh-Hans-MO + 0x5293810d: 761, // zh-Hans-SG + 0x52939000: 762, // zh-Hant + 0x5293908d: 763, // zh-Hant-HK + 0x529390c6: 764, // zh-Hant-MO + 0x5293912e: 765, // zh-Hant-TW + 0x52f00000: 766, // zu + 0x52f00161: 767, // zu-ZA +} + +// Total table size 4676 bytes (4KiB); checksum: 17BE3673 diff --git a/vendor/golang.org/x/text/language/language.go b/vendor/golang.org/x/text/language/language.go new file mode 100644 index 000000000..b65e213ff --- /dev/null +++ b/vendor/golang.org/x/text/language/language.go @@ -0,0 +1,907 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen.go gen_common.go -output tables.go +//go:generate go run gen_index.go + +package language + +// TODO: Remove above NOTE after: +// - verifying that tables are dropped correctly (most notably matcher tables). + +import ( + "errors" + "fmt" + "strings" +) + +const ( + // maxCoreSize is the maximum size of a BCP 47 tag without variants and + // extensions. Equals max lang (3) + script (4) + max reg (3) + 2 dashes. + maxCoreSize = 12 + + // max99thPercentileSize is a somewhat arbitrary buffer size that presumably + // is large enough to hold at least 99% of the BCP 47 tags. + max99thPercentileSize = 32 + + // maxSimpleUExtensionSize is the maximum size of a -u extension with one + // key-type pair. Equals len("-u-") + key (2) + dash + max value (8). + maxSimpleUExtensionSize = 14 +) + +// Tag represents a BCP 47 language tag. It is used to specify an instance of a +// specific language or locale. All language tag values are guaranteed to be +// well-formed. +type Tag struct { + lang langID + region regionID + // TODO: we will soon run out of positions for script. Idea: instead of + // storing lang, region, and script codes, store only the compact index and + // have a lookup table from this code to its expansion. This greatly speeds + // up table lookup, speed up common variant cases. + // This will also immediately free up 3 extra bytes. Also, the pVariant + // field can now be moved to the lookup table, as the compact index uniquely + // determines the offset of a possible variant. + script scriptID + pVariant byte // offset in str, includes preceding '-' + pExt uint16 // offset of first extension, includes preceding '-' + + // str is the string representation of the Tag. It will only be used if the + // tag has variants or extensions. + str string +} + +// Make is a convenience wrapper for Parse that omits the error. +// In case of an error, a sensible default is returned. +func Make(s string) Tag { + return Default.Make(s) +} + +// Make is a convenience wrapper for c.Parse that omits the error. +// In case of an error, a sensible default is returned. +func (c CanonType) Make(s string) Tag { + t, _ := c.Parse(s) + return t +} + +// Raw returns the raw base language, script and region, without making an +// attempt to infer their values. +func (t Tag) Raw() (b Base, s Script, r Region) { + return Base{t.lang}, Script{t.script}, Region{t.region} +} + +// equalTags compares language, script and region subtags only. +func (t Tag) equalTags(a Tag) bool { + return t.lang == a.lang && t.script == a.script && t.region == a.region +} + +// IsRoot returns true if t is equal to language "und". +func (t Tag) IsRoot() bool { + if int(t.pVariant) < len(t.str) { + return false + } + return t.equalTags(und) +} + +// private reports whether the Tag consists solely of a private use tag. +func (t Tag) private() bool { + return t.str != "" && t.pVariant == 0 +} + +// CanonType can be used to enable or disable various types of canonicalization. +type CanonType int + +const ( + // Replace deprecated base languages with their preferred replacements. + DeprecatedBase CanonType = 1 << iota + // Replace deprecated scripts with their preferred replacements. + DeprecatedScript + // Replace deprecated regions with their preferred replacements. + DeprecatedRegion + // Remove redundant scripts. + SuppressScript + // Normalize legacy encodings. This includes legacy languages defined in + // CLDR as well as bibliographic codes defined in ISO-639. + Legacy + // Map the dominant language of a macro language group to the macro language + // subtag. For example cmn -> zh. + Macro + // The CLDR flag should be used if full compatibility with CLDR is required. + // There are a few cases where language.Tag may differ from CLDR. To follow all + // of CLDR's suggestions, use All|CLDR. + CLDR + + // Raw can be used to Compose or Parse without Canonicalization. + Raw CanonType = 0 + + // Replace all deprecated tags with their preferred replacements. + Deprecated = DeprecatedBase | DeprecatedScript | DeprecatedRegion + + // All canonicalizations recommended by BCP 47. + BCP47 = Deprecated | SuppressScript + + // All canonicalizations. + All = BCP47 | Legacy | Macro + + // Default is the canonicalization used by Parse, Make and Compose. To + // preserve as much information as possible, canonicalizations that remove + // potentially valuable information are not included. The Matcher is + // designed to recognize similar tags that would be the same if + // they were canonicalized using All. + Default = Deprecated | Legacy + + canonLang = DeprecatedBase | Legacy | Macro + + // TODO: LikelyScript, LikelyRegion: suppress similar to ICU. +) + +// canonicalize returns the canonicalized equivalent of the tag and +// whether there was any change. +func (t Tag) canonicalize(c CanonType) (Tag, bool) { + if c == Raw { + return t, false + } + changed := false + if c&SuppressScript != 0 { + if t.lang < langNoIndexOffset && uint8(t.script) == suppressScript[t.lang] { + t.script = 0 + changed = true + } + } + if c&canonLang != 0 { + for { + if l, aliasType := normLang(t.lang); l != t.lang { + switch aliasType { + case langLegacy: + if c&Legacy != 0 { + if t.lang == _sh && t.script == 0 { + t.script = _Latn + } + t.lang = l + changed = true + } + case langMacro: + if c&Macro != 0 { + // We deviate here from CLDR. The mapping "nb" -> "no" + // qualifies as a typical Macro language mapping. However, + // for legacy reasons, CLDR maps "no", the macro language + // code for Norwegian, to the dominant variant "nb". This + // change is currently under consideration for CLDR as well. + // See http://unicode.org/cldr/trac/ticket/2698 and also + // http://unicode.org/cldr/trac/ticket/1790 for some of the + // practical implications. TODO: this check could be removed + // if CLDR adopts this change. + if c&CLDR == 0 || t.lang != _nb { + changed = true + t.lang = l + } + } + case langDeprecated: + if c&DeprecatedBase != 0 { + if t.lang == _mo && t.region == 0 { + t.region = _MD + } + t.lang = l + changed = true + // Other canonicalization types may still apply. + continue + } + } + } else if c&Legacy != 0 && t.lang == _no && c&CLDR != 0 { + t.lang = _nb + changed = true + } + break + } + } + if c&DeprecatedScript != 0 { + if t.script == _Qaai { + changed = true + t.script = _Zinh + } + } + if c&DeprecatedRegion != 0 { + if r := normRegion(t.region); r != 0 { + changed = true + t.region = r + } + } + return t, changed +} + +// Canonicalize returns the canonicalized equivalent of the tag. +func (c CanonType) Canonicalize(t Tag) (Tag, error) { + t, changed := t.canonicalize(c) + if changed { + t.remakeString() + } + return t, nil +} + +// Confidence indicates the level of certainty for a given return value. +// For example, Serbian may be written in Cyrillic or Latin script. +// The confidence level indicates whether a value was explicitly specified, +// whether it is typically the only possible value, or whether there is +// an ambiguity. +type Confidence int + +const ( + No Confidence = iota // full confidence that there was no match + Low // most likely value picked out of a set of alternatives + High // value is generally assumed to be the correct match + Exact // exact match or explicitly specified value +) + +var confName = []string{"No", "Low", "High", "Exact"} + +func (c Confidence) String() string { + return confName[c] +} + +// remakeString is used to update t.str in case lang, script or region changed. +// It is assumed that pExt and pVariant still point to the start of the +// respective parts. +func (t *Tag) remakeString() { + if t.str == "" { + return + } + extra := t.str[t.pVariant:] + if t.pVariant > 0 { + extra = extra[1:] + } + if t.equalTags(und) && strings.HasPrefix(extra, "x-") { + t.str = extra + t.pVariant = 0 + t.pExt = 0 + return + } + var buf [max99thPercentileSize]byte // avoid extra memory allocation in most cases. + b := buf[:t.genCoreBytes(buf[:])] + if extra != "" { + diff := len(b) - int(t.pVariant) + b = append(b, '-') + b = append(b, extra...) + t.pVariant = uint8(int(t.pVariant) + diff) + t.pExt = uint16(int(t.pExt) + diff) + } else { + t.pVariant = uint8(len(b)) + t.pExt = uint16(len(b)) + } + t.str = string(b) +} + +// genCoreBytes writes a string for the base languages, script and region tags +// to the given buffer and returns the number of bytes written. It will never +// write more than maxCoreSize bytes. +func (t *Tag) genCoreBytes(buf []byte) int { + n := t.lang.stringToBuf(buf[:]) + if t.script != 0 { + n += copy(buf[n:], "-") + n += copy(buf[n:], t.script.String()) + } + if t.region != 0 { + n += copy(buf[n:], "-") + n += copy(buf[n:], t.region.String()) + } + return n +} + +// String returns the canonical string representation of the language tag. +func (t Tag) String() string { + if t.str != "" { + return t.str + } + if t.script == 0 && t.region == 0 { + return t.lang.String() + } + buf := [maxCoreSize]byte{} + return string(buf[:t.genCoreBytes(buf[:])]) +} + +// MarshalText implements encoding.TextMarshaler. +func (t Tag) MarshalText() (text []byte, err error) { + if t.str != "" { + text = append(text, t.str...) + } else if t.script == 0 && t.region == 0 { + text = append(text, t.lang.String()...) + } else { + buf := [maxCoreSize]byte{} + text = buf[:t.genCoreBytes(buf[:])] + } + return text, nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (t *Tag) UnmarshalText(text []byte) error { + tag, err := Raw.Parse(string(text)) + *t = tag + return err +} + +// Base returns the base language of the language tag. If the base language is +// unspecified, an attempt will be made to infer it from the context. +// It uses a variant of CLDR's Add Likely Subtags algorithm. This is subject to change. +func (t Tag) Base() (Base, Confidence) { + if t.lang != 0 { + return Base{t.lang}, Exact + } + c := High + if t.script == 0 && !(Region{t.region}).IsCountry() { + c = Low + } + if tag, err := addTags(t); err == nil && tag.lang != 0 { + return Base{tag.lang}, c + } + return Base{0}, No +} + +// Script infers the script for the language tag. If it was not explicitly given, it will infer +// a most likely candidate. +// If more than one script is commonly used for a language, the most likely one +// is returned with a low confidence indication. For example, it returns (Cyrl, Low) +// for Serbian. +// If a script cannot be inferred (Zzzz, No) is returned. We do not use Zyyy (undetermined) +// as one would suspect from the IANA registry for BCP 47. In a Unicode context Zyyy marks +// common characters (like 1, 2, 3, '.', etc.) and is therefore more like multiple scripts. +// See http://www.unicode.org/reports/tr24/#Values for more details. Zzzz is also used for +// unknown value in CLDR. (Zzzz, Exact) is returned if Zzzz was explicitly specified. +// Note that an inferred script is never guaranteed to be the correct one. Latin is +// almost exclusively used for Afrikaans, but Arabic has been used for some texts +// in the past. Also, the script that is commonly used may change over time. +// It uses a variant of CLDR's Add Likely Subtags algorithm. This is subject to change. +func (t Tag) Script() (Script, Confidence) { + if t.script != 0 { + return Script{t.script}, Exact + } + sc, c := scriptID(_Zzzz), No + if t.lang < langNoIndexOffset { + if scr := scriptID(suppressScript[t.lang]); scr != 0 { + // Note: it is not always the case that a language with a suppress + // script value is only written in one script (e.g. kk, ms, pa). + if t.region == 0 { + return Script{scriptID(scr)}, High + } + sc, c = scr, High + } + } + if tag, err := addTags(t); err == nil { + if tag.script != sc { + sc, c = tag.script, Low + } + } else { + t, _ = (Deprecated | Macro).Canonicalize(t) + if tag, err := addTags(t); err == nil && tag.script != sc { + sc, c = tag.script, Low + } + } + return Script{sc}, c +} + +// Region returns the region for the language tag. If it was not explicitly given, it will +// infer a most likely candidate from the context. +// It uses a variant of CLDR's Add Likely Subtags algorithm. This is subject to change. +func (t Tag) Region() (Region, Confidence) { + if t.region != 0 { + return Region{t.region}, Exact + } + if t, err := addTags(t); err == nil { + return Region{t.region}, Low // TODO: differentiate between high and low. + } + t, _ = (Deprecated | Macro).Canonicalize(t) + if tag, err := addTags(t); err == nil { + return Region{tag.region}, Low + } + return Region{_ZZ}, No // TODO: return world instead of undetermined? +} + +// Variant returns the variants specified explicitly for this language tag. +// or nil if no variant was specified. +func (t Tag) Variants() []Variant { + v := []Variant{} + if int(t.pVariant) < int(t.pExt) { + for x, str := "", t.str[t.pVariant:t.pExt]; str != ""; { + x, str = nextToken(str) + v = append(v, Variant{x}) + } + } + return v +} + +// Parent returns the CLDR parent of t. In CLDR, missing fields in data for a +// specific language are substituted with fields from the parent language. +// The parent for a language may change for newer versions of CLDR. +func (t Tag) Parent() Tag { + if t.str != "" { + // Strip the variants and extensions. + t, _ = Raw.Compose(t.Raw()) + if t.region == 0 && t.script != 0 && t.lang != 0 { + base, _ := addTags(Tag{lang: t.lang}) + if base.script == t.script { + return Tag{lang: t.lang} + } + } + return t + } + if t.lang != 0 { + if t.region != 0 { + maxScript := t.script + if maxScript == 0 { + max, _ := addTags(t) + maxScript = max.script + } + + for i := range parents { + if langID(parents[i].lang) == t.lang && scriptID(parents[i].maxScript) == maxScript { + for _, r := range parents[i].fromRegion { + if regionID(r) == t.region { + return Tag{ + lang: t.lang, + script: scriptID(parents[i].script), + region: regionID(parents[i].toRegion), + } + } + } + } + } + + // Strip the script if it is the default one. + base, _ := addTags(Tag{lang: t.lang}) + if base.script != maxScript { + return Tag{lang: t.lang, script: maxScript} + } + return Tag{lang: t.lang} + } else if t.script != 0 { + // The parent for an base-script pair with a non-default script is + // "und" instead of the base language. + base, _ := addTags(Tag{lang: t.lang}) + if base.script != t.script { + return und + } + return Tag{lang: t.lang} + } + } + return und +} + +// returns token t and the rest of the string. +func nextToken(s string) (t, tail string) { + p := strings.Index(s[1:], "-") + if p == -1 { + return s[1:], "" + } + p++ + return s[1:p], s[p:] +} + +// Extension is a single BCP 47 extension. +type Extension struct { + s string +} + +// String returns the string representation of the extension, including the +// type tag. +func (e Extension) String() string { + return e.s +} + +// ParseExtension parses s as an extension and returns it on success. +func ParseExtension(s string) (e Extension, err error) { + scan := makeScannerString(s) + var end int + if n := len(scan.token); n != 1 { + return Extension{}, errSyntax + } + scan.toLower(0, len(scan.b)) + end = parseExtension(&scan) + if end != len(s) { + return Extension{}, errSyntax + } + return Extension{string(scan.b)}, nil +} + +// Type returns the one-byte extension type of e. It returns 0 for the zero +// exception. +func (e Extension) Type() byte { + if e.s == "" { + return 0 + } + return e.s[0] +} + +// Tokens returns the list of tokens of e. +func (e Extension) Tokens() []string { + return strings.Split(e.s, "-") +} + +// Extension returns the extension of type x for tag t. It will return +// false for ok if t does not have the requested extension. The returned +// extension will be invalid in this case. +func (t Tag) Extension(x byte) (ext Extension, ok bool) { + for i := int(t.pExt); i < len(t.str)-1; { + var ext string + i, ext = getExtension(t.str, i) + if ext[0] == x { + return Extension{ext}, true + } + } + return Extension{}, false +} + +// Extensions returns all extensions of t. +func (t Tag) Extensions() []Extension { + e := []Extension{} + for i := int(t.pExt); i < len(t.str)-1; { + var ext string + i, ext = getExtension(t.str, i) + e = append(e, Extension{ext}) + } + return e +} + +// TypeForKey returns the type associated with the given key, where key and type +// are of the allowed values defined for the Unicode locale extension ('u') in +// http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers. +// TypeForKey will traverse the inheritance chain to get the correct value. +func (t Tag) TypeForKey(key string) string { + if start, end, _ := t.findTypeForKey(key); end != start { + return t.str[start:end] + } + return "" +} + +var ( + errPrivateUse = errors.New("cannot set a key on a private use tag") + errInvalidArguments = errors.New("invalid key or type") +) + +// SetTypeForKey returns a new Tag with the key set to type, where key and type +// are of the allowed values defined for the Unicode locale extension ('u') in +// http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers. +// An empty value removes an existing pair with the same key. +func (t Tag) SetTypeForKey(key, value string) (Tag, error) { + if t.private() { + return t, errPrivateUse + } + if len(key) != 2 { + return t, errInvalidArguments + } + + // Remove the setting if value is "". + if value == "" { + start, end, _ := t.findTypeForKey(key) + if start != end { + // Remove key tag and leading '-'. + start -= 4 + + // Remove a possible empty extension. + if (end == len(t.str) || t.str[end+2] == '-') && t.str[start-2] == '-' { + start -= 2 + } + if start == int(t.pVariant) && end == len(t.str) { + t.str = "" + t.pVariant, t.pExt = 0, 0 + } else { + t.str = fmt.Sprintf("%s%s", t.str[:start], t.str[end:]) + } + } + return t, nil + } + + if len(value) < 3 || len(value) > 8 { + return t, errInvalidArguments + } + + var ( + buf [maxCoreSize + maxSimpleUExtensionSize]byte + uStart int // start of the -u extension. + ) + + // Generate the tag string if needed. + if t.str == "" { + uStart = t.genCoreBytes(buf[:]) + buf[uStart] = '-' + uStart++ + } + + // Create new key-type pair and parse it to verify. + b := buf[uStart:] + copy(b, "u-") + copy(b[2:], key) + b[4] = '-' + b = b[:5+copy(b[5:], value)] + scan := makeScanner(b) + if parseExtensions(&scan); scan.err != nil { + return t, scan.err + } + + // Assemble the replacement string. + if t.str == "" { + t.pVariant, t.pExt = byte(uStart-1), uint16(uStart-1) + t.str = string(buf[:uStart+len(b)]) + } else { + s := t.str + start, end, hasExt := t.findTypeForKey(key) + if start == end { + if hasExt { + b = b[2:] + } + t.str = fmt.Sprintf("%s-%s%s", s[:start], b, s[end:]) + } else { + t.str = fmt.Sprintf("%s%s%s", s[:start], value, s[end:]) + } + } + return t, nil +} + +// findKeyAndType returns the start and end position for the type corresponding +// to key or the point at which to insert the key-value pair if the type +// wasn't found. The hasExt return value reports whether an -u extension was present. +// Note: the extensions are typically very small and are likely to contain +// only one key-type pair. +func (t Tag) findTypeForKey(key string) (start, end int, hasExt bool) { + p := int(t.pExt) + if len(key) != 2 || p == len(t.str) || p == 0 { + return p, p, false + } + s := t.str + + // Find the correct extension. + for p++; s[p] != 'u'; p++ { + if s[p] > 'u' { + p-- + return p, p, false + } + if p = nextExtension(s, p); p == len(s) { + return len(s), len(s), false + } + } + // Proceed to the hyphen following the extension name. + p++ + + // curKey is the key currently being processed. + curKey := "" + + // Iterate over keys until we get the end of a section. + for { + // p points to the hyphen preceding the current token. + if p3 := p + 3; s[p3] == '-' { + // Found a key. + // Check whether we just processed the key that was requested. + if curKey == key { + return start, p, true + } + // Set to the next key and continue scanning type tokens. + curKey = s[p+1 : p3] + if curKey > key { + return p, p, true + } + // Start of the type token sequence. + start = p + 4 + // A type is at least 3 characters long. + p += 7 // 4 + 3 + } else { + // Attribute or type, which is at least 3 characters long. + p += 4 + } + // p points past the third character of a type or attribute. + max := p + 5 // maximum length of token plus hyphen. + if len(s) < max { + max = len(s) + } + for ; p < max && s[p] != '-'; p++ { + } + // Bail if we have exhausted all tokens or if the next token starts + // a new extension. + if p == len(s) || s[p+2] == '-' { + if curKey == key { + return start, p, true + } + return p, p, true + } + } +} + +// CompactIndex returns an index, where 0 <= index < NumCompactTags, for tags +// for which data exists in the text repository. The index will change over time +// and should not be stored in persistent storage. Extensions, except for the +// 'va' type of the 'u' extension, are ignored. It will return 0, false if no +// compact tag exists, where 0 is the index for the root language (Und). +func CompactIndex(t Tag) (index int, ok bool) { + // TODO: perhaps give more frequent tags a lower index. + // TODO: we could make the indexes stable. This will excluded some + // possibilities for optimization, so don't do this quite yet. + b, s, r := t.Raw() + if len(t.str) > 0 { + if strings.HasPrefix(t.str, "x-") { + // We have no entries for user-defined tags. + return 0, false + } + if uint16(t.pVariant) != t.pExt { + // There are no tags with variants and an u-va type. + if t.TypeForKey("va") != "" { + return 0, false + } + t, _ = Raw.Compose(b, s, r, t.Variants()) + } else if _, ok := t.Extension('u'); ok { + // Strip all but the 'va' entry. + variant := t.TypeForKey("va") + t, _ = Raw.Compose(b, s, r) + t, _ = t.SetTypeForKey("va", variant) + } + if len(t.str) > 0 { + // We have some variants. + for i, s := range specialTags { + if s == t { + return i + 1, true + } + } + return 0, false + } + } + // No variants specified: just compare core components. + // The key has the form lllssrrr, where l, s, and r are nibbles for + // respectively the langID, scriptID, and regionID. + key := uint32(b.langID) << (8 + 12) + key |= uint32(s.scriptID) << 12 + key |= uint32(r.regionID) + x, ok := coreTags[key] + return int(x), ok +} + +// Base is an ISO 639 language code, used for encoding the base language +// of a language tag. +type Base struct { + langID +} + +// ParseBase parses a 2- or 3-letter ISO 639 code. +// It returns a ValueError if s is a well-formed but unknown language identifier +// or another error if another error occurred. +func ParseBase(s string) (Base, error) { + if n := len(s); n < 2 || 3 < n { + return Base{}, errSyntax + } + var buf [3]byte + l, err := getLangID(buf[:copy(buf[:], s)]) + return Base{l}, err +} + +// Script is a 4-letter ISO 15924 code for representing scripts. +// It is idiomatically represented in title case. +type Script struct { + scriptID +} + +// ParseScript parses a 4-letter ISO 15924 code. +// It returns a ValueError if s is a well-formed but unknown script identifier +// or another error if another error occurred. +func ParseScript(s string) (Script, error) { + if len(s) != 4 { + return Script{}, errSyntax + } + var buf [4]byte + sc, err := getScriptID(script, buf[:copy(buf[:], s)]) + return Script{sc}, err +} + +// Region is an ISO 3166-1 or UN M.49 code for representing countries and regions. +type Region struct { + regionID +} + +// EncodeM49 returns the Region for the given UN M.49 code. +// It returns an error if r is not a valid code. +func EncodeM49(r int) (Region, error) { + rid, err := getRegionM49(r) + return Region{rid}, err +} + +// ParseRegion parses a 2- or 3-letter ISO 3166-1 or a UN M.49 code. +// It returns a ValueError if s is a well-formed but unknown region identifier +// or another error if another error occurred. +func ParseRegion(s string) (Region, error) { + if n := len(s); n < 2 || 3 < n { + return Region{}, errSyntax + } + var buf [3]byte + r, err := getRegionID(buf[:copy(buf[:], s)]) + return Region{r}, err +} + +// IsCountry returns whether this region is a country or autonomous area. This +// includes non-standard definitions from CLDR. +func (r Region) IsCountry() bool { + if r.regionID == 0 || r.IsGroup() || r.IsPrivateUse() && r.regionID != _XK { + return false + } + return true +} + +// IsGroup returns whether this region defines a collection of regions. This +// includes non-standard definitions from CLDR. +func (r Region) IsGroup() bool { + if r.regionID == 0 { + return false + } + return int(regionInclusion[r.regionID]) < len(regionContainment) +} + +// Contains returns whether Region c is contained by Region r. It returns true +// if c == r. +func (r Region) Contains(c Region) bool { + return r.regionID.contains(c.regionID) +} + +func (r regionID) contains(c regionID) bool { + if r == c { + return true + } + g := regionInclusion[r] + if g >= nRegionGroups { + return false + } + m := regionContainment[g] + + d := regionInclusion[c] + b := regionInclusionBits[d] + + // A contained country may belong to multiple disjoint groups. Matching any + // of these indicates containment. If the contained region is a group, it + // must strictly be a subset. + if d >= nRegionGroups { + return b&m != 0 + } + return b&^m == 0 +} + +var errNoTLD = errors.New("language: region is not a valid ccTLD") + +// TLD returns the country code top-level domain (ccTLD). UK is returned for GB. +// In all other cases it returns either the region itself or an error. +// +// This method may return an error for a region for which there exists a +// canonical form with a ccTLD. To get that ccTLD canonicalize r first. The +// region will already be canonicalized it was obtained from a Tag that was +// obtained using any of the default methods. +func (r Region) TLD() (Region, error) { + // See http://en.wikipedia.org/wiki/Country_code_top-level_domain for the + // difference between ISO 3166-1 and IANA ccTLD. + if r.regionID == _GB { + r = Region{_UK} + } + if (r.typ() & ccTLD) == 0 { + return Region{}, errNoTLD + } + return r, nil +} + +// Canonicalize returns the region or a possible replacement if the region is +// deprecated. It will not return a replacement for deprecated regions that +// are split into multiple regions. +func (r Region) Canonicalize() Region { + if cr := normRegion(r.regionID); cr != 0 { + return Region{cr} + } + return r +} + +// Variant represents a registered variant of a language as defined by BCP 47. +type Variant struct { + variant string +} + +// ParseVariant parses and returns a Variant. An error is returned if s is not +// a valid variant. +func ParseVariant(s string) (Variant, error) { + s = strings.ToLower(s) + if _, ok := variantIndex[s]; ok { + return Variant{s}, nil + } + return Variant{}, mkErrInvalid([]byte(s)) +} + +// String returns the string representation of the variant. +func (v Variant) String() string { + return v.variant +} diff --git a/vendor/golang.org/x/text/language/lookup.go b/vendor/golang.org/x/text/language/lookup.go new file mode 100644 index 000000000..1d80ac370 --- /dev/null +++ b/vendor/golang.org/x/text/language/lookup.go @@ -0,0 +1,396 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package language + +import ( + "bytes" + "fmt" + "sort" + "strconv" + + "golang.org/x/text/internal/tag" +) + +// findIndex tries to find the given tag in idx and returns a standardized error +// if it could not be found. +func findIndex(idx tag.Index, key []byte, form string) (index int, err error) { + if !tag.FixCase(form, key) { + return 0, errSyntax + } + i := idx.Index(key) + if i == -1 { + return 0, mkErrInvalid(key) + } + return i, nil +} + +func searchUint(imap []uint16, key uint16) int { + return sort.Search(len(imap), func(i int) bool { + return imap[i] >= key + }) +} + +type langID uint16 + +// getLangID returns the langID of s if s is a canonical subtag +// or langUnknown if s is not a canonical subtag. +func getLangID(s []byte) (langID, error) { + if len(s) == 2 { + return getLangISO2(s) + } + return getLangISO3(s) +} + +// mapLang returns the mapped langID of id according to mapping m. +func normLang(id langID) (langID, langAliasType) { + k := sort.Search(len(langAliasMap), func(i int) bool { + return langAliasMap[i].from >= uint16(id) + }) + if k < len(langAliasMap) && langAliasMap[k].from == uint16(id) { + return langID(langAliasMap[k].to), langAliasTypes[k] + } + return id, langAliasTypeUnknown +} + +// getLangISO2 returns the langID for the given 2-letter ISO language code +// or unknownLang if this does not exist. +func getLangISO2(s []byte) (langID, error) { + if !tag.FixCase("zz", s) { + return 0, errSyntax + } + if i := lang.Index(s); i != -1 && lang.Elem(i)[3] != 0 { + return langID(i), nil + } + return 0, mkErrInvalid(s) +} + +const base = 'z' - 'a' + 1 + +func strToInt(s []byte) uint { + v := uint(0) + for i := 0; i < len(s); i++ { + v *= base + v += uint(s[i] - 'a') + } + return v +} + +// converts the given integer to the original ASCII string passed to strToInt. +// len(s) must match the number of characters obtained. +func intToStr(v uint, s []byte) { + for i := len(s) - 1; i >= 0; i-- { + s[i] = byte(v%base) + 'a' + v /= base + } +} + +// getLangISO3 returns the langID for the given 3-letter ISO language code +// or unknownLang if this does not exist. +func getLangISO3(s []byte) (langID, error) { + if tag.FixCase("und", s) { + // first try to match canonical 3-letter entries + for i := lang.Index(s[:2]); i != -1; i = lang.Next(s[:2], i) { + if e := lang.Elem(i); e[3] == 0 && e[2] == s[2] { + // We treat "und" as special and always translate it to "unspecified". + // Note that ZZ and Zzzz are private use and are not treated as + // unspecified by default. + id := langID(i) + if id == nonCanonicalUnd { + return 0, nil + } + return id, nil + } + } + if i := altLangISO3.Index(s); i != -1 { + return langID(altLangIndex[altLangISO3.Elem(i)[3]]), nil + } + n := strToInt(s) + if langNoIndex[n/8]&(1<<(n%8)) != 0 { + return langID(n) + langNoIndexOffset, nil + } + // Check for non-canonical uses of ISO3. + for i := lang.Index(s[:1]); i != -1; i = lang.Next(s[:1], i) { + if e := lang.Elem(i); e[2] == s[1] && e[3] == s[2] { + return langID(i), nil + } + } + return 0, mkErrInvalid(s) + } + return 0, errSyntax +} + +// stringToBuf writes the string to b and returns the number of bytes +// written. cap(b) must be >= 3. +func (id langID) stringToBuf(b []byte) int { + if id >= langNoIndexOffset { + intToStr(uint(id)-langNoIndexOffset, b[:3]) + return 3 + } else if id == 0 { + return copy(b, "und") + } + l := lang[id<<2:] + if l[3] == 0 { + return copy(b, l[:3]) + } + return copy(b, l[:2]) +} + +// String returns the BCP 47 representation of the langID. +// Use b as variable name, instead of id, to ensure the variable +// used is consistent with that of Base in which this type is embedded. +func (b langID) String() string { + if b == 0 { + return "und" + } else if b >= langNoIndexOffset { + b -= langNoIndexOffset + buf := [3]byte{} + intToStr(uint(b), buf[:]) + return string(buf[:]) + } + l := lang.Elem(int(b)) + if l[3] == 0 { + return l[:3] + } + return l[:2] +} + +// ISO3 returns the ISO 639-3 language code. +func (b langID) ISO3() string { + if b == 0 || b >= langNoIndexOffset { + return b.String() + } + l := lang.Elem(int(b)) + if l[3] == 0 { + return l[:3] + } else if l[2] == 0 { + return altLangISO3.Elem(int(l[3]))[:3] + } + // This allocation will only happen for 3-letter ISO codes + // that are non-canonical BCP 47 language identifiers. + return l[0:1] + l[2:4] +} + +// IsPrivateUse reports whether this language code is reserved for private use. +func (b langID) IsPrivateUse() bool { + return langPrivateStart <= b && b <= langPrivateEnd +} + +type regionID uint16 + +// getRegionID returns the region id for s if s is a valid 2-letter region code +// or unknownRegion. +func getRegionID(s []byte) (regionID, error) { + if len(s) == 3 { + if isAlpha(s[0]) { + return getRegionISO3(s) + } + if i, err := strconv.ParseUint(string(s), 10, 10); err == nil { + return getRegionM49(int(i)) + } + } + return getRegionISO2(s) +} + +// getRegionISO2 returns the regionID for the given 2-letter ISO country code +// or unknownRegion if this does not exist. +func getRegionISO2(s []byte) (regionID, error) { + i, err := findIndex(regionISO, s, "ZZ") + if err != nil { + return 0, err + } + return regionID(i) + isoRegionOffset, nil +} + +// getRegionISO3 returns the regionID for the given 3-letter ISO country code +// or unknownRegion if this does not exist. +func getRegionISO3(s []byte) (regionID, error) { + if tag.FixCase("ZZZ", s) { + for i := regionISO.Index(s[:1]); i != -1; i = regionISO.Next(s[:1], i) { + if e := regionISO.Elem(i); e[2] == s[1] && e[3] == s[2] { + return regionID(i) + isoRegionOffset, nil + } + } + for i := 0; i < len(altRegionISO3); i += 3 { + if tag.Compare(altRegionISO3[i:i+3], s) == 0 { + return regionID(altRegionIDs[i/3]), nil + } + } + return 0, mkErrInvalid(s) + } + return 0, errSyntax +} + +func getRegionM49(n int) (regionID, error) { + if 0 < n && n <= 999 { + const ( + searchBits = 7 + regionBits = 9 + regionMask = 1<> searchBits + buf := fromM49[m49Index[idx]:m49Index[idx+1]] + val := uint16(n) << regionBits // we rely on bits shifting out + i := sort.Search(len(buf), func(i int) bool { + return buf[i] >= val + }) + if r := fromM49[int(m49Index[idx])+i]; r&^regionMask == val { + return regionID(r & regionMask), nil + } + } + var e ValueError + fmt.Fprint(bytes.NewBuffer([]byte(e.v[:])), n) + return 0, e +} + +// normRegion returns a region if r is deprecated or 0 otherwise. +// TODO: consider supporting BYS (-> BLR), CSK (-> 200 or CZ), PHI (-> PHL) and AFI (-> DJ). +// TODO: consider mapping split up regions to new most populous one (like CLDR). +func normRegion(r regionID) regionID { + m := regionOldMap + k := sort.Search(len(m), func(i int) bool { + return m[i].from >= uint16(r) + }) + if k < len(m) && m[k].from == uint16(r) { + return regionID(m[k].to) + } + return 0 +} + +const ( + iso3166UserAssigned = 1 << iota + ccTLD + bcp47Region +) + +func (r regionID) typ() byte { + return regionTypes[r] +} + +// String returns the BCP 47 representation for the region. +// It returns "ZZ" for an unspecified region. +func (r regionID) String() string { + if r < isoRegionOffset { + if r == 0 { + return "ZZ" + } + return fmt.Sprintf("%03d", r.M49()) + } + r -= isoRegionOffset + return regionISO.Elem(int(r))[:2] +} + +// ISO3 returns the 3-letter ISO code of r. +// Note that not all regions have a 3-letter ISO code. +// In such cases this method returns "ZZZ". +func (r regionID) ISO3() string { + if r < isoRegionOffset { + return "ZZZ" + } + r -= isoRegionOffset + reg := regionISO.Elem(int(r)) + switch reg[2] { + case 0: + return altRegionISO3[reg[3]:][:3] + case ' ': + return "ZZZ" + } + return reg[0:1] + reg[2:4] +} + +// M49 returns the UN M.49 encoding of r, or 0 if this encoding +// is not defined for r. +func (r regionID) M49() int { + return int(m49[r]) +} + +// IsPrivateUse reports whether r has the ISO 3166 User-assigned status. This +// may include private-use tags that are assigned by CLDR and used in this +// implementation. So IsPrivateUse and IsCountry can be simultaneously true. +func (r regionID) IsPrivateUse() bool { + return r.typ()&iso3166UserAssigned != 0 +} + +type scriptID uint8 + +// getScriptID returns the script id for string s. It assumes that s +// is of the format [A-Z][a-z]{3}. +func getScriptID(idx tag.Index, s []byte) (scriptID, error) { + i, err := findIndex(idx, s, "Zzzz") + return scriptID(i), err +} + +// String returns the script code in title case. +// It returns "Zzzz" for an unspecified script. +func (s scriptID) String() string { + if s == 0 { + return "Zzzz" + } + return script.Elem(int(s)) +} + +// IsPrivateUse reports whether this script code is reserved for private use. +func (s scriptID) IsPrivateUse() bool { + return _Qaaa <= s && s <= _Qabx +} + +const ( + maxAltTaglen = len("en-US-POSIX") + maxLen = maxAltTaglen +) + +var ( + // grandfatheredMap holds a mapping from legacy and grandfathered tags to + // their base language or index to more elaborate tag. + grandfatheredMap = map[[maxLen]byte]int16{ + [maxLen]byte{'a', 'r', 't', '-', 'l', 'o', 'j', 'b', 'a', 'n'}: _jbo, // art-lojban + [maxLen]byte{'i', '-', 'a', 'm', 'i'}: _ami, // i-ami + [maxLen]byte{'i', '-', 'b', 'n', 'n'}: _bnn, // i-bnn + [maxLen]byte{'i', '-', 'h', 'a', 'k'}: _hak, // i-hak + [maxLen]byte{'i', '-', 'k', 'l', 'i', 'n', 'g', 'o', 'n'}: _tlh, // i-klingon + [maxLen]byte{'i', '-', 'l', 'u', 'x'}: _lb, // i-lux + [maxLen]byte{'i', '-', 'n', 'a', 'v', 'a', 'j', 'o'}: _nv, // i-navajo + [maxLen]byte{'i', '-', 'p', 'w', 'n'}: _pwn, // i-pwn + [maxLen]byte{'i', '-', 't', 'a', 'o'}: _tao, // i-tao + [maxLen]byte{'i', '-', 't', 'a', 'y'}: _tay, // i-tay + [maxLen]byte{'i', '-', 't', 's', 'u'}: _tsu, // i-tsu + [maxLen]byte{'n', 'o', '-', 'b', 'o', 'k'}: _nb, // no-bok + [maxLen]byte{'n', 'o', '-', 'n', 'y', 'n'}: _nn, // no-nyn + [maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'f', 'r'}: _sfb, // sgn-BE-FR + [maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'n', 'l'}: _vgt, // sgn-BE-NL + [maxLen]byte{'s', 'g', 'n', '-', 'c', 'h', '-', 'd', 'e'}: _sgg, // sgn-CH-DE + [maxLen]byte{'z', 'h', '-', 'g', 'u', 'o', 'y', 'u'}: _cmn, // zh-guoyu + [maxLen]byte{'z', 'h', '-', 'h', 'a', 'k', 'k', 'a'}: _hak, // zh-hakka + [maxLen]byte{'z', 'h', '-', 'm', 'i', 'n', '-', 'n', 'a', 'n'}: _nan, // zh-min-nan + [maxLen]byte{'z', 'h', '-', 'x', 'i', 'a', 'n', 'g'}: _hsn, // zh-xiang + + // Grandfathered tags with no modern replacement will be converted as + // follows: + [maxLen]byte{'c', 'e', 'l', '-', 'g', 'a', 'u', 'l', 'i', 's', 'h'}: -1, // cel-gaulish + [maxLen]byte{'e', 'n', '-', 'g', 'b', '-', 'o', 'e', 'd'}: -2, // en-GB-oed + [maxLen]byte{'i', '-', 'd', 'e', 'f', 'a', 'u', 'l', 't'}: -3, // i-default + [maxLen]byte{'i', '-', 'e', 'n', 'o', 'c', 'h', 'i', 'a', 'n'}: -4, // i-enochian + [maxLen]byte{'i', '-', 'm', 'i', 'n', 'g', 'o'}: -5, // i-mingo + [maxLen]byte{'z', 'h', '-', 'm', 'i', 'n'}: -6, // zh-min + + // CLDR-specific tag. + [maxLen]byte{'r', 'o', 'o', 't'}: 0, // root + [maxLen]byte{'e', 'n', '-', 'u', 's', '-', 'p', 'o', 's', 'i', 'x'}: -7, // en_US_POSIX" + } + + altTagIndex = [...]uint8{0, 17, 31, 45, 61, 74, 86, 102} + + altTags = "xtg-x-cel-gaulishen-GB-oxendicten-x-i-defaultund-x-i-enochiansee-x-i-mingonan-x-zh-minen-US-u-va-posix" +) + +func grandfathered(s [maxAltTaglen]byte) (t Tag, ok bool) { + if v, ok := grandfatheredMap[s]; ok { + if v < 0 { + return Make(altTags[altTagIndex[-v-1]:altTagIndex[-v]]), true + } + t.lang = langID(v) + return t, true + } + return t, false +} diff --git a/vendor/golang.org/x/text/language/match.go b/vendor/golang.org/x/text/language/match.go new file mode 100644 index 000000000..15b74d125 --- /dev/null +++ b/vendor/golang.org/x/text/language/match.go @@ -0,0 +1,933 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package language + +import "errors" + +// A MatchOption configures a Matcher. +type MatchOption func(*matcher) + +// PreferSameScript will, in the absence of a match, result in the first +// preferred tag with the same script as a supported tag to match this supported +// tag. The default is currently true, but this may change in the future. +func PreferSameScript(preferSame bool) MatchOption { + return func(m *matcher) { m.preferSameScript = preferSame } +} + +// TODO(v1.0.0): consider making Matcher a concrete type, instead of interface. +// There doesn't seem to be too much need for multiple types. +// Making it a concrete type allows MatchStrings to be a method, which will +// improve its discoverability. + +// MatchStrings parses and matches the given strings until one of them matches +// the language in the Matcher. A string may be an Accept-Language header as +// handled by ParseAcceptLanguage. The default language is returned if no +// other language matched. +func MatchStrings(m Matcher, lang ...string) (tag Tag, index int) { + for _, accept := range lang { + desired, _, err := ParseAcceptLanguage(accept) + if err != nil { + continue + } + if tag, index, conf := m.Match(desired...); conf != No { + return tag, index + } + } + tag, index, _ = m.Match() + return +} + +// Matcher is the interface that wraps the Match method. +// +// Match returns the best match for any of the given tags, along with +// a unique index associated with the returned tag and a confidence +// score. +type Matcher interface { + Match(t ...Tag) (tag Tag, index int, c Confidence) +} + +// Comprehends reports the confidence score for a speaker of a given language +// to being able to comprehend the written form of an alternative language. +func Comprehends(speaker, alternative Tag) Confidence { + _, _, c := NewMatcher([]Tag{alternative}).Match(speaker) + return c +} + +// NewMatcher returns a Matcher that matches an ordered list of preferred tags +// against a list of supported tags based on written intelligibility, closeness +// of dialect, equivalence of subtags and various other rules. It is initialized +// with the list of supported tags. The first element is used as the default +// value in case no match is found. +// +// Its Match method matches the first of the given Tags to reach a certain +// confidence threshold. The tags passed to Match should therefore be specified +// in order of preference. Extensions are ignored for matching. +// +// The index returned by the Match method corresponds to the index of the +// matched tag in t, but is augmented with the Unicode extension ('u')of the +// corresponding preferred tag. This allows user locale options to be passed +// transparently. +func NewMatcher(t []Tag, options ...MatchOption) Matcher { + return newMatcher(t, options) +} + +func (m *matcher) Match(want ...Tag) (t Tag, index int, c Confidence) { + match, w, c := m.getBest(want...) + if match != nil { + t, index = match.tag, match.index + } else { + // TODO: this should be an option + t = m.default_.tag + if m.preferSameScript { + outer: + for _, w := range want { + script, _ := w.Script() + if script.scriptID == 0 { + // Don't do anything if there is no script, such as with + // private subtags. + continue + } + for i, h := range m.supported { + if script.scriptID == h.maxScript { + t, index = h.tag, i + break outer + } + } + } + } + // TODO: select first language tag based on script. + } + if w.region != 0 && t.region != 0 && t.region.contains(w.region) { + t, _ = Raw.Compose(t, Region{w.region}) + } + // Copy options from the user-provided tag into the result tag. This is hard + // to do after the fact, so we do it here. + // TODO: add in alternative variants to -u-va-. + // TODO: add preferred region to -u-rg-. + if e := w.Extensions(); len(e) > 0 { + t, _ = Raw.Compose(t, e) + } + return t, index, c +} + +type scriptRegionFlags uint8 + +const ( + isList = 1 << iota + scriptInFrom + regionInFrom +) + +func (t *Tag) setUndefinedLang(id langID) { + if t.lang == 0 { + t.lang = id + } +} + +func (t *Tag) setUndefinedScript(id scriptID) { + if t.script == 0 { + t.script = id + } +} + +func (t *Tag) setUndefinedRegion(id regionID) { + if t.region == 0 || t.region.contains(id) { + t.region = id + } +} + +// ErrMissingLikelyTagsData indicates no information was available +// to compute likely values of missing tags. +var ErrMissingLikelyTagsData = errors.New("missing likely tags data") + +// addLikelySubtags sets subtags to their most likely value, given the locale. +// In most cases this means setting fields for unknown values, but in some +// cases it may alter a value. It returns an ErrMissingLikelyTagsData error +// if the given locale cannot be expanded. +func (t Tag) addLikelySubtags() (Tag, error) { + id, err := addTags(t) + if err != nil { + return t, err + } else if id.equalTags(t) { + return t, nil + } + id.remakeString() + return id, nil +} + +// specializeRegion attempts to specialize a group region. +func specializeRegion(t *Tag) bool { + if i := regionInclusion[t.region]; i < nRegionGroups { + x := likelyRegionGroup[i] + if langID(x.lang) == t.lang && scriptID(x.script) == t.script { + t.region = regionID(x.region) + } + return true + } + return false +} + +func addTags(t Tag) (Tag, error) { + // We leave private use identifiers alone. + if t.private() { + return t, nil + } + if t.script != 0 && t.region != 0 { + if t.lang != 0 { + // already fully specified + specializeRegion(&t) + return t, nil + } + // Search matches for und-script-region. Note that for these cases + // region will never be a group so there is no need to check for this. + list := likelyRegion[t.region : t.region+1] + if x := list[0]; x.flags&isList != 0 { + list = likelyRegionList[x.lang : x.lang+uint16(x.script)] + } + for _, x := range list { + // Deviating from the spec. See match_test.go for details. + if scriptID(x.script) == t.script { + t.setUndefinedLang(langID(x.lang)) + return t, nil + } + } + } + if t.lang != 0 { + // Search matches for lang-script and lang-region, where lang != und. + if t.lang < langNoIndexOffset { + x := likelyLang[t.lang] + if x.flags&isList != 0 { + list := likelyLangList[x.region : x.region+uint16(x.script)] + if t.script != 0 { + for _, x := range list { + if scriptID(x.script) == t.script && x.flags&scriptInFrom != 0 { + t.setUndefinedRegion(regionID(x.region)) + return t, nil + } + } + } else if t.region != 0 { + count := 0 + goodScript := true + tt := t + for _, x := range list { + // We visit all entries for which the script was not + // defined, including the ones where the region was not + // defined. This allows for proper disambiguation within + // regions. + if x.flags&scriptInFrom == 0 && t.region.contains(regionID(x.region)) { + tt.region = regionID(x.region) + tt.setUndefinedScript(scriptID(x.script)) + goodScript = goodScript && tt.script == scriptID(x.script) + count++ + } + } + if count == 1 { + return tt, nil + } + // Even if we fail to find a unique Region, we might have + // an unambiguous script. + if goodScript { + t.script = tt.script + } + } + } + } + } else { + // Search matches for und-script. + if t.script != 0 { + x := likelyScript[t.script] + if x.region != 0 { + t.setUndefinedRegion(regionID(x.region)) + t.setUndefinedLang(langID(x.lang)) + return t, nil + } + } + // Search matches for und-region. If und-script-region exists, it would + // have been found earlier. + if t.region != 0 { + if i := regionInclusion[t.region]; i < nRegionGroups { + x := likelyRegionGroup[i] + if x.region != 0 { + t.setUndefinedLang(langID(x.lang)) + t.setUndefinedScript(scriptID(x.script)) + t.region = regionID(x.region) + } + } else { + x := likelyRegion[t.region] + if x.flags&isList != 0 { + x = likelyRegionList[x.lang] + } + if x.script != 0 && x.flags != scriptInFrom { + t.setUndefinedLang(langID(x.lang)) + t.setUndefinedScript(scriptID(x.script)) + return t, nil + } + } + } + } + + // Search matches for lang. + if t.lang < langNoIndexOffset { + x := likelyLang[t.lang] + if x.flags&isList != 0 { + x = likelyLangList[x.region] + } + if x.region != 0 { + t.setUndefinedScript(scriptID(x.script)) + t.setUndefinedRegion(regionID(x.region)) + } + specializeRegion(&t) + if t.lang == 0 { + t.lang = _en // default language + } + return t, nil + } + return t, ErrMissingLikelyTagsData +} + +func (t *Tag) setTagsFrom(id Tag) { + t.lang = id.lang + t.script = id.script + t.region = id.region +} + +// minimize removes the region or script subtags from t such that +// t.addLikelySubtags() == t.minimize().addLikelySubtags(). +func (t Tag) minimize() (Tag, error) { + t, err := minimizeTags(t) + if err != nil { + return t, err + } + t.remakeString() + return t, nil +} + +// minimizeTags mimics the behavior of the ICU 51 C implementation. +func minimizeTags(t Tag) (Tag, error) { + if t.equalTags(und) { + return t, nil + } + max, err := addTags(t) + if err != nil { + return t, err + } + for _, id := range [...]Tag{ + {lang: t.lang}, + {lang: t.lang, region: t.region}, + {lang: t.lang, script: t.script}, + } { + if x, err := addTags(id); err == nil && max.equalTags(x) { + t.setTagsFrom(id) + break + } + } + return t, nil +} + +// Tag Matching +// CLDR defines an algorithm for finding the best match between two sets of language +// tags. The basic algorithm defines how to score a possible match and then find +// the match with the best score +// (see http://www.unicode.org/reports/tr35/#LanguageMatching). +// Using scoring has several disadvantages. The scoring obfuscates the importance of +// the various factors considered, making the algorithm harder to understand. Using +// scoring also requires the full score to be computed for each pair of tags. +// +// We will use a different algorithm which aims to have the following properties: +// - clarity on the precedence of the various selection factors, and +// - improved performance by allowing early termination of a comparison. +// +// Matching algorithm (overview) +// Input: +// - supported: a set of supported tags +// - default: the default tag to return in case there is no match +// - desired: list of desired tags, ordered by preference, starting with +// the most-preferred. +// +// Algorithm: +// 1) Set the best match to the lowest confidence level +// 2) For each tag in "desired": +// a) For each tag in "supported": +// 1) compute the match between the two tags. +// 2) if the match is better than the previous best match, replace it +// with the new match. (see next section) +// b) if the current best match is Exact and pin is true the result will be +// frozen to the language found thusfar, although better matches may +// still be found for the same language. +// 3) If the best match so far is below a certain threshold, return "default". +// +// Ranking: +// We use two phases to determine whether one pair of tags are a better match +// than another pair of tags. First, we determine a rough confidence level. If the +// levels are different, the one with the highest confidence wins. +// Second, if the rough confidence levels are identical, we use a set of tie-breaker +// rules. +// +// The confidence level of matching a pair of tags is determined by finding the +// lowest confidence level of any matches of the corresponding subtags (the +// result is deemed as good as its weakest link). +// We define the following levels: +// Exact - An exact match of a subtag, before adding likely subtags. +// MaxExact - An exact match of a subtag, after adding likely subtags. +// [See Note 2]. +// High - High level of mutual intelligibility between different subtag +// variants. +// Low - Low level of mutual intelligibility between different subtag +// variants. +// No - No mutual intelligibility. +// +// The following levels can occur for each type of subtag: +// Base: Exact, MaxExact, High, Low, No +// Script: Exact, MaxExact [see Note 3], Low, No +// Region: Exact, MaxExact, High +// Variant: Exact, High +// Private: Exact, No +// +// Any result with a confidence level of Low or higher is deemed a possible match. +// Once a desired tag matches any of the supported tags with a level of MaxExact +// or higher, the next desired tag is not considered (see Step 2.b). +// Note that CLDR provides languageMatching data that defines close equivalence +// classes for base languages, scripts and regions. +// +// Tie-breaking +// If we get the same confidence level for two matches, we apply a sequence of +// tie-breaking rules. The first that succeeds defines the result. The rules are +// applied in the following order. +// 1) Original language was defined and was identical. +// 2) Original region was defined and was identical. +// 3) Distance between two maximized regions was the smallest. +// 4) Original script was defined and was identical. +// 5) Distance from want tag to have tag using the parent relation [see Note 5.] +// If there is still no winner after these rules are applied, the first match +// found wins. +// +// Notes: +// [2] In practice, as matching of Exact is done in a separate phase from +// matching the other levels, we reuse the Exact level to mean MaxExact in +// the second phase. As a consequence, we only need the levels defined by +// the Confidence type. The MaxExact confidence level is mapped to High in +// the public API. +// [3] We do not differentiate between maximized script values that were derived +// from suppressScript versus most likely tag data. We determined that in +// ranking the two, one ranks just after the other. Moreover, the two cannot +// occur concurrently. As a consequence, they are identical for practical +// purposes. +// [4] In case of deprecated, macro-equivalents and legacy mappings, we assign +// the MaxExact level to allow iw vs he to still be a closer match than +// en-AU vs en-US, for example. +// [5] In CLDR a locale inherits fields that are unspecified for this locale +// from its parent. Therefore, if a locale is a parent of another locale, +// it is a strong measure for closeness, especially when no other tie +// breaker rule applies. One could also argue it is inconsistent, for +// example, when pt-AO matches pt (which CLDR equates with pt-BR), even +// though its parent is pt-PT according to the inheritance rules. +// +// Implementation Details: +// There are several performance considerations worth pointing out. Most notably, +// we preprocess as much as possible (within reason) at the time of creation of a +// matcher. This includes: +// - creating a per-language map, which includes data for the raw base language +// and its canonicalized variant (if applicable), +// - expanding entries for the equivalence classes defined in CLDR's +// languageMatch data. +// The per-language map ensures that typically only a very small number of tags +// need to be considered. The pre-expansion of canonicalized subtags and +// equivalence classes reduces the amount of map lookups that need to be done at +// runtime. + +// matcher keeps a set of supported language tags, indexed by language. +type matcher struct { + default_ *haveTag + supported []*haveTag + index map[langID]*matchHeader + passSettings bool + preferSameScript bool +} + +// matchHeader has the lists of tags for exact matches and matches based on +// maximized and canonicalized tags for a given language. +type matchHeader struct { + haveTags []*haveTag + original bool +} + +// haveTag holds a supported Tag and its maximized script and region. The maximized +// or canonicalized language is not stored as it is not needed during matching. +type haveTag struct { + tag Tag + + // index of this tag in the original list of supported tags. + index int + + // conf is the maximum confidence that can result from matching this haveTag. + // When conf < Exact this means it was inserted after applying a CLDR equivalence rule. + conf Confidence + + // Maximized region and script. + maxRegion regionID + maxScript scriptID + + // altScript may be checked as an alternative match to maxScript. If altScript + // matches, the confidence level for this match is Low. Theoretically there + // could be multiple alternative scripts. This does not occur in practice. + altScript scriptID + + // nextMax is the index of the next haveTag with the same maximized tags. + nextMax uint16 +} + +func makeHaveTag(tag Tag, index int) (haveTag, langID) { + max := tag + if tag.lang != 0 || tag.region != 0 || tag.script != 0 { + max, _ = max.canonicalize(All) + max, _ = addTags(max) + max.remakeString() + } + return haveTag{tag, index, Exact, max.region, max.script, altScript(max.lang, max.script), 0}, max.lang +} + +// altScript returns an alternative script that may match the given script with +// a low confidence. At the moment, the langMatch data allows for at most one +// script to map to another and we rely on this to keep the code simple. +func altScript(l langID, s scriptID) scriptID { + for _, alt := range matchScript { + // TODO: also match cases where language is not the same. + if (langID(alt.wantLang) == l || langID(alt.haveLang) == l) && + scriptID(alt.haveScript) == s { + return scriptID(alt.wantScript) + } + } + return 0 +} + +// addIfNew adds a haveTag to the list of tags only if it is a unique tag. +// Tags that have the same maximized values are linked by index. +func (h *matchHeader) addIfNew(n haveTag, exact bool) { + h.original = h.original || exact + // Don't add new exact matches. + for _, v := range h.haveTags { + if v.tag.equalsRest(n.tag) { + return + } + } + // Allow duplicate maximized tags, but create a linked list to allow quickly + // comparing the equivalents and bail out. + for i, v := range h.haveTags { + if v.maxScript == n.maxScript && + v.maxRegion == n.maxRegion && + v.tag.variantOrPrivateTagStr() == n.tag.variantOrPrivateTagStr() { + for h.haveTags[i].nextMax != 0 { + i = int(h.haveTags[i].nextMax) + } + h.haveTags[i].nextMax = uint16(len(h.haveTags)) + break + } + } + h.haveTags = append(h.haveTags, &n) +} + +// header returns the matchHeader for the given language. It creates one if +// it doesn't already exist. +func (m *matcher) header(l langID) *matchHeader { + if h := m.index[l]; h != nil { + return h + } + h := &matchHeader{} + m.index[l] = h + return h +} + +func toConf(d uint8) Confidence { + if d <= 10 { + return High + } + if d < 30 { + return Low + } + return No +} + +// newMatcher builds an index for the given supported tags and returns it as +// a matcher. It also expands the index by considering various equivalence classes +// for a given tag. +func newMatcher(supported []Tag, options []MatchOption) *matcher { + m := &matcher{ + index: make(map[langID]*matchHeader), + preferSameScript: true, + } + for _, o := range options { + o(m) + } + if len(supported) == 0 { + m.default_ = &haveTag{} + return m + } + // Add supported languages to the index. Add exact matches first to give + // them precedence. + for i, tag := range supported { + pair, _ := makeHaveTag(tag, i) + m.header(tag.lang).addIfNew(pair, true) + m.supported = append(m.supported, &pair) + } + m.default_ = m.header(supported[0].lang).haveTags[0] + // Keep these in two different loops to support the case that two equivalent + // languages are distinguished, such as iw and he. + for i, tag := range supported { + pair, max := makeHaveTag(tag, i) + if max != tag.lang { + m.header(max).addIfNew(pair, true) + } + } + + // update is used to add indexes in the map for equivalent languages. + // update will only add entries to original indexes, thus not computing any + // transitive relations. + update := func(want, have uint16, conf Confidence) { + if hh := m.index[langID(have)]; hh != nil { + if !hh.original { + return + } + hw := m.header(langID(want)) + for _, ht := range hh.haveTags { + v := *ht + if conf < v.conf { + v.conf = conf + } + v.nextMax = 0 // this value needs to be recomputed + if v.altScript != 0 { + v.altScript = altScript(langID(want), v.maxScript) + } + hw.addIfNew(v, conf == Exact && hh.original) + } + } + } + + // Add entries for languages with mutual intelligibility as defined by CLDR's + // languageMatch data. + for _, ml := range matchLang { + update(ml.want, ml.have, toConf(ml.distance)) + if !ml.oneway { + update(ml.have, ml.want, toConf(ml.distance)) + } + } + + // Add entries for possible canonicalizations. This is an optimization to + // ensure that only one map lookup needs to be done at runtime per desired tag. + // First we match deprecated equivalents. If they are perfect equivalents + // (their canonicalization simply substitutes a different language code, but + // nothing else), the match confidence is Exact, otherwise it is High. + for i, lm := range langAliasMap { + // If deprecated codes match and there is no fiddling with the script or + // or region, we consider it an exact match. + conf := Exact + if langAliasTypes[i] != langMacro { + if !isExactEquivalent(langID(lm.from)) { + conf = High + } + update(lm.to, lm.from, conf) + } + update(lm.from, lm.to, conf) + } + return m +} + +// getBest gets the best matching tag in m for any of the given tags, taking into +// account the order of preference of the given tags. +func (m *matcher) getBest(want ...Tag) (got *haveTag, orig Tag, c Confidence) { + best := bestMatch{} + for i, w := range want { + var max Tag + // Check for exact match first. + h := m.index[w.lang] + if w.lang != 0 { + if h == nil { + continue + } + // Base language is defined. + max, _ = w.canonicalize(Legacy | Deprecated | Macro) + // A region that is added through canonicalization is stronger than + // a maximized region: set it in the original (e.g. mo -> ro-MD). + if w.region != max.region { + w.region = max.region + } + // TODO: should we do the same for scripts? + // See test case: en, sr, nl ; sh ; sr + max, _ = addTags(max) + } else { + // Base language is not defined. + if h != nil { + for i := range h.haveTags { + have := h.haveTags[i] + if have.tag.equalsRest(w) { + return have, w, Exact + } + } + } + if w.script == 0 && w.region == 0 { + // We skip all tags matching und for approximate matching, including + // private tags. + continue + } + max, _ = addTags(w) + if h = m.index[max.lang]; h == nil { + continue + } + } + pin := true + for _, t := range want[i+1:] { + if w.lang == t.lang { + pin = false + break + } + } + // Check for match based on maximized tag. + for i := range h.haveTags { + have := h.haveTags[i] + best.update(have, w, max.script, max.region, pin) + if best.conf == Exact { + for have.nextMax != 0 { + have = h.haveTags[have.nextMax] + best.update(have, w, max.script, max.region, pin) + } + return best.have, best.want, best.conf + } + } + } + if best.conf <= No { + if len(want) != 0 { + return nil, want[0], No + } + return nil, Tag{}, No + } + return best.have, best.want, best.conf +} + +// bestMatch accumulates the best match so far. +type bestMatch struct { + have *haveTag + want Tag + conf Confidence + pinnedRegion regionID + pinLanguage bool + sameRegionGroup bool + // Cached results from applying tie-breaking rules. + origLang bool + origReg bool + paradigmReg bool + regGroupDist uint8 + origScript bool +} + +// update updates the existing best match if the new pair is considered to be a +// better match. To determine if the given pair is a better match, it first +// computes the rough confidence level. If this surpasses the current match, it +// will replace it and update the tie-breaker rule cache. If there is a tie, it +// proceeds with applying a series of tie-breaker rules. If there is no +// conclusive winner after applying the tie-breaker rules, it leaves the current +// match as the preferred match. +// +// If pin is true and have and tag are a strong match, it will henceforth only +// consider matches for this language. This corresponds to the nothing that most +// users have a strong preference for the first defined language. A user can +// still prefer a second language over a dialect of the preferred language by +// explicitly specifying dialects, e.g. "en, nl, en-GB". In this case pin should +// be false. +func (m *bestMatch) update(have *haveTag, tag Tag, maxScript scriptID, maxRegion regionID, pin bool) { + // Bail if the maximum attainable confidence is below that of the current best match. + c := have.conf + if c < m.conf { + return + } + // Don't change the language once we already have found an exact match. + if m.pinLanguage && tag.lang != m.want.lang { + return + } + // Pin the region group if we are comparing tags for the same language. + if tag.lang == m.want.lang && m.sameRegionGroup { + _, sameGroup := regionGroupDist(m.pinnedRegion, have.maxRegion, have.maxScript, m.want.lang) + if !sameGroup { + return + } + } + if c == Exact && have.maxScript == maxScript { + // If there is another language and then another entry of this language, + // don't pin anything, otherwise pin the language. + m.pinLanguage = pin + } + if have.tag.equalsRest(tag) { + } else if have.maxScript != maxScript { + // There is usually very little comprehension between different scripts. + // In a few cases there may still be Low comprehension. This possibility + // is pre-computed and stored in have.altScript. + if Low < m.conf || have.altScript != maxScript { + return + } + c = Low + } else if have.maxRegion != maxRegion { + if High < c { + // There is usually a small difference between languages across regions. + c = High + } + } + + // We store the results of the computations of the tie-breaker rules along + // with the best match. There is no need to do the checks once we determine + // we have a winner, but we do still need to do the tie-breaker computations. + // We use "beaten" to keep track if we still need to do the checks. + beaten := false // true if the new pair defeats the current one. + if c != m.conf { + if c < m.conf { + return + } + beaten = true + } + + // Tie-breaker rules: + // We prefer if the pre-maximized language was specified and identical. + origLang := have.tag.lang == tag.lang && tag.lang != 0 + if !beaten && m.origLang != origLang { + if m.origLang { + return + } + beaten = true + } + + // We prefer if the pre-maximized region was specified and identical. + origReg := have.tag.region == tag.region && tag.region != 0 + if !beaten && m.origReg != origReg { + if m.origReg { + return + } + beaten = true + } + + regGroupDist, sameGroup := regionGroupDist(have.maxRegion, maxRegion, maxScript, tag.lang) + if !beaten && m.regGroupDist != regGroupDist { + if regGroupDist > m.regGroupDist { + return + } + beaten = true + } + + paradigmReg := isParadigmLocale(tag.lang, have.maxRegion) + if !beaten && m.paradigmReg != paradigmReg { + if !paradigmReg { + return + } + beaten = true + } + + // Next we prefer if the pre-maximized script was specified and identical. + origScript := have.tag.script == tag.script && tag.script != 0 + if !beaten && m.origScript != origScript { + if m.origScript { + return + } + beaten = true + } + + // Update m to the newly found best match. + if beaten { + m.have = have + m.want = tag + m.conf = c + m.pinnedRegion = maxRegion + m.sameRegionGroup = sameGroup + m.origLang = origLang + m.origReg = origReg + m.paradigmReg = paradigmReg + m.origScript = origScript + m.regGroupDist = regGroupDist + } +} + +func isParadigmLocale(lang langID, r regionID) bool { + for _, e := range paradigmLocales { + if langID(e[0]) == lang && (r == regionID(e[1]) || r == regionID(e[2])) { + return true + } + } + return false +} + +// regionGroupDist computes the distance between two regions based on their +// CLDR grouping. +func regionGroupDist(a, b regionID, script scriptID, lang langID) (dist uint8, same bool) { + const defaultDistance = 4 + + aGroup := uint(regionToGroups[a]) << 1 + bGroup := uint(regionToGroups[b]) << 1 + for _, ri := range matchRegion { + if langID(ri.lang) == lang && (ri.script == 0 || scriptID(ri.script) == script) { + group := uint(1 << (ri.group &^ 0x80)) + if 0x80&ri.group == 0 { + if aGroup&bGroup&group != 0 { // Both regions are in the group. + return ri.distance, ri.distance == defaultDistance + } + } else { + if (aGroup|bGroup)&group == 0 { // Both regions are not in the group. + return ri.distance, ri.distance == defaultDistance + } + } + } + } + return defaultDistance, true +} + +func (t Tag) variants() string { + if t.pVariant == 0 { + return "" + } + return t.str[t.pVariant:t.pExt] +} + +// variantOrPrivateTagStr returns variants or private use tags. +func (t Tag) variantOrPrivateTagStr() string { + if t.pExt > 0 { + return t.str[t.pVariant:t.pExt] + } + return t.str[t.pVariant:] +} + +// equalsRest compares everything except the language. +func (a Tag) equalsRest(b Tag) bool { + // TODO: don't include extensions in this comparison. To do this efficiently, + // though, we should handle private tags separately. + return a.script == b.script && a.region == b.region && a.variantOrPrivateTagStr() == b.variantOrPrivateTagStr() +} + +// isExactEquivalent returns true if canonicalizing the language will not alter +// the script or region of a tag. +func isExactEquivalent(l langID) bool { + for _, o := range notEquivalent { + if o == l { + return false + } + } + return true +} + +var notEquivalent []langID + +func init() { + // Create a list of all languages for which canonicalization may alter the + // script or region. + for _, lm := range langAliasMap { + tag := Tag{lang: langID(lm.from)} + if tag, _ = tag.canonicalize(All); tag.script != 0 || tag.region != 0 { + notEquivalent = append(notEquivalent, langID(lm.from)) + } + } + // Maximize undefined regions of paradigm locales. + for i, v := range paradigmLocales { + max, _ := addTags(Tag{lang: langID(v[0])}) + if v[1] == 0 { + paradigmLocales[i][1] = uint16(max.region) + } + if v[2] == 0 { + paradigmLocales[i][2] = uint16(max.region) + } + } +} diff --git a/vendor/golang.org/x/text/language/parse.go b/vendor/golang.org/x/text/language/parse.go new file mode 100644 index 000000000..fca2d30e5 --- /dev/null +++ b/vendor/golang.org/x/text/language/parse.go @@ -0,0 +1,859 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package language + +import ( + "bytes" + "errors" + "fmt" + "sort" + "strconv" + "strings" + + "golang.org/x/text/internal/tag" +) + +// isAlpha returns true if the byte is not a digit. +// b must be an ASCII letter or digit. +func isAlpha(b byte) bool { + return b > '9' +} + +// isAlphaNum returns true if the string contains only ASCII letters or digits. +func isAlphaNum(s []byte) bool { + for _, c := range s { + if !('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9') { + return false + } + } + return true +} + +// errSyntax is returned by any of the parsing functions when the +// input is not well-formed, according to BCP 47. +// TODO: return the position at which the syntax error occurred? +var errSyntax = errors.New("language: tag is not well-formed") + +// ValueError is returned by any of the parsing functions when the +// input is well-formed but the respective subtag is not recognized +// as a valid value. +type ValueError struct { + v [8]byte +} + +func mkErrInvalid(s []byte) error { + var e ValueError + copy(e.v[:], s) + return e +} + +func (e ValueError) tag() []byte { + n := bytes.IndexByte(e.v[:], 0) + if n == -1 { + n = 8 + } + return e.v[:n] +} + +// Error implements the error interface. +func (e ValueError) Error() string { + return fmt.Sprintf("language: subtag %q is well-formed but unknown", e.tag()) +} + +// Subtag returns the subtag for which the error occurred. +func (e ValueError) Subtag() string { + return string(e.tag()) +} + +// scanner is used to scan BCP 47 tokens, which are separated by _ or -. +type scanner struct { + b []byte + bytes [max99thPercentileSize]byte + token []byte + start int // start position of the current token + end int // end position of the current token + next int // next point for scan + err error + done bool +} + +func makeScannerString(s string) scanner { + scan := scanner{} + if len(s) <= len(scan.bytes) { + scan.b = scan.bytes[:copy(scan.bytes[:], s)] + } else { + scan.b = []byte(s) + } + scan.init() + return scan +} + +// makeScanner returns a scanner using b as the input buffer. +// b is not copied and may be modified by the scanner routines. +func makeScanner(b []byte) scanner { + scan := scanner{b: b} + scan.init() + return scan +} + +func (s *scanner) init() { + for i, c := range s.b { + if c == '_' { + s.b[i] = '-' + } + } + s.scan() +} + +// restToLower converts the string between start and end to lower case. +func (s *scanner) toLower(start, end int) { + for i := start; i < end; i++ { + c := s.b[i] + if 'A' <= c && c <= 'Z' { + s.b[i] += 'a' - 'A' + } + } +} + +func (s *scanner) setError(e error) { + if s.err == nil || (e == errSyntax && s.err != errSyntax) { + s.err = e + } +} + +// resizeRange shrinks or grows the array at position oldStart such that +// a new string of size newSize can fit between oldStart and oldEnd. +// Sets the scan point to after the resized range. +func (s *scanner) resizeRange(oldStart, oldEnd, newSize int) { + s.start = oldStart + if end := oldStart + newSize; end != oldEnd { + diff := end - oldEnd + if end < cap(s.b) { + b := make([]byte, len(s.b)+diff) + copy(b, s.b[:oldStart]) + copy(b[end:], s.b[oldEnd:]) + s.b = b + } else { + s.b = append(s.b[end:], s.b[oldEnd:]...) + } + s.next = end + (s.next - s.end) + s.end = end + } +} + +// replace replaces the current token with repl. +func (s *scanner) replace(repl string) { + s.resizeRange(s.start, s.end, len(repl)) + copy(s.b[s.start:], repl) +} + +// gobble removes the current token from the input. +// Caller must call scan after calling gobble. +func (s *scanner) gobble(e error) { + s.setError(e) + if s.start == 0 { + s.b = s.b[:+copy(s.b, s.b[s.next:])] + s.end = 0 + } else { + s.b = s.b[:s.start-1+copy(s.b[s.start-1:], s.b[s.end:])] + s.end = s.start - 1 + } + s.next = s.start +} + +// deleteRange removes the given range from s.b before the current token. +func (s *scanner) deleteRange(start, end int) { + s.setError(errSyntax) + s.b = s.b[:start+copy(s.b[start:], s.b[end:])] + diff := end - start + s.next -= diff + s.start -= diff + s.end -= diff +} + +// scan parses the next token of a BCP 47 string. Tokens that are larger +// than 8 characters or include non-alphanumeric characters result in an error +// and are gobbled and removed from the output. +// It returns the end position of the last token consumed. +func (s *scanner) scan() (end int) { + end = s.end + s.token = nil + for s.start = s.next; s.next < len(s.b); { + i := bytes.IndexByte(s.b[s.next:], '-') + if i == -1 { + s.end = len(s.b) + s.next = len(s.b) + i = s.end - s.start + } else { + s.end = s.next + i + s.next = s.end + 1 + } + token := s.b[s.start:s.end] + if i < 1 || i > 8 || !isAlphaNum(token) { + s.gobble(errSyntax) + continue + } + s.token = token + return end + } + if n := len(s.b); n > 0 && s.b[n-1] == '-' { + s.setError(errSyntax) + s.b = s.b[:len(s.b)-1] + } + s.done = true + return end +} + +// acceptMinSize parses multiple tokens of the given size or greater. +// It returns the end position of the last token consumed. +func (s *scanner) acceptMinSize(min int) (end int) { + end = s.end + s.scan() + for ; len(s.token) >= min; s.scan() { + end = s.end + } + return end +} + +// Parse parses the given BCP 47 string and returns a valid Tag. If parsing +// failed it returns an error and any part of the tag that could be parsed. +// If parsing succeeded but an unknown value was found, it returns +// ValueError. The Tag returned in this case is just stripped of the unknown +// value. All other values are preserved. It accepts tags in the BCP 47 format +// and extensions to this standard defined in +// http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers. +// The resulting tag is canonicalized using the default canonicalization type. +func Parse(s string) (t Tag, err error) { + return Default.Parse(s) +} + +// Parse parses the given BCP 47 string and returns a valid Tag. If parsing +// failed it returns an error and any part of the tag that could be parsed. +// If parsing succeeded but an unknown value was found, it returns +// ValueError. The Tag returned in this case is just stripped of the unknown +// value. All other values are preserved. It accepts tags in the BCP 47 format +// and extensions to this standard defined in +// http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers. +// The resulting tag is canonicalized using the the canonicalization type c. +func (c CanonType) Parse(s string) (t Tag, err error) { + // TODO: consider supporting old-style locale key-value pairs. + if s == "" { + return und, errSyntax + } + if len(s) <= maxAltTaglen { + b := [maxAltTaglen]byte{} + for i, c := range s { + // Generating invalid UTF-8 is okay as it won't match. + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } else if c == '_' { + c = '-' + } + b[i] = byte(c) + } + if t, ok := grandfathered(b); ok { + return t, nil + } + } + scan := makeScannerString(s) + t, err = parse(&scan, s) + t, changed := t.canonicalize(c) + if changed { + t.remakeString() + } + return t, err +} + +func parse(scan *scanner, s string) (t Tag, err error) { + t = und + var end int + if n := len(scan.token); n <= 1 { + scan.toLower(0, len(scan.b)) + if n == 0 || scan.token[0] != 'x' { + return t, errSyntax + } + end = parseExtensions(scan) + } else if n >= 4 { + return und, errSyntax + } else { // the usual case + t, end = parseTag(scan) + if n := len(scan.token); n == 1 { + t.pExt = uint16(end) + end = parseExtensions(scan) + } else if end < len(scan.b) { + scan.setError(errSyntax) + scan.b = scan.b[:end] + } + } + if int(t.pVariant) < len(scan.b) { + if end < len(s) { + s = s[:end] + } + if len(s) > 0 && tag.Compare(s, scan.b) == 0 { + t.str = s + } else { + t.str = string(scan.b) + } + } else { + t.pVariant, t.pExt = 0, 0 + } + return t, scan.err +} + +// parseTag parses language, script, region and variants. +// It returns a Tag and the end position in the input that was parsed. +func parseTag(scan *scanner) (t Tag, end int) { + var e error + // TODO: set an error if an unknown lang, script or region is encountered. + t.lang, e = getLangID(scan.token) + scan.setError(e) + scan.replace(t.lang.String()) + langStart := scan.start + end = scan.scan() + for len(scan.token) == 3 && isAlpha(scan.token[0]) { + // From http://tools.ietf.org/html/bcp47, - tags are equivalent + // to a tag of the form . + lang, e := getLangID(scan.token) + if lang != 0 { + t.lang = lang + copy(scan.b[langStart:], lang.String()) + scan.b[langStart+3] = '-' + scan.start = langStart + 4 + } + scan.gobble(e) + end = scan.scan() + } + if len(scan.token) == 4 && isAlpha(scan.token[0]) { + t.script, e = getScriptID(script, scan.token) + if t.script == 0 { + scan.gobble(e) + } + end = scan.scan() + } + if n := len(scan.token); n >= 2 && n <= 3 { + t.region, e = getRegionID(scan.token) + if t.region == 0 { + scan.gobble(e) + } else { + scan.replace(t.region.String()) + } + end = scan.scan() + } + scan.toLower(scan.start, len(scan.b)) + t.pVariant = byte(end) + end = parseVariants(scan, end, t) + t.pExt = uint16(end) + return t, end +} + +var separator = []byte{'-'} + +// parseVariants scans tokens as long as each token is a valid variant string. +// Duplicate variants are removed. +func parseVariants(scan *scanner, end int, t Tag) int { + start := scan.start + varIDBuf := [4]uint8{} + variantBuf := [4][]byte{} + varID := varIDBuf[:0] + variant := variantBuf[:0] + last := -1 + needSort := false + for ; len(scan.token) >= 4; scan.scan() { + // TODO: measure the impact of needing this conversion and redesign + // the data structure if there is an issue. + v, ok := variantIndex[string(scan.token)] + if !ok { + // unknown variant + // TODO: allow user-defined variants? + scan.gobble(mkErrInvalid(scan.token)) + continue + } + varID = append(varID, v) + variant = append(variant, scan.token) + if !needSort { + if last < int(v) { + last = int(v) + } else { + needSort = true + // There is no legal combinations of more than 7 variants + // (and this is by no means a useful sequence). + const maxVariants = 8 + if len(varID) > maxVariants { + break + } + } + } + end = scan.end + } + if needSort { + sort.Sort(variantsSort{varID, variant}) + k, l := 0, -1 + for i, v := range varID { + w := int(v) + if l == w { + // Remove duplicates. + continue + } + varID[k] = varID[i] + variant[k] = variant[i] + k++ + l = w + } + if str := bytes.Join(variant[:k], separator); len(str) == 0 { + end = start - 1 + } else { + scan.resizeRange(start, end, len(str)) + copy(scan.b[scan.start:], str) + end = scan.end + } + } + return end +} + +type variantsSort struct { + i []uint8 + v [][]byte +} + +func (s variantsSort) Len() int { + return len(s.i) +} + +func (s variantsSort) Swap(i, j int) { + s.i[i], s.i[j] = s.i[j], s.i[i] + s.v[i], s.v[j] = s.v[j], s.v[i] +} + +func (s variantsSort) Less(i, j int) bool { + return s.i[i] < s.i[j] +} + +type bytesSort [][]byte + +func (b bytesSort) Len() int { + return len(b) +} + +func (b bytesSort) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b bytesSort) Less(i, j int) bool { + return bytes.Compare(b[i], b[j]) == -1 +} + +// parseExtensions parses and normalizes the extensions in the buffer. +// It returns the last position of scan.b that is part of any extension. +// It also trims scan.b to remove excess parts accordingly. +func parseExtensions(scan *scanner) int { + start := scan.start + exts := [][]byte{} + private := []byte{} + end := scan.end + for len(scan.token) == 1 { + extStart := scan.start + ext := scan.token[0] + end = parseExtension(scan) + extension := scan.b[extStart:end] + if len(extension) < 3 || (ext != 'x' && len(extension) < 4) { + scan.setError(errSyntax) + end = extStart + continue + } else if start == extStart && (ext == 'x' || scan.start == len(scan.b)) { + scan.b = scan.b[:end] + return end + } else if ext == 'x' { + private = extension + break + } + exts = append(exts, extension) + } + sort.Sort(bytesSort(exts)) + if len(private) > 0 { + exts = append(exts, private) + } + scan.b = scan.b[:start] + if len(exts) > 0 { + scan.b = append(scan.b, bytes.Join(exts, separator)...) + } else if start > 0 { + // Strip trailing '-'. + scan.b = scan.b[:start-1] + } + return end +} + +// parseExtension parses a single extension and returns the position of +// the extension end. +func parseExtension(scan *scanner) int { + start, end := scan.start, scan.end + switch scan.token[0] { + case 'u': + attrStart := end + scan.scan() + for last := []byte{}; len(scan.token) > 2; scan.scan() { + if bytes.Compare(scan.token, last) != -1 { + // Attributes are unsorted. Start over from scratch. + p := attrStart + 1 + scan.next = p + attrs := [][]byte{} + for scan.scan(); len(scan.token) > 2; scan.scan() { + attrs = append(attrs, scan.token) + end = scan.end + } + sort.Sort(bytesSort(attrs)) + copy(scan.b[p:], bytes.Join(attrs, separator)) + break + } + last = scan.token + end = scan.end + } + var last, key []byte + for attrEnd := end; len(scan.token) == 2; last = key { + key = scan.token + keyEnd := scan.end + end = scan.acceptMinSize(3) + // TODO: check key value validity + if keyEnd == end || bytes.Compare(key, last) != 1 { + // We have an invalid key or the keys are not sorted. + // Start scanning keys from scratch and reorder. + p := attrEnd + 1 + scan.next = p + keys := [][]byte{} + for scan.scan(); len(scan.token) == 2; { + keyStart, keyEnd := scan.start, scan.end + end = scan.acceptMinSize(3) + if keyEnd != end { + keys = append(keys, scan.b[keyStart:end]) + } else { + scan.setError(errSyntax) + end = keyStart + } + } + sort.Sort(bytesSort(keys)) + reordered := bytes.Join(keys, separator) + if e := p + len(reordered); e < end { + scan.deleteRange(e, end) + end = e + } + copy(scan.b[p:], bytes.Join(keys, separator)) + break + } + } + case 't': + scan.scan() + if n := len(scan.token); n >= 2 && n <= 3 && isAlpha(scan.token[1]) { + _, end = parseTag(scan) + scan.toLower(start, end) + } + for len(scan.token) == 2 && !isAlpha(scan.token[1]) { + end = scan.acceptMinSize(3) + } + case 'x': + end = scan.acceptMinSize(1) + default: + end = scan.acceptMinSize(2) + } + return end +} + +// Compose creates a Tag from individual parts, which may be of type Tag, Base, +// Script, Region, Variant, []Variant, Extension, []Extension or error. If a +// Base, Script or Region or slice of type Variant or Extension is passed more +// than once, the latter will overwrite the former. Variants and Extensions are +// accumulated, but if two extensions of the same type are passed, the latter +// will replace the former. A Tag overwrites all former values and typically +// only makes sense as the first argument. The resulting tag is returned after +// canonicalizing using the Default CanonType. If one or more errors are +// encountered, one of the errors is returned. +func Compose(part ...interface{}) (t Tag, err error) { + return Default.Compose(part...) +} + +// Compose creates a Tag from individual parts, which may be of type Tag, Base, +// Script, Region, Variant, []Variant, Extension, []Extension or error. If a +// Base, Script or Region or slice of type Variant or Extension is passed more +// than once, the latter will overwrite the former. Variants and Extensions are +// accumulated, but if two extensions of the same type are passed, the latter +// will replace the former. A Tag overwrites all former values and typically +// only makes sense as the first argument. The resulting tag is returned after +// canonicalizing using CanonType c. If one or more errors are encountered, +// one of the errors is returned. +func (c CanonType) Compose(part ...interface{}) (t Tag, err error) { + var b builder + if err = b.update(part...); err != nil { + return und, err + } + t, _ = b.tag.canonicalize(c) + + if len(b.ext) > 0 || len(b.variant) > 0 { + sort.Sort(sortVariant(b.variant)) + sort.Strings(b.ext) + if b.private != "" { + b.ext = append(b.ext, b.private) + } + n := maxCoreSize + tokenLen(b.variant...) + tokenLen(b.ext...) + buf := make([]byte, n) + p := t.genCoreBytes(buf) + t.pVariant = byte(p) + p += appendTokens(buf[p:], b.variant...) + t.pExt = uint16(p) + p += appendTokens(buf[p:], b.ext...) + t.str = string(buf[:p]) + } else if b.private != "" { + t.str = b.private + t.remakeString() + } + return +} + +type builder struct { + tag Tag + + private string // the x extension + ext []string + variant []string + + err error +} + +func (b *builder) addExt(e string) { + if e == "" { + } else if e[0] == 'x' { + b.private = e + } else { + b.ext = append(b.ext, e) + } +} + +var errInvalidArgument = errors.New("invalid Extension or Variant") + +func (b *builder) update(part ...interface{}) (err error) { + replace := func(l *[]string, s string, eq func(a, b string) bool) bool { + if s == "" { + b.err = errInvalidArgument + return true + } + for i, v := range *l { + if eq(v, s) { + (*l)[i] = s + return true + } + } + return false + } + for _, x := range part { + switch v := x.(type) { + case Tag: + b.tag.lang = v.lang + b.tag.region = v.region + b.tag.script = v.script + if v.str != "" { + b.variant = nil + for x, s := "", v.str[v.pVariant:v.pExt]; s != ""; { + x, s = nextToken(s) + b.variant = append(b.variant, x) + } + b.ext, b.private = nil, "" + for i, e := int(v.pExt), ""; i < len(v.str); { + i, e = getExtension(v.str, i) + b.addExt(e) + } + } + case Base: + b.tag.lang = v.langID + case Script: + b.tag.script = v.scriptID + case Region: + b.tag.region = v.regionID + case Variant: + if !replace(&b.variant, v.variant, func(a, b string) bool { return a == b }) { + b.variant = append(b.variant, v.variant) + } + case Extension: + if !replace(&b.ext, v.s, func(a, b string) bool { return a[0] == b[0] }) { + b.addExt(v.s) + } + case []Variant: + b.variant = nil + for _, x := range v { + b.update(x) + } + case []Extension: + b.ext, b.private = nil, "" + for _, e := range v { + b.update(e) + } + // TODO: support parsing of raw strings based on morphology or just extensions? + case error: + err = v + } + } + return +} + +func tokenLen(token ...string) (n int) { + for _, t := range token { + n += len(t) + 1 + } + return +} + +func appendTokens(b []byte, token ...string) int { + p := 0 + for _, t := range token { + b[p] = '-' + copy(b[p+1:], t) + p += 1 + len(t) + } + return p +} + +type sortVariant []string + +func (s sortVariant) Len() int { + return len(s) +} + +func (s sortVariant) Swap(i, j int) { + s[j], s[i] = s[i], s[j] +} + +func (s sortVariant) Less(i, j int) bool { + return variantIndex[s[i]] < variantIndex[s[j]] +} + +func findExt(list []string, x byte) int { + for i, e := range list { + if e[0] == x { + return i + } + } + return -1 +} + +// getExtension returns the name, body and end position of the extension. +func getExtension(s string, p int) (end int, ext string) { + if s[p] == '-' { + p++ + } + if s[p] == 'x' { + return len(s), s[p:] + } + end = nextExtension(s, p) + return end, s[p:end] +} + +// nextExtension finds the next extension within the string, searching +// for the -- pattern from position p. +// In the fast majority of cases, language tags will have at most +// one extension and extensions tend to be small. +func nextExtension(s string, p int) int { + for n := len(s) - 3; p < n; { + if s[p] == '-' { + if s[p+2] == '-' { + return p + } + p += 3 + } else { + p++ + } + } + return len(s) +} + +var errInvalidWeight = errors.New("ParseAcceptLanguage: invalid weight") + +// ParseAcceptLanguage parses the contents of an Accept-Language header as +// defined in http://www.ietf.org/rfc/rfc2616.txt and returns a list of Tags and +// a list of corresponding quality weights. It is more permissive than RFC 2616 +// and may return non-nil slices even if the input is not valid. +// The Tags will be sorted by highest weight first and then by first occurrence. +// Tags with a weight of zero will be dropped. An error will be returned if the +// input could not be parsed. +func ParseAcceptLanguage(s string) (tag []Tag, q []float32, err error) { + var entry string + for s != "" { + if entry, s = split(s, ','); entry == "" { + continue + } + + entry, weight := split(entry, ';') + + // Scan the language. + t, err := Parse(entry) + if err != nil { + id, ok := acceptFallback[entry] + if !ok { + return nil, nil, err + } + t = Tag{lang: id} + } + + // Scan the optional weight. + w := 1.0 + if weight != "" { + weight = consume(weight, 'q') + weight = consume(weight, '=') + // consume returns the empty string when a token could not be + // consumed, resulting in an error for ParseFloat. + if w, err = strconv.ParseFloat(weight, 32); err != nil { + return nil, nil, errInvalidWeight + } + // Drop tags with a quality weight of 0. + if w <= 0 { + continue + } + } + + tag = append(tag, t) + q = append(q, float32(w)) + } + sortStable(&tagSort{tag, q}) + return tag, q, nil +} + +// consume removes a leading token c from s and returns the result or the empty +// string if there is no such token. +func consume(s string, c byte) string { + if s == "" || s[0] != c { + return "" + } + return strings.TrimSpace(s[1:]) +} + +func split(s string, c byte) (head, tail string) { + if i := strings.IndexByte(s, c); i >= 0 { + return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+1:]) + } + return strings.TrimSpace(s), "" +} + +// Add hack mapping to deal with a small number of cases that that occur +// in Accept-Language (with reasonable frequency). +var acceptFallback = map[string]langID{ + "english": _en, + "deutsch": _de, + "italian": _it, + "french": _fr, + "*": _mul, // defined in the spec to match all languages. +} + +type tagSort struct { + tag []Tag + q []float32 +} + +func (s *tagSort) Len() int { + return len(s.q) +} + +func (s *tagSort) Less(i, j int) bool { + return s.q[i] > s.q[j] +} + +func (s *tagSort) Swap(i, j int) { + s.tag[i], s.tag[j] = s.tag[j], s.tag[i] + s.q[i], s.q[j] = s.q[j], s.q[i] +} diff --git a/vendor/golang.org/x/text/language/tables.go b/vendor/golang.org/x/text/language/tables.go new file mode 100644 index 000000000..b738d457b --- /dev/null +++ b/vendor/golang.org/x/text/language/tables.go @@ -0,0 +1,3686 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +package language + +import "golang.org/x/text/internal/tag" + +// CLDRVersion is the CLDR version from which the tables in this package are derived. +const CLDRVersion = "32" + +const numLanguages = 8665 + +const numScripts = 242 + +const numRegions = 357 + +type fromTo struct { + from uint16 + to uint16 +} + +const nonCanonicalUnd = 1201 +const ( + _af = 22 + _am = 39 + _ar = 58 + _az = 88 + _bg = 126 + _bn = 165 + _ca = 215 + _cs = 250 + _da = 257 + _de = 269 + _el = 310 + _en = 313 + _es = 318 + _et = 320 + _fa = 328 + _fi = 337 + _fil = 339 + _fr = 350 + _gu = 420 + _he = 444 + _hi = 446 + _hr = 465 + _hu = 469 + _hy = 471 + _id = 481 + _is = 504 + _it = 505 + _ja = 512 + _ka = 528 + _kk = 578 + _km = 586 + _kn = 593 + _ko = 596 + _ky = 650 + _lo = 696 + _lt = 704 + _lv = 711 + _mk = 767 + _ml = 772 + _mn = 779 + _mo = 784 + _mr = 795 + _ms = 799 + _mul = 806 + _my = 817 + _nb = 839 + _ne = 849 + _nl = 871 + _no = 879 + _pa = 925 + _pl = 947 + _pt = 960 + _ro = 988 + _ru = 994 + _sh = 1031 + _si = 1036 + _sk = 1042 + _sl = 1046 + _sq = 1073 + _sr = 1074 + _sv = 1092 + _sw = 1093 + _ta = 1104 + _te = 1121 + _th = 1131 + _tl = 1146 + _tn = 1152 + _tr = 1162 + _uk = 1198 + _ur = 1204 + _uz = 1212 + _vi = 1219 + _zh = 1321 + _zu = 1327 + _jbo = 515 + _ami = 1650 + _bnn = 2357 + _hak = 438 + _tlh = 14467 + _lb = 661 + _nv = 899 + _pwn = 12055 + _tao = 14188 + _tay = 14198 + _tsu = 14662 + _nn = 874 + _sfb = 13629 + _vgt = 15701 + _sgg = 13660 + _cmn = 3007 + _nan = 835 + _hsn = 467 +) + +const langPrivateStart = 0x2f72 + +const langPrivateEnd = 0x3179 + +// lang holds an alphabetically sorted list of ISO-639 language identifiers. +// All entries are 4 bytes. The index of the identifier (divided by 4) is the language tag. +// For 2-byte language identifiers, the two successive bytes have the following meaning: +// - if the first letter of the 2- and 3-letter ISO codes are the same: +// the second and third letter of the 3-letter ISO code. +// - otherwise: a 0 and a by 2 bits right-shifted index into altLangISO3. +// For 3-byte language identifiers the 4th byte is 0. +const lang tag.Index = "" + // Size: 5324 bytes + "---\x00aaaraai\x00aak\x00aau\x00abbkabi\x00abq\x00abr\x00abt\x00aby\x00a" + + "cd\x00ace\x00ach\x00ada\x00ade\x00adj\x00ady\x00adz\x00aeveaeb\x00aey" + + "\x00affragc\x00agd\x00agg\x00agm\x00ago\x00agq\x00aha\x00ahl\x00aho\x00a" + + "jg\x00akkaakk\x00ala\x00ali\x00aln\x00alt\x00ammhamm\x00amn\x00amo\x00am" + + "p\x00anrganc\x00ank\x00ann\x00any\x00aoj\x00aom\x00aoz\x00apc\x00apd\x00" + + "ape\x00apr\x00aps\x00apz\x00arraarc\x00arh\x00arn\x00aro\x00arq\x00ars" + + "\x00ary\x00arz\x00assmasa\x00ase\x00asg\x00aso\x00ast\x00ata\x00atg\x00a" + + "tj\x00auy\x00avvaavl\x00avn\x00avt\x00avu\x00awa\x00awb\x00awo\x00awx" + + "\x00ayymayb\x00azzebaakbal\x00ban\x00bap\x00bar\x00bas\x00bav\x00bax\x00" + + "bba\x00bbb\x00bbc\x00bbd\x00bbj\x00bbp\x00bbr\x00bcf\x00bch\x00bci\x00bc" + + "m\x00bcn\x00bco\x00bcq\x00bcu\x00bdd\x00beelbef\x00beh\x00bej\x00bem\x00" + + "bet\x00bew\x00bex\x00bez\x00bfd\x00bfq\x00bft\x00bfy\x00bgulbgc\x00bgn" + + "\x00bgx\x00bhihbhb\x00bhg\x00bhi\x00bhk\x00bhl\x00bho\x00bhy\x00biisbib" + + "\x00big\x00bik\x00bim\x00bin\x00bio\x00biq\x00bjh\x00bji\x00bjj\x00bjn" + + "\x00bjo\x00bjr\x00bjt\x00bjz\x00bkc\x00bkm\x00bkq\x00bku\x00bkv\x00blt" + + "\x00bmambmh\x00bmk\x00bmq\x00bmu\x00bnenbng\x00bnm\x00bnp\x00boodboj\x00" + + "bom\x00bon\x00bpy\x00bqc\x00bqi\x00bqp\x00bqv\x00brrebra\x00brh\x00brx" + + "\x00brz\x00bsosbsj\x00bsq\x00bss\x00bst\x00bto\x00btt\x00btv\x00bua\x00b" + + "uc\x00bud\x00bug\x00buk\x00bum\x00buo\x00bus\x00buu\x00bvb\x00bwd\x00bwr" + + "\x00bxh\x00bye\x00byn\x00byr\x00bys\x00byv\x00byx\x00bza\x00bze\x00bzf" + + "\x00bzh\x00bzw\x00caatcan\x00cbj\x00cch\x00ccp\x00ceheceb\x00cfa\x00cgg" + + "\x00chhachk\x00chm\x00cho\x00chp\x00chr\x00cja\x00cjm\x00cjv\x00ckb\x00c" + + "kl\x00cko\x00cky\x00cla\x00cme\x00cmg\x00cooscop\x00cps\x00crrecrh\x00cr" + + "j\x00crk\x00crl\x00crm\x00crs\x00csescsb\x00csw\x00ctd\x00cuhucvhvcyymda" + + "andad\x00daf\x00dag\x00dah\x00dak\x00dar\x00dav\x00dbd\x00dbq\x00dcc\x00" + + "ddn\x00deeuded\x00den\x00dga\x00dgh\x00dgi\x00dgl\x00dgr\x00dgz\x00dia" + + "\x00dje\x00dnj\x00dob\x00doi\x00dop\x00dow\x00dri\x00drs\x00dsb\x00dtm" + + "\x00dtp\x00dts\x00dty\x00dua\x00duc\x00dud\x00dug\x00dvivdva\x00dww\x00d" + + "yo\x00dyu\x00dzzodzg\x00ebu\x00eeweefi\x00egl\x00egy\x00eka\x00eky\x00el" + + "llema\x00emi\x00enngenn\x00enq\x00eopoeri\x00es\x00\x05esu\x00etstetr" + + "\x00ett\x00etu\x00etx\x00euusewo\x00ext\x00faasfaa\x00fab\x00fag\x00fai" + + "\x00fan\x00ffulffi\x00ffm\x00fiinfia\x00fil\x00fit\x00fjijflr\x00fmp\x00" + + "foaofod\x00fon\x00for\x00fpe\x00fqs\x00frrafrc\x00frp\x00frr\x00frs\x00f" + + "ub\x00fud\x00fue\x00fuf\x00fuh\x00fuq\x00fur\x00fuv\x00fuy\x00fvr\x00fyr" + + "ygalegaa\x00gaf\x00gag\x00gah\x00gaj\x00gam\x00gan\x00gaw\x00gay\x00gba" + + "\x00gbf\x00gbm\x00gby\x00gbz\x00gcr\x00gdlagde\x00gdn\x00gdr\x00geb\x00g" + + "ej\x00gel\x00gez\x00gfk\x00ggn\x00ghs\x00gil\x00gim\x00gjk\x00gjn\x00gju" + + "\x00gkn\x00gkp\x00gllgglk\x00gmm\x00gmv\x00gnrngnd\x00gng\x00god\x00gof" + + "\x00goi\x00gom\x00gon\x00gor\x00gos\x00got\x00grb\x00grc\x00grt\x00grw" + + "\x00gsw\x00guujgub\x00guc\x00gud\x00gur\x00guw\x00gux\x00guz\x00gvlvgvf" + + "\x00gvr\x00gvs\x00gwc\x00gwi\x00gwt\x00gyi\x00haauhag\x00hak\x00ham\x00h" + + "aw\x00haz\x00hbb\x00hdy\x00heebhhy\x00hiinhia\x00hif\x00hig\x00hih\x00hi" + + "l\x00hla\x00hlu\x00hmd\x00hmt\x00hnd\x00hne\x00hnj\x00hnn\x00hno\x00homo" + + "hoc\x00hoj\x00hot\x00hrrvhsb\x00hsn\x00htathuunhui\x00hyyehzerianaian" + + "\x00iar\x00iba\x00ibb\x00iby\x00ica\x00ich\x00idndidd\x00idi\x00idu\x00i" + + "eleife\x00igboigb\x00ige\x00iiiiijj\x00ikpkikk\x00ikt\x00ikw\x00ikx\x00i" + + "lo\x00imo\x00inndinh\x00iodoiou\x00iri\x00isslittaiukuiw\x00\x03iwm\x00i" + + "ws\x00izh\x00izi\x00japnjab\x00jam\x00jbo\x00jbu\x00jen\x00jgk\x00jgo" + + "\x00ji\x00\x06jib\x00jmc\x00jml\x00jra\x00jut\x00jvavjwavkaatkaa\x00kab" + + "\x00kac\x00kad\x00kai\x00kaj\x00kam\x00kao\x00kbd\x00kbm\x00kbp\x00kbq" + + "\x00kbx\x00kby\x00kcg\x00kck\x00kcl\x00kct\x00kde\x00kdh\x00kdl\x00kdt" + + "\x00kea\x00ken\x00kez\x00kfo\x00kfr\x00kfy\x00kgonkge\x00kgf\x00kgp\x00k" + + "ha\x00khb\x00khn\x00khq\x00khs\x00kht\x00khw\x00khz\x00kiikkij\x00kiu" + + "\x00kiw\x00kjuakjd\x00kjg\x00kjs\x00kjy\x00kkazkkc\x00kkj\x00klalkln\x00" + + "klq\x00klt\x00klx\x00kmhmkmb\x00kmh\x00kmo\x00kms\x00kmu\x00kmw\x00knank" + + "nf\x00knp\x00koorkoi\x00kok\x00kol\x00kos\x00koz\x00kpe\x00kpf\x00kpo" + + "\x00kpr\x00kpx\x00kqb\x00kqf\x00kqs\x00kqy\x00kraukrc\x00kri\x00krj\x00k" + + "rl\x00krs\x00kru\x00ksasksb\x00ksd\x00ksf\x00ksh\x00ksj\x00ksr\x00ktb" + + "\x00ktm\x00kto\x00kuurkub\x00kud\x00kue\x00kuj\x00kum\x00kun\x00kup\x00k" + + "us\x00kvomkvg\x00kvr\x00kvx\x00kw\x00\x01kwj\x00kwo\x00kxa\x00kxc\x00kxm" + + "\x00kxp\x00kxw\x00kxz\x00kyirkye\x00kyx\x00kzr\x00laatlab\x00lad\x00lag" + + "\x00lah\x00laj\x00las\x00lbtzlbe\x00lbu\x00lbw\x00lcm\x00lcp\x00ldb\x00l" + + "ed\x00lee\x00lem\x00lep\x00leq\x00leu\x00lez\x00lguglgg\x00liimlia\x00li" + + "d\x00lif\x00lig\x00lih\x00lij\x00lis\x00ljp\x00lki\x00lkt\x00lle\x00lln" + + "\x00lmn\x00lmo\x00lmp\x00lninlns\x00lnu\x00loaoloj\x00lok\x00lol\x00lor" + + "\x00los\x00loz\x00lrc\x00ltitltg\x00luublua\x00luo\x00luy\x00luz\x00lvav" + + "lwl\x00lzh\x00lzz\x00mad\x00maf\x00mag\x00mai\x00mak\x00man\x00mas\x00ma" + + "w\x00maz\x00mbh\x00mbo\x00mbq\x00mbu\x00mbw\x00mci\x00mcp\x00mcq\x00mcr" + + "\x00mcu\x00mda\x00mde\x00mdf\x00mdh\x00mdj\x00mdr\x00mdx\x00med\x00mee" + + "\x00mek\x00men\x00mer\x00met\x00meu\x00mfa\x00mfe\x00mfn\x00mfo\x00mfq" + + "\x00mglgmgh\x00mgl\x00mgo\x00mgp\x00mgy\x00mhahmhi\x00mhl\x00mirimif\x00" + + "min\x00mis\x00miw\x00mkkdmki\x00mkl\x00mkp\x00mkw\x00mlalmle\x00mlp\x00m" + + "ls\x00mmo\x00mmu\x00mmx\x00mnonmna\x00mnf\x00mni\x00mnw\x00moolmoa\x00mo" + + "e\x00moh\x00mos\x00mox\x00mpp\x00mps\x00mpt\x00mpx\x00mql\x00mrarmrd\x00" + + "mrj\x00mro\x00mssamtltmtc\x00mtf\x00mti\x00mtr\x00mua\x00mul\x00mur\x00m" + + "us\x00mva\x00mvn\x00mvy\x00mwk\x00mwr\x00mwv\x00mxc\x00mxm\x00myyamyk" + + "\x00mym\x00myv\x00myw\x00myx\x00myz\x00mzk\x00mzm\x00mzn\x00mzp\x00mzw" + + "\x00mzz\x00naaunac\x00naf\x00nah\x00nak\x00nan\x00nap\x00naq\x00nas\x00n" + + "bobnca\x00nce\x00ncf\x00nch\x00nco\x00ncu\x00nddendc\x00nds\x00neepneb" + + "\x00new\x00nex\x00nfr\x00ngdonga\x00ngb\x00ngl\x00nhb\x00nhe\x00nhw\x00n" + + "if\x00nii\x00nij\x00nin\x00niu\x00niy\x00niz\x00njo\x00nkg\x00nko\x00nll" + + "dnmg\x00nmz\x00nnnonnf\x00nnh\x00nnk\x00nnm\x00noornod\x00noe\x00non\x00" + + "nop\x00nou\x00nqo\x00nrblnrb\x00nsk\x00nsn\x00nso\x00nss\x00ntm\x00ntr" + + "\x00nui\x00nup\x00nus\x00nuv\x00nux\x00nvavnwb\x00nxq\x00nxr\x00nyyanym" + + "\x00nyn\x00nzi\x00occiogc\x00ojjiokr\x00okv\x00omrmong\x00onn\x00ons\x00" + + "opm\x00orrioro\x00oru\x00osssosa\x00ota\x00otk\x00ozm\x00paanpag\x00pal" + + "\x00pam\x00pap\x00pau\x00pbi\x00pcd\x00pcm\x00pdc\x00pdt\x00ped\x00peo" + + "\x00pex\x00pfl\x00phl\x00phn\x00pilipil\x00pip\x00pka\x00pko\x00plolpla" + + "\x00pms\x00png\x00pnn\x00pnt\x00pon\x00ppo\x00pra\x00prd\x00prg\x00psusp" + + "ss\x00ptorptp\x00puu\x00pwa\x00quuequc\x00qug\x00rai\x00raj\x00rao\x00rc" + + "f\x00rej\x00rel\x00res\x00rgn\x00rhg\x00ria\x00rif\x00rjs\x00rkt\x00rmoh" + + "rmf\x00rmo\x00rmt\x00rmu\x00rnunrna\x00rng\x00roonrob\x00rof\x00roo\x00r" + + "ro\x00rtm\x00ruusrue\x00rug\x00rw\x00\x04rwk\x00rwo\x00ryu\x00saansaf" + + "\x00sah\x00saq\x00sas\x00sat\x00sav\x00saz\x00sba\x00sbe\x00sbp\x00scrds" + + "ck\x00scl\x00scn\x00sco\x00scs\x00sdndsdc\x00sdh\x00semesef\x00seh\x00se" + + "i\x00ses\x00sgagsga\x00sgs\x00sgw\x00sgz\x00sh\x00\x02shi\x00shk\x00shn" + + "\x00shu\x00siinsid\x00sig\x00sil\x00sim\x00sjr\x00sklkskc\x00skr\x00sks" + + "\x00sllvsld\x00sli\x00sll\x00sly\x00smmosma\x00smi\x00smj\x00smn\x00smp" + + "\x00smq\x00sms\x00snnasnc\x00snk\x00snp\x00snx\x00sny\x00soomsok\x00soq" + + "\x00sou\x00soy\x00spd\x00spl\x00sps\x00sqqisrrpsrb\x00srn\x00srr\x00srx" + + "\x00ssswssd\x00ssg\x00ssy\x00stotstk\x00stq\x00suunsua\x00sue\x00suk\x00" + + "sur\x00sus\x00svweswwaswb\x00swc\x00swg\x00swp\x00swv\x00sxn\x00sxw\x00s" + + "yl\x00syr\x00szl\x00taamtaj\x00tal\x00tan\x00taq\x00tbc\x00tbd\x00tbf" + + "\x00tbg\x00tbo\x00tbw\x00tbz\x00tci\x00tcy\x00tdd\x00tdg\x00tdh\x00teelt" + + "ed\x00tem\x00teo\x00tet\x00tfi\x00tggktgc\x00tgo\x00tgu\x00thhathl\x00th" + + "q\x00thr\x00tiirtif\x00tig\x00tik\x00tim\x00tio\x00tiv\x00tkuktkl\x00tkr" + + "\x00tkt\x00tlgltlf\x00tlx\x00tly\x00tmh\x00tmy\x00tnsntnh\x00toontof\x00" + + "tog\x00toq\x00tpi\x00tpm\x00tpz\x00tqo\x00trurtru\x00trv\x00trw\x00tssot" + + "sd\x00tsf\x00tsg\x00tsj\x00tsw\x00ttatttd\x00tte\x00ttj\x00ttr\x00tts" + + "\x00ttt\x00tuh\x00tul\x00tum\x00tuq\x00tvd\x00tvl\x00tvu\x00twwitwh\x00t" + + "wq\x00txg\x00tyahtya\x00tyv\x00tzm\x00ubu\x00udm\x00ugiguga\x00ukkruli" + + "\x00umb\x00und\x00unr\x00unx\x00urrduri\x00urt\x00urw\x00usa\x00utr\x00u" + + "vh\x00uvl\x00uzzbvag\x00vai\x00van\x00veenvec\x00vep\x00viievic\x00viv" + + "\x00vls\x00vmf\x00vmw\x00voolvot\x00vro\x00vun\x00vut\x00walnwae\x00waj" + + "\x00wal\x00wan\x00war\x00wbp\x00wbq\x00wbr\x00wci\x00wer\x00wgi\x00whg" + + "\x00wib\x00wiu\x00wiv\x00wja\x00wji\x00wls\x00wmo\x00wnc\x00wni\x00wnu" + + "\x00woolwob\x00wos\x00wrs\x00wsk\x00wtm\x00wuu\x00wuv\x00wwa\x00xav\x00x" + + "bi\x00xcr\x00xes\x00xhhoxla\x00xlc\x00xld\x00xmf\x00xmn\x00xmr\x00xna" + + "\x00xnr\x00xog\x00xon\x00xpr\x00xrb\x00xsa\x00xsi\x00xsm\x00xsr\x00xwe" + + "\x00yam\x00yao\x00yap\x00yas\x00yat\x00yav\x00yay\x00yaz\x00yba\x00ybb" + + "\x00yby\x00yer\x00ygr\x00ygw\x00yiidyko\x00yle\x00ylg\x00yll\x00yml\x00y" + + "ooryon\x00yrb\x00yre\x00yrl\x00yss\x00yua\x00yue\x00yuj\x00yut\x00yuw" + + "\x00zahazag\x00zbl\x00zdj\x00zea\x00zgh\x00zhhozhx\x00zia\x00zlm\x00zmi" + + "\x00zne\x00zuulzxx\x00zza\x00\xff\xff\xff\xff" + +const langNoIndexOffset = 1330 + +// langNoIndex is a bit vector of all 3-letter language codes that are not used as an index +// in lookup tables. The language ids for these language codes are derived directly +// from the letters and are not consecutive. +// Size: 2197 bytes, 2197 elements +var langNoIndex = [2197]uint8{ + // Entry 0 - 3F + 0xff, 0xf8, 0xed, 0xfe, 0xeb, 0xd3, 0x3b, 0xd2, + 0xfb, 0xbf, 0x7a, 0xfa, 0x37, 0x1d, 0x3c, 0x57, + 0x6e, 0x97, 0x73, 0x38, 0xfb, 0xea, 0xbf, 0x70, + 0xad, 0x03, 0xff, 0xff, 0xcf, 0x05, 0x84, 0x62, + 0xe9, 0xbf, 0xfd, 0xbf, 0xbf, 0xf7, 0xfd, 0x77, + 0x0f, 0xff, 0xef, 0x6f, 0xff, 0xfb, 0xdf, 0xe2, + 0xc9, 0xf8, 0x7f, 0x7e, 0x4d, 0xb8, 0x0a, 0x6a, + 0x7c, 0xea, 0xe3, 0xfa, 0x7a, 0xbf, 0x67, 0xff, + // Entry 40 - 7F + 0xff, 0xff, 0xff, 0xdf, 0x2a, 0x54, 0x91, 0xc0, + 0x5d, 0xe3, 0x97, 0x14, 0x07, 0x20, 0xdd, 0xed, + 0x9f, 0x3f, 0xc9, 0x21, 0xf8, 0x3f, 0x94, 0x35, + 0x7c, 0x5f, 0xff, 0x5f, 0x8e, 0x6e, 0xdf, 0xff, + 0xff, 0xff, 0x55, 0x7c, 0xd3, 0xfd, 0xbf, 0xb5, + 0x7b, 0xdf, 0x7f, 0xf7, 0xca, 0xfe, 0xdb, 0xa3, + 0xa8, 0xff, 0x1f, 0x67, 0x7d, 0xeb, 0xef, 0xce, + 0xff, 0xff, 0x9f, 0xff, 0xb7, 0xef, 0xfe, 0xcf, + // Entry 80 - BF + 0xdb, 0xff, 0xf3, 0xcd, 0xfb, 0x2f, 0xff, 0xff, + 0xbb, 0xee, 0xf7, 0xbd, 0xdb, 0xff, 0x5f, 0xf7, + 0xfd, 0xf2, 0xfd, 0xff, 0x5e, 0x2f, 0x3b, 0xba, + 0x7e, 0xff, 0xff, 0xfe, 0xf7, 0xff, 0xdd, 0xff, + 0xfd, 0xdf, 0xfb, 0xfe, 0x9d, 0xb4, 0xd3, 0xff, + 0xef, 0xff, 0xdf, 0xf7, 0x7f, 0xb7, 0xfd, 0xd5, + 0xa5, 0x77, 0x40, 0xff, 0x9c, 0xc1, 0x41, 0x2c, + 0x08, 0x20, 0x41, 0x00, 0x50, 0x40, 0x00, 0x80, + // Entry C0 - FF + 0xfb, 0x4a, 0xf2, 0x9f, 0xb4, 0x42, 0x41, 0x96, + 0x1b, 0x14, 0x08, 0xf2, 0x2b, 0xe7, 0x17, 0x56, + 0x05, 0x7d, 0x0e, 0x1c, 0x37, 0x71, 0xf3, 0xef, + 0x97, 0xff, 0x5d, 0x38, 0x64, 0x08, 0x00, 0x10, + 0xbc, 0x85, 0xaf, 0xdf, 0xff, 0xf7, 0x73, 0x35, + 0x3e, 0x87, 0xc7, 0xdf, 0xff, 0x00, 0x81, 0x00, + 0xb0, 0x05, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x40, 0x00, 0x40, 0x92, 0x21, 0x50, 0xb1, 0x5d, + // Entry 100 - 13F + 0xfd, 0xdc, 0xbe, 0x5e, 0x00, 0x00, 0x02, 0x64, + 0x0d, 0x19, 0x41, 0xdf, 0x79, 0x22, 0x00, 0x00, + 0x00, 0x5e, 0x64, 0xdc, 0x24, 0xe5, 0xd9, 0xe3, + 0xfe, 0xff, 0xfd, 0xcb, 0x9f, 0x14, 0x01, 0x0c, + 0x86, 0x00, 0xd1, 0x00, 0xf0, 0xc5, 0x67, 0x5f, + 0x56, 0x89, 0x5e, 0xb5, 0x6c, 0xaf, 0x03, 0x00, + 0x02, 0x00, 0x00, 0x00, 0xc0, 0x37, 0xda, 0x56, + 0x90, 0x69, 0x01, 0x2c, 0x96, 0x69, 0x20, 0xfb, + // Entry 140 - 17F + 0xff, 0x3f, 0x00, 0x00, 0x00, 0x01, 0x08, 0x16, + 0x01, 0x00, 0x00, 0xb0, 0x14, 0x03, 0x50, 0x06, + 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x09, + 0x00, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x44, 0x00, 0x00, 0x10, 0x00, 0x04, + 0x08, 0x00, 0x00, 0x04, 0x00, 0x80, 0x28, 0x04, + 0x00, 0x00, 0x40, 0xd5, 0x2d, 0x00, 0x64, 0x35, + 0x24, 0x52, 0xf4, 0xd4, 0xbd, 0x62, 0xc9, 0x03, + // Entry 180 - 1BF + 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x13, 0x39, 0x01, 0xdd, 0x57, 0x98, + 0x21, 0x18, 0x81, 0x00, 0x00, 0x01, 0x40, 0x82, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x40, 0x00, 0x44, 0x00, 0x00, 0x80, 0xea, + 0xa9, 0x39, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + // Entry 1C0 - 1FF + 0x00, 0x01, 0x28, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x20, 0x04, 0xa6, 0x00, 0x04, 0x00, 0x00, + 0x81, 0x50, 0x00, 0x00, 0x00, 0x11, 0x84, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x55, + 0x02, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, 0x40, + 0x30, 0x83, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1e, 0xcd, 0xbf, 0x7a, 0xbf, + // Entry 200 - 23F + 0xdf, 0xc3, 0x83, 0x82, 0xc0, 0xfb, 0x57, 0x27, + 0xcd, 0x55, 0xe7, 0x01, 0x00, 0x20, 0xb2, 0xc5, + 0xa4, 0x45, 0x25, 0x9b, 0x02, 0xdf, 0xe0, 0xdf, + 0x03, 0x44, 0x08, 0x10, 0x01, 0x04, 0x01, 0xe3, + 0x92, 0x54, 0xdb, 0x28, 0xd1, 0x5f, 0xf6, 0x6d, + 0x79, 0xed, 0x1c, 0x7d, 0x04, 0x08, 0x00, 0x01, + 0x21, 0x12, 0x64, 0x5f, 0xdd, 0x0e, 0x85, 0x4f, + 0x40, 0x40, 0x00, 0x04, 0xf1, 0xfd, 0x3d, 0x54, + // Entry 240 - 27F + 0xe8, 0x03, 0xb4, 0x27, 0x23, 0x0d, 0x00, 0x00, + 0x20, 0x7b, 0x38, 0x02, 0x05, 0x84, 0x00, 0xf0, + 0xbb, 0x7e, 0x5a, 0x00, 0x18, 0x04, 0x81, 0x00, + 0x00, 0x00, 0x80, 0x10, 0x90, 0x1c, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x04, + 0x08, 0xa0, 0x70, 0xa5, 0x0c, 0x40, 0x00, 0x00, + 0x11, 0x04, 0x04, 0x68, 0x00, 0x20, 0x70, 0xff, + 0x7b, 0x7f, 0x60, 0x00, 0x05, 0x9b, 0xdd, 0x66, + // Entry 280 - 2BF + 0x03, 0x00, 0x11, 0x00, 0x00, 0x00, 0x40, 0x05, + 0xb5, 0xb6, 0x80, 0x08, 0x04, 0x00, 0x04, 0x51, + 0xe2, 0xef, 0xfd, 0x3f, 0x05, 0x09, 0x08, 0x05, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x60, + 0xe7, 0x48, 0x00, 0x81, 0x20, 0xc0, 0x05, 0x80, + 0x03, 0x00, 0x00, 0x00, 0x8c, 0x50, 0x40, 0x04, + 0x84, 0x47, 0x84, 0x40, 0x20, 0x10, 0x00, 0x20, + // Entry 2C0 - 2FF + 0x02, 0x50, 0x80, 0x11, 0x00, 0x91, 0x6c, 0xe2, + 0x50, 0x27, 0x1d, 0x11, 0x29, 0x06, 0x59, 0xe9, + 0x33, 0x08, 0x00, 0x20, 0x04, 0x40, 0x10, 0x00, + 0x00, 0x00, 0x50, 0x44, 0x92, 0x49, 0xd6, 0x5d, + 0xa7, 0x81, 0x47, 0x97, 0xfb, 0x00, 0x10, 0x00, + 0x08, 0x00, 0x80, 0x00, 0x40, 0x04, 0x00, 0x01, + 0x02, 0x00, 0x01, 0x40, 0x80, 0x00, 0x00, 0x08, + 0xd8, 0xeb, 0xf6, 0x39, 0xc4, 0x89, 0x12, 0x00, + // Entry 300 - 33F + 0x00, 0x0c, 0x04, 0x01, 0x20, 0x20, 0xdd, 0xa0, + 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x04, 0x10, 0xd0, 0x9d, 0x95, 0x13, 0x04, 0x80, + 0x00, 0x01, 0xd0, 0x12, 0x40, 0x00, 0x10, 0xb0, + 0x10, 0x62, 0x4c, 0xd2, 0x02, 0x01, 0x4a, 0x00, + 0x46, 0x04, 0x00, 0x08, 0x02, 0x00, 0x20, 0x80, + 0x00, 0x80, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0xd8, 0x6f, 0x15, 0x02, 0x08, 0x00, + // Entry 340 - 37F + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, + 0x00, 0x10, 0x00, 0x00, 0x00, 0xf0, 0x84, 0xe3, + 0xdd, 0xbf, 0xf9, 0xf9, 0x3b, 0x7f, 0x7f, 0xdb, + 0xfd, 0xfc, 0xfe, 0xdf, 0xff, 0xfd, 0xff, 0xf6, + 0xfb, 0xfc, 0xf7, 0x1f, 0xff, 0xb3, 0x6c, 0xff, + 0xd9, 0xad, 0xdf, 0xfe, 0xef, 0xba, 0xdf, 0xff, + 0xff, 0xff, 0xb7, 0xdd, 0x7d, 0xbf, 0xab, 0x7f, + 0xfd, 0xfd, 0xdf, 0x2f, 0x9c, 0xdf, 0xf3, 0x6f, + // Entry 380 - 3BF + 0xdf, 0xdd, 0xff, 0xfb, 0xee, 0xd2, 0xab, 0x5f, + 0xd5, 0xdf, 0x7f, 0xff, 0xeb, 0xff, 0xe4, 0x4d, + 0xf9, 0xff, 0xfe, 0xf7, 0xfd, 0xdf, 0xfb, 0xbf, + 0xee, 0xdb, 0x6f, 0xef, 0xff, 0x7f, 0xff, 0xff, + 0xf7, 0x5f, 0xd3, 0x3b, 0xfd, 0xd9, 0xdf, 0xeb, + 0xbc, 0x08, 0x05, 0x24, 0xff, 0x07, 0x70, 0xfe, + 0xe6, 0x5e, 0x00, 0x08, 0x00, 0x83, 0x3d, 0x1b, + 0x06, 0xe6, 0x72, 0x60, 0xd1, 0x3c, 0x7f, 0x44, + // Entry 3C0 - 3FF + 0x02, 0x30, 0x9f, 0x7a, 0x16, 0xbd, 0x7f, 0x57, + 0xf2, 0xff, 0x31, 0xff, 0xf2, 0x1e, 0x90, 0xf7, + 0xf1, 0xf9, 0x45, 0x80, 0x01, 0x02, 0x00, 0x00, + 0x40, 0x54, 0x9f, 0x8a, 0xd9, 0xd9, 0x0e, 0x11, + 0x86, 0x51, 0xc0, 0xf3, 0xfb, 0x47, 0x00, 0x01, + 0x05, 0xd1, 0x50, 0x58, 0x00, 0x00, 0x00, 0x10, + 0x04, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x17, 0xd2, + 0xb9, 0xfd, 0xfc, 0xba, 0xfe, 0xef, 0xc7, 0xbe, + // Entry 400 - 43F + 0x53, 0x6f, 0xdf, 0xe7, 0xdb, 0x65, 0xbb, 0x7f, + 0xfa, 0xff, 0x77, 0xf3, 0xef, 0xbf, 0xfd, 0xf7, + 0xdf, 0xdf, 0x9b, 0x7f, 0xff, 0xff, 0x7f, 0x6f, + 0xf7, 0xfb, 0xeb, 0xdf, 0xbc, 0xff, 0xbf, 0x6b, + 0x7b, 0xfb, 0xff, 0xce, 0x76, 0xbd, 0xf7, 0xf7, + 0xdf, 0xdc, 0xf7, 0xf7, 0xff, 0xdf, 0xf3, 0xfe, + 0xef, 0xff, 0xff, 0xff, 0xb6, 0x7f, 0x7f, 0xde, + 0xf7, 0xb9, 0xeb, 0x77, 0xff, 0xfb, 0xbf, 0xdf, + // Entry 440 - 47F + 0xfd, 0xfe, 0xfb, 0xff, 0xfe, 0xeb, 0x1f, 0x7d, + 0x2f, 0xfd, 0xb6, 0xb5, 0xa5, 0xfc, 0xff, 0xfd, + 0x7f, 0x4e, 0xbf, 0x8f, 0xae, 0xff, 0xee, 0xdf, + 0x7f, 0xf7, 0x73, 0x02, 0x02, 0x04, 0xfc, 0xf7, + 0xff, 0xb7, 0xd7, 0xef, 0xfe, 0xcd, 0xf5, 0xce, + 0xe2, 0x8e, 0xe7, 0xbf, 0xb7, 0xff, 0x56, 0xbd, + 0xcd, 0xff, 0xfb, 0xff, 0xdf, 0xd7, 0xea, 0xff, + 0xe5, 0x5f, 0x6d, 0x0f, 0xa7, 0x51, 0x06, 0xc4, + // Entry 480 - 4BF + 0x13, 0x50, 0x5d, 0xaf, 0xa6, 0xfd, 0x99, 0xfb, + 0x63, 0x1d, 0x53, 0xff, 0xef, 0xb7, 0x35, 0x20, + 0x14, 0x00, 0x55, 0x51, 0x82, 0x65, 0xf5, 0x41, + 0xe2, 0xff, 0xfc, 0xdf, 0x00, 0x05, 0xc5, 0x05, + 0x00, 0x22, 0x00, 0x74, 0x69, 0x10, 0x08, 0x04, + 0x41, 0x00, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x51, 0x20, 0x05, 0x04, 0x01, 0x00, 0x00, + 0x06, 0x01, 0x20, 0x00, 0x18, 0x01, 0x92, 0xb1, + // Entry 4C0 - 4FF + 0xfd, 0x47, 0x49, 0x06, 0x95, 0x06, 0x57, 0xed, + 0xfb, 0x4c, 0x1c, 0x6b, 0x83, 0x04, 0x62, 0x40, + 0x00, 0x11, 0x42, 0x00, 0x00, 0x00, 0x54, 0x83, + 0xb8, 0x4f, 0x10, 0x8c, 0x89, 0x46, 0xde, 0xf7, + 0x13, 0x31, 0x00, 0x20, 0x00, 0x00, 0x00, 0x90, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x00, + 0x01, 0x00, 0x00, 0xf0, 0x5b, 0xf4, 0xbe, 0x3d, + 0xba, 0xcf, 0xf7, 0xaf, 0x42, 0x04, 0x84, 0x41, + // Entry 500 - 53F + 0x30, 0xff, 0x79, 0x72, 0x04, 0x00, 0x00, 0x49, + 0x2d, 0x14, 0x27, 0x57, 0xed, 0xf1, 0x3f, 0xe7, + 0x3f, 0x00, 0x00, 0x02, 0xc6, 0xa0, 0x1e, 0xf8, + 0xbb, 0xff, 0xfd, 0xfb, 0xb7, 0xfd, 0xe5, 0xf7, + 0xfd, 0xfc, 0xd5, 0xed, 0x47, 0xf4, 0x7e, 0x10, + 0x01, 0x01, 0x84, 0x6d, 0xff, 0xf7, 0xdd, 0xf9, + 0x5b, 0x05, 0x86, 0xed, 0xf5, 0x77, 0xbd, 0x3c, + 0x00, 0x00, 0x00, 0x42, 0x71, 0x42, 0x00, 0x40, + // Entry 540 - 57F + 0x00, 0x00, 0x01, 0x43, 0x19, 0x00, 0x08, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // Entry 580 - 5BF + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xab, 0xbd, 0xe7, 0x57, 0xee, 0x13, 0x5d, + 0x09, 0xc1, 0x40, 0x21, 0xfa, 0x17, 0x01, 0x80, + 0x00, 0x00, 0x00, 0x00, 0xf0, 0xce, 0xfb, 0xbf, + 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x30, 0x15, 0xa3, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x04, 0x16, 0x00, 0x00, 0x02, 0x00, 0x81, + 0xa3, 0x01, 0x50, 0x00, 0x00, 0x83, 0x11, 0x40, + // Entry 5C0 - 5FF + 0x00, 0x00, 0x00, 0xf0, 0xdd, 0x7b, 0x3e, 0x02, + 0xaa, 0x10, 0x5d, 0x98, 0x52, 0x00, 0x80, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x02, 0x02, + 0x19, 0x00, 0x10, 0x02, 0x10, 0x61, 0x5a, 0x9d, + 0x31, 0x00, 0x00, 0x00, 0x01, 0x10, 0x02, 0x20, + 0x00, 0x00, 0x01, 0x00, 0x42, 0x00, 0x20, 0x00, + 0x00, 0x1f, 0xdf, 0xd2, 0xb9, 0xff, 0xfd, 0x3f, + 0x1f, 0x98, 0xcf, 0x9c, 0xbf, 0xaf, 0x5f, 0xfe, + // Entry 600 - 63F + 0x7b, 0x4b, 0x40, 0x10, 0xe1, 0xfd, 0xaf, 0xd9, + 0xb7, 0xf6, 0xfb, 0xb3, 0xc7, 0xff, 0x6f, 0xf1, + 0x73, 0xb1, 0x7f, 0x9f, 0x7f, 0xbd, 0xfc, 0xb7, + 0xee, 0x1c, 0xfa, 0xcb, 0xef, 0xdd, 0xf9, 0xbd, + 0x6e, 0xae, 0x55, 0xfd, 0x6e, 0x81, 0x76, 0x1f, + 0xd4, 0x77, 0xf5, 0x7d, 0xfb, 0xff, 0xeb, 0xfe, + 0xbe, 0x5f, 0x46, 0x1b, 0xe9, 0x5f, 0x50, 0x18, + 0x02, 0xfa, 0xf7, 0x9d, 0x15, 0x97, 0x05, 0x0f, + // Entry 640 - 67F + 0x75, 0xc4, 0x7d, 0x81, 0x92, 0xf1, 0x57, 0x6c, + 0xff, 0xe4, 0xef, 0x6f, 0xff, 0xfc, 0xdd, 0xde, + 0xfc, 0xfd, 0x76, 0x5f, 0x7a, 0x1f, 0x00, 0x98, + 0x02, 0xfb, 0xa3, 0xef, 0xf3, 0xd6, 0xf2, 0xff, + 0xb9, 0xda, 0x7d, 0x50, 0x1e, 0x15, 0x7b, 0xb4, + 0xf5, 0x3e, 0xff, 0xff, 0xf1, 0xf7, 0xff, 0xe7, + 0x5f, 0xff, 0xff, 0x9e, 0xdb, 0xf6, 0xd7, 0xb9, + 0xef, 0x27, 0x80, 0xbb, 0xc5, 0xff, 0xff, 0xe3, + // Entry 680 - 6BF + 0x97, 0x9d, 0xbf, 0x9f, 0xf7, 0xc7, 0xfd, 0x37, + 0xce, 0x7f, 0x04, 0x1d, 0x53, 0x7f, 0xf8, 0xda, + 0x5d, 0xce, 0x7d, 0x06, 0xb9, 0xea, 0x69, 0xa0, + 0x1a, 0x20, 0x00, 0x30, 0x02, 0x04, 0x24, 0x08, + 0x04, 0x00, 0x00, 0x40, 0xd4, 0x02, 0x04, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x20, 0x01, 0x06, + 0x50, 0x00, 0x08, 0x00, 0x00, 0x00, 0x24, 0x00, + 0x04, 0x00, 0x10, 0xcc, 0x58, 0xd5, 0x0d, 0x0f, + // Entry 6C0 - 6FF + 0x14, 0x4d, 0xf1, 0x16, 0x44, 0xd1, 0x42, 0x08, + 0x40, 0x00, 0x00, 0x40, 0x00, 0x08, 0x00, 0x00, + 0x00, 0xdc, 0xfb, 0xcb, 0x0e, 0x58, 0x08, 0x41, + 0x04, 0x20, 0x04, 0x00, 0x30, 0x12, 0x40, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x80, 0x10, 0x10, 0xab, + 0x6d, 0x93, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x80, 0x25, 0x00, 0x00, + // Entry 700 - 73F + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x80, 0x86, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xdf, 0x18, 0x00, 0x00, 0x02, 0xf0, 0xfd, 0x79, + 0x3b, 0x00, 0x25, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x03, 0x00, 0x09, 0x20, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 740 - 77F + 0x00, 0x00, 0x00, 0xef, 0xd5, 0xfd, 0xcf, 0x7e, + 0xb0, 0x11, 0x00, 0x00, 0x00, 0x92, 0x01, 0x44, + 0xcd, 0xf9, 0x5c, 0x00, 0x01, 0x00, 0x30, 0x04, + 0x04, 0x55, 0x00, 0x01, 0x04, 0xf4, 0x3f, 0x4a, + 0x01, 0x00, 0x00, 0xb0, 0x80, 0x00, 0x55, 0x55, + 0x97, 0x7c, 0x9f, 0x31, 0xcc, 0x68, 0xd1, 0x03, + 0xd5, 0x57, 0x27, 0x14, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2c, 0xf7, 0xcb, 0x1f, 0x14, 0x60, + // Entry 780 - 7BF + 0x03, 0x68, 0x01, 0x10, 0x8b, 0x38, 0x8a, 0x01, + 0x00, 0x00, 0x20, 0x00, 0x24, 0x44, 0x00, 0x00, + 0x10, 0x03, 0x11, 0x02, 0x01, 0x00, 0x00, 0xf0, + 0xf5, 0xff, 0xd5, 0x97, 0xbc, 0x70, 0xd6, 0x78, + 0x78, 0x15, 0x50, 0x01, 0xa4, 0x84, 0xa9, 0x41, + 0x00, 0x00, 0x00, 0x6b, 0x39, 0x52, 0x74, 0x00, + 0xe8, 0x30, 0x90, 0x6a, 0x92, 0x00, 0x00, 0x02, + 0xff, 0xef, 0xff, 0x4b, 0x85, 0x53, 0xf4, 0xed, + // Entry 7C0 - 7FF + 0xdd, 0xbf, 0x72, 0x19, 0xc7, 0x0c, 0xd5, 0x42, + 0x54, 0xdd, 0x77, 0x14, 0x00, 0x80, 0x40, 0x56, + 0xcc, 0x16, 0x9e, 0xea, 0x35, 0x7d, 0xef, 0xff, + 0xbd, 0xa4, 0xaf, 0x01, 0x44, 0x18, 0x01, 0x4d, + 0x4e, 0x4a, 0x08, 0x50, 0x28, 0x30, 0xe0, 0x80, + 0x10, 0x20, 0x24, 0x00, 0xff, 0x2f, 0xd3, 0x60, + 0xfe, 0x01, 0x02, 0x88, 0x0a, 0x40, 0x16, 0x01, + 0x01, 0x15, 0x2b, 0x3c, 0x01, 0x00, 0x00, 0x10, + // Entry 800 - 83F + 0x90, 0x49, 0x41, 0x02, 0x02, 0x01, 0xe1, 0xbf, + 0xbf, 0x03, 0x00, 0x00, 0x10, 0xd4, 0xa3, 0xd1, + 0x40, 0x9c, 0x44, 0xdf, 0xf5, 0x8f, 0x66, 0xb3, + 0x55, 0x20, 0xd4, 0xc1, 0xd8, 0x30, 0x3d, 0x80, + 0x00, 0x00, 0x00, 0x04, 0xd4, 0x11, 0xc5, 0x84, + 0x2e, 0x50, 0x00, 0x22, 0x50, 0x6e, 0xbd, 0x93, + 0x07, 0x00, 0x20, 0x10, 0x84, 0xb2, 0x45, 0x10, + 0x06, 0x44, 0x00, 0x00, 0x12, 0x02, 0x11, 0x00, + // Entry 840 - 87F + 0xf0, 0xfb, 0xfd, 0x3f, 0x05, 0x00, 0x12, 0x81, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x02, 0x28, + 0x84, 0x00, 0x21, 0xc0, 0x23, 0x24, 0x00, 0x00, + 0x00, 0xcb, 0xe4, 0x3a, 0x42, 0x88, 0x14, 0xf1, + 0xef, 0xff, 0x7f, 0x12, 0x01, 0x01, 0x84, 0x50, + 0x07, 0xfc, 0xff, 0xff, 0x0f, 0x01, 0x00, 0x40, + 0x10, 0x38, 0x01, 0x01, 0x1c, 0x12, 0x40, 0xe1, + // Entry 880 - 8BF + 0x76, 0x16, 0x08, 0x03, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x24, + 0x0a, 0x00, 0x80, 0x00, 0x00, +} + +// altLangISO3 holds an alphabetically sorted list of 3-letter language code alternatives +// to 2-letter language codes that cannot be derived using the method described above. +// Each 3-letter code is followed by its 1-byte langID. +const altLangISO3 tag.Index = "---\x00cor\x00hbs\x01heb\x02kin\x03spa\x04yid\x05\xff\xff\xff\xff" + +// altLangIndex is used to convert indexes in altLangISO3 to langIDs. +// Size: 12 bytes, 6 elements +var altLangIndex = [6]uint16{ + 0x0281, 0x0407, 0x01fb, 0x03e5, 0x013e, 0x0208, +} + +// langAliasMap maps langIDs to their suggested replacements. +// Size: 656 bytes, 164 elements +var langAliasMap = [164]fromTo{ + 0: {from: 0x82, to: 0x88}, + 1: {from: 0x187, to: 0x1ae}, + 2: {from: 0x1f3, to: 0x1e1}, + 3: {from: 0x1fb, to: 0x1bc}, + 4: {from: 0x208, to: 0x512}, + 5: {from: 0x20f, to: 0x20e}, + 6: {from: 0x310, to: 0x3dc}, + 7: {from: 0x347, to: 0x36f}, + 8: {from: 0x407, to: 0x432}, + 9: {from: 0x47a, to: 0x153}, + 10: {from: 0x490, to: 0x451}, + 11: {from: 0x4a2, to: 0x21}, + 12: {from: 0x53e, to: 0x544}, + 13: {from: 0x58f, to: 0x12d}, + 14: {from: 0x630, to: 0x1eb1}, + 15: {from: 0x651, to: 0x431}, + 16: {from: 0x662, to: 0x431}, + 17: {from: 0x6ed, to: 0x3a}, + 18: {from: 0x6f8, to: 0x1d7}, + 19: {from: 0x73e, to: 0x21a1}, + 20: {from: 0x7b3, to: 0x56}, + 21: {from: 0x7b9, to: 0x299b}, + 22: {from: 0x7c5, to: 0x58}, + 23: {from: 0x7e6, to: 0x145}, + 24: {from: 0x80c, to: 0x5a}, + 25: {from: 0x815, to: 0x8d}, + 26: {from: 0x87e, to: 0x810}, + 27: {from: 0x8c3, to: 0xee3}, + 28: {from: 0x9ef, to: 0x331}, + 29: {from: 0xa36, to: 0x2c5}, + 30: {from: 0xa3d, to: 0xbf}, + 31: {from: 0xabe, to: 0x3322}, + 32: {from: 0xb38, to: 0x529}, + 33: {from: 0xb75, to: 0x265a}, + 34: {from: 0xb7e, to: 0xbc3}, + 35: {from: 0xb9b, to: 0x44e}, + 36: {from: 0xbbc, to: 0x4229}, + 37: {from: 0xbbf, to: 0x529}, + 38: {from: 0xbfe, to: 0x2da7}, + 39: {from: 0xc2e, to: 0x3181}, + 40: {from: 0xcb9, to: 0xf3}, + 41: {from: 0xd08, to: 0xfa}, + 42: {from: 0xdc8, to: 0x11a}, + 43: {from: 0xdd7, to: 0x32d}, + 44: {from: 0xdf8, to: 0xdfb}, + 45: {from: 0xdfe, to: 0x531}, + 46: {from: 0xedf, to: 0x205a}, + 47: {from: 0xeee, to: 0x2e9a}, + 48: {from: 0xf39, to: 0x367}, + 49: {from: 0x10d0, to: 0x140}, + 50: {from: 0x1104, to: 0x2d0}, + 51: {from: 0x11a0, to: 0x1ec}, + 52: {from: 0x1279, to: 0x21}, + 53: {from: 0x1424, to: 0x15e}, + 54: {from: 0x1470, to: 0x14e}, + 55: {from: 0x151f, to: 0xd9b}, + 56: {from: 0x1523, to: 0x390}, + 57: {from: 0x1532, to: 0x19f}, + 58: {from: 0x1580, to: 0x210}, + 59: {from: 0x1583, to: 0x10d}, + 60: {from: 0x15a3, to: 0x3caf}, + 61: {from: 0x166a, to: 0x19b}, + 62: {from: 0x16c8, to: 0x136}, + 63: {from: 0x1700, to: 0x29f8}, + 64: {from: 0x1718, to: 0x194}, + 65: {from: 0x1727, to: 0xf3f}, + 66: {from: 0x177a, to: 0x178}, + 67: {from: 0x1809, to: 0x17b6}, + 68: {from: 0x1816, to: 0x18f3}, + 69: {from: 0x188a, to: 0x436}, + 70: {from: 0x1979, to: 0x1d01}, + 71: {from: 0x1a74, to: 0x2bb0}, + 72: {from: 0x1a8a, to: 0x1f8}, + 73: {from: 0x1b5a, to: 0x1fa}, + 74: {from: 0x1b86, to: 0x1515}, + 75: {from: 0x1d64, to: 0x2c9b}, + 76: {from: 0x2038, to: 0x37b1}, + 77: {from: 0x203d, to: 0x20dd}, + 78: {from: 0x205a, to: 0x30b}, + 79: {from: 0x20e3, to: 0x274}, + 80: {from: 0x20ee, to: 0x263}, + 81: {from: 0x20f2, to: 0x22d}, + 82: {from: 0x20f9, to: 0x256}, + 83: {from: 0x210f, to: 0x21eb}, + 84: {from: 0x2135, to: 0x27d}, + 85: {from: 0x2160, to: 0x913}, + 86: {from: 0x2199, to: 0x121}, + 87: {from: 0x21ce, to: 0x1561}, + 88: {from: 0x21e6, to: 0x504}, + 89: {from: 0x21f4, to: 0x49f}, + 90: {from: 0x222d, to: 0x121}, + 91: {from: 0x2237, to: 0x121}, + 92: {from: 0x2262, to: 0x92a}, + 93: {from: 0x2316, to: 0x3226}, + 94: {from: 0x2382, to: 0x3365}, + 95: {from: 0x2472, to: 0x2c7}, + 96: {from: 0x24e4, to: 0x2ff}, + 97: {from: 0x24f0, to: 0x2fa}, + 98: {from: 0x24fa, to: 0x31f}, + 99: {from: 0x2550, to: 0xb5b}, + 100: {from: 0x25a9, to: 0xe2}, + 101: {from: 0x263e, to: 0x2d0}, + 102: {from: 0x26c9, to: 0x26b4}, + 103: {from: 0x26f9, to: 0x3c8}, + 104: {from: 0x2727, to: 0x3caf}, + 105: {from: 0x2765, to: 0x26b4}, + 106: {from: 0x2789, to: 0x4358}, + 107: {from: 0x28ef, to: 0x2837}, + 108: {from: 0x2914, to: 0x351}, + 109: {from: 0x2986, to: 0x2da7}, + 110: {from: 0x2b1a, to: 0x38d}, + 111: {from: 0x2bfc, to: 0x395}, + 112: {from: 0x2c3f, to: 0x3caf}, + 113: {from: 0x2cfc, to: 0x3be}, + 114: {from: 0x2d13, to: 0x597}, + 115: {from: 0x2d47, to: 0x148}, + 116: {from: 0x2d48, to: 0x148}, + 117: {from: 0x2dff, to: 0x2f1}, + 118: {from: 0x2e08, to: 0x19cc}, + 119: {from: 0x2e1a, to: 0x2d95}, + 120: {from: 0x2e21, to: 0x292}, + 121: {from: 0x2e54, to: 0x7d}, + 122: {from: 0x2e65, to: 0x2282}, + 123: {from: 0x2ea0, to: 0x2e9b}, + 124: {from: 0x2eef, to: 0x2ed7}, + 125: {from: 0x3193, to: 0x3c4}, + 126: {from: 0x3366, to: 0x338e}, + 127: {from: 0x342a, to: 0x3dc}, + 128: {from: 0x34ee, to: 0x18d0}, + 129: {from: 0x35c8, to: 0x2c9b}, + 130: {from: 0x35e6, to: 0x412}, + 131: {from: 0x3658, to: 0x246}, + 132: {from: 0x3676, to: 0x3f4}, + 133: {from: 0x36fd, to: 0x445}, + 134: {from: 0x37c0, to: 0x121}, + 135: {from: 0x3816, to: 0x38f2}, + 136: {from: 0x382b, to: 0x2c9b}, + 137: {from: 0x382f, to: 0xa9}, + 138: {from: 0x3832, to: 0x3228}, + 139: {from: 0x386c, to: 0x39a6}, + 140: {from: 0x3892, to: 0x3fc0}, + 141: {from: 0x38a5, to: 0x39d7}, + 142: {from: 0x38b4, to: 0x1fa4}, + 143: {from: 0x38b5, to: 0x2e9a}, + 144: {from: 0x395c, to: 0x47e}, + 145: {from: 0x3b4e, to: 0xd91}, + 146: {from: 0x3b78, to: 0x137}, + 147: {from: 0x3c99, to: 0x4bc}, + 148: {from: 0x3fbd, to: 0x100}, + 149: {from: 0x4208, to: 0xa91}, + 150: {from: 0x42be, to: 0x573}, + 151: {from: 0x42f9, to: 0x3f60}, + 152: {from: 0x4378, to: 0x25a}, + 153: {from: 0x43cb, to: 0x36cb}, + 154: {from: 0x43cd, to: 0x10f}, + 155: {from: 0x44af, to: 0x3322}, + 156: {from: 0x44e3, to: 0x512}, + 157: {from: 0x45ca, to: 0x2409}, + 158: {from: 0x45dd, to: 0x26dc}, + 159: {from: 0x4610, to: 0x48ae}, + 160: {from: 0x46ae, to: 0x46a0}, + 161: {from: 0x473e, to: 0x4745}, + 162: {from: 0x4916, to: 0x31f}, + 163: {from: 0x49a7, to: 0x523}, +} + +// Size: 164 bytes, 164 elements +var langAliasTypes = [164]langAliasType{ + // Entry 0 - 3F + 1, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 1, 0, 0, 1, 2, + 1, 1, 2, 0, 1, 0, 1, 2, 1, 1, 0, 0, 2, 1, 1, 0, + 2, 0, 0, 1, 0, 1, 0, 0, 1, 2, 1, 1, 1, 1, 0, 0, + 2, 1, 1, 1, 1, 2, 1, 0, 1, 1, 2, 2, 0, 1, 2, 0, + // Entry 40 - 7F + 1, 0, 1, 1, 1, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, + 2, 2, 2, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, + 0, 1, 0, 2, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 2, + // Entry 80 - BF + 0, 0, 2, 1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 2, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, + 0, 1, 1, 1, +} + +const ( + _Latn = 87 + _Hani = 54 + _Hans = 56 + _Hant = 57 + _Qaaa = 139 + _Qaai = 147 + _Qabx = 188 + _Zinh = 236 + _Zyyy = 241 + _Zzzz = 242 +) + +// script is an alphabetically sorted list of ISO 15924 codes. The index +// of the script in the string, divided by 4, is the internal scriptID. +const script tag.Index = "" + // Size: 976 bytes + "----AdlmAfakAghbAhomArabAranArmiArmnAvstBaliBamuBassBatkBengBhksBlisBopo" + + "BrahBraiBugiBuhdCakmCansCariChamCherCirtCoptCpmnCprtCyrlCyrsDevaDogrDsrt" + + "DuplEgydEgyhEgypElbaEthiGeokGeorGlagGongGonmGothGranGrekGujrGuruHanbHang" + + "HaniHanoHansHantHatrHebrHiraHluwHmngHmnpHrktHungIndsItalJamoJavaJpanJurc" + + "KaliKanaKharKhmrKhojKitlKitsKndaKoreKpelKthiLanaLaooLatfLatgLatnLekeLepc" + + "LimbLinaLinbLisuLomaLyciLydiMahjMakaMandManiMarcMayaMedfMendMercMeroMlym" + + "ModiMongMoonMrooMteiMultMymrNarbNbatNewaNkdbNkgbNkooNshuOgamOlckOrkhOrya" + + "OsgeOsmaPalmPaucPermPhagPhliPhlpPhlvPhnxPiqdPlrdPrtiQaaaQaabQaacQaadQaae" + + "QaafQaagQaahQaaiQaajQaakQaalQaamQaanQaaoQaapQaaqQaarQaasQaatQaauQaavQaaw" + + "QaaxQaayQaazQabaQabbQabcQabdQabeQabfQabgQabhQabiQabjQabkQablQabmQabnQabo" + + "QabpQabqQabrQabsQabtQabuQabvQabwQabxRjngRoroRunrSamrSaraSarbSaurSgnwShaw" + + "ShrdShuiSiddSindSinhSoraSoyoSundSyloSyrcSyreSyrjSyrnTagbTakrTaleTaluTaml" + + "TangTavtTeluTengTfngTglgThaaThaiTibtTirhUgarVaiiVispWaraWchoWoleXpeoXsux" + + "YiiiZanbZinhZmthZsyeZsymZxxxZyyyZzzz\xff\xff\xff\xff" + +// suppressScript is an index from langID to the dominant script for that language, +// if it exists. If a script is given, it should be suppressed from the language tag. +// Size: 1330 bytes, 1330 elements +var suppressScript = [1330]uint8{ + // Entry 0 - 3F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 40 - 7F + 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, + // Entry 80 - BF + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry C0 - FF + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 100 - 13F + 0x57, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xde, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x57, 0x00, 0x57, 0x00, + // Entry 140 - 17F + 0x57, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x00, 0x57, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x57, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 180 - 1BF + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x57, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x21, 0x00, + // Entry 1C0 - 1FF + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x57, 0x00, 0x57, 0x57, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x57, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, + // Entry 200 - 23F + 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 240 - 27F + 0x00, 0x00, 0x1f, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0x00, 0x00, 0x50, 0x00, 0x21, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 280 - 2BF + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 2C0 - 2FF + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, + // Entry 300 - 33F + 0x00, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x57, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, + // Entry 340 - 37F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x57, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x57, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 380 - 3BF + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, + // Entry 3C0 - 3FF + 0x57, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1f, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 400 - 43F + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, + // Entry 440 - 47F + 0x00, 0x00, 0x00, 0x00, 0x57, 0x57, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xda, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xdf, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x57, 0x00, + // Entry 480 - 4BF + 0x57, 0x00, 0x57, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x57, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 4C0 - 4FF + 0x57, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 500 - 53F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x00, +} + +const ( + _001 = 1 + _419 = 31 + _BR = 65 + _CA = 73 + _ES = 110 + _GB = 123 + _MD = 188 + _PT = 238 + _UK = 306 + _US = 309 + _ZZ = 357 + _XA = 323 + _XC = 325 + _XK = 333 +) + +// isoRegionOffset needs to be added to the index of regionISO to obtain the regionID +// for 2-letter ISO codes. (The first isoRegionOffset regionIDs are reserved for +// the UN.M49 codes used for groups.) +const isoRegionOffset = 32 + +// regionTypes defines the status of a region for various standards. +// Size: 358 bytes, 358 elements +var regionTypes = [358]uint8{ + // Entry 0 - 3F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + // Entry 40 - 7F + 0x06, 0x06, 0x06, 0x06, 0x04, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x04, 0x06, 0x04, + 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x04, + 0x06, 0x04, 0x06, 0x06, 0x06, 0x06, 0x00, 0x06, + 0x04, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x04, 0x06, 0x06, 0x06, 0x06, 0x06, 0x00, + 0x06, 0x04, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + // Entry 80 - BF + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x00, 0x04, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + // Entry C0 - FF + 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x00, + 0x06, 0x06, 0x06, 0x06, 0x00, 0x06, 0x04, 0x06, + 0x06, 0x06, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x00, + 0x06, 0x06, 0x00, 0x06, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + // Entry 100 - 13F + 0x05, 0x05, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x04, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x02, 0x06, 0x04, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, + // Entry 140 - 17F + 0x06, 0x00, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 0x06, 0x06, + 0x04, 0x06, 0x06, 0x04, 0x06, 0x05, +} + +// regionISO holds a list of alphabetically sorted 2-letter ISO region codes. +// Each 2-letter codes is followed by two bytes with the following meaning: +// - [A-Z}{2}: the first letter of the 2-letter code plus these two +// letters form the 3-letter ISO code. +// - 0, n: index into altRegionISO3. +const regionISO tag.Index = "" + // Size: 1308 bytes + "AAAAACSCADNDAEREAFFGAGTGAIIAALLBAMRMANNTAOGOAQTAARRGASSMATUTAUUSAWBWAXLA" + + "AZZEBAIHBBRBBDGDBEELBFFABGGRBHHRBIDIBJENBLLMBMMUBNRNBOOLBQESBRRABSHSBTTN" + + "BUURBVVTBWWABYLRBZLZCAANCCCKCDODCFAFCGOGCHHECIIVCKOKCLHLCMMRCNHNCOOLCPPT" + + "CRRICS\x00\x00CTTECUUBCVPVCWUWCXXRCYYPCZZEDDDRDEEUDGGADJJIDKNKDMMADOOMDY" + + "HYDZZAEA ECCUEESTEGGYEHSHERRIESSPETTHEU\x00\x03EZ FIINFJJIFKLKFMSMFORO" + + "FQ\x00\x18FRRAFXXXGAABGBBRGDRDGEEOGFUFGGGYGHHAGIIBGLRLGMMBGNINGPLPGQNQGR" + + "RCGS\x00\x06GTTMGUUMGWNBGYUYHKKGHMMDHNNDHRRVHTTIHUUNHVVOIC IDDNIERLILSR" + + "IMMNINNDIOOTIQRQIRRNISSLITTAJEEYJMAMJOORJPPNJTTNKEENKGGZKHHMKIIRKM\x00" + + "\x09KNNAKP\x00\x0cKRORKWWTKY\x00\x0fKZAZLAAOLBBNLCCALIIELKKALRBRLSSOLTTU" + + "LUUXLVVALYBYMAARMCCOMDDAMENEMFAFMGDGMHHLMIIDMKKDMLLIMMMRMNNGMOACMPNPMQTQ" + + "MRRTMSSRMTLTMUUSMVDVMWWIMXEXMYYSMZOZNAAMNCCLNEERNFFKNGGANHHBNIICNLLDNOOR" + + "NPPLNQ\x00\x1eNRRUNTTZNUIUNZZLOMMNPAANPCCIPEERPFYFPGNGPHHLPKAKPLOLPM\x00" + + "\x12PNCNPRRIPSSEPTRTPUUSPWLWPYRYPZCZQAATQMMMQNNNQOOOQPPPQQQQQRRRQSSSQTTT" + + "QU\x00\x03QVVVQWWWQXXXQYYYQZZZREEURHHOROOURS\x00\x15RUUSRWWASAAUSBLBSCYC" + + "SDDNSEWESGGPSHHNSIVNSJJMSKVKSLLESMMRSNENSOOMSRURSSSDSTTPSUUNSVLVSXXMSYYR" + + "SZWZTAAATCCATDCDTF\x00\x18TGGOTHHATJJKTKKLTLLSTMKMTNUNTOONTPMPTRURTTTOTV" + + "UVTWWNTZZAUAKRUGGAUK UMMIUN USSAUYRYUZZBVAATVCCTVDDRVEENVGGBVIIRVNNMVU" + + "UTWFLFWKAKWSSMXAAAXBBBXCCCXDDDXEEEXFFFXGGGXHHHXIIIXJJJXKKKXLLLXMMMXNNNXO" + + "OOXPPPXQQQXRRRXSSSXTTTXUUUXVVVXWWWXXXXXYYYXZZZYDMDYEEMYT\x00\x1bYUUGZAAF" + + "ZMMBZRARZWWEZZZZ\xff\xff\xff\xff" + +// altRegionISO3 holds a list of 3-letter region codes that cannot be +// mapped to 2-letter codes using the default algorithm. This is a short list. +const altRegionISO3 string = "SCGQUUSGSCOMPRKCYMSPMSRBATFMYTATN" + +// altRegionIDs holds a list of regionIDs the positions of which match those +// of the 3-letter ISO codes in altRegionISO3. +// Size: 22 bytes, 11 elements +var altRegionIDs = [11]uint16{ + 0x0057, 0x0070, 0x0088, 0x00a8, 0x00aa, 0x00ad, 0x00ea, 0x0105, + 0x0121, 0x015f, 0x00dc, +} + +// Size: 80 bytes, 20 elements +var regionOldMap = [20]fromTo{ + 0: {from: 0x44, to: 0xc4}, + 1: {from: 0x58, to: 0xa7}, + 2: {from: 0x5f, to: 0x60}, + 3: {from: 0x66, to: 0x3b}, + 4: {from: 0x79, to: 0x78}, + 5: {from: 0x93, to: 0x37}, + 6: {from: 0xa3, to: 0x133}, + 7: {from: 0xc1, to: 0x133}, + 8: {from: 0xd7, to: 0x13f}, + 9: {from: 0xdc, to: 0x2b}, + 10: {from: 0xef, to: 0x133}, + 11: {from: 0xf2, to: 0xe2}, + 12: {from: 0xfc, to: 0x70}, + 13: {from: 0x103, to: 0x164}, + 14: {from: 0x12a, to: 0x126}, + 15: {from: 0x132, to: 0x7b}, + 16: {from: 0x13a, to: 0x13e}, + 17: {from: 0x141, to: 0x133}, + 18: {from: 0x15d, to: 0x15e}, + 19: {from: 0x163, to: 0x4b}, +} + +// m49 maps regionIDs to UN.M49 codes. The first isoRegionOffset entries are +// codes indicating collections of regions. +// Size: 716 bytes, 358 elements +var m49 = [358]int16{ + // Entry 0 - 3F + 0, 1, 2, 3, 5, 9, 11, 13, + 14, 15, 17, 18, 19, 21, 29, 30, + 34, 35, 39, 53, 54, 57, 61, 142, + 143, 145, 150, 151, 154, 155, 202, 419, + 958, 0, 20, 784, 4, 28, 660, 8, + 51, 530, 24, 10, 32, 16, 40, 36, + 533, 248, 31, 70, 52, 50, 56, 854, + 100, 48, 108, 204, 652, 60, 96, 68, + // Entry 40 - 7F + 535, 76, 44, 64, 104, 74, 72, 112, + 84, 124, 166, 180, 140, 178, 756, 384, + 184, 152, 120, 156, 170, 0, 188, 891, + 296, 192, 132, 531, 162, 196, 203, 278, + 276, 0, 262, 208, 212, 214, 204, 12, + 0, 218, 233, 818, 732, 232, 724, 231, + 967, 0, 246, 242, 238, 583, 234, 0, + 250, 249, 266, 826, 308, 268, 254, 831, + // Entry 80 - BF + 288, 292, 304, 270, 324, 312, 226, 300, + 239, 320, 316, 624, 328, 344, 334, 340, + 191, 332, 348, 854, 0, 360, 372, 376, + 833, 356, 86, 368, 364, 352, 380, 832, + 388, 400, 392, 581, 404, 417, 116, 296, + 174, 659, 408, 410, 414, 136, 398, 418, + 422, 662, 438, 144, 430, 426, 440, 442, + 428, 434, 504, 492, 498, 499, 663, 450, + // Entry C0 - FF + 584, 581, 807, 466, 104, 496, 446, 580, + 474, 478, 500, 470, 480, 462, 454, 484, + 458, 508, 516, 540, 562, 574, 566, 548, + 558, 528, 578, 524, 10, 520, 536, 570, + 554, 512, 591, 0, 604, 258, 598, 608, + 586, 616, 666, 612, 630, 275, 620, 581, + 585, 600, 591, 634, 959, 960, 961, 962, + 963, 964, 965, 966, 967, 968, 969, 970, + // Entry 100 - 13F + 971, 972, 638, 716, 642, 688, 643, 646, + 682, 90, 690, 729, 752, 702, 654, 705, + 744, 703, 694, 674, 686, 706, 740, 728, + 678, 810, 222, 534, 760, 748, 0, 796, + 148, 260, 768, 764, 762, 772, 626, 795, + 788, 776, 626, 792, 780, 798, 158, 834, + 804, 800, 826, 581, 0, 840, 858, 860, + 336, 670, 704, 862, 92, 850, 704, 548, + // Entry 140 - 17F + 876, 581, 882, 973, 974, 975, 976, 977, + 978, 979, 980, 981, 982, 983, 984, 985, + 986, 987, 988, 989, 990, 991, 992, 993, + 994, 995, 996, 997, 998, 720, 887, 175, + 891, 710, 894, 180, 716, 999, +} + +// m49Index gives indexes into fromM49 based on the three most significant bits +// of a 10-bit UN.M49 code. To search an UN.M49 code in fromM49, search in +// fromM49[m49Index[msb39(code)]:m49Index[msb3(code)+1]] +// for an entry where the first 7 bits match the 7 lsb of the UN.M49 code. +// The region code is stored in the 9 lsb of the indexed value. +// Size: 18 bytes, 9 elements +var m49Index = [9]int16{ + 0, 59, 108, 143, 181, 220, 259, 291, + 333, +} + +// fromM49 contains entries to map UN.M49 codes to regions. See m49Index for details. +// Size: 666 bytes, 333 elements +var fromM49 = [333]uint16{ + // Entry 0 - 3F + 0x0201, 0x0402, 0x0603, 0x0824, 0x0a04, 0x1027, 0x1205, 0x142b, + 0x1606, 0x1867, 0x1a07, 0x1c08, 0x1e09, 0x202d, 0x220a, 0x240b, + 0x260c, 0x2822, 0x2a0d, 0x302a, 0x3825, 0x3a0e, 0x3c0f, 0x3e32, + 0x402c, 0x4410, 0x4611, 0x482f, 0x4e12, 0x502e, 0x5842, 0x6039, + 0x6435, 0x6628, 0x6834, 0x6a13, 0x6c14, 0x7036, 0x7215, 0x783d, + 0x7a16, 0x8043, 0x883f, 0x8c33, 0x9046, 0x9445, 0x9841, 0xa848, + 0xac9a, 0xb509, 0xb93c, 0xc03e, 0xc838, 0xd0c4, 0xd83a, 0xe047, + 0xe8a6, 0xf052, 0xf849, 0x085a, 0x10ad, 0x184c, 0x1c17, 0x1e18, + // Entry 40 - 7F + 0x20b3, 0x2219, 0x2920, 0x2c1a, 0x2e1b, 0x3051, 0x341c, 0x361d, + 0x3853, 0x3d2e, 0x445c, 0x4c4a, 0x5454, 0x5ca8, 0x5f5f, 0x644d, + 0x684b, 0x7050, 0x7856, 0x7e90, 0x8059, 0x885d, 0x941e, 0x965e, + 0x983b, 0xa063, 0xa864, 0xac65, 0xb469, 0xbd1a, 0xc486, 0xcc6f, + 0xce6f, 0xd06d, 0xd26a, 0xd476, 0xdc74, 0xde88, 0xe473, 0xec72, + 0xf031, 0xf279, 0xf478, 0xfc7e, 0x04e5, 0x0921, 0x0c62, 0x147a, + 0x187d, 0x1c83, 0x26ed, 0x2860, 0x2c5f, 0x3060, 0x4080, 0x4881, + 0x50a7, 0x5887, 0x6082, 0x687c, 0x7085, 0x788a, 0x8089, 0x8884, + // Entry 80 - BF + 0x908c, 0x9891, 0x9c8e, 0xa138, 0xa88f, 0xb08d, 0xb892, 0xc09d, + 0xc899, 0xd095, 0xd89c, 0xe09b, 0xe896, 0xf097, 0xf89e, 0x004f, + 0x08a0, 0x10a2, 0x1cae, 0x20a1, 0x28a4, 0x30aa, 0x34ab, 0x3cac, + 0x42a5, 0x44af, 0x461f, 0x4cb0, 0x54b5, 0x58b8, 0x5cb4, 0x64b9, + 0x6cb2, 0x70b6, 0x74b7, 0x7cc6, 0x84bf, 0x8cce, 0x94d0, 0x9ccd, + 0xa4c3, 0xaccb, 0xb4c8, 0xbcc9, 0xc0cc, 0xc8cf, 0xd8bb, 0xe0c5, + 0xe4bc, 0xe6bd, 0xe8ca, 0xf0ba, 0xf8d1, 0x00e1, 0x08d2, 0x10dd, + 0x18db, 0x20d9, 0x2429, 0x265b, 0x2a30, 0x2d1b, 0x2e40, 0x30de, + // Entry C0 - FF + 0x38d3, 0x493f, 0x54e0, 0x5cd8, 0x64d4, 0x6cd6, 0x74df, 0x7cd5, + 0x84da, 0x88c7, 0x8b33, 0x8e75, 0x90c0, 0x92f0, 0x94e8, 0x9ee2, + 0xace6, 0xb0f1, 0xb8e4, 0xc0e7, 0xc8eb, 0xd0e9, 0xd8ee, 0xe08b, + 0xe526, 0xecec, 0xf4f3, 0xfd02, 0x0504, 0x0706, 0x0d07, 0x183c, + 0x1d0e, 0x26a9, 0x2826, 0x2cb1, 0x2ebe, 0x34ea, 0x3d39, 0x4513, + 0x4d18, 0x5508, 0x5d14, 0x6105, 0x650a, 0x6d12, 0x7d0d, 0x7f11, + 0x813e, 0x830f, 0x8515, 0x8d61, 0x9964, 0xa15d, 0xa86e, 0xb117, + 0xb30b, 0xb86c, 0xc10b, 0xc916, 0xd110, 0xd91d, 0xe10c, 0xe84e, + // Entry 100 - 13F + 0xf11c, 0xf524, 0xf923, 0x0122, 0x0925, 0x1129, 0x192c, 0x2023, + 0x2928, 0x312b, 0x3727, 0x391f, 0x3d2d, 0x4131, 0x4930, 0x4ec2, + 0x5519, 0x646b, 0x747b, 0x7e7f, 0x809f, 0x8298, 0x852f, 0x9135, + 0xa53d, 0xac37, 0xb536, 0xb937, 0xbd3b, 0xd940, 0xe542, 0xed5e, + 0xef5e, 0xf657, 0xfd62, 0x7c20, 0x7ef4, 0x80f5, 0x82f6, 0x84f7, + 0x86f8, 0x88f9, 0x8afa, 0x8cfb, 0x8e70, 0x90fd, 0x92fe, 0x94ff, + 0x9700, 0x9901, 0x9b43, 0x9d44, 0x9f45, 0xa146, 0xa347, 0xa548, + 0xa749, 0xa94a, 0xab4b, 0xad4c, 0xaf4d, 0xb14e, 0xb34f, 0xb550, + // Entry 140 - 17F + 0xb751, 0xb952, 0xbb53, 0xbd54, 0xbf55, 0xc156, 0xc357, 0xc558, + 0xc759, 0xc95a, 0xcb5b, 0xcd5c, 0xcf65, +} + +// Size: 1615 bytes +var variantIndex = map[string]uint8{ + "1606nict": 0x0, + "1694acad": 0x1, + "1901": 0x2, + "1959acad": 0x3, + "1994": 0x4d, + "1996": 0x4, + "abl1943": 0x5, + "akuapem": 0x6, + "alalc97": 0x4f, + "aluku": 0x7, + "ao1990": 0x8, + "arevela": 0x9, + "arevmda": 0xa, + "asante": 0xb, + "baku1926": 0xc, + "balanka": 0xd, + "barla": 0xe, + "basiceng": 0xf, + "bauddha": 0x10, + "biscayan": 0x11, + "biske": 0x48, + "bohoric": 0x12, + "boont": 0x13, + "colb1945": 0x14, + "cornu": 0x15, + "dajnko": 0x16, + "ekavsk": 0x17, + "emodeng": 0x18, + "fonipa": 0x50, + "fonnapa": 0x51, + "fonupa": 0x52, + "fonxsamp": 0x53, + "hepburn": 0x19, + "heploc": 0x4e, + "hognorsk": 0x1a, + "hsistemo": 0x1b, + "ijekavsk": 0x1c, + "itihasa": 0x1d, + "jauer": 0x1e, + "jyutping": 0x1f, + "kkcor": 0x20, + "kociewie": 0x21, + "kscor": 0x22, + "laukika": 0x23, + "lipaw": 0x49, + "luna1918": 0x24, + "metelko": 0x25, + "monoton": 0x26, + "ndyuka": 0x27, + "nedis": 0x28, + "newfound": 0x29, + "njiva": 0x4a, + "nulik": 0x2a, + "osojs": 0x4b, + "oxendict": 0x2b, + "pahawh2": 0x2c, + "pahawh3": 0x2d, + "pahawh4": 0x2e, + "pamaka": 0x2f, + "petr1708": 0x30, + "pinyin": 0x31, + "polyton": 0x32, + "puter": 0x33, + "rigik": 0x34, + "rozaj": 0x35, + "rumgr": 0x36, + "scotland": 0x37, + "scouse": 0x38, + "simple": 0x54, + "solba": 0x4c, + "sotav": 0x39, + "spanglis": 0x3a, + "surmiran": 0x3b, + "sursilv": 0x3c, + "sutsilv": 0x3d, + "tarask": 0x3e, + "uccor": 0x3f, + "ucrcor": 0x40, + "ulster": 0x41, + "unifon": 0x42, + "vaidika": 0x43, + "valencia": 0x44, + "vallader": 0x45, + "wadegile": 0x46, + "xsistemo": 0x47, +} + +// variantNumSpecialized is the number of specialized variants in variants. +const variantNumSpecialized = 79 + +// nRegionGroups is the number of region groups. +const nRegionGroups = 33 + +type likelyLangRegion struct { + lang uint16 + region uint16 +} + +// likelyScript is a lookup table, indexed by scriptID, for the most likely +// languages and regions given a script. +// Size: 976 bytes, 244 elements +var likelyScript = [244]likelyLangRegion{ + 1: {lang: 0x14e, region: 0x84}, + 3: {lang: 0x2a2, region: 0x106}, + 4: {lang: 0x1f, region: 0x99}, + 5: {lang: 0x3a, region: 0x6b}, + 7: {lang: 0x3b, region: 0x9c}, + 8: {lang: 0x1d7, region: 0x28}, + 9: {lang: 0x13, region: 0x9c}, + 10: {lang: 0x5b, region: 0x95}, + 11: {lang: 0x60, region: 0x52}, + 12: {lang: 0xb9, region: 0xb4}, + 13: {lang: 0x63, region: 0x95}, + 14: {lang: 0xa5, region: 0x35}, + 15: {lang: 0x3e9, region: 0x99}, + 17: {lang: 0x529, region: 0x12e}, + 18: {lang: 0x3b1, region: 0x99}, + 19: {lang: 0x15e, region: 0x78}, + 20: {lang: 0xc2, region: 0x95}, + 21: {lang: 0x9d, region: 0xe7}, + 22: {lang: 0xdb, region: 0x35}, + 23: {lang: 0xf3, region: 0x49}, + 24: {lang: 0x4f0, region: 0x12b}, + 25: {lang: 0xe7, region: 0x13e}, + 26: {lang: 0xe5, region: 0x135}, + 28: {lang: 0xf1, region: 0x6b}, + 30: {lang: 0x1a0, region: 0x5d}, + 31: {lang: 0x3e2, region: 0x106}, + 33: {lang: 0x1be, region: 0x99}, + 36: {lang: 0x15e, region: 0x78}, + 39: {lang: 0x133, region: 0x6b}, + 40: {lang: 0x431, region: 0x27}, + 41: {lang: 0x27, region: 0x6f}, + 43: {lang: 0x210, region: 0x7d}, + 44: {lang: 0xfe, region: 0x38}, + 46: {lang: 0x19b, region: 0x99}, + 47: {lang: 0x19e, region: 0x130}, + 48: {lang: 0x3e9, region: 0x99}, + 49: {lang: 0x136, region: 0x87}, + 50: {lang: 0x1a4, region: 0x99}, + 51: {lang: 0x39d, region: 0x99}, + 52: {lang: 0x529, region: 0x12e}, + 53: {lang: 0x254, region: 0xab}, + 54: {lang: 0x529, region: 0x53}, + 55: {lang: 0x1cb, region: 0xe7}, + 56: {lang: 0x529, region: 0x53}, + 57: {lang: 0x529, region: 0x12e}, + 58: {lang: 0x2fd, region: 0x9b}, + 59: {lang: 0x1bc, region: 0x97}, + 60: {lang: 0x200, region: 0xa2}, + 61: {lang: 0x1c5, region: 0x12b}, + 62: {lang: 0x1ca, region: 0xaf}, + 65: {lang: 0x1d5, region: 0x92}, + 67: {lang: 0x142, region: 0x9e}, + 68: {lang: 0x254, region: 0xab}, + 69: {lang: 0x20e, region: 0x95}, + 70: {lang: 0x200, region: 0xa2}, + 72: {lang: 0x135, region: 0xc4}, + 73: {lang: 0x200, region: 0xa2}, + 74: {lang: 0x3bb, region: 0xe8}, + 75: {lang: 0x24a, region: 0xa6}, + 76: {lang: 0x3fa, region: 0x99}, + 79: {lang: 0x251, region: 0x99}, + 80: {lang: 0x254, region: 0xab}, + 82: {lang: 0x88, region: 0x99}, + 83: {lang: 0x370, region: 0x123}, + 84: {lang: 0x2b8, region: 0xaf}, + 89: {lang: 0x29f, region: 0x99}, + 90: {lang: 0x2a8, region: 0x99}, + 91: {lang: 0x28f, region: 0x87}, + 92: {lang: 0x1a0, region: 0x87}, + 93: {lang: 0x2ac, region: 0x53}, + 95: {lang: 0x4f4, region: 0x12b}, + 96: {lang: 0x4f5, region: 0x12b}, + 97: {lang: 0x1be, region: 0x99}, + 99: {lang: 0x337, region: 0x9c}, + 100: {lang: 0x4f7, region: 0x53}, + 101: {lang: 0xa9, region: 0x53}, + 104: {lang: 0x2e8, region: 0x112}, + 105: {lang: 0x4f8, region: 0x10b}, + 106: {lang: 0x4f8, region: 0x10b}, + 107: {lang: 0x304, region: 0x99}, + 108: {lang: 0x31b, region: 0x99}, + 109: {lang: 0x30b, region: 0x53}, + 111: {lang: 0x31e, region: 0x35}, + 112: {lang: 0x30e, region: 0x99}, + 113: {lang: 0x414, region: 0xe8}, + 114: {lang: 0x331, region: 0xc4}, + 115: {lang: 0x4f9, region: 0x108}, + 116: {lang: 0x3b, region: 0xa1}, + 117: {lang: 0x353, region: 0xdb}, + 120: {lang: 0x2d0, region: 0x84}, + 121: {lang: 0x52a, region: 0x53}, + 122: {lang: 0x403, region: 0x96}, + 123: {lang: 0x3ee, region: 0x99}, + 124: {lang: 0x39b, region: 0xc5}, + 125: {lang: 0x395, region: 0x99}, + 126: {lang: 0x399, region: 0x135}, + 127: {lang: 0x429, region: 0x115}, + 128: {lang: 0x3b, region: 0x11c}, + 129: {lang: 0xfd, region: 0xc4}, + 130: {lang: 0x27d, region: 0x106}, + 131: {lang: 0x2c9, region: 0x53}, + 132: {lang: 0x39f, region: 0x9c}, + 133: {lang: 0x39f, region: 0x53}, + 135: {lang: 0x3ad, region: 0xb0}, + 137: {lang: 0x1c6, region: 0x53}, + 138: {lang: 0x4fd, region: 0x9c}, + 189: {lang: 0x3cb, region: 0x95}, + 191: {lang: 0x372, region: 0x10c}, + 192: {lang: 0x420, region: 0x97}, + 194: {lang: 0x4ff, region: 0x15e}, + 195: {lang: 0x3f0, region: 0x99}, + 196: {lang: 0x45, region: 0x135}, + 197: {lang: 0x139, region: 0x7b}, + 198: {lang: 0x3e9, region: 0x99}, + 200: {lang: 0x3e9, region: 0x99}, + 201: {lang: 0x3fa, region: 0x99}, + 202: {lang: 0x40c, region: 0xb3}, + 203: {lang: 0x433, region: 0x99}, + 204: {lang: 0xef, region: 0xc5}, + 205: {lang: 0x43e, region: 0x95}, + 206: {lang: 0x44d, region: 0x35}, + 207: {lang: 0x44e, region: 0x9b}, + 211: {lang: 0x45a, region: 0xe7}, + 212: {lang: 0x11a, region: 0x99}, + 213: {lang: 0x45e, region: 0x53}, + 214: {lang: 0x232, region: 0x53}, + 215: {lang: 0x450, region: 0x99}, + 216: {lang: 0x4a5, region: 0x53}, + 217: {lang: 0x9f, region: 0x13e}, + 218: {lang: 0x461, region: 0x99}, + 220: {lang: 0x528, region: 0xba}, + 221: {lang: 0x153, region: 0xe7}, + 222: {lang: 0x128, region: 0xcd}, + 223: {lang: 0x46b, region: 0x123}, + 224: {lang: 0xa9, region: 0x53}, + 225: {lang: 0x2ce, region: 0x99}, + 226: {lang: 0x4ad, region: 0x11c}, + 227: {lang: 0x4be, region: 0xb4}, + 229: {lang: 0x1ce, region: 0x99}, + 232: {lang: 0x3a9, region: 0x9c}, + 233: {lang: 0x22, region: 0x9b}, + 234: {lang: 0x1ea, region: 0x53}, + 235: {lang: 0xef, region: 0xc5}, +} + +type likelyScriptRegion struct { + region uint16 + script uint8 + flags uint8 +} + +// likelyLang is a lookup table, indexed by langID, for the most likely +// scripts and regions given incomplete information. If more entries exist for a +// given language, region and script are the index and size respectively +// of the list in likelyLangList. +// Size: 5320 bytes, 1330 elements +var likelyLang = [1330]likelyScriptRegion{ + 0: {region: 0x135, script: 0x57, flags: 0x0}, + 1: {region: 0x6f, script: 0x57, flags: 0x0}, + 2: {region: 0x165, script: 0x57, flags: 0x0}, + 3: {region: 0x165, script: 0x57, flags: 0x0}, + 4: {region: 0x165, script: 0x57, flags: 0x0}, + 5: {region: 0x7d, script: 0x1f, flags: 0x0}, + 6: {region: 0x165, script: 0x57, flags: 0x0}, + 7: {region: 0x165, script: 0x1f, flags: 0x0}, + 8: {region: 0x80, script: 0x57, flags: 0x0}, + 9: {region: 0x165, script: 0x57, flags: 0x0}, + 10: {region: 0x165, script: 0x57, flags: 0x0}, + 11: {region: 0x165, script: 0x57, flags: 0x0}, + 12: {region: 0x95, script: 0x57, flags: 0x0}, + 13: {region: 0x131, script: 0x57, flags: 0x0}, + 14: {region: 0x80, script: 0x57, flags: 0x0}, + 15: {region: 0x165, script: 0x57, flags: 0x0}, + 16: {region: 0x165, script: 0x57, flags: 0x0}, + 17: {region: 0x106, script: 0x1f, flags: 0x0}, + 18: {region: 0x165, script: 0x57, flags: 0x0}, + 19: {region: 0x9c, script: 0x9, flags: 0x0}, + 20: {region: 0x128, script: 0x5, flags: 0x0}, + 21: {region: 0x165, script: 0x57, flags: 0x0}, + 22: {region: 0x161, script: 0x57, flags: 0x0}, + 23: {region: 0x165, script: 0x57, flags: 0x0}, + 24: {region: 0x165, script: 0x57, flags: 0x0}, + 25: {region: 0x165, script: 0x57, flags: 0x0}, + 26: {region: 0x165, script: 0x57, flags: 0x0}, + 27: {region: 0x165, script: 0x57, flags: 0x0}, + 28: {region: 0x52, script: 0x57, flags: 0x0}, + 29: {region: 0x165, script: 0x57, flags: 0x0}, + 30: {region: 0x165, script: 0x57, flags: 0x0}, + 31: {region: 0x99, script: 0x4, flags: 0x0}, + 32: {region: 0x165, script: 0x57, flags: 0x0}, + 33: {region: 0x80, script: 0x57, flags: 0x0}, + 34: {region: 0x9b, script: 0xe9, flags: 0x0}, + 35: {region: 0x165, script: 0x57, flags: 0x0}, + 36: {region: 0x165, script: 0x57, flags: 0x0}, + 37: {region: 0x14d, script: 0x57, flags: 0x0}, + 38: {region: 0x106, script: 0x1f, flags: 0x0}, + 39: {region: 0x6f, script: 0x29, flags: 0x0}, + 40: {region: 0x165, script: 0x57, flags: 0x0}, + 41: {region: 0x165, script: 0x57, flags: 0x0}, + 42: {region: 0xd6, script: 0x57, flags: 0x0}, + 43: {region: 0x165, script: 0x57, flags: 0x0}, + 45: {region: 0x165, script: 0x57, flags: 0x0}, + 46: {region: 0x165, script: 0x57, flags: 0x0}, + 47: {region: 0x165, script: 0x57, flags: 0x0}, + 48: {region: 0x165, script: 0x57, flags: 0x0}, + 49: {region: 0x165, script: 0x57, flags: 0x0}, + 50: {region: 0x165, script: 0x57, flags: 0x0}, + 51: {region: 0x95, script: 0x57, flags: 0x0}, + 52: {region: 0x165, script: 0x5, flags: 0x0}, + 53: {region: 0x122, script: 0x5, flags: 0x0}, + 54: {region: 0x165, script: 0x57, flags: 0x0}, + 55: {region: 0x165, script: 0x57, flags: 0x0}, + 56: {region: 0x165, script: 0x57, flags: 0x0}, + 57: {region: 0x165, script: 0x57, flags: 0x0}, + 58: {region: 0x6b, script: 0x5, flags: 0x0}, + 59: {region: 0x0, script: 0x3, flags: 0x1}, + 60: {region: 0x165, script: 0x57, flags: 0x0}, + 61: {region: 0x51, script: 0x57, flags: 0x0}, + 62: {region: 0x3f, script: 0x57, flags: 0x0}, + 63: {region: 0x67, script: 0x5, flags: 0x0}, + 65: {region: 0xba, script: 0x5, flags: 0x0}, + 66: {region: 0x6b, script: 0x5, flags: 0x0}, + 67: {region: 0x99, script: 0xe, flags: 0x0}, + 68: {region: 0x12f, script: 0x57, flags: 0x0}, + 69: {region: 0x135, script: 0xc4, flags: 0x0}, + 70: {region: 0x165, script: 0x57, flags: 0x0}, + 71: {region: 0x165, script: 0x57, flags: 0x0}, + 72: {region: 0x6e, script: 0x57, flags: 0x0}, + 73: {region: 0x165, script: 0x57, flags: 0x0}, + 74: {region: 0x165, script: 0x57, flags: 0x0}, + 75: {region: 0x49, script: 0x57, flags: 0x0}, + 76: {region: 0x165, script: 0x57, flags: 0x0}, + 77: {region: 0x106, script: 0x1f, flags: 0x0}, + 78: {region: 0x165, script: 0x5, flags: 0x0}, + 79: {region: 0x165, script: 0x57, flags: 0x0}, + 80: {region: 0x165, script: 0x57, flags: 0x0}, + 81: {region: 0x165, script: 0x57, flags: 0x0}, + 82: {region: 0x99, script: 0x21, flags: 0x0}, + 83: {region: 0x165, script: 0x57, flags: 0x0}, + 84: {region: 0x165, script: 0x57, flags: 0x0}, + 85: {region: 0x165, script: 0x57, flags: 0x0}, + 86: {region: 0x3f, script: 0x57, flags: 0x0}, + 87: {region: 0x165, script: 0x57, flags: 0x0}, + 88: {region: 0x3, script: 0x5, flags: 0x1}, + 89: {region: 0x106, script: 0x1f, flags: 0x0}, + 90: {region: 0xe8, script: 0x5, flags: 0x0}, + 91: {region: 0x95, script: 0x57, flags: 0x0}, + 92: {region: 0xdb, script: 0x21, flags: 0x0}, + 93: {region: 0x2e, script: 0x57, flags: 0x0}, + 94: {region: 0x52, script: 0x57, flags: 0x0}, + 95: {region: 0x165, script: 0x57, flags: 0x0}, + 96: {region: 0x52, script: 0xb, flags: 0x0}, + 97: {region: 0x165, script: 0x57, flags: 0x0}, + 98: {region: 0x165, script: 0x57, flags: 0x0}, + 99: {region: 0x95, script: 0x57, flags: 0x0}, + 100: {region: 0x165, script: 0x57, flags: 0x0}, + 101: {region: 0x52, script: 0x57, flags: 0x0}, + 102: {region: 0x165, script: 0x57, flags: 0x0}, + 103: {region: 0x165, script: 0x57, flags: 0x0}, + 104: {region: 0x165, script: 0x57, flags: 0x0}, + 105: {region: 0x165, script: 0x57, flags: 0x0}, + 106: {region: 0x4f, script: 0x57, flags: 0x0}, + 107: {region: 0x165, script: 0x57, flags: 0x0}, + 108: {region: 0x165, script: 0x57, flags: 0x0}, + 109: {region: 0x165, script: 0x57, flags: 0x0}, + 110: {region: 0x165, script: 0x29, flags: 0x0}, + 111: {region: 0x165, script: 0x57, flags: 0x0}, + 112: {region: 0x165, script: 0x57, flags: 0x0}, + 113: {region: 0x47, script: 0x1f, flags: 0x0}, + 114: {region: 0x165, script: 0x57, flags: 0x0}, + 115: {region: 0x165, script: 0x57, flags: 0x0}, + 116: {region: 0x10b, script: 0x5, flags: 0x0}, + 117: {region: 0x162, script: 0x57, flags: 0x0}, + 118: {region: 0x165, script: 0x57, flags: 0x0}, + 119: {region: 0x95, script: 0x57, flags: 0x0}, + 120: {region: 0x165, script: 0x57, flags: 0x0}, + 121: {region: 0x12f, script: 0x57, flags: 0x0}, + 122: {region: 0x52, script: 0x57, flags: 0x0}, + 123: {region: 0x99, script: 0xd7, flags: 0x0}, + 124: {region: 0xe8, script: 0x5, flags: 0x0}, + 125: {region: 0x99, script: 0x21, flags: 0x0}, + 126: {region: 0x38, script: 0x1f, flags: 0x0}, + 127: {region: 0x99, script: 0x21, flags: 0x0}, + 128: {region: 0xe8, script: 0x5, flags: 0x0}, + 129: {region: 0x12b, script: 0x31, flags: 0x0}, + 131: {region: 0x99, script: 0x21, flags: 0x0}, + 132: {region: 0x165, script: 0x57, flags: 0x0}, + 133: {region: 0x99, script: 0x21, flags: 0x0}, + 134: {region: 0xe7, script: 0x57, flags: 0x0}, + 135: {region: 0x165, script: 0x57, flags: 0x0}, + 136: {region: 0x99, script: 0x21, flags: 0x0}, + 137: {region: 0x165, script: 0x57, flags: 0x0}, + 138: {region: 0x13f, script: 0x57, flags: 0x0}, + 139: {region: 0x165, script: 0x57, flags: 0x0}, + 140: {region: 0x165, script: 0x57, flags: 0x0}, + 141: {region: 0xe7, script: 0x57, flags: 0x0}, + 142: {region: 0x165, script: 0x57, flags: 0x0}, + 143: {region: 0xd6, script: 0x57, flags: 0x0}, + 144: {region: 0x165, script: 0x57, flags: 0x0}, + 145: {region: 0x165, script: 0x57, flags: 0x0}, + 146: {region: 0x165, script: 0x57, flags: 0x0}, + 147: {region: 0x165, script: 0x29, flags: 0x0}, + 148: {region: 0x99, script: 0x21, flags: 0x0}, + 149: {region: 0x95, script: 0x57, flags: 0x0}, + 150: {region: 0x165, script: 0x57, flags: 0x0}, + 151: {region: 0x165, script: 0x57, flags: 0x0}, + 152: {region: 0x114, script: 0x57, flags: 0x0}, + 153: {region: 0x165, script: 0x57, flags: 0x0}, + 154: {region: 0x165, script: 0x57, flags: 0x0}, + 155: {region: 0x52, script: 0x57, flags: 0x0}, + 156: {region: 0x165, script: 0x57, flags: 0x0}, + 157: {region: 0xe7, script: 0x57, flags: 0x0}, + 158: {region: 0x165, script: 0x57, flags: 0x0}, + 159: {region: 0x13e, script: 0xd9, flags: 0x0}, + 160: {region: 0xc3, script: 0x57, flags: 0x0}, + 161: {region: 0x165, script: 0x57, flags: 0x0}, + 162: {region: 0x165, script: 0x57, flags: 0x0}, + 163: {region: 0xc3, script: 0x57, flags: 0x0}, + 164: {region: 0x165, script: 0x57, flags: 0x0}, + 165: {region: 0x35, script: 0xe, flags: 0x0}, + 166: {region: 0x165, script: 0x57, flags: 0x0}, + 167: {region: 0x165, script: 0x57, flags: 0x0}, + 168: {region: 0x165, script: 0x57, flags: 0x0}, + 169: {region: 0x53, script: 0xe0, flags: 0x0}, + 170: {region: 0x165, script: 0x57, flags: 0x0}, + 171: {region: 0x165, script: 0x57, flags: 0x0}, + 172: {region: 0x165, script: 0x57, flags: 0x0}, + 173: {region: 0x99, script: 0xe, flags: 0x0}, + 174: {region: 0x165, script: 0x57, flags: 0x0}, + 175: {region: 0x9c, script: 0x5, flags: 0x0}, + 176: {region: 0x165, script: 0x57, flags: 0x0}, + 177: {region: 0x4f, script: 0x57, flags: 0x0}, + 178: {region: 0x78, script: 0x57, flags: 0x0}, + 179: {region: 0x99, script: 0x21, flags: 0x0}, + 180: {region: 0xe8, script: 0x5, flags: 0x0}, + 181: {region: 0x99, script: 0x21, flags: 0x0}, + 182: {region: 0x165, script: 0x57, flags: 0x0}, + 183: {region: 0x33, script: 0x57, flags: 0x0}, + 184: {region: 0x165, script: 0x57, flags: 0x0}, + 185: {region: 0xb4, script: 0xc, flags: 0x0}, + 186: {region: 0x52, script: 0x57, flags: 0x0}, + 187: {region: 0x165, script: 0x29, flags: 0x0}, + 188: {region: 0xe7, script: 0x57, flags: 0x0}, + 189: {region: 0x165, script: 0x57, flags: 0x0}, + 190: {region: 0xe8, script: 0x21, flags: 0x0}, + 191: {region: 0x106, script: 0x1f, flags: 0x0}, + 192: {region: 0x15f, script: 0x57, flags: 0x0}, + 193: {region: 0x165, script: 0x57, flags: 0x0}, + 194: {region: 0x95, script: 0x57, flags: 0x0}, + 195: {region: 0x165, script: 0x57, flags: 0x0}, + 196: {region: 0x52, script: 0x57, flags: 0x0}, + 197: {region: 0x165, script: 0x57, flags: 0x0}, + 198: {region: 0x165, script: 0x57, flags: 0x0}, + 199: {region: 0x165, script: 0x57, flags: 0x0}, + 200: {region: 0x86, script: 0x57, flags: 0x0}, + 201: {region: 0x165, script: 0x57, flags: 0x0}, + 202: {region: 0x165, script: 0x57, flags: 0x0}, + 203: {region: 0x165, script: 0x57, flags: 0x0}, + 204: {region: 0x165, script: 0x57, flags: 0x0}, + 205: {region: 0x6d, script: 0x29, flags: 0x0}, + 206: {region: 0x165, script: 0x57, flags: 0x0}, + 207: {region: 0x165, script: 0x57, flags: 0x0}, + 208: {region: 0x52, script: 0x57, flags: 0x0}, + 209: {region: 0x165, script: 0x57, flags: 0x0}, + 210: {region: 0x165, script: 0x57, flags: 0x0}, + 211: {region: 0xc3, script: 0x57, flags: 0x0}, + 212: {region: 0x165, script: 0x57, flags: 0x0}, + 213: {region: 0x165, script: 0x57, flags: 0x0}, + 214: {region: 0x165, script: 0x57, flags: 0x0}, + 215: {region: 0x6e, script: 0x57, flags: 0x0}, + 216: {region: 0x165, script: 0x57, flags: 0x0}, + 217: {region: 0x165, script: 0x57, flags: 0x0}, + 218: {region: 0xd6, script: 0x57, flags: 0x0}, + 219: {region: 0x35, script: 0x16, flags: 0x0}, + 220: {region: 0x106, script: 0x1f, flags: 0x0}, + 221: {region: 0xe7, script: 0x57, flags: 0x0}, + 222: {region: 0x165, script: 0x57, flags: 0x0}, + 223: {region: 0x131, script: 0x57, flags: 0x0}, + 224: {region: 0x8a, script: 0x57, flags: 0x0}, + 225: {region: 0x75, script: 0x57, flags: 0x0}, + 226: {region: 0x106, script: 0x1f, flags: 0x0}, + 227: {region: 0x135, script: 0x57, flags: 0x0}, + 228: {region: 0x49, script: 0x57, flags: 0x0}, + 229: {region: 0x135, script: 0x1a, flags: 0x0}, + 230: {region: 0xa6, script: 0x5, flags: 0x0}, + 231: {region: 0x13e, script: 0x19, flags: 0x0}, + 232: {region: 0x165, script: 0x57, flags: 0x0}, + 233: {region: 0x9b, script: 0x5, flags: 0x0}, + 234: {region: 0x165, script: 0x57, flags: 0x0}, + 235: {region: 0x165, script: 0x57, flags: 0x0}, + 236: {region: 0x165, script: 0x57, flags: 0x0}, + 237: {region: 0x165, script: 0x57, flags: 0x0}, + 238: {region: 0x165, script: 0x57, flags: 0x0}, + 239: {region: 0xc5, script: 0xcc, flags: 0x0}, + 240: {region: 0x78, script: 0x57, flags: 0x0}, + 241: {region: 0x6b, script: 0x1c, flags: 0x0}, + 242: {region: 0xe7, script: 0x57, flags: 0x0}, + 243: {region: 0x49, script: 0x17, flags: 0x0}, + 244: {region: 0x130, script: 0x1f, flags: 0x0}, + 245: {region: 0x49, script: 0x17, flags: 0x0}, + 246: {region: 0x49, script: 0x17, flags: 0x0}, + 247: {region: 0x49, script: 0x17, flags: 0x0}, + 248: {region: 0x49, script: 0x17, flags: 0x0}, + 249: {region: 0x10a, script: 0x57, flags: 0x0}, + 250: {region: 0x5e, script: 0x57, flags: 0x0}, + 251: {region: 0xe9, script: 0x57, flags: 0x0}, + 252: {region: 0x49, script: 0x17, flags: 0x0}, + 253: {region: 0xc4, script: 0x81, flags: 0x0}, + 254: {region: 0x8, script: 0x2, flags: 0x1}, + 255: {region: 0x106, script: 0x1f, flags: 0x0}, + 256: {region: 0x7b, script: 0x57, flags: 0x0}, + 257: {region: 0x63, script: 0x57, flags: 0x0}, + 258: {region: 0x165, script: 0x57, flags: 0x0}, + 259: {region: 0x165, script: 0x57, flags: 0x0}, + 260: {region: 0x165, script: 0x57, flags: 0x0}, + 261: {region: 0x165, script: 0x57, flags: 0x0}, + 262: {region: 0x135, script: 0x57, flags: 0x0}, + 263: {region: 0x106, script: 0x1f, flags: 0x0}, + 264: {region: 0xa4, script: 0x57, flags: 0x0}, + 265: {region: 0x165, script: 0x57, flags: 0x0}, + 266: {region: 0x165, script: 0x57, flags: 0x0}, + 267: {region: 0x99, script: 0x5, flags: 0x0}, + 268: {region: 0x165, script: 0x57, flags: 0x0}, + 269: {region: 0x60, script: 0x57, flags: 0x0}, + 270: {region: 0x165, script: 0x57, flags: 0x0}, + 271: {region: 0x49, script: 0x57, flags: 0x0}, + 272: {region: 0x165, script: 0x57, flags: 0x0}, + 273: {region: 0x165, script: 0x57, flags: 0x0}, + 274: {region: 0x165, script: 0x57, flags: 0x0}, + 275: {region: 0x165, script: 0x5, flags: 0x0}, + 276: {region: 0x49, script: 0x57, flags: 0x0}, + 277: {region: 0x165, script: 0x57, flags: 0x0}, + 278: {region: 0x165, script: 0x57, flags: 0x0}, + 279: {region: 0xd4, script: 0x57, flags: 0x0}, + 280: {region: 0x4f, script: 0x57, flags: 0x0}, + 281: {region: 0x165, script: 0x57, flags: 0x0}, + 282: {region: 0x99, script: 0x5, flags: 0x0}, + 283: {region: 0x165, script: 0x57, flags: 0x0}, + 284: {region: 0x165, script: 0x57, flags: 0x0}, + 285: {region: 0x165, script: 0x57, flags: 0x0}, + 286: {region: 0x165, script: 0x29, flags: 0x0}, + 287: {region: 0x60, script: 0x57, flags: 0x0}, + 288: {region: 0xc3, script: 0x57, flags: 0x0}, + 289: {region: 0xd0, script: 0x57, flags: 0x0}, + 290: {region: 0x165, script: 0x57, flags: 0x0}, + 291: {region: 0xdb, script: 0x21, flags: 0x0}, + 292: {region: 0x52, script: 0x57, flags: 0x0}, + 293: {region: 0x165, script: 0x57, flags: 0x0}, + 294: {region: 0x165, script: 0x57, flags: 0x0}, + 295: {region: 0x165, script: 0x57, flags: 0x0}, + 296: {region: 0xcd, script: 0xde, flags: 0x0}, + 297: {region: 0x165, script: 0x57, flags: 0x0}, + 298: {region: 0x165, script: 0x57, flags: 0x0}, + 299: {region: 0x114, script: 0x57, flags: 0x0}, + 300: {region: 0x37, script: 0x57, flags: 0x0}, + 301: {region: 0x43, script: 0xe0, flags: 0x0}, + 302: {region: 0x165, script: 0x57, flags: 0x0}, + 303: {region: 0xa4, script: 0x57, flags: 0x0}, + 304: {region: 0x80, script: 0x57, flags: 0x0}, + 305: {region: 0xd6, script: 0x57, flags: 0x0}, + 306: {region: 0x9e, script: 0x57, flags: 0x0}, + 307: {region: 0x6b, script: 0x27, flags: 0x0}, + 308: {region: 0x165, script: 0x57, flags: 0x0}, + 309: {region: 0xc4, script: 0x48, flags: 0x0}, + 310: {region: 0x87, script: 0x31, flags: 0x0}, + 311: {region: 0x165, script: 0x57, flags: 0x0}, + 312: {region: 0x165, script: 0x57, flags: 0x0}, + 313: {region: 0xa, script: 0x2, flags: 0x1}, + 314: {region: 0x165, script: 0x57, flags: 0x0}, + 315: {region: 0x165, script: 0x57, flags: 0x0}, + 316: {region: 0x1, script: 0x57, flags: 0x0}, + 317: {region: 0x165, script: 0x57, flags: 0x0}, + 318: {region: 0x6e, script: 0x57, flags: 0x0}, + 319: {region: 0x135, script: 0x57, flags: 0x0}, + 320: {region: 0x6a, script: 0x57, flags: 0x0}, + 321: {region: 0x165, script: 0x57, flags: 0x0}, + 322: {region: 0x9e, script: 0x43, flags: 0x0}, + 323: {region: 0x165, script: 0x57, flags: 0x0}, + 324: {region: 0x165, script: 0x57, flags: 0x0}, + 325: {region: 0x6e, script: 0x57, flags: 0x0}, + 326: {region: 0x52, script: 0x57, flags: 0x0}, + 327: {region: 0x6e, script: 0x57, flags: 0x0}, + 328: {region: 0x9c, script: 0x5, flags: 0x0}, + 329: {region: 0x165, script: 0x57, flags: 0x0}, + 330: {region: 0x165, script: 0x57, flags: 0x0}, + 331: {region: 0x165, script: 0x57, flags: 0x0}, + 332: {region: 0x165, script: 0x57, flags: 0x0}, + 333: {region: 0x86, script: 0x57, flags: 0x0}, + 334: {region: 0xc, script: 0x2, flags: 0x1}, + 335: {region: 0x165, script: 0x57, flags: 0x0}, + 336: {region: 0xc3, script: 0x57, flags: 0x0}, + 337: {region: 0x72, script: 0x57, flags: 0x0}, + 338: {region: 0x10b, script: 0x5, flags: 0x0}, + 339: {region: 0xe7, script: 0x57, flags: 0x0}, + 340: {region: 0x10c, script: 0x57, flags: 0x0}, + 341: {region: 0x73, script: 0x57, flags: 0x0}, + 342: {region: 0x165, script: 0x57, flags: 0x0}, + 343: {region: 0x165, script: 0x57, flags: 0x0}, + 344: {region: 0x76, script: 0x57, flags: 0x0}, + 345: {region: 0x165, script: 0x57, flags: 0x0}, + 346: {region: 0x3b, script: 0x57, flags: 0x0}, + 347: {region: 0x165, script: 0x57, flags: 0x0}, + 348: {region: 0x165, script: 0x57, flags: 0x0}, + 349: {region: 0x165, script: 0x57, flags: 0x0}, + 350: {region: 0x78, script: 0x57, flags: 0x0}, + 351: {region: 0x135, script: 0x57, flags: 0x0}, + 352: {region: 0x78, script: 0x57, flags: 0x0}, + 353: {region: 0x60, script: 0x57, flags: 0x0}, + 354: {region: 0x60, script: 0x57, flags: 0x0}, + 355: {region: 0x52, script: 0x5, flags: 0x0}, + 356: {region: 0x140, script: 0x57, flags: 0x0}, + 357: {region: 0x165, script: 0x57, flags: 0x0}, + 358: {region: 0x84, script: 0x57, flags: 0x0}, + 359: {region: 0x165, script: 0x57, flags: 0x0}, + 360: {region: 0xd4, script: 0x57, flags: 0x0}, + 361: {region: 0x9e, script: 0x57, flags: 0x0}, + 362: {region: 0xd6, script: 0x57, flags: 0x0}, + 363: {region: 0x165, script: 0x57, flags: 0x0}, + 364: {region: 0x10b, script: 0x57, flags: 0x0}, + 365: {region: 0xd9, script: 0x57, flags: 0x0}, + 366: {region: 0x96, script: 0x57, flags: 0x0}, + 367: {region: 0x80, script: 0x57, flags: 0x0}, + 368: {region: 0x165, script: 0x57, flags: 0x0}, + 369: {region: 0xbc, script: 0x57, flags: 0x0}, + 370: {region: 0x165, script: 0x57, flags: 0x0}, + 371: {region: 0x165, script: 0x57, flags: 0x0}, + 372: {region: 0x165, script: 0x57, flags: 0x0}, + 373: {region: 0x53, script: 0x38, flags: 0x0}, + 374: {region: 0x165, script: 0x57, flags: 0x0}, + 375: {region: 0x95, script: 0x57, flags: 0x0}, + 376: {region: 0x165, script: 0x57, flags: 0x0}, + 377: {region: 0x165, script: 0x57, flags: 0x0}, + 378: {region: 0x99, script: 0x21, flags: 0x0}, + 379: {region: 0x165, script: 0x57, flags: 0x0}, + 380: {region: 0x9c, script: 0x5, flags: 0x0}, + 381: {region: 0x7e, script: 0x57, flags: 0x0}, + 382: {region: 0x7b, script: 0x57, flags: 0x0}, + 383: {region: 0x165, script: 0x57, flags: 0x0}, + 384: {region: 0x165, script: 0x57, flags: 0x0}, + 385: {region: 0x165, script: 0x57, flags: 0x0}, + 386: {region: 0x165, script: 0x57, flags: 0x0}, + 387: {region: 0x165, script: 0x57, flags: 0x0}, + 388: {region: 0x165, script: 0x57, flags: 0x0}, + 389: {region: 0x6f, script: 0x29, flags: 0x0}, + 390: {region: 0x165, script: 0x57, flags: 0x0}, + 391: {region: 0xdb, script: 0x21, flags: 0x0}, + 392: {region: 0x165, script: 0x57, flags: 0x0}, + 393: {region: 0xa7, script: 0x57, flags: 0x0}, + 394: {region: 0x165, script: 0x57, flags: 0x0}, + 395: {region: 0xe8, script: 0x5, flags: 0x0}, + 396: {region: 0x165, script: 0x57, flags: 0x0}, + 397: {region: 0xe8, script: 0x5, flags: 0x0}, + 398: {region: 0x165, script: 0x57, flags: 0x0}, + 399: {region: 0x165, script: 0x57, flags: 0x0}, + 400: {region: 0x6e, script: 0x57, flags: 0x0}, + 401: {region: 0x9c, script: 0x5, flags: 0x0}, + 402: {region: 0x165, script: 0x57, flags: 0x0}, + 403: {region: 0x165, script: 0x29, flags: 0x0}, + 404: {region: 0xf1, script: 0x57, flags: 0x0}, + 405: {region: 0x165, script: 0x57, flags: 0x0}, + 406: {region: 0x165, script: 0x57, flags: 0x0}, + 407: {region: 0x165, script: 0x57, flags: 0x0}, + 408: {region: 0x165, script: 0x29, flags: 0x0}, + 409: {region: 0x165, script: 0x57, flags: 0x0}, + 410: {region: 0x99, script: 0x21, flags: 0x0}, + 411: {region: 0x99, script: 0xda, flags: 0x0}, + 412: {region: 0x95, script: 0x57, flags: 0x0}, + 413: {region: 0xd9, script: 0x57, flags: 0x0}, + 414: {region: 0x130, script: 0x2f, flags: 0x0}, + 415: {region: 0x165, script: 0x57, flags: 0x0}, + 416: {region: 0xe, script: 0x2, flags: 0x1}, + 417: {region: 0x99, script: 0xe, flags: 0x0}, + 418: {region: 0x165, script: 0x57, flags: 0x0}, + 419: {region: 0x4e, script: 0x57, flags: 0x0}, + 420: {region: 0x99, script: 0x32, flags: 0x0}, + 421: {region: 0x41, script: 0x57, flags: 0x0}, + 422: {region: 0x54, script: 0x57, flags: 0x0}, + 423: {region: 0x165, script: 0x57, flags: 0x0}, + 424: {region: 0x80, script: 0x57, flags: 0x0}, + 425: {region: 0x165, script: 0x57, flags: 0x0}, + 426: {region: 0x165, script: 0x57, flags: 0x0}, + 427: {region: 0xa4, script: 0x57, flags: 0x0}, + 428: {region: 0x98, script: 0x57, flags: 0x0}, + 429: {region: 0x165, script: 0x57, flags: 0x0}, + 430: {region: 0xdb, script: 0x21, flags: 0x0}, + 431: {region: 0x165, script: 0x57, flags: 0x0}, + 432: {region: 0x165, script: 0x5, flags: 0x0}, + 433: {region: 0x49, script: 0x57, flags: 0x0}, + 434: {region: 0x165, script: 0x5, flags: 0x0}, + 435: {region: 0x165, script: 0x57, flags: 0x0}, + 436: {region: 0x10, script: 0x3, flags: 0x1}, + 437: {region: 0x165, script: 0x57, flags: 0x0}, + 438: {region: 0x53, script: 0x38, flags: 0x0}, + 439: {region: 0x165, script: 0x57, flags: 0x0}, + 440: {region: 0x135, script: 0x57, flags: 0x0}, + 441: {region: 0x24, script: 0x5, flags: 0x0}, + 442: {region: 0x165, script: 0x57, flags: 0x0}, + 443: {region: 0x165, script: 0x29, flags: 0x0}, + 444: {region: 0x97, script: 0x3b, flags: 0x0}, + 445: {region: 0x165, script: 0x57, flags: 0x0}, + 446: {region: 0x99, script: 0x21, flags: 0x0}, + 447: {region: 0x165, script: 0x57, flags: 0x0}, + 448: {region: 0x73, script: 0x57, flags: 0x0}, + 449: {region: 0x165, script: 0x57, flags: 0x0}, + 450: {region: 0x165, script: 0x57, flags: 0x0}, + 451: {region: 0xe7, script: 0x57, flags: 0x0}, + 452: {region: 0x165, script: 0x57, flags: 0x0}, + 453: {region: 0x12b, script: 0x3d, flags: 0x0}, + 454: {region: 0x53, script: 0x89, flags: 0x0}, + 455: {region: 0x165, script: 0x57, flags: 0x0}, + 456: {region: 0xe8, script: 0x5, flags: 0x0}, + 457: {region: 0x99, script: 0x21, flags: 0x0}, + 458: {region: 0xaf, script: 0x3e, flags: 0x0}, + 459: {region: 0xe7, script: 0x57, flags: 0x0}, + 460: {region: 0xe8, script: 0x5, flags: 0x0}, + 461: {region: 0xe6, script: 0x57, flags: 0x0}, + 462: {region: 0x99, script: 0x21, flags: 0x0}, + 463: {region: 0x99, script: 0x21, flags: 0x0}, + 464: {region: 0x165, script: 0x57, flags: 0x0}, + 465: {region: 0x90, script: 0x57, flags: 0x0}, + 466: {region: 0x60, script: 0x57, flags: 0x0}, + 467: {region: 0x53, script: 0x38, flags: 0x0}, + 468: {region: 0x91, script: 0x57, flags: 0x0}, + 469: {region: 0x92, script: 0x57, flags: 0x0}, + 470: {region: 0x165, script: 0x57, flags: 0x0}, + 471: {region: 0x28, script: 0x8, flags: 0x0}, + 472: {region: 0xd2, script: 0x57, flags: 0x0}, + 473: {region: 0x78, script: 0x57, flags: 0x0}, + 474: {region: 0x165, script: 0x57, flags: 0x0}, + 475: {region: 0x165, script: 0x57, flags: 0x0}, + 476: {region: 0xd0, script: 0x57, flags: 0x0}, + 477: {region: 0xd6, script: 0x57, flags: 0x0}, + 478: {region: 0x165, script: 0x57, flags: 0x0}, + 479: {region: 0x165, script: 0x57, flags: 0x0}, + 480: {region: 0x165, script: 0x57, flags: 0x0}, + 481: {region: 0x95, script: 0x57, flags: 0x0}, + 482: {region: 0x165, script: 0x57, flags: 0x0}, + 483: {region: 0x165, script: 0x57, flags: 0x0}, + 484: {region: 0x165, script: 0x57, flags: 0x0}, + 486: {region: 0x122, script: 0x57, flags: 0x0}, + 487: {region: 0xd6, script: 0x57, flags: 0x0}, + 488: {region: 0x165, script: 0x57, flags: 0x0}, + 489: {region: 0x165, script: 0x57, flags: 0x0}, + 490: {region: 0x53, script: 0xea, flags: 0x0}, + 491: {region: 0x165, script: 0x57, flags: 0x0}, + 492: {region: 0x135, script: 0x57, flags: 0x0}, + 493: {region: 0x165, script: 0x57, flags: 0x0}, + 494: {region: 0x49, script: 0x57, flags: 0x0}, + 495: {region: 0x165, script: 0x57, flags: 0x0}, + 496: {region: 0x165, script: 0x57, flags: 0x0}, + 497: {region: 0xe7, script: 0x57, flags: 0x0}, + 498: {region: 0x165, script: 0x57, flags: 0x0}, + 499: {region: 0x95, script: 0x57, flags: 0x0}, + 500: {region: 0x106, script: 0x1f, flags: 0x0}, + 501: {region: 0x1, script: 0x57, flags: 0x0}, + 502: {region: 0x165, script: 0x57, flags: 0x0}, + 503: {region: 0x165, script: 0x57, flags: 0x0}, + 504: {region: 0x9d, script: 0x57, flags: 0x0}, + 505: {region: 0x9e, script: 0x57, flags: 0x0}, + 506: {region: 0x49, script: 0x17, flags: 0x0}, + 507: {region: 0x97, script: 0x3b, flags: 0x0}, + 508: {region: 0x165, script: 0x57, flags: 0x0}, + 509: {region: 0x165, script: 0x57, flags: 0x0}, + 510: {region: 0x106, script: 0x57, flags: 0x0}, + 511: {region: 0x165, script: 0x57, flags: 0x0}, + 512: {region: 0xa2, script: 0x46, flags: 0x0}, + 513: {region: 0x165, script: 0x57, flags: 0x0}, + 514: {region: 0xa0, script: 0x57, flags: 0x0}, + 515: {region: 0x1, script: 0x57, flags: 0x0}, + 516: {region: 0x165, script: 0x57, flags: 0x0}, + 517: {region: 0x165, script: 0x57, flags: 0x0}, + 518: {region: 0x165, script: 0x57, flags: 0x0}, + 519: {region: 0x52, script: 0x57, flags: 0x0}, + 520: {region: 0x130, script: 0x3b, flags: 0x0}, + 521: {region: 0x165, script: 0x57, flags: 0x0}, + 522: {region: 0x12f, script: 0x57, flags: 0x0}, + 523: {region: 0xdb, script: 0x21, flags: 0x0}, + 524: {region: 0x165, script: 0x57, flags: 0x0}, + 525: {region: 0x63, script: 0x57, flags: 0x0}, + 526: {region: 0x95, script: 0x57, flags: 0x0}, + 527: {region: 0x95, script: 0x57, flags: 0x0}, + 528: {region: 0x7d, script: 0x2b, flags: 0x0}, + 529: {region: 0x137, script: 0x1f, flags: 0x0}, + 530: {region: 0x67, script: 0x57, flags: 0x0}, + 531: {region: 0xc4, script: 0x57, flags: 0x0}, + 532: {region: 0x165, script: 0x57, flags: 0x0}, + 533: {region: 0x165, script: 0x57, flags: 0x0}, + 534: {region: 0xd6, script: 0x57, flags: 0x0}, + 535: {region: 0xa4, script: 0x57, flags: 0x0}, + 536: {region: 0xc3, script: 0x57, flags: 0x0}, + 537: {region: 0x106, script: 0x1f, flags: 0x0}, + 538: {region: 0x165, script: 0x57, flags: 0x0}, + 539: {region: 0x165, script: 0x57, flags: 0x0}, + 540: {region: 0x165, script: 0x57, flags: 0x0}, + 541: {region: 0x165, script: 0x57, flags: 0x0}, + 542: {region: 0xd4, script: 0x5, flags: 0x0}, + 543: {region: 0xd6, script: 0x57, flags: 0x0}, + 544: {region: 0x164, script: 0x57, flags: 0x0}, + 545: {region: 0x165, script: 0x57, flags: 0x0}, + 546: {region: 0x165, script: 0x57, flags: 0x0}, + 547: {region: 0x12f, script: 0x57, flags: 0x0}, + 548: {region: 0x122, script: 0x5, flags: 0x0}, + 549: {region: 0x165, script: 0x57, flags: 0x0}, + 550: {region: 0x123, script: 0xdf, flags: 0x0}, + 551: {region: 0x5a, script: 0x57, flags: 0x0}, + 552: {region: 0x52, script: 0x57, flags: 0x0}, + 553: {region: 0x165, script: 0x57, flags: 0x0}, + 554: {region: 0x4f, script: 0x57, flags: 0x0}, + 555: {region: 0x99, script: 0x21, flags: 0x0}, + 556: {region: 0x99, script: 0x21, flags: 0x0}, + 557: {region: 0x4b, script: 0x57, flags: 0x0}, + 558: {region: 0x95, script: 0x57, flags: 0x0}, + 559: {region: 0x165, script: 0x57, flags: 0x0}, + 560: {region: 0x41, script: 0x57, flags: 0x0}, + 561: {region: 0x99, script: 0x57, flags: 0x0}, + 562: {region: 0x53, script: 0xd6, flags: 0x0}, + 563: {region: 0x99, script: 0x21, flags: 0x0}, + 564: {region: 0xc3, script: 0x57, flags: 0x0}, + 565: {region: 0x165, script: 0x57, flags: 0x0}, + 566: {region: 0x99, script: 0x72, flags: 0x0}, + 567: {region: 0xe8, script: 0x5, flags: 0x0}, + 568: {region: 0x165, script: 0x57, flags: 0x0}, + 569: {region: 0xa4, script: 0x57, flags: 0x0}, + 570: {region: 0x165, script: 0x57, flags: 0x0}, + 571: {region: 0x12b, script: 0x57, flags: 0x0}, + 572: {region: 0x165, script: 0x57, flags: 0x0}, + 573: {region: 0xd2, script: 0x57, flags: 0x0}, + 574: {region: 0x165, script: 0x57, flags: 0x0}, + 575: {region: 0xaf, script: 0x54, flags: 0x0}, + 576: {region: 0x165, script: 0x57, flags: 0x0}, + 577: {region: 0x165, script: 0x57, flags: 0x0}, + 578: {region: 0x13, script: 0x6, flags: 0x1}, + 579: {region: 0x165, script: 0x57, flags: 0x0}, + 580: {region: 0x52, script: 0x57, flags: 0x0}, + 581: {region: 0x82, script: 0x57, flags: 0x0}, + 582: {region: 0xa4, script: 0x57, flags: 0x0}, + 583: {region: 0x165, script: 0x57, flags: 0x0}, + 584: {region: 0x165, script: 0x57, flags: 0x0}, + 585: {region: 0x165, script: 0x57, flags: 0x0}, + 586: {region: 0xa6, script: 0x4b, flags: 0x0}, + 587: {region: 0x2a, script: 0x57, flags: 0x0}, + 588: {region: 0x165, script: 0x57, flags: 0x0}, + 589: {region: 0x165, script: 0x57, flags: 0x0}, + 590: {region: 0x165, script: 0x57, flags: 0x0}, + 591: {region: 0x165, script: 0x57, flags: 0x0}, + 592: {region: 0x165, script: 0x57, flags: 0x0}, + 593: {region: 0x99, script: 0x4f, flags: 0x0}, + 594: {region: 0x8b, script: 0x57, flags: 0x0}, + 595: {region: 0x165, script: 0x57, flags: 0x0}, + 596: {region: 0xab, script: 0x50, flags: 0x0}, + 597: {region: 0x106, script: 0x1f, flags: 0x0}, + 598: {region: 0x99, script: 0x21, flags: 0x0}, + 599: {region: 0x165, script: 0x57, flags: 0x0}, + 600: {region: 0x75, script: 0x57, flags: 0x0}, + 601: {region: 0x165, script: 0x57, flags: 0x0}, + 602: {region: 0xb4, script: 0x57, flags: 0x0}, + 603: {region: 0x165, script: 0x57, flags: 0x0}, + 604: {region: 0x165, script: 0x57, flags: 0x0}, + 605: {region: 0x165, script: 0x57, flags: 0x0}, + 606: {region: 0x165, script: 0x57, flags: 0x0}, + 607: {region: 0x165, script: 0x57, flags: 0x0}, + 608: {region: 0x165, script: 0x57, flags: 0x0}, + 609: {region: 0x165, script: 0x57, flags: 0x0}, + 610: {region: 0x165, script: 0x29, flags: 0x0}, + 611: {region: 0x165, script: 0x57, flags: 0x0}, + 612: {region: 0x106, script: 0x1f, flags: 0x0}, + 613: {region: 0x112, script: 0x57, flags: 0x0}, + 614: {region: 0xe7, script: 0x57, flags: 0x0}, + 615: {region: 0x106, script: 0x57, flags: 0x0}, + 616: {region: 0x165, script: 0x57, flags: 0x0}, + 617: {region: 0x99, script: 0x21, flags: 0x0}, + 618: {region: 0x99, script: 0x5, flags: 0x0}, + 619: {region: 0x12f, script: 0x57, flags: 0x0}, + 620: {region: 0x165, script: 0x57, flags: 0x0}, + 621: {region: 0x52, script: 0x57, flags: 0x0}, + 622: {region: 0x60, script: 0x57, flags: 0x0}, + 623: {region: 0x165, script: 0x57, flags: 0x0}, + 624: {region: 0x165, script: 0x57, flags: 0x0}, + 625: {region: 0x165, script: 0x29, flags: 0x0}, + 626: {region: 0x165, script: 0x57, flags: 0x0}, + 627: {region: 0x165, script: 0x57, flags: 0x0}, + 628: {region: 0x19, script: 0x3, flags: 0x1}, + 629: {region: 0x165, script: 0x57, flags: 0x0}, + 630: {region: 0x165, script: 0x57, flags: 0x0}, + 631: {region: 0x165, script: 0x57, flags: 0x0}, + 632: {region: 0x165, script: 0x57, flags: 0x0}, + 633: {region: 0x106, script: 0x1f, flags: 0x0}, + 634: {region: 0x165, script: 0x57, flags: 0x0}, + 635: {region: 0x165, script: 0x57, flags: 0x0}, + 636: {region: 0x165, script: 0x57, flags: 0x0}, + 637: {region: 0x106, script: 0x1f, flags: 0x0}, + 638: {region: 0x165, script: 0x57, flags: 0x0}, + 639: {region: 0x95, script: 0x57, flags: 0x0}, + 640: {region: 0xe8, script: 0x5, flags: 0x0}, + 641: {region: 0x7b, script: 0x57, flags: 0x0}, + 642: {region: 0x165, script: 0x57, flags: 0x0}, + 643: {region: 0x165, script: 0x57, flags: 0x0}, + 644: {region: 0x165, script: 0x57, flags: 0x0}, + 645: {region: 0x165, script: 0x29, flags: 0x0}, + 646: {region: 0x123, script: 0xdf, flags: 0x0}, + 647: {region: 0xe8, script: 0x5, flags: 0x0}, + 648: {region: 0x165, script: 0x57, flags: 0x0}, + 649: {region: 0x165, script: 0x57, flags: 0x0}, + 650: {region: 0x1c, script: 0x5, flags: 0x1}, + 651: {region: 0x165, script: 0x57, flags: 0x0}, + 652: {region: 0x165, script: 0x57, flags: 0x0}, + 653: {region: 0x165, script: 0x57, flags: 0x0}, + 654: {region: 0x138, script: 0x57, flags: 0x0}, + 655: {region: 0x87, script: 0x5b, flags: 0x0}, + 656: {region: 0x97, script: 0x3b, flags: 0x0}, + 657: {region: 0x12f, script: 0x57, flags: 0x0}, + 658: {region: 0xe8, script: 0x5, flags: 0x0}, + 659: {region: 0x131, script: 0x57, flags: 0x0}, + 660: {region: 0x165, script: 0x57, flags: 0x0}, + 661: {region: 0xb7, script: 0x57, flags: 0x0}, + 662: {region: 0x106, script: 0x1f, flags: 0x0}, + 663: {region: 0x165, script: 0x57, flags: 0x0}, + 664: {region: 0x95, script: 0x57, flags: 0x0}, + 665: {region: 0x165, script: 0x57, flags: 0x0}, + 666: {region: 0x53, script: 0xdf, flags: 0x0}, + 667: {region: 0x165, script: 0x57, flags: 0x0}, + 668: {region: 0x165, script: 0x57, flags: 0x0}, + 669: {region: 0x165, script: 0x57, flags: 0x0}, + 670: {region: 0x165, script: 0x57, flags: 0x0}, + 671: {region: 0x99, script: 0x59, flags: 0x0}, + 672: {region: 0x165, script: 0x57, flags: 0x0}, + 673: {region: 0x165, script: 0x57, flags: 0x0}, + 674: {region: 0x106, script: 0x1f, flags: 0x0}, + 675: {region: 0x131, script: 0x57, flags: 0x0}, + 676: {region: 0x165, script: 0x57, flags: 0x0}, + 677: {region: 0xd9, script: 0x57, flags: 0x0}, + 678: {region: 0x165, script: 0x57, flags: 0x0}, + 679: {region: 0x165, script: 0x57, flags: 0x0}, + 680: {region: 0x21, script: 0x2, flags: 0x1}, + 681: {region: 0x165, script: 0x57, flags: 0x0}, + 682: {region: 0x165, script: 0x57, flags: 0x0}, + 683: {region: 0x9e, script: 0x57, flags: 0x0}, + 684: {region: 0x53, script: 0x5d, flags: 0x0}, + 685: {region: 0x95, script: 0x57, flags: 0x0}, + 686: {region: 0x9c, script: 0x5, flags: 0x0}, + 687: {region: 0x135, script: 0x57, flags: 0x0}, + 688: {region: 0x165, script: 0x57, flags: 0x0}, + 689: {region: 0x165, script: 0x57, flags: 0x0}, + 690: {region: 0x99, script: 0xda, flags: 0x0}, + 691: {region: 0x9e, script: 0x57, flags: 0x0}, + 692: {region: 0x165, script: 0x57, flags: 0x0}, + 693: {region: 0x4b, script: 0x57, flags: 0x0}, + 694: {region: 0x165, script: 0x57, flags: 0x0}, + 695: {region: 0x165, script: 0x57, flags: 0x0}, + 696: {region: 0xaf, script: 0x54, flags: 0x0}, + 697: {region: 0x165, script: 0x57, flags: 0x0}, + 698: {region: 0x165, script: 0x57, flags: 0x0}, + 699: {region: 0x4b, script: 0x57, flags: 0x0}, + 700: {region: 0x165, script: 0x57, flags: 0x0}, + 701: {region: 0x165, script: 0x57, flags: 0x0}, + 702: {region: 0x162, script: 0x57, flags: 0x0}, + 703: {region: 0x9c, script: 0x5, flags: 0x0}, + 704: {region: 0xb6, script: 0x57, flags: 0x0}, + 705: {region: 0xb8, script: 0x57, flags: 0x0}, + 706: {region: 0x4b, script: 0x57, flags: 0x0}, + 707: {region: 0x4b, script: 0x57, flags: 0x0}, + 708: {region: 0xa4, script: 0x57, flags: 0x0}, + 709: {region: 0xa4, script: 0x57, flags: 0x0}, + 710: {region: 0x9c, script: 0x5, flags: 0x0}, + 711: {region: 0xb8, script: 0x57, flags: 0x0}, + 712: {region: 0x123, script: 0xdf, flags: 0x0}, + 713: {region: 0x53, script: 0x38, flags: 0x0}, + 714: {region: 0x12b, script: 0x57, flags: 0x0}, + 715: {region: 0x95, script: 0x57, flags: 0x0}, + 716: {region: 0x52, script: 0x57, flags: 0x0}, + 717: {region: 0x99, script: 0x21, flags: 0x0}, + 718: {region: 0x99, script: 0x21, flags: 0x0}, + 719: {region: 0x95, script: 0x57, flags: 0x0}, + 720: {region: 0x23, script: 0x3, flags: 0x1}, + 721: {region: 0xa4, script: 0x57, flags: 0x0}, + 722: {region: 0x165, script: 0x57, flags: 0x0}, + 723: {region: 0xcf, script: 0x57, flags: 0x0}, + 724: {region: 0x165, script: 0x57, flags: 0x0}, + 725: {region: 0x165, script: 0x57, flags: 0x0}, + 726: {region: 0x165, script: 0x57, flags: 0x0}, + 727: {region: 0x165, script: 0x57, flags: 0x0}, + 728: {region: 0x165, script: 0x57, flags: 0x0}, + 729: {region: 0x165, script: 0x57, flags: 0x0}, + 730: {region: 0x165, script: 0x57, flags: 0x0}, + 731: {region: 0x165, script: 0x57, flags: 0x0}, + 732: {region: 0x165, script: 0x57, flags: 0x0}, + 733: {region: 0x165, script: 0x57, flags: 0x0}, + 734: {region: 0x165, script: 0x57, flags: 0x0}, + 735: {region: 0x165, script: 0x5, flags: 0x0}, + 736: {region: 0x106, script: 0x1f, flags: 0x0}, + 737: {region: 0xe7, script: 0x57, flags: 0x0}, + 738: {region: 0x165, script: 0x57, flags: 0x0}, + 739: {region: 0x95, script: 0x57, flags: 0x0}, + 740: {region: 0x165, script: 0x29, flags: 0x0}, + 741: {region: 0x165, script: 0x57, flags: 0x0}, + 742: {region: 0x165, script: 0x57, flags: 0x0}, + 743: {region: 0x165, script: 0x57, flags: 0x0}, + 744: {region: 0x112, script: 0x57, flags: 0x0}, + 745: {region: 0xa4, script: 0x57, flags: 0x0}, + 746: {region: 0x165, script: 0x57, flags: 0x0}, + 747: {region: 0x165, script: 0x57, flags: 0x0}, + 748: {region: 0x123, script: 0x5, flags: 0x0}, + 749: {region: 0xcc, script: 0x57, flags: 0x0}, + 750: {region: 0x165, script: 0x57, flags: 0x0}, + 751: {region: 0x165, script: 0x57, flags: 0x0}, + 752: {region: 0x165, script: 0x57, flags: 0x0}, + 753: {region: 0xbf, script: 0x57, flags: 0x0}, + 754: {region: 0xd1, script: 0x57, flags: 0x0}, + 755: {region: 0x165, script: 0x57, flags: 0x0}, + 756: {region: 0x52, script: 0x57, flags: 0x0}, + 757: {region: 0xdb, script: 0x21, flags: 0x0}, + 758: {region: 0x12f, script: 0x57, flags: 0x0}, + 759: {region: 0xc0, script: 0x57, flags: 0x0}, + 760: {region: 0x165, script: 0x57, flags: 0x0}, + 761: {region: 0x165, script: 0x57, flags: 0x0}, + 762: {region: 0xe0, script: 0x57, flags: 0x0}, + 763: {region: 0x165, script: 0x57, flags: 0x0}, + 764: {region: 0x95, script: 0x57, flags: 0x0}, + 765: {region: 0x9b, script: 0x3a, flags: 0x0}, + 766: {region: 0x165, script: 0x57, flags: 0x0}, + 767: {region: 0xc2, script: 0x1f, flags: 0x0}, + 768: {region: 0x165, script: 0x5, flags: 0x0}, + 769: {region: 0x165, script: 0x57, flags: 0x0}, + 770: {region: 0x165, script: 0x57, flags: 0x0}, + 771: {region: 0x165, script: 0x57, flags: 0x0}, + 772: {region: 0x99, script: 0x6b, flags: 0x0}, + 773: {region: 0x165, script: 0x57, flags: 0x0}, + 774: {region: 0x165, script: 0x57, flags: 0x0}, + 775: {region: 0x10b, script: 0x57, flags: 0x0}, + 776: {region: 0x165, script: 0x57, flags: 0x0}, + 777: {region: 0x165, script: 0x57, flags: 0x0}, + 778: {region: 0x165, script: 0x57, flags: 0x0}, + 779: {region: 0x26, script: 0x3, flags: 0x1}, + 780: {region: 0x165, script: 0x57, flags: 0x0}, + 781: {region: 0x165, script: 0x57, flags: 0x0}, + 782: {region: 0x99, script: 0xe, flags: 0x0}, + 783: {region: 0xc4, script: 0x72, flags: 0x0}, + 785: {region: 0x165, script: 0x57, flags: 0x0}, + 786: {region: 0x49, script: 0x57, flags: 0x0}, + 787: {region: 0x49, script: 0x57, flags: 0x0}, + 788: {region: 0x37, script: 0x57, flags: 0x0}, + 789: {region: 0x165, script: 0x57, flags: 0x0}, + 790: {region: 0x165, script: 0x57, flags: 0x0}, + 791: {region: 0x165, script: 0x57, flags: 0x0}, + 792: {region: 0x165, script: 0x57, flags: 0x0}, + 793: {region: 0x165, script: 0x57, flags: 0x0}, + 794: {region: 0x165, script: 0x57, flags: 0x0}, + 795: {region: 0x99, script: 0x21, flags: 0x0}, + 796: {region: 0xdb, script: 0x21, flags: 0x0}, + 797: {region: 0x106, script: 0x1f, flags: 0x0}, + 798: {region: 0x35, script: 0x6f, flags: 0x0}, + 799: {region: 0x29, script: 0x3, flags: 0x1}, + 800: {region: 0xcb, script: 0x57, flags: 0x0}, + 801: {region: 0x165, script: 0x57, flags: 0x0}, + 802: {region: 0x165, script: 0x57, flags: 0x0}, + 803: {region: 0x165, script: 0x57, flags: 0x0}, + 804: {region: 0x99, script: 0x21, flags: 0x0}, + 805: {region: 0x52, script: 0x57, flags: 0x0}, + 807: {region: 0x165, script: 0x57, flags: 0x0}, + 808: {region: 0x135, script: 0x57, flags: 0x0}, + 809: {region: 0x165, script: 0x57, flags: 0x0}, + 810: {region: 0x165, script: 0x57, flags: 0x0}, + 811: {region: 0xe8, script: 0x5, flags: 0x0}, + 812: {region: 0xc3, script: 0x57, flags: 0x0}, + 813: {region: 0x99, script: 0x21, flags: 0x0}, + 814: {region: 0x95, script: 0x57, flags: 0x0}, + 815: {region: 0x164, script: 0x57, flags: 0x0}, + 816: {region: 0x165, script: 0x57, flags: 0x0}, + 817: {region: 0xc4, script: 0x72, flags: 0x0}, + 818: {region: 0x165, script: 0x57, flags: 0x0}, + 819: {region: 0x165, script: 0x29, flags: 0x0}, + 820: {region: 0x106, script: 0x1f, flags: 0x0}, + 821: {region: 0x165, script: 0x57, flags: 0x0}, + 822: {region: 0x131, script: 0x57, flags: 0x0}, + 823: {region: 0x9c, script: 0x63, flags: 0x0}, + 824: {region: 0x165, script: 0x57, flags: 0x0}, + 825: {region: 0x165, script: 0x57, flags: 0x0}, + 826: {region: 0x9c, script: 0x5, flags: 0x0}, + 827: {region: 0x165, script: 0x57, flags: 0x0}, + 828: {region: 0x165, script: 0x57, flags: 0x0}, + 829: {region: 0x165, script: 0x57, flags: 0x0}, + 830: {region: 0xdd, script: 0x57, flags: 0x0}, + 831: {region: 0x165, script: 0x57, flags: 0x0}, + 832: {region: 0x165, script: 0x57, flags: 0x0}, + 834: {region: 0x165, script: 0x57, flags: 0x0}, + 835: {region: 0x53, script: 0x38, flags: 0x0}, + 836: {region: 0x9e, script: 0x57, flags: 0x0}, + 837: {region: 0xd2, script: 0x57, flags: 0x0}, + 838: {region: 0x165, script: 0x57, flags: 0x0}, + 839: {region: 0xda, script: 0x57, flags: 0x0}, + 840: {region: 0x165, script: 0x57, flags: 0x0}, + 841: {region: 0x165, script: 0x57, flags: 0x0}, + 842: {region: 0x165, script: 0x57, flags: 0x0}, + 843: {region: 0xcf, script: 0x57, flags: 0x0}, + 844: {region: 0x165, script: 0x57, flags: 0x0}, + 845: {region: 0x165, script: 0x57, flags: 0x0}, + 846: {region: 0x164, script: 0x57, flags: 0x0}, + 847: {region: 0xd1, script: 0x57, flags: 0x0}, + 848: {region: 0x60, script: 0x57, flags: 0x0}, + 849: {region: 0xdb, script: 0x21, flags: 0x0}, + 850: {region: 0x165, script: 0x57, flags: 0x0}, + 851: {region: 0xdb, script: 0x21, flags: 0x0}, + 852: {region: 0x165, script: 0x57, flags: 0x0}, + 853: {region: 0x165, script: 0x57, flags: 0x0}, + 854: {region: 0xd2, script: 0x57, flags: 0x0}, + 855: {region: 0x165, script: 0x57, flags: 0x0}, + 856: {region: 0x165, script: 0x57, flags: 0x0}, + 857: {region: 0xd1, script: 0x57, flags: 0x0}, + 858: {region: 0x165, script: 0x57, flags: 0x0}, + 859: {region: 0xcf, script: 0x57, flags: 0x0}, + 860: {region: 0xcf, script: 0x57, flags: 0x0}, + 861: {region: 0x165, script: 0x57, flags: 0x0}, + 862: {region: 0x165, script: 0x57, flags: 0x0}, + 863: {region: 0x95, script: 0x57, flags: 0x0}, + 864: {region: 0x165, script: 0x57, flags: 0x0}, + 865: {region: 0xdf, script: 0x57, flags: 0x0}, + 866: {region: 0x165, script: 0x57, flags: 0x0}, + 867: {region: 0x165, script: 0x57, flags: 0x0}, + 868: {region: 0x99, script: 0x57, flags: 0x0}, + 869: {region: 0x165, script: 0x57, flags: 0x0}, + 870: {region: 0x165, script: 0x57, flags: 0x0}, + 871: {region: 0xd9, script: 0x57, flags: 0x0}, + 872: {region: 0x52, script: 0x57, flags: 0x0}, + 873: {region: 0x165, script: 0x57, flags: 0x0}, + 874: {region: 0xda, script: 0x57, flags: 0x0}, + 875: {region: 0x165, script: 0x57, flags: 0x0}, + 876: {region: 0x52, script: 0x57, flags: 0x0}, + 877: {region: 0x165, script: 0x57, flags: 0x0}, + 878: {region: 0x165, script: 0x57, flags: 0x0}, + 879: {region: 0xda, script: 0x57, flags: 0x0}, + 880: {region: 0x123, script: 0x53, flags: 0x0}, + 881: {region: 0x99, script: 0x21, flags: 0x0}, + 882: {region: 0x10c, script: 0xbf, flags: 0x0}, + 883: {region: 0x165, script: 0x57, flags: 0x0}, + 884: {region: 0x165, script: 0x57, flags: 0x0}, + 885: {region: 0x84, script: 0x78, flags: 0x0}, + 886: {region: 0x161, script: 0x57, flags: 0x0}, + 887: {region: 0x165, script: 0x57, flags: 0x0}, + 888: {region: 0x49, script: 0x17, flags: 0x0}, + 889: {region: 0x165, script: 0x57, flags: 0x0}, + 890: {region: 0x161, script: 0x57, flags: 0x0}, + 891: {region: 0x165, script: 0x57, flags: 0x0}, + 892: {region: 0x165, script: 0x57, flags: 0x0}, + 893: {region: 0x165, script: 0x57, flags: 0x0}, + 894: {region: 0x165, script: 0x57, flags: 0x0}, + 895: {region: 0x165, script: 0x57, flags: 0x0}, + 896: {region: 0x117, script: 0x57, flags: 0x0}, + 897: {region: 0x165, script: 0x57, flags: 0x0}, + 898: {region: 0x165, script: 0x57, flags: 0x0}, + 899: {region: 0x135, script: 0x57, flags: 0x0}, + 900: {region: 0x165, script: 0x57, flags: 0x0}, + 901: {region: 0x53, script: 0x57, flags: 0x0}, + 902: {region: 0x165, script: 0x57, flags: 0x0}, + 903: {region: 0xce, script: 0x57, flags: 0x0}, + 904: {region: 0x12f, script: 0x57, flags: 0x0}, + 905: {region: 0x131, script: 0x57, flags: 0x0}, + 906: {region: 0x80, script: 0x57, flags: 0x0}, + 907: {region: 0x78, script: 0x57, flags: 0x0}, + 908: {region: 0x165, script: 0x57, flags: 0x0}, + 910: {region: 0x165, script: 0x57, flags: 0x0}, + 911: {region: 0x165, script: 0x57, flags: 0x0}, + 912: {region: 0x6f, script: 0x57, flags: 0x0}, + 913: {region: 0x165, script: 0x57, flags: 0x0}, + 914: {region: 0x165, script: 0x57, flags: 0x0}, + 915: {region: 0x165, script: 0x57, flags: 0x0}, + 916: {region: 0x165, script: 0x57, flags: 0x0}, + 917: {region: 0x99, script: 0x7d, flags: 0x0}, + 918: {region: 0x165, script: 0x57, flags: 0x0}, + 919: {region: 0x165, script: 0x5, flags: 0x0}, + 920: {region: 0x7d, script: 0x1f, flags: 0x0}, + 921: {region: 0x135, script: 0x7e, flags: 0x0}, + 922: {region: 0x165, script: 0x5, flags: 0x0}, + 923: {region: 0xc5, script: 0x7c, flags: 0x0}, + 924: {region: 0x165, script: 0x57, flags: 0x0}, + 925: {region: 0x2c, script: 0x3, flags: 0x1}, + 926: {region: 0xe7, script: 0x57, flags: 0x0}, + 927: {region: 0x2f, script: 0x2, flags: 0x1}, + 928: {region: 0xe7, script: 0x57, flags: 0x0}, + 929: {region: 0x30, script: 0x57, flags: 0x0}, + 930: {region: 0xf0, script: 0x57, flags: 0x0}, + 931: {region: 0x165, script: 0x57, flags: 0x0}, + 932: {region: 0x78, script: 0x57, flags: 0x0}, + 933: {region: 0xd6, script: 0x57, flags: 0x0}, + 934: {region: 0x135, script: 0x57, flags: 0x0}, + 935: {region: 0x49, script: 0x57, flags: 0x0}, + 936: {region: 0x165, script: 0x57, flags: 0x0}, + 937: {region: 0x9c, script: 0xe8, flags: 0x0}, + 938: {region: 0x165, script: 0x57, flags: 0x0}, + 939: {region: 0x60, script: 0x57, flags: 0x0}, + 940: {region: 0x165, script: 0x5, flags: 0x0}, + 941: {region: 0xb0, script: 0x87, flags: 0x0}, + 943: {region: 0x165, script: 0x57, flags: 0x0}, + 944: {region: 0x165, script: 0x57, flags: 0x0}, + 945: {region: 0x99, script: 0x12, flags: 0x0}, + 946: {region: 0xa4, script: 0x57, flags: 0x0}, + 947: {region: 0xe9, script: 0x57, flags: 0x0}, + 948: {region: 0x165, script: 0x57, flags: 0x0}, + 949: {region: 0x9e, script: 0x57, flags: 0x0}, + 950: {region: 0x165, script: 0x57, flags: 0x0}, + 951: {region: 0x165, script: 0x57, flags: 0x0}, + 952: {region: 0x87, script: 0x31, flags: 0x0}, + 953: {region: 0x75, script: 0x57, flags: 0x0}, + 954: {region: 0x165, script: 0x57, flags: 0x0}, + 955: {region: 0xe8, script: 0x4a, flags: 0x0}, + 956: {region: 0x9c, script: 0x5, flags: 0x0}, + 957: {region: 0x1, script: 0x57, flags: 0x0}, + 958: {region: 0x24, script: 0x5, flags: 0x0}, + 959: {region: 0x165, script: 0x57, flags: 0x0}, + 960: {region: 0x41, script: 0x57, flags: 0x0}, + 961: {region: 0x165, script: 0x57, flags: 0x0}, + 962: {region: 0x7a, script: 0x57, flags: 0x0}, + 963: {region: 0x165, script: 0x57, flags: 0x0}, + 964: {region: 0xe4, script: 0x57, flags: 0x0}, + 965: {region: 0x89, script: 0x57, flags: 0x0}, + 966: {region: 0x69, script: 0x57, flags: 0x0}, + 967: {region: 0x165, script: 0x57, flags: 0x0}, + 968: {region: 0x99, script: 0x21, flags: 0x0}, + 969: {region: 0x165, script: 0x57, flags: 0x0}, + 970: {region: 0x102, script: 0x57, flags: 0x0}, + 971: {region: 0x95, script: 0x57, flags: 0x0}, + 972: {region: 0x165, script: 0x57, flags: 0x0}, + 973: {region: 0x165, script: 0x57, flags: 0x0}, + 974: {region: 0x9e, script: 0x57, flags: 0x0}, + 975: {region: 0x165, script: 0x5, flags: 0x0}, + 976: {region: 0x99, script: 0x57, flags: 0x0}, + 977: {region: 0x31, script: 0x2, flags: 0x1}, + 978: {region: 0xdb, script: 0x21, flags: 0x0}, + 979: {region: 0x35, script: 0xe, flags: 0x0}, + 980: {region: 0x4e, script: 0x57, flags: 0x0}, + 981: {region: 0x72, script: 0x57, flags: 0x0}, + 982: {region: 0x4e, script: 0x57, flags: 0x0}, + 983: {region: 0x9c, script: 0x5, flags: 0x0}, + 984: {region: 0x10c, script: 0x57, flags: 0x0}, + 985: {region: 0x3a, script: 0x57, flags: 0x0}, + 986: {region: 0x165, script: 0x57, flags: 0x0}, + 987: {region: 0xd1, script: 0x57, flags: 0x0}, + 988: {region: 0x104, script: 0x57, flags: 0x0}, + 989: {region: 0x95, script: 0x57, flags: 0x0}, + 990: {region: 0x12f, script: 0x57, flags: 0x0}, + 991: {region: 0x165, script: 0x57, flags: 0x0}, + 992: {region: 0x165, script: 0x57, flags: 0x0}, + 993: {region: 0x73, script: 0x57, flags: 0x0}, + 994: {region: 0x106, script: 0x1f, flags: 0x0}, + 995: {region: 0x130, script: 0x1f, flags: 0x0}, + 996: {region: 0x109, script: 0x57, flags: 0x0}, + 997: {region: 0x107, script: 0x57, flags: 0x0}, + 998: {region: 0x12f, script: 0x57, flags: 0x0}, + 999: {region: 0x165, script: 0x57, flags: 0x0}, + 1000: {region: 0xa2, script: 0x49, flags: 0x0}, + 1001: {region: 0x99, script: 0x21, flags: 0x0}, + 1002: {region: 0x80, script: 0x57, flags: 0x0}, + 1003: {region: 0x106, script: 0x1f, flags: 0x0}, + 1004: {region: 0xa4, script: 0x57, flags: 0x0}, + 1005: {region: 0x95, script: 0x57, flags: 0x0}, + 1006: {region: 0x99, script: 0x57, flags: 0x0}, + 1007: {region: 0x114, script: 0x57, flags: 0x0}, + 1008: {region: 0x99, script: 0xc3, flags: 0x0}, + 1009: {region: 0x165, script: 0x57, flags: 0x0}, + 1010: {region: 0x165, script: 0x57, flags: 0x0}, + 1011: {region: 0x12f, script: 0x57, flags: 0x0}, + 1012: {region: 0x9e, script: 0x57, flags: 0x0}, + 1013: {region: 0x99, script: 0x21, flags: 0x0}, + 1014: {region: 0x165, script: 0x5, flags: 0x0}, + 1015: {region: 0x9e, script: 0x57, flags: 0x0}, + 1016: {region: 0x7b, script: 0x57, flags: 0x0}, + 1017: {region: 0x49, script: 0x57, flags: 0x0}, + 1018: {region: 0x33, script: 0x4, flags: 0x1}, + 1019: {region: 0x9e, script: 0x57, flags: 0x0}, + 1020: {region: 0x9c, script: 0x5, flags: 0x0}, + 1021: {region: 0xda, script: 0x57, flags: 0x0}, + 1022: {region: 0x4f, script: 0x57, flags: 0x0}, + 1023: {region: 0xd1, script: 0x57, flags: 0x0}, + 1024: {region: 0xcf, script: 0x57, flags: 0x0}, + 1025: {region: 0xc3, script: 0x57, flags: 0x0}, + 1026: {region: 0x4c, script: 0x57, flags: 0x0}, + 1027: {region: 0x96, script: 0x7a, flags: 0x0}, + 1028: {region: 0xb6, script: 0x57, flags: 0x0}, + 1029: {region: 0x165, script: 0x29, flags: 0x0}, + 1030: {region: 0x165, script: 0x57, flags: 0x0}, + 1032: {region: 0xba, script: 0xdc, flags: 0x0}, + 1033: {region: 0x165, script: 0x57, flags: 0x0}, + 1034: {region: 0xc4, script: 0x72, flags: 0x0}, + 1035: {region: 0x165, script: 0x5, flags: 0x0}, + 1036: {region: 0xb3, script: 0xca, flags: 0x0}, + 1037: {region: 0x6f, script: 0x57, flags: 0x0}, + 1038: {region: 0x165, script: 0x57, flags: 0x0}, + 1039: {region: 0x165, script: 0x57, flags: 0x0}, + 1040: {region: 0x165, script: 0x57, flags: 0x0}, + 1041: {region: 0x165, script: 0x57, flags: 0x0}, + 1042: {region: 0x111, script: 0x57, flags: 0x0}, + 1043: {region: 0x165, script: 0x57, flags: 0x0}, + 1044: {region: 0xe8, script: 0x5, flags: 0x0}, + 1045: {region: 0x165, script: 0x57, flags: 0x0}, + 1046: {region: 0x10f, script: 0x57, flags: 0x0}, + 1047: {region: 0x165, script: 0x57, flags: 0x0}, + 1048: {region: 0xe9, script: 0x57, flags: 0x0}, + 1049: {region: 0x165, script: 0x57, flags: 0x0}, + 1050: {region: 0x95, script: 0x57, flags: 0x0}, + 1051: {region: 0x142, script: 0x57, flags: 0x0}, + 1052: {region: 0x10c, script: 0x57, flags: 0x0}, + 1054: {region: 0x10c, script: 0x57, flags: 0x0}, + 1055: {region: 0x72, script: 0x57, flags: 0x0}, + 1056: {region: 0x97, script: 0xc0, flags: 0x0}, + 1057: {region: 0x165, script: 0x57, flags: 0x0}, + 1058: {region: 0x72, script: 0x57, flags: 0x0}, + 1059: {region: 0x164, script: 0x57, flags: 0x0}, + 1060: {region: 0x165, script: 0x57, flags: 0x0}, + 1061: {region: 0xc3, script: 0x57, flags: 0x0}, + 1062: {region: 0x165, script: 0x57, flags: 0x0}, + 1063: {region: 0x165, script: 0x57, flags: 0x0}, + 1064: {region: 0x165, script: 0x57, flags: 0x0}, + 1065: {region: 0x115, script: 0x57, flags: 0x0}, + 1066: {region: 0x165, script: 0x57, flags: 0x0}, + 1067: {region: 0x165, script: 0x57, flags: 0x0}, + 1068: {region: 0x123, script: 0xdf, flags: 0x0}, + 1069: {region: 0x165, script: 0x57, flags: 0x0}, + 1070: {region: 0x165, script: 0x57, flags: 0x0}, + 1071: {region: 0x165, script: 0x57, flags: 0x0}, + 1072: {region: 0x165, script: 0x57, flags: 0x0}, + 1073: {region: 0x27, script: 0x57, flags: 0x0}, + 1074: {region: 0x37, script: 0x5, flags: 0x1}, + 1075: {region: 0x99, script: 0xcb, flags: 0x0}, + 1076: {region: 0x116, script: 0x57, flags: 0x0}, + 1077: {region: 0x114, script: 0x57, flags: 0x0}, + 1078: {region: 0x99, script: 0x21, flags: 0x0}, + 1079: {region: 0x161, script: 0x57, flags: 0x0}, + 1080: {region: 0x165, script: 0x57, flags: 0x0}, + 1081: {region: 0x165, script: 0x57, flags: 0x0}, + 1082: {region: 0x6d, script: 0x57, flags: 0x0}, + 1083: {region: 0x161, script: 0x57, flags: 0x0}, + 1084: {region: 0x165, script: 0x57, flags: 0x0}, + 1085: {region: 0x60, script: 0x57, flags: 0x0}, + 1086: {region: 0x95, script: 0x57, flags: 0x0}, + 1087: {region: 0x165, script: 0x57, flags: 0x0}, + 1088: {region: 0x165, script: 0x57, flags: 0x0}, + 1089: {region: 0x12f, script: 0x57, flags: 0x0}, + 1090: {region: 0x165, script: 0x57, flags: 0x0}, + 1091: {region: 0x84, script: 0x57, flags: 0x0}, + 1092: {region: 0x10c, script: 0x57, flags: 0x0}, + 1093: {region: 0x12f, script: 0x57, flags: 0x0}, + 1094: {region: 0x15f, script: 0x5, flags: 0x0}, + 1095: {region: 0x4b, script: 0x57, flags: 0x0}, + 1096: {region: 0x60, script: 0x57, flags: 0x0}, + 1097: {region: 0x165, script: 0x57, flags: 0x0}, + 1098: {region: 0x99, script: 0x21, flags: 0x0}, + 1099: {region: 0x95, script: 0x57, flags: 0x0}, + 1100: {region: 0x165, script: 0x57, flags: 0x0}, + 1101: {region: 0x35, script: 0xe, flags: 0x0}, + 1102: {region: 0x9b, script: 0xcf, flags: 0x0}, + 1103: {region: 0xe9, script: 0x57, flags: 0x0}, + 1104: {region: 0x99, script: 0xd7, flags: 0x0}, + 1105: {region: 0xdb, script: 0x21, flags: 0x0}, + 1106: {region: 0x165, script: 0x57, flags: 0x0}, + 1107: {region: 0x165, script: 0x57, flags: 0x0}, + 1108: {region: 0x165, script: 0x57, flags: 0x0}, + 1109: {region: 0x165, script: 0x57, flags: 0x0}, + 1110: {region: 0x165, script: 0x57, flags: 0x0}, + 1111: {region: 0x165, script: 0x57, flags: 0x0}, + 1112: {region: 0x165, script: 0x57, flags: 0x0}, + 1113: {region: 0x165, script: 0x57, flags: 0x0}, + 1114: {region: 0xe7, script: 0x57, flags: 0x0}, + 1115: {region: 0x165, script: 0x57, flags: 0x0}, + 1116: {region: 0x165, script: 0x57, flags: 0x0}, + 1117: {region: 0x99, script: 0x4f, flags: 0x0}, + 1118: {region: 0x53, script: 0xd5, flags: 0x0}, + 1119: {region: 0xdb, script: 0x21, flags: 0x0}, + 1120: {region: 0xdb, script: 0x21, flags: 0x0}, + 1121: {region: 0x99, script: 0xda, flags: 0x0}, + 1122: {region: 0x165, script: 0x57, flags: 0x0}, + 1123: {region: 0x112, script: 0x57, flags: 0x0}, + 1124: {region: 0x131, script: 0x57, flags: 0x0}, + 1125: {region: 0x126, script: 0x57, flags: 0x0}, + 1126: {region: 0x165, script: 0x57, flags: 0x0}, + 1127: {region: 0x3c, script: 0x3, flags: 0x1}, + 1128: {region: 0x165, script: 0x57, flags: 0x0}, + 1129: {region: 0x165, script: 0x57, flags: 0x0}, + 1130: {region: 0x165, script: 0x57, flags: 0x0}, + 1131: {region: 0x123, script: 0xdf, flags: 0x0}, + 1132: {region: 0xdb, script: 0x21, flags: 0x0}, + 1133: {region: 0xdb, script: 0x21, flags: 0x0}, + 1134: {region: 0xdb, script: 0x21, flags: 0x0}, + 1135: {region: 0x6f, script: 0x29, flags: 0x0}, + 1136: {region: 0x165, script: 0x57, flags: 0x0}, + 1137: {region: 0x6d, script: 0x29, flags: 0x0}, + 1138: {region: 0x165, script: 0x57, flags: 0x0}, + 1139: {region: 0x165, script: 0x57, flags: 0x0}, + 1140: {region: 0x165, script: 0x57, flags: 0x0}, + 1141: {region: 0xd6, script: 0x57, flags: 0x0}, + 1142: {region: 0x127, script: 0x57, flags: 0x0}, + 1143: {region: 0x125, script: 0x57, flags: 0x0}, + 1144: {region: 0x32, script: 0x57, flags: 0x0}, + 1145: {region: 0xdb, script: 0x21, flags: 0x0}, + 1146: {region: 0xe7, script: 0x57, flags: 0x0}, + 1147: {region: 0x165, script: 0x57, flags: 0x0}, + 1148: {region: 0x165, script: 0x57, flags: 0x0}, + 1149: {region: 0x32, script: 0x57, flags: 0x0}, + 1150: {region: 0xd4, script: 0x57, flags: 0x0}, + 1151: {region: 0x165, script: 0x57, flags: 0x0}, + 1152: {region: 0x161, script: 0x57, flags: 0x0}, + 1153: {region: 0x165, script: 0x57, flags: 0x0}, + 1154: {region: 0x129, script: 0x57, flags: 0x0}, + 1155: {region: 0x165, script: 0x57, flags: 0x0}, + 1156: {region: 0xce, script: 0x57, flags: 0x0}, + 1157: {region: 0x165, script: 0x57, flags: 0x0}, + 1158: {region: 0xe6, script: 0x57, flags: 0x0}, + 1159: {region: 0x165, script: 0x57, flags: 0x0}, + 1160: {region: 0x165, script: 0x57, flags: 0x0}, + 1161: {region: 0x165, script: 0x57, flags: 0x0}, + 1162: {region: 0x12b, script: 0x57, flags: 0x0}, + 1163: {region: 0x12b, script: 0x57, flags: 0x0}, + 1164: {region: 0x12e, script: 0x57, flags: 0x0}, + 1165: {region: 0x165, script: 0x5, flags: 0x0}, + 1166: {region: 0x161, script: 0x57, flags: 0x0}, + 1167: {region: 0x87, script: 0x31, flags: 0x0}, + 1168: {region: 0xdb, script: 0x21, flags: 0x0}, + 1169: {region: 0xe7, script: 0x57, flags: 0x0}, + 1170: {region: 0x43, script: 0xe0, flags: 0x0}, + 1171: {region: 0x165, script: 0x57, flags: 0x0}, + 1172: {region: 0x106, script: 0x1f, flags: 0x0}, + 1173: {region: 0x165, script: 0x57, flags: 0x0}, + 1174: {region: 0x165, script: 0x57, flags: 0x0}, + 1175: {region: 0x131, script: 0x57, flags: 0x0}, + 1176: {region: 0x165, script: 0x57, flags: 0x0}, + 1177: {region: 0x123, script: 0xdf, flags: 0x0}, + 1178: {region: 0x32, script: 0x57, flags: 0x0}, + 1179: {region: 0x165, script: 0x57, flags: 0x0}, + 1180: {region: 0x165, script: 0x57, flags: 0x0}, + 1181: {region: 0xce, script: 0x57, flags: 0x0}, + 1182: {region: 0x165, script: 0x57, flags: 0x0}, + 1183: {region: 0x165, script: 0x57, flags: 0x0}, + 1184: {region: 0x12d, script: 0x57, flags: 0x0}, + 1185: {region: 0x165, script: 0x57, flags: 0x0}, + 1187: {region: 0x165, script: 0x57, flags: 0x0}, + 1188: {region: 0xd4, script: 0x57, flags: 0x0}, + 1189: {region: 0x53, script: 0xd8, flags: 0x0}, + 1190: {region: 0xe5, script: 0x57, flags: 0x0}, + 1191: {region: 0x165, script: 0x57, flags: 0x0}, + 1192: {region: 0x106, script: 0x1f, flags: 0x0}, + 1193: {region: 0xba, script: 0x57, flags: 0x0}, + 1194: {region: 0x165, script: 0x57, flags: 0x0}, + 1195: {region: 0x106, script: 0x1f, flags: 0x0}, + 1196: {region: 0x3f, script: 0x4, flags: 0x1}, + 1197: {region: 0x11c, script: 0xe2, flags: 0x0}, + 1198: {region: 0x130, script: 0x1f, flags: 0x0}, + 1199: {region: 0x75, script: 0x57, flags: 0x0}, + 1200: {region: 0x2a, script: 0x57, flags: 0x0}, + 1202: {region: 0x43, script: 0x3, flags: 0x1}, + 1203: {region: 0x99, script: 0xe, flags: 0x0}, + 1204: {region: 0xe8, script: 0x5, flags: 0x0}, + 1205: {region: 0x165, script: 0x57, flags: 0x0}, + 1206: {region: 0x165, script: 0x57, flags: 0x0}, + 1207: {region: 0x165, script: 0x57, flags: 0x0}, + 1208: {region: 0x165, script: 0x57, flags: 0x0}, + 1209: {region: 0x165, script: 0x57, flags: 0x0}, + 1210: {region: 0x165, script: 0x57, flags: 0x0}, + 1211: {region: 0x165, script: 0x57, flags: 0x0}, + 1212: {region: 0x46, script: 0x4, flags: 0x1}, + 1213: {region: 0x165, script: 0x57, flags: 0x0}, + 1214: {region: 0xb4, script: 0xe3, flags: 0x0}, + 1215: {region: 0x165, script: 0x57, flags: 0x0}, + 1216: {region: 0x161, script: 0x57, flags: 0x0}, + 1217: {region: 0x9e, script: 0x57, flags: 0x0}, + 1218: {region: 0x106, script: 0x57, flags: 0x0}, + 1219: {region: 0x13e, script: 0x57, flags: 0x0}, + 1220: {region: 0x11b, script: 0x57, flags: 0x0}, + 1221: {region: 0x165, script: 0x57, flags: 0x0}, + 1222: {region: 0x36, script: 0x57, flags: 0x0}, + 1223: {region: 0x60, script: 0x57, flags: 0x0}, + 1224: {region: 0xd1, script: 0x57, flags: 0x0}, + 1225: {region: 0x1, script: 0x57, flags: 0x0}, + 1226: {region: 0x106, script: 0x57, flags: 0x0}, + 1227: {region: 0x6a, script: 0x57, flags: 0x0}, + 1228: {region: 0x12f, script: 0x57, flags: 0x0}, + 1229: {region: 0x165, script: 0x57, flags: 0x0}, + 1230: {region: 0x36, script: 0x57, flags: 0x0}, + 1231: {region: 0x4e, script: 0x57, flags: 0x0}, + 1232: {region: 0x165, script: 0x57, flags: 0x0}, + 1233: {region: 0x6f, script: 0x29, flags: 0x0}, + 1234: {region: 0x165, script: 0x57, flags: 0x0}, + 1235: {region: 0xe7, script: 0x57, flags: 0x0}, + 1236: {region: 0x2f, script: 0x57, flags: 0x0}, + 1237: {region: 0x99, script: 0xda, flags: 0x0}, + 1238: {region: 0x99, script: 0x21, flags: 0x0}, + 1239: {region: 0x165, script: 0x57, flags: 0x0}, + 1240: {region: 0x165, script: 0x57, flags: 0x0}, + 1241: {region: 0x165, script: 0x57, flags: 0x0}, + 1242: {region: 0x165, script: 0x57, flags: 0x0}, + 1243: {region: 0x165, script: 0x57, flags: 0x0}, + 1244: {region: 0x165, script: 0x57, flags: 0x0}, + 1245: {region: 0x165, script: 0x57, flags: 0x0}, + 1246: {region: 0x165, script: 0x57, flags: 0x0}, + 1247: {region: 0x165, script: 0x57, flags: 0x0}, + 1248: {region: 0x140, script: 0x57, flags: 0x0}, + 1249: {region: 0x165, script: 0x57, flags: 0x0}, + 1250: {region: 0x165, script: 0x57, flags: 0x0}, + 1251: {region: 0xa8, script: 0x5, flags: 0x0}, + 1252: {region: 0x165, script: 0x57, flags: 0x0}, + 1253: {region: 0x114, script: 0x57, flags: 0x0}, + 1254: {region: 0x165, script: 0x57, flags: 0x0}, + 1255: {region: 0x165, script: 0x57, flags: 0x0}, + 1256: {region: 0x165, script: 0x57, flags: 0x0}, + 1257: {region: 0x165, script: 0x57, flags: 0x0}, + 1258: {region: 0x99, script: 0x21, flags: 0x0}, + 1259: {region: 0x53, script: 0x38, flags: 0x0}, + 1260: {region: 0x165, script: 0x57, flags: 0x0}, + 1261: {region: 0x165, script: 0x57, flags: 0x0}, + 1262: {region: 0x41, script: 0x57, flags: 0x0}, + 1263: {region: 0x165, script: 0x57, flags: 0x0}, + 1264: {region: 0x12b, script: 0x18, flags: 0x0}, + 1265: {region: 0x165, script: 0x57, flags: 0x0}, + 1266: {region: 0x161, script: 0x57, flags: 0x0}, + 1267: {region: 0x165, script: 0x57, flags: 0x0}, + 1268: {region: 0x12b, script: 0x5f, flags: 0x0}, + 1269: {region: 0x12b, script: 0x60, flags: 0x0}, + 1270: {region: 0x7d, script: 0x2b, flags: 0x0}, + 1271: {region: 0x53, script: 0x64, flags: 0x0}, + 1272: {region: 0x10b, script: 0x69, flags: 0x0}, + 1273: {region: 0x108, script: 0x73, flags: 0x0}, + 1274: {region: 0x99, script: 0x21, flags: 0x0}, + 1275: {region: 0x131, script: 0x57, flags: 0x0}, + 1276: {region: 0x165, script: 0x57, flags: 0x0}, + 1277: {region: 0x9c, script: 0x8a, flags: 0x0}, + 1278: {region: 0x165, script: 0x57, flags: 0x0}, + 1279: {region: 0x15e, script: 0xc2, flags: 0x0}, + 1280: {region: 0x165, script: 0x57, flags: 0x0}, + 1281: {region: 0x165, script: 0x57, flags: 0x0}, + 1282: {region: 0xdb, script: 0x21, flags: 0x0}, + 1283: {region: 0x165, script: 0x57, flags: 0x0}, + 1284: {region: 0x165, script: 0x57, flags: 0x0}, + 1285: {region: 0xd1, script: 0x57, flags: 0x0}, + 1286: {region: 0x75, script: 0x57, flags: 0x0}, + 1287: {region: 0x165, script: 0x57, flags: 0x0}, + 1288: {region: 0x165, script: 0x57, flags: 0x0}, + 1289: {region: 0x52, script: 0x57, flags: 0x0}, + 1290: {region: 0x165, script: 0x57, flags: 0x0}, + 1291: {region: 0x165, script: 0x57, flags: 0x0}, + 1292: {region: 0x165, script: 0x57, flags: 0x0}, + 1293: {region: 0x52, script: 0x57, flags: 0x0}, + 1294: {region: 0x165, script: 0x57, flags: 0x0}, + 1295: {region: 0x165, script: 0x57, flags: 0x0}, + 1296: {region: 0x165, script: 0x57, flags: 0x0}, + 1297: {region: 0x165, script: 0x57, flags: 0x0}, + 1298: {region: 0x1, script: 0x3b, flags: 0x0}, + 1299: {region: 0x165, script: 0x57, flags: 0x0}, + 1300: {region: 0x165, script: 0x57, flags: 0x0}, + 1301: {region: 0x165, script: 0x57, flags: 0x0}, + 1302: {region: 0x165, script: 0x57, flags: 0x0}, + 1303: {region: 0x165, script: 0x57, flags: 0x0}, + 1304: {region: 0xd6, script: 0x57, flags: 0x0}, + 1305: {region: 0x165, script: 0x57, flags: 0x0}, + 1306: {region: 0x165, script: 0x57, flags: 0x0}, + 1307: {region: 0x165, script: 0x57, flags: 0x0}, + 1308: {region: 0x41, script: 0x57, flags: 0x0}, + 1309: {region: 0x165, script: 0x57, flags: 0x0}, + 1310: {region: 0xcf, script: 0x57, flags: 0x0}, + 1311: {region: 0x4a, script: 0x3, flags: 0x1}, + 1312: {region: 0x165, script: 0x57, flags: 0x0}, + 1313: {region: 0x165, script: 0x57, flags: 0x0}, + 1314: {region: 0x165, script: 0x57, flags: 0x0}, + 1315: {region: 0x53, script: 0x57, flags: 0x0}, + 1316: {region: 0x10b, script: 0x57, flags: 0x0}, + 1318: {region: 0xa8, script: 0x5, flags: 0x0}, + 1319: {region: 0xd9, script: 0x57, flags: 0x0}, + 1320: {region: 0xba, script: 0xdc, flags: 0x0}, + 1321: {region: 0x4d, script: 0x14, flags: 0x1}, + 1322: {region: 0x53, script: 0x79, flags: 0x0}, + 1323: {region: 0x165, script: 0x57, flags: 0x0}, + 1324: {region: 0x122, script: 0x57, flags: 0x0}, + 1325: {region: 0xd0, script: 0x57, flags: 0x0}, + 1326: {region: 0x165, script: 0x57, flags: 0x0}, + 1327: {region: 0x161, script: 0x57, flags: 0x0}, + 1329: {region: 0x12b, script: 0x57, flags: 0x0}, +} + +// likelyLangList holds lists info associated with likelyLang. +// Size: 388 bytes, 97 elements +var likelyLangList = [97]likelyScriptRegion{ + 0: {region: 0x9c, script: 0x7, flags: 0x0}, + 1: {region: 0xa1, script: 0x74, flags: 0x2}, + 2: {region: 0x11c, script: 0x80, flags: 0x2}, + 3: {region: 0x32, script: 0x57, flags: 0x0}, + 4: {region: 0x9b, script: 0x5, flags: 0x4}, + 5: {region: 0x9c, script: 0x5, flags: 0x4}, + 6: {region: 0x106, script: 0x1f, flags: 0x4}, + 7: {region: 0x9c, script: 0x5, flags: 0x2}, + 8: {region: 0x106, script: 0x1f, flags: 0x0}, + 9: {region: 0x38, script: 0x2c, flags: 0x2}, + 10: {region: 0x135, script: 0x57, flags: 0x0}, + 11: {region: 0x7b, script: 0xc5, flags: 0x2}, + 12: {region: 0x114, script: 0x57, flags: 0x0}, + 13: {region: 0x84, script: 0x1, flags: 0x2}, + 14: {region: 0x5d, script: 0x1e, flags: 0x0}, + 15: {region: 0x87, script: 0x5c, flags: 0x2}, + 16: {region: 0xd6, script: 0x57, flags: 0x0}, + 17: {region: 0x52, script: 0x5, flags: 0x4}, + 18: {region: 0x10b, script: 0x5, flags: 0x4}, + 19: {region: 0xae, script: 0x1f, flags: 0x0}, + 20: {region: 0x24, script: 0x5, flags: 0x4}, + 21: {region: 0x53, script: 0x5, flags: 0x4}, + 22: {region: 0x9c, script: 0x5, flags: 0x4}, + 23: {region: 0xc5, script: 0x5, flags: 0x4}, + 24: {region: 0x53, script: 0x5, flags: 0x2}, + 25: {region: 0x12b, script: 0x57, flags: 0x0}, + 26: {region: 0xb0, script: 0x5, flags: 0x4}, + 27: {region: 0x9b, script: 0x5, flags: 0x2}, + 28: {region: 0xa5, script: 0x1f, flags: 0x0}, + 29: {region: 0x53, script: 0x5, flags: 0x4}, + 30: {region: 0x12b, script: 0x57, flags: 0x4}, + 31: {region: 0x53, script: 0x5, flags: 0x2}, + 32: {region: 0x12b, script: 0x57, flags: 0x2}, + 33: {region: 0xdb, script: 0x21, flags: 0x0}, + 34: {region: 0x99, script: 0x5a, flags: 0x2}, + 35: {region: 0x83, script: 0x57, flags: 0x0}, + 36: {region: 0x84, script: 0x78, flags: 0x4}, + 37: {region: 0x84, script: 0x78, flags: 0x2}, + 38: {region: 0xc5, script: 0x1f, flags: 0x0}, + 39: {region: 0x53, script: 0x6d, flags: 0x4}, + 40: {region: 0x53, script: 0x6d, flags: 0x2}, + 41: {region: 0xd0, script: 0x57, flags: 0x0}, + 42: {region: 0x4a, script: 0x5, flags: 0x4}, + 43: {region: 0x95, script: 0x5, flags: 0x4}, + 44: {region: 0x99, script: 0x33, flags: 0x0}, + 45: {region: 0xe8, script: 0x5, flags: 0x4}, + 46: {region: 0xe8, script: 0x5, flags: 0x2}, + 47: {region: 0x9c, script: 0x84, flags: 0x0}, + 48: {region: 0x53, script: 0x85, flags: 0x2}, + 49: {region: 0xba, script: 0xdc, flags: 0x0}, + 50: {region: 0xd9, script: 0x57, flags: 0x4}, + 51: {region: 0xe8, script: 0x5, flags: 0x0}, + 52: {region: 0x99, script: 0x21, flags: 0x2}, + 53: {region: 0x99, script: 0x4c, flags: 0x2}, + 54: {region: 0x99, script: 0xc9, flags: 0x2}, + 55: {region: 0x105, script: 0x1f, flags: 0x0}, + 56: {region: 0xbd, script: 0x57, flags: 0x4}, + 57: {region: 0x104, script: 0x57, flags: 0x4}, + 58: {region: 0x106, script: 0x57, flags: 0x4}, + 59: {region: 0x12b, script: 0x57, flags: 0x4}, + 60: {region: 0x124, script: 0x1f, flags: 0x0}, + 61: {region: 0xe8, script: 0x5, flags: 0x4}, + 62: {region: 0xe8, script: 0x5, flags: 0x2}, + 63: {region: 0x53, script: 0x5, flags: 0x0}, + 64: {region: 0xae, script: 0x1f, flags: 0x4}, + 65: {region: 0xc5, script: 0x1f, flags: 0x4}, + 66: {region: 0xae, script: 0x1f, flags: 0x2}, + 67: {region: 0x99, script: 0xe, flags: 0x0}, + 68: {region: 0xdb, script: 0x21, flags: 0x4}, + 69: {region: 0xdb, script: 0x21, flags: 0x2}, + 70: {region: 0x137, script: 0x57, flags: 0x0}, + 71: {region: 0x24, script: 0x5, flags: 0x4}, + 72: {region: 0x53, script: 0x1f, flags: 0x4}, + 73: {region: 0x24, script: 0x5, flags: 0x2}, + 74: {region: 0x8d, script: 0x39, flags: 0x0}, + 75: {region: 0x53, script: 0x38, flags: 0x4}, + 76: {region: 0x53, script: 0x38, flags: 0x2}, + 77: {region: 0x53, script: 0x38, flags: 0x0}, + 78: {region: 0x2f, script: 0x39, flags: 0x4}, + 79: {region: 0x3e, script: 0x39, flags: 0x4}, + 80: {region: 0x7b, script: 0x39, flags: 0x4}, + 81: {region: 0x7e, script: 0x39, flags: 0x4}, + 82: {region: 0x8d, script: 0x39, flags: 0x4}, + 83: {region: 0x95, script: 0x39, flags: 0x4}, + 84: {region: 0xc6, script: 0x39, flags: 0x4}, + 85: {region: 0xd0, script: 0x39, flags: 0x4}, + 86: {region: 0xe2, script: 0x39, flags: 0x4}, + 87: {region: 0xe5, script: 0x39, flags: 0x4}, + 88: {region: 0xe7, script: 0x39, flags: 0x4}, + 89: {region: 0x116, script: 0x39, flags: 0x4}, + 90: {region: 0x123, script: 0x39, flags: 0x4}, + 91: {region: 0x12e, script: 0x39, flags: 0x4}, + 92: {region: 0x135, script: 0x39, flags: 0x4}, + 93: {region: 0x13e, script: 0x39, flags: 0x4}, + 94: {region: 0x12e, script: 0x11, flags: 0x2}, + 95: {region: 0x12e, script: 0x34, flags: 0x2}, + 96: {region: 0x12e, script: 0x39, flags: 0x2}, +} + +type likelyLangScript struct { + lang uint16 + script uint8 + flags uint8 +} + +// likelyRegion is a lookup table, indexed by regionID, for the most likely +// languages and scripts given incomplete information. If more entries exist +// for a given regionID, lang and script are the index and size respectively +// of the list in likelyRegionList. +// TODO: exclude containers and user-definable regions from the list. +// Size: 1432 bytes, 358 elements +var likelyRegion = [358]likelyLangScript{ + 34: {lang: 0xd7, script: 0x57, flags: 0x0}, + 35: {lang: 0x3a, script: 0x5, flags: 0x0}, + 36: {lang: 0x0, script: 0x2, flags: 0x1}, + 39: {lang: 0x2, script: 0x2, flags: 0x1}, + 40: {lang: 0x4, script: 0x2, flags: 0x1}, + 42: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 43: {lang: 0x0, script: 0x57, flags: 0x0}, + 44: {lang: 0x13e, script: 0x57, flags: 0x0}, + 45: {lang: 0x41b, script: 0x57, flags: 0x0}, + 46: {lang: 0x10d, script: 0x57, flags: 0x0}, + 48: {lang: 0x367, script: 0x57, flags: 0x0}, + 49: {lang: 0x444, script: 0x57, flags: 0x0}, + 50: {lang: 0x58, script: 0x57, flags: 0x0}, + 51: {lang: 0x6, script: 0x2, flags: 0x1}, + 53: {lang: 0xa5, script: 0xe, flags: 0x0}, + 54: {lang: 0x367, script: 0x57, flags: 0x0}, + 55: {lang: 0x15e, script: 0x57, flags: 0x0}, + 56: {lang: 0x7e, script: 0x1f, flags: 0x0}, + 57: {lang: 0x3a, script: 0x5, flags: 0x0}, + 58: {lang: 0x3d9, script: 0x57, flags: 0x0}, + 59: {lang: 0x15e, script: 0x57, flags: 0x0}, + 60: {lang: 0x15e, script: 0x57, flags: 0x0}, + 62: {lang: 0x31f, script: 0x57, flags: 0x0}, + 63: {lang: 0x13e, script: 0x57, flags: 0x0}, + 64: {lang: 0x3a1, script: 0x57, flags: 0x0}, + 65: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 67: {lang: 0x8, script: 0x2, flags: 0x1}, + 69: {lang: 0x0, script: 0x57, flags: 0x0}, + 71: {lang: 0x71, script: 0x1f, flags: 0x0}, + 73: {lang: 0x512, script: 0x3b, flags: 0x2}, + 74: {lang: 0x31f, script: 0x5, flags: 0x2}, + 75: {lang: 0x445, script: 0x57, flags: 0x0}, + 76: {lang: 0x15e, script: 0x57, flags: 0x0}, + 77: {lang: 0x15e, script: 0x57, flags: 0x0}, + 78: {lang: 0x10d, script: 0x57, flags: 0x0}, + 79: {lang: 0x15e, script: 0x57, flags: 0x0}, + 81: {lang: 0x13e, script: 0x57, flags: 0x0}, + 82: {lang: 0x15e, script: 0x57, flags: 0x0}, + 83: {lang: 0xa, script: 0x4, flags: 0x1}, + 84: {lang: 0x13e, script: 0x57, flags: 0x0}, + 85: {lang: 0x0, script: 0x57, flags: 0x0}, + 86: {lang: 0x13e, script: 0x57, flags: 0x0}, + 89: {lang: 0x13e, script: 0x57, flags: 0x0}, + 90: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 91: {lang: 0x3a1, script: 0x57, flags: 0x0}, + 93: {lang: 0xe, script: 0x2, flags: 0x1}, + 94: {lang: 0xfa, script: 0x57, flags: 0x0}, + 96: {lang: 0x10d, script: 0x57, flags: 0x0}, + 98: {lang: 0x1, script: 0x57, flags: 0x0}, + 99: {lang: 0x101, script: 0x57, flags: 0x0}, + 101: {lang: 0x13e, script: 0x57, flags: 0x0}, + 103: {lang: 0x10, script: 0x2, flags: 0x1}, + 104: {lang: 0x13e, script: 0x57, flags: 0x0}, + 105: {lang: 0x13e, script: 0x57, flags: 0x0}, + 106: {lang: 0x140, script: 0x57, flags: 0x0}, + 107: {lang: 0x3a, script: 0x5, flags: 0x0}, + 108: {lang: 0x3a, script: 0x5, flags: 0x0}, + 109: {lang: 0x46f, script: 0x29, flags: 0x0}, + 110: {lang: 0x13e, script: 0x57, flags: 0x0}, + 111: {lang: 0x12, script: 0x2, flags: 0x1}, + 113: {lang: 0x10d, script: 0x57, flags: 0x0}, + 114: {lang: 0x151, script: 0x57, flags: 0x0}, + 115: {lang: 0x1c0, script: 0x21, flags: 0x2}, + 118: {lang: 0x158, script: 0x57, flags: 0x0}, + 120: {lang: 0x15e, script: 0x57, flags: 0x0}, + 122: {lang: 0x15e, script: 0x57, flags: 0x0}, + 123: {lang: 0x14, script: 0x2, flags: 0x1}, + 125: {lang: 0x16, script: 0x3, flags: 0x1}, + 126: {lang: 0x15e, script: 0x57, flags: 0x0}, + 128: {lang: 0x21, script: 0x57, flags: 0x0}, + 130: {lang: 0x245, script: 0x57, flags: 0x0}, + 132: {lang: 0x15e, script: 0x57, flags: 0x0}, + 133: {lang: 0x15e, script: 0x57, flags: 0x0}, + 134: {lang: 0x13e, script: 0x57, flags: 0x0}, + 135: {lang: 0x19, script: 0x2, flags: 0x1}, + 136: {lang: 0x0, script: 0x57, flags: 0x0}, + 137: {lang: 0x13e, script: 0x57, flags: 0x0}, + 139: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 141: {lang: 0x529, script: 0x39, flags: 0x0}, + 142: {lang: 0x0, script: 0x57, flags: 0x0}, + 143: {lang: 0x13e, script: 0x57, flags: 0x0}, + 144: {lang: 0x1d1, script: 0x57, flags: 0x0}, + 145: {lang: 0x1d4, script: 0x57, flags: 0x0}, + 146: {lang: 0x1d5, script: 0x57, flags: 0x0}, + 148: {lang: 0x13e, script: 0x57, flags: 0x0}, + 149: {lang: 0x1b, script: 0x2, flags: 0x1}, + 151: {lang: 0x1bc, script: 0x3b, flags: 0x0}, + 153: {lang: 0x1d, script: 0x3, flags: 0x1}, + 155: {lang: 0x3a, script: 0x5, flags: 0x0}, + 156: {lang: 0x20, script: 0x2, flags: 0x1}, + 157: {lang: 0x1f8, script: 0x57, flags: 0x0}, + 158: {lang: 0x1f9, script: 0x57, flags: 0x0}, + 161: {lang: 0x3a, script: 0x5, flags: 0x0}, + 162: {lang: 0x200, script: 0x46, flags: 0x0}, + 164: {lang: 0x445, script: 0x57, flags: 0x0}, + 165: {lang: 0x28a, script: 0x1f, flags: 0x0}, + 166: {lang: 0x22, script: 0x3, flags: 0x1}, + 168: {lang: 0x25, script: 0x2, flags: 0x1}, + 170: {lang: 0x254, script: 0x50, flags: 0x0}, + 171: {lang: 0x254, script: 0x50, flags: 0x0}, + 172: {lang: 0x3a, script: 0x5, flags: 0x0}, + 174: {lang: 0x3e2, script: 0x1f, flags: 0x0}, + 175: {lang: 0x27, script: 0x2, flags: 0x1}, + 176: {lang: 0x3a, script: 0x5, flags: 0x0}, + 178: {lang: 0x10d, script: 0x57, flags: 0x0}, + 179: {lang: 0x40c, script: 0xca, flags: 0x0}, + 181: {lang: 0x43b, script: 0x57, flags: 0x0}, + 182: {lang: 0x2c0, script: 0x57, flags: 0x0}, + 183: {lang: 0x15e, script: 0x57, flags: 0x0}, + 184: {lang: 0x2c7, script: 0x57, flags: 0x0}, + 185: {lang: 0x3a, script: 0x5, flags: 0x0}, + 186: {lang: 0x29, script: 0x2, flags: 0x1}, + 187: {lang: 0x15e, script: 0x57, flags: 0x0}, + 188: {lang: 0x2b, script: 0x2, flags: 0x1}, + 189: {lang: 0x432, script: 0x57, flags: 0x0}, + 190: {lang: 0x15e, script: 0x57, flags: 0x0}, + 191: {lang: 0x2f1, script: 0x57, flags: 0x0}, + 194: {lang: 0x2d, script: 0x2, flags: 0x1}, + 195: {lang: 0xa0, script: 0x57, flags: 0x0}, + 196: {lang: 0x2f, script: 0x2, flags: 0x1}, + 197: {lang: 0x31, script: 0x2, flags: 0x1}, + 198: {lang: 0x33, script: 0x2, flags: 0x1}, + 200: {lang: 0x15e, script: 0x57, flags: 0x0}, + 201: {lang: 0x35, script: 0x2, flags: 0x1}, + 203: {lang: 0x320, script: 0x57, flags: 0x0}, + 204: {lang: 0x37, script: 0x3, flags: 0x1}, + 205: {lang: 0x128, script: 0xde, flags: 0x0}, + 207: {lang: 0x13e, script: 0x57, flags: 0x0}, + 208: {lang: 0x31f, script: 0x57, flags: 0x0}, + 209: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 210: {lang: 0x16, script: 0x57, flags: 0x0}, + 211: {lang: 0x15e, script: 0x57, flags: 0x0}, + 212: {lang: 0x1b4, script: 0x57, flags: 0x0}, + 214: {lang: 0x1b4, script: 0x5, flags: 0x2}, + 216: {lang: 0x13e, script: 0x57, flags: 0x0}, + 217: {lang: 0x367, script: 0x57, flags: 0x0}, + 218: {lang: 0x347, script: 0x57, flags: 0x0}, + 219: {lang: 0x351, script: 0x21, flags: 0x0}, + 225: {lang: 0x3a, script: 0x5, flags: 0x0}, + 226: {lang: 0x13e, script: 0x57, flags: 0x0}, + 228: {lang: 0x13e, script: 0x57, flags: 0x0}, + 229: {lang: 0x15e, script: 0x57, flags: 0x0}, + 230: {lang: 0x486, script: 0x57, flags: 0x0}, + 231: {lang: 0x153, script: 0x57, flags: 0x0}, + 232: {lang: 0x3a, script: 0x3, flags: 0x1}, + 233: {lang: 0x3b3, script: 0x57, flags: 0x0}, + 234: {lang: 0x15e, script: 0x57, flags: 0x0}, + 236: {lang: 0x13e, script: 0x57, flags: 0x0}, + 237: {lang: 0x3a, script: 0x5, flags: 0x0}, + 238: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 240: {lang: 0x3a2, script: 0x57, flags: 0x0}, + 241: {lang: 0x194, script: 0x57, flags: 0x0}, + 243: {lang: 0x3a, script: 0x5, flags: 0x0}, + 258: {lang: 0x15e, script: 0x57, flags: 0x0}, + 260: {lang: 0x3d, script: 0x2, flags: 0x1}, + 261: {lang: 0x432, script: 0x1f, flags: 0x0}, + 262: {lang: 0x3f, script: 0x2, flags: 0x1}, + 263: {lang: 0x3e5, script: 0x57, flags: 0x0}, + 264: {lang: 0x3a, script: 0x5, flags: 0x0}, + 266: {lang: 0x15e, script: 0x57, flags: 0x0}, + 267: {lang: 0x3a, script: 0x5, flags: 0x0}, + 268: {lang: 0x41, script: 0x2, flags: 0x1}, + 271: {lang: 0x416, script: 0x57, flags: 0x0}, + 272: {lang: 0x347, script: 0x57, flags: 0x0}, + 273: {lang: 0x43, script: 0x2, flags: 0x1}, + 275: {lang: 0x1f9, script: 0x57, flags: 0x0}, + 276: {lang: 0x15e, script: 0x57, flags: 0x0}, + 277: {lang: 0x429, script: 0x57, flags: 0x0}, + 278: {lang: 0x367, script: 0x57, flags: 0x0}, + 280: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 282: {lang: 0x13e, script: 0x57, flags: 0x0}, + 284: {lang: 0x45, script: 0x2, flags: 0x1}, + 288: {lang: 0x15e, script: 0x57, flags: 0x0}, + 289: {lang: 0x15e, script: 0x57, flags: 0x0}, + 290: {lang: 0x47, script: 0x2, flags: 0x1}, + 291: {lang: 0x49, script: 0x3, flags: 0x1}, + 292: {lang: 0x4c, script: 0x2, flags: 0x1}, + 293: {lang: 0x477, script: 0x57, flags: 0x0}, + 294: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 295: {lang: 0x476, script: 0x57, flags: 0x0}, + 296: {lang: 0x4e, script: 0x2, flags: 0x1}, + 297: {lang: 0x482, script: 0x57, flags: 0x0}, + 299: {lang: 0x50, script: 0x4, flags: 0x1}, + 301: {lang: 0x4a0, script: 0x57, flags: 0x0}, + 302: {lang: 0x54, script: 0x2, flags: 0x1}, + 303: {lang: 0x445, script: 0x57, flags: 0x0}, + 304: {lang: 0x56, script: 0x3, flags: 0x1}, + 305: {lang: 0x445, script: 0x57, flags: 0x0}, + 309: {lang: 0x512, script: 0x3b, flags: 0x2}, + 310: {lang: 0x13e, script: 0x57, flags: 0x0}, + 311: {lang: 0x4bc, script: 0x57, flags: 0x0}, + 312: {lang: 0x1f9, script: 0x57, flags: 0x0}, + 315: {lang: 0x13e, script: 0x57, flags: 0x0}, + 318: {lang: 0x4c3, script: 0x57, flags: 0x0}, + 319: {lang: 0x8a, script: 0x57, flags: 0x0}, + 320: {lang: 0x15e, script: 0x57, flags: 0x0}, + 322: {lang: 0x41b, script: 0x57, flags: 0x0}, + 333: {lang: 0x59, script: 0x2, flags: 0x1}, + 350: {lang: 0x3a, script: 0x5, flags: 0x0}, + 351: {lang: 0x5b, script: 0x2, flags: 0x1}, + 356: {lang: 0x423, script: 0x57, flags: 0x0}, +} + +// likelyRegionList holds lists info associated with likelyRegion. +// Size: 372 bytes, 93 elements +var likelyRegionList = [93]likelyLangScript{ + 0: {lang: 0x148, script: 0x5, flags: 0x0}, + 1: {lang: 0x476, script: 0x57, flags: 0x0}, + 2: {lang: 0x431, script: 0x57, flags: 0x0}, + 3: {lang: 0x2ff, script: 0x1f, flags: 0x0}, + 4: {lang: 0x1d7, script: 0x8, flags: 0x0}, + 5: {lang: 0x274, script: 0x57, flags: 0x0}, + 6: {lang: 0xb7, script: 0x57, flags: 0x0}, + 7: {lang: 0x432, script: 0x1f, flags: 0x0}, + 8: {lang: 0x12d, script: 0xe0, flags: 0x0}, + 9: {lang: 0x351, script: 0x21, flags: 0x0}, + 10: {lang: 0x529, script: 0x38, flags: 0x0}, + 11: {lang: 0x4ac, script: 0x5, flags: 0x0}, + 12: {lang: 0x523, script: 0x57, flags: 0x0}, + 13: {lang: 0x29a, script: 0xdf, flags: 0x0}, + 14: {lang: 0x136, script: 0x31, flags: 0x0}, + 15: {lang: 0x48a, script: 0x57, flags: 0x0}, + 16: {lang: 0x3a, script: 0x5, flags: 0x0}, + 17: {lang: 0x15e, script: 0x57, flags: 0x0}, + 18: {lang: 0x27, script: 0x29, flags: 0x0}, + 19: {lang: 0x139, script: 0x57, flags: 0x0}, + 20: {lang: 0x26a, script: 0x5, flags: 0x2}, + 21: {lang: 0x512, script: 0x3b, flags: 0x2}, + 22: {lang: 0x210, script: 0x2b, flags: 0x0}, + 23: {lang: 0x5, script: 0x1f, flags: 0x0}, + 24: {lang: 0x274, script: 0x57, flags: 0x0}, + 25: {lang: 0x136, script: 0x31, flags: 0x0}, + 26: {lang: 0x2ff, script: 0x1f, flags: 0x0}, + 27: {lang: 0x1e1, script: 0x57, flags: 0x0}, + 28: {lang: 0x31f, script: 0x5, flags: 0x0}, + 29: {lang: 0x1be, script: 0x21, flags: 0x0}, + 30: {lang: 0x4b4, script: 0x5, flags: 0x0}, + 31: {lang: 0x236, script: 0x72, flags: 0x0}, + 32: {lang: 0x148, script: 0x5, flags: 0x0}, + 33: {lang: 0x476, script: 0x57, flags: 0x0}, + 34: {lang: 0x24a, script: 0x4b, flags: 0x0}, + 35: {lang: 0xe6, script: 0x5, flags: 0x0}, + 36: {lang: 0x226, script: 0xdf, flags: 0x0}, + 37: {lang: 0x3a, script: 0x5, flags: 0x0}, + 38: {lang: 0x15e, script: 0x57, flags: 0x0}, + 39: {lang: 0x2b8, script: 0x54, flags: 0x0}, + 40: {lang: 0x226, script: 0xdf, flags: 0x0}, + 41: {lang: 0x3a, script: 0x5, flags: 0x0}, + 42: {lang: 0x15e, script: 0x57, flags: 0x0}, + 43: {lang: 0x3dc, script: 0x57, flags: 0x0}, + 44: {lang: 0x4ae, script: 0x1f, flags: 0x0}, + 45: {lang: 0x2ff, script: 0x1f, flags: 0x0}, + 46: {lang: 0x431, script: 0x57, flags: 0x0}, + 47: {lang: 0x331, script: 0x72, flags: 0x0}, + 48: {lang: 0x213, script: 0x57, flags: 0x0}, + 49: {lang: 0x30b, script: 0x1f, flags: 0x0}, + 50: {lang: 0x242, script: 0x5, flags: 0x0}, + 51: {lang: 0x529, script: 0x39, flags: 0x0}, + 52: {lang: 0x3c0, script: 0x57, flags: 0x0}, + 53: {lang: 0x3a, script: 0x5, flags: 0x0}, + 54: {lang: 0x15e, script: 0x57, flags: 0x0}, + 55: {lang: 0x2ed, script: 0x57, flags: 0x0}, + 56: {lang: 0x4b4, script: 0x5, flags: 0x0}, + 57: {lang: 0x88, script: 0x21, flags: 0x0}, + 58: {lang: 0x4b4, script: 0x5, flags: 0x0}, + 59: {lang: 0x4b4, script: 0x5, flags: 0x0}, + 60: {lang: 0xbe, script: 0x21, flags: 0x0}, + 61: {lang: 0x3dc, script: 0x57, flags: 0x0}, + 62: {lang: 0x7e, script: 0x1f, flags: 0x0}, + 63: {lang: 0x3e2, script: 0x1f, flags: 0x0}, + 64: {lang: 0x267, script: 0x57, flags: 0x0}, + 65: {lang: 0x444, script: 0x57, flags: 0x0}, + 66: {lang: 0x512, script: 0x3b, flags: 0x0}, + 67: {lang: 0x412, script: 0x57, flags: 0x0}, + 68: {lang: 0x4ae, script: 0x1f, flags: 0x0}, + 69: {lang: 0x3a, script: 0x5, flags: 0x0}, + 70: {lang: 0x15e, script: 0x57, flags: 0x0}, + 71: {lang: 0x15e, script: 0x57, flags: 0x0}, + 72: {lang: 0x35, script: 0x5, flags: 0x0}, + 73: {lang: 0x46b, script: 0xdf, flags: 0x0}, + 74: {lang: 0x2ec, script: 0x5, flags: 0x0}, + 75: {lang: 0x30f, script: 0x72, flags: 0x0}, + 76: {lang: 0x467, script: 0x1f, flags: 0x0}, + 77: {lang: 0x148, script: 0x5, flags: 0x0}, + 78: {lang: 0x3a, script: 0x5, flags: 0x0}, + 79: {lang: 0x15e, script: 0x57, flags: 0x0}, + 80: {lang: 0x48a, script: 0x57, flags: 0x0}, + 81: {lang: 0x58, script: 0x5, flags: 0x0}, + 82: {lang: 0x219, script: 0x1f, flags: 0x0}, + 83: {lang: 0x81, script: 0x31, flags: 0x0}, + 84: {lang: 0x529, script: 0x39, flags: 0x0}, + 85: {lang: 0x48c, script: 0x57, flags: 0x0}, + 86: {lang: 0x4ae, script: 0x1f, flags: 0x0}, + 87: {lang: 0x512, script: 0x3b, flags: 0x0}, + 88: {lang: 0x3b3, script: 0x57, flags: 0x0}, + 89: {lang: 0x431, script: 0x57, flags: 0x0}, + 90: {lang: 0x432, script: 0x1f, flags: 0x0}, + 91: {lang: 0x15e, script: 0x57, flags: 0x0}, + 92: {lang: 0x446, script: 0x5, flags: 0x0}, +} + +type likelyTag struct { + lang uint16 + region uint16 + script uint8 +} + +// Size: 198 bytes, 33 elements +var likelyRegionGroup = [33]likelyTag{ + 1: {lang: 0x139, region: 0xd6, script: 0x57}, + 2: {lang: 0x139, region: 0x135, script: 0x57}, + 3: {lang: 0x3c0, region: 0x41, script: 0x57}, + 4: {lang: 0x139, region: 0x2f, script: 0x57}, + 5: {lang: 0x139, region: 0xd6, script: 0x57}, + 6: {lang: 0x13e, region: 0xcf, script: 0x57}, + 7: {lang: 0x445, region: 0x12f, script: 0x57}, + 8: {lang: 0x3a, region: 0x6b, script: 0x5}, + 9: {lang: 0x445, region: 0x4b, script: 0x57}, + 10: {lang: 0x139, region: 0x161, script: 0x57}, + 11: {lang: 0x139, region: 0x135, script: 0x57}, + 12: {lang: 0x139, region: 0x135, script: 0x57}, + 13: {lang: 0x13e, region: 0x59, script: 0x57}, + 14: {lang: 0x529, region: 0x53, script: 0x38}, + 15: {lang: 0x1be, region: 0x99, script: 0x21}, + 16: {lang: 0x1e1, region: 0x95, script: 0x57}, + 17: {lang: 0x1f9, region: 0x9e, script: 0x57}, + 18: {lang: 0x139, region: 0x2f, script: 0x57}, + 19: {lang: 0x139, region: 0xe6, script: 0x57}, + 20: {lang: 0x139, region: 0x8a, script: 0x57}, + 21: {lang: 0x41b, region: 0x142, script: 0x57}, + 22: {lang: 0x529, region: 0x53, script: 0x38}, + 23: {lang: 0x4bc, region: 0x137, script: 0x57}, + 24: {lang: 0x3a, region: 0x108, script: 0x5}, + 25: {lang: 0x3e2, region: 0x106, script: 0x1f}, + 26: {lang: 0x3e2, region: 0x106, script: 0x1f}, + 27: {lang: 0x139, region: 0x7b, script: 0x57}, + 28: {lang: 0x10d, region: 0x60, script: 0x57}, + 29: {lang: 0x139, region: 0xd6, script: 0x57}, + 30: {lang: 0x13e, region: 0x1f, script: 0x57}, + 31: {lang: 0x139, region: 0x9a, script: 0x57}, + 32: {lang: 0x139, region: 0x7b, script: 0x57}, +} + +// Size: 358 bytes, 358 elements +var regionToGroups = [358]uint8{ + // Entry 0 - 3F + 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x04, + // Entry 40 - 7F + 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + // Entry 80 - BF + 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x00, 0x04, 0x02, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, + // Entry C0 - FF + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, + 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Entry 100 - 13F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x04, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x04, 0x05, 0x00, 0x00, + // Entry 140 - 17F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +} + +// Size: 18 bytes, 3 elements +var paradigmLocales = [3][3]uint16{ + 0: [3]uint16{0x139, 0x0, 0x7b}, + 1: [3]uint16{0x13e, 0x0, 0x1f}, + 2: [3]uint16{0x3c0, 0x41, 0xee}, +} + +type mutualIntelligibility struct { + want uint16 + have uint16 + distance uint8 + oneway bool +} + +type scriptIntelligibility struct { + wantLang uint16 + haveLang uint16 + wantScript uint8 + haveScript uint8 + distance uint8 +} + +type regionIntelligibility struct { + lang uint16 + script uint8 + group uint8 + distance uint8 +} + +// matchLang holds pairs of langIDs of base languages that are typically +// mutually intelligible. Each pair is associated with a confidence and +// whether the intelligibility goes one or both ways. +// Size: 678 bytes, 113 elements +var matchLang = [113]mutualIntelligibility{ + 0: {want: 0x1d1, have: 0xb7, distance: 0x4, oneway: false}, + 1: {want: 0x407, have: 0xb7, distance: 0x4, oneway: false}, + 2: {want: 0x407, have: 0x1d1, distance: 0x4, oneway: false}, + 3: {want: 0x407, have: 0x432, distance: 0x4, oneway: false}, + 4: {want: 0x43a, have: 0x1, distance: 0x4, oneway: false}, + 5: {want: 0x1a3, have: 0x10d, distance: 0x4, oneway: true}, + 6: {want: 0x295, have: 0x10d, distance: 0x4, oneway: true}, + 7: {want: 0x101, have: 0x36f, distance: 0x8, oneway: false}, + 8: {want: 0x101, have: 0x347, distance: 0x8, oneway: false}, + 9: {want: 0x5, have: 0x3e2, distance: 0xa, oneway: true}, + 10: {want: 0xd, have: 0x139, distance: 0xa, oneway: true}, + 11: {want: 0x16, have: 0x367, distance: 0xa, oneway: true}, + 12: {want: 0x21, have: 0x139, distance: 0xa, oneway: true}, + 13: {want: 0x56, have: 0x13e, distance: 0xa, oneway: true}, + 14: {want: 0x58, have: 0x3e2, distance: 0xa, oneway: true}, + 15: {want: 0x71, have: 0x3e2, distance: 0xa, oneway: true}, + 16: {want: 0x75, have: 0x139, distance: 0xa, oneway: true}, + 17: {want: 0x82, have: 0x1be, distance: 0xa, oneway: true}, + 18: {want: 0xa5, have: 0x139, distance: 0xa, oneway: true}, + 19: {want: 0xb2, have: 0x15e, distance: 0xa, oneway: true}, + 20: {want: 0xdd, have: 0x153, distance: 0xa, oneway: true}, + 21: {want: 0xe5, have: 0x139, distance: 0xa, oneway: true}, + 22: {want: 0xe9, have: 0x3a, distance: 0xa, oneway: true}, + 23: {want: 0xf0, have: 0x15e, distance: 0xa, oneway: true}, + 24: {want: 0xf9, have: 0x15e, distance: 0xa, oneway: true}, + 25: {want: 0x100, have: 0x139, distance: 0xa, oneway: true}, + 26: {want: 0x130, have: 0x139, distance: 0xa, oneway: true}, + 27: {want: 0x13c, have: 0x139, distance: 0xa, oneway: true}, + 28: {want: 0x140, have: 0x151, distance: 0xa, oneway: true}, + 29: {want: 0x145, have: 0x13e, distance: 0xa, oneway: true}, + 30: {want: 0x158, have: 0x101, distance: 0xa, oneway: true}, + 31: {want: 0x16d, have: 0x367, distance: 0xa, oneway: true}, + 32: {want: 0x16e, have: 0x139, distance: 0xa, oneway: true}, + 33: {want: 0x16f, have: 0x139, distance: 0xa, oneway: true}, + 34: {want: 0x17e, have: 0x139, distance: 0xa, oneway: true}, + 35: {want: 0x190, have: 0x13e, distance: 0xa, oneway: true}, + 36: {want: 0x194, have: 0x13e, distance: 0xa, oneway: true}, + 37: {want: 0x1a4, have: 0x1be, distance: 0xa, oneway: true}, + 38: {want: 0x1b4, have: 0x139, distance: 0xa, oneway: true}, + 39: {want: 0x1b8, have: 0x139, distance: 0xa, oneway: true}, + 40: {want: 0x1d4, have: 0x15e, distance: 0xa, oneway: true}, + 41: {want: 0x1d7, have: 0x3e2, distance: 0xa, oneway: true}, + 42: {want: 0x1d9, have: 0x139, distance: 0xa, oneway: true}, + 43: {want: 0x1e7, have: 0x139, distance: 0xa, oneway: true}, + 44: {want: 0x1f8, have: 0x139, distance: 0xa, oneway: true}, + 45: {want: 0x20e, have: 0x1e1, distance: 0xa, oneway: true}, + 46: {want: 0x210, have: 0x139, distance: 0xa, oneway: true}, + 47: {want: 0x22d, have: 0x15e, distance: 0xa, oneway: true}, + 48: {want: 0x242, have: 0x3e2, distance: 0xa, oneway: true}, + 49: {want: 0x24a, have: 0x139, distance: 0xa, oneway: true}, + 50: {want: 0x251, have: 0x139, distance: 0xa, oneway: true}, + 51: {want: 0x265, have: 0x139, distance: 0xa, oneway: true}, + 52: {want: 0x274, have: 0x48a, distance: 0xa, oneway: true}, + 53: {want: 0x28a, have: 0x3e2, distance: 0xa, oneway: true}, + 54: {want: 0x28e, have: 0x1f9, distance: 0xa, oneway: true}, + 55: {want: 0x2a3, have: 0x139, distance: 0xa, oneway: true}, + 56: {want: 0x2b5, have: 0x15e, distance: 0xa, oneway: true}, + 57: {want: 0x2b8, have: 0x139, distance: 0xa, oneway: true}, + 58: {want: 0x2be, have: 0x139, distance: 0xa, oneway: true}, + 59: {want: 0x2c3, have: 0x15e, distance: 0xa, oneway: true}, + 60: {want: 0x2ed, have: 0x139, distance: 0xa, oneway: true}, + 61: {want: 0x2f1, have: 0x15e, distance: 0xa, oneway: true}, + 62: {want: 0x2fa, have: 0x139, distance: 0xa, oneway: true}, + 63: {want: 0x2ff, have: 0x7e, distance: 0xa, oneway: true}, + 64: {want: 0x304, have: 0x139, distance: 0xa, oneway: true}, + 65: {want: 0x30b, have: 0x3e2, distance: 0xa, oneway: true}, + 66: {want: 0x31b, have: 0x1be, distance: 0xa, oneway: true}, + 67: {want: 0x31f, have: 0x1e1, distance: 0xa, oneway: true}, + 68: {want: 0x320, have: 0x139, distance: 0xa, oneway: true}, + 69: {want: 0x331, have: 0x139, distance: 0xa, oneway: true}, + 70: {want: 0x351, have: 0x139, distance: 0xa, oneway: true}, + 71: {want: 0x36a, have: 0x347, distance: 0xa, oneway: false}, + 72: {want: 0x36a, have: 0x36f, distance: 0xa, oneway: true}, + 73: {want: 0x37a, have: 0x139, distance: 0xa, oneway: true}, + 74: {want: 0x387, have: 0x139, distance: 0xa, oneway: true}, + 75: {want: 0x389, have: 0x139, distance: 0xa, oneway: true}, + 76: {want: 0x38b, have: 0x15e, distance: 0xa, oneway: true}, + 77: {want: 0x390, have: 0x139, distance: 0xa, oneway: true}, + 78: {want: 0x395, have: 0x139, distance: 0xa, oneway: true}, + 79: {want: 0x39d, have: 0x139, distance: 0xa, oneway: true}, + 80: {want: 0x3a5, have: 0x139, distance: 0xa, oneway: true}, + 81: {want: 0x3be, have: 0x139, distance: 0xa, oneway: true}, + 82: {want: 0x3c4, have: 0x13e, distance: 0xa, oneway: true}, + 83: {want: 0x3d4, have: 0x10d, distance: 0xa, oneway: true}, + 84: {want: 0x3d9, have: 0x139, distance: 0xa, oneway: true}, + 85: {want: 0x3e5, have: 0x15e, distance: 0xa, oneway: true}, + 86: {want: 0x3e9, have: 0x1be, distance: 0xa, oneway: true}, + 87: {want: 0x3fa, have: 0x139, distance: 0xa, oneway: true}, + 88: {want: 0x40c, have: 0x139, distance: 0xa, oneway: true}, + 89: {want: 0x423, have: 0x139, distance: 0xa, oneway: true}, + 90: {want: 0x429, have: 0x139, distance: 0xa, oneway: true}, + 91: {want: 0x431, have: 0x139, distance: 0xa, oneway: true}, + 92: {want: 0x43b, have: 0x139, distance: 0xa, oneway: true}, + 93: {want: 0x43e, have: 0x1e1, distance: 0xa, oneway: true}, + 94: {want: 0x445, have: 0x139, distance: 0xa, oneway: true}, + 95: {want: 0x450, have: 0x139, distance: 0xa, oneway: true}, + 96: {want: 0x461, have: 0x139, distance: 0xa, oneway: true}, + 97: {want: 0x467, have: 0x3e2, distance: 0xa, oneway: true}, + 98: {want: 0x46f, have: 0x139, distance: 0xa, oneway: true}, + 99: {want: 0x476, have: 0x3e2, distance: 0xa, oneway: true}, + 100: {want: 0x3883, have: 0x139, distance: 0xa, oneway: true}, + 101: {want: 0x480, have: 0x139, distance: 0xa, oneway: true}, + 102: {want: 0x482, have: 0x139, distance: 0xa, oneway: true}, + 103: {want: 0x494, have: 0x3e2, distance: 0xa, oneway: true}, + 104: {want: 0x49d, have: 0x139, distance: 0xa, oneway: true}, + 105: {want: 0x4ac, have: 0x529, distance: 0xa, oneway: true}, + 106: {want: 0x4b4, have: 0x139, distance: 0xa, oneway: true}, + 107: {want: 0x4bc, have: 0x3e2, distance: 0xa, oneway: true}, + 108: {want: 0x4e5, have: 0x15e, distance: 0xa, oneway: true}, + 109: {want: 0x4f2, have: 0x139, distance: 0xa, oneway: true}, + 110: {want: 0x512, have: 0x139, distance: 0xa, oneway: true}, + 111: {want: 0x518, have: 0x139, distance: 0xa, oneway: true}, + 112: {want: 0x52f, have: 0x139, distance: 0xa, oneway: true}, +} + +// matchScript holds pairs of scriptIDs where readers of one script +// can typically also read the other. Each is associated with a confidence. +// Size: 208 bytes, 26 elements +var matchScript = [26]scriptIntelligibility{ + 0: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x57, haveScript: 0x1f, distance: 0x5}, + 1: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x1f, haveScript: 0x57, distance: 0x5}, + 2: {wantLang: 0x58, haveLang: 0x3e2, wantScript: 0x57, haveScript: 0x1f, distance: 0xa}, + 3: {wantLang: 0xa5, haveLang: 0x139, wantScript: 0xe, haveScript: 0x57, distance: 0xa}, + 4: {wantLang: 0x1d7, haveLang: 0x3e2, wantScript: 0x8, haveScript: 0x1f, distance: 0xa}, + 5: {wantLang: 0x210, haveLang: 0x139, wantScript: 0x2b, haveScript: 0x57, distance: 0xa}, + 6: {wantLang: 0x24a, haveLang: 0x139, wantScript: 0x4b, haveScript: 0x57, distance: 0xa}, + 7: {wantLang: 0x251, haveLang: 0x139, wantScript: 0x4f, haveScript: 0x57, distance: 0xa}, + 8: {wantLang: 0x2b8, haveLang: 0x139, wantScript: 0x54, haveScript: 0x57, distance: 0xa}, + 9: {wantLang: 0x304, haveLang: 0x139, wantScript: 0x6b, haveScript: 0x57, distance: 0xa}, + 10: {wantLang: 0x331, haveLang: 0x139, wantScript: 0x72, haveScript: 0x57, distance: 0xa}, + 11: {wantLang: 0x351, haveLang: 0x139, wantScript: 0x21, haveScript: 0x57, distance: 0xa}, + 12: {wantLang: 0x395, haveLang: 0x139, wantScript: 0x7d, haveScript: 0x57, distance: 0xa}, + 13: {wantLang: 0x39d, haveLang: 0x139, wantScript: 0x33, haveScript: 0x57, distance: 0xa}, + 14: {wantLang: 0x3be, haveLang: 0x139, wantScript: 0x5, haveScript: 0x57, distance: 0xa}, + 15: {wantLang: 0x3fa, haveLang: 0x139, wantScript: 0x5, haveScript: 0x57, distance: 0xa}, + 16: {wantLang: 0x40c, haveLang: 0x139, wantScript: 0xca, haveScript: 0x57, distance: 0xa}, + 17: {wantLang: 0x450, haveLang: 0x139, wantScript: 0xd7, haveScript: 0x57, distance: 0xa}, + 18: {wantLang: 0x461, haveLang: 0x139, wantScript: 0xda, haveScript: 0x57, distance: 0xa}, + 19: {wantLang: 0x46f, haveLang: 0x139, wantScript: 0x29, haveScript: 0x57, distance: 0xa}, + 20: {wantLang: 0x476, haveLang: 0x3e2, wantScript: 0x57, haveScript: 0x1f, distance: 0xa}, + 21: {wantLang: 0x4b4, haveLang: 0x139, wantScript: 0x5, haveScript: 0x57, distance: 0xa}, + 22: {wantLang: 0x4bc, haveLang: 0x3e2, wantScript: 0x57, haveScript: 0x1f, distance: 0xa}, + 23: {wantLang: 0x512, haveLang: 0x139, wantScript: 0x3b, haveScript: 0x57, distance: 0xa}, + 24: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x38, haveScript: 0x39, distance: 0xf}, + 25: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x39, haveScript: 0x38, distance: 0x13}, +} + +// Size: 90 bytes, 15 elements +var matchRegion = [15]regionIntelligibility{ + 0: {lang: 0x3a, script: 0x0, group: 0x4, distance: 0x4}, + 1: {lang: 0x3a, script: 0x0, group: 0x84, distance: 0x4}, + 2: {lang: 0x139, script: 0x0, group: 0x1, distance: 0x4}, + 3: {lang: 0x139, script: 0x0, group: 0x81, distance: 0x4}, + 4: {lang: 0x13e, script: 0x0, group: 0x3, distance: 0x4}, + 5: {lang: 0x13e, script: 0x0, group: 0x83, distance: 0x4}, + 6: {lang: 0x3c0, script: 0x0, group: 0x3, distance: 0x4}, + 7: {lang: 0x3c0, script: 0x0, group: 0x83, distance: 0x4}, + 8: {lang: 0x529, script: 0x39, group: 0x2, distance: 0x4}, + 9: {lang: 0x529, script: 0x39, group: 0x82, distance: 0x4}, + 10: {lang: 0x3a, script: 0x0, group: 0x80, distance: 0x5}, + 11: {lang: 0x139, script: 0x0, group: 0x80, distance: 0x5}, + 12: {lang: 0x13e, script: 0x0, group: 0x80, distance: 0x5}, + 13: {lang: 0x3c0, script: 0x0, group: 0x80, distance: 0x5}, + 14: {lang: 0x529, script: 0x39, group: 0x80, distance: 0x5}, +} + +// Size: 264 bytes, 33 elements +var regionContainment = [33]uint64{ + // Entry 0 - 1F + 0x00000001ffffffff, 0x00000000200007a2, 0x0000000000003044, 0x0000000000000008, + 0x00000000803c0010, 0x0000000000000020, 0x0000000000000040, 0x0000000000000080, + 0x0000000000000100, 0x0000000000000200, 0x0000000000000400, 0x000000004000384c, + 0x0000000000001000, 0x0000000000002000, 0x0000000000004000, 0x0000000000008000, + 0x0000000000010000, 0x0000000000020000, 0x0000000000040000, 0x0000000000080000, + 0x0000000000100000, 0x0000000000200000, 0x0000000001c1c000, 0x0000000000800000, + 0x0000000001000000, 0x000000001e020000, 0x0000000004000000, 0x0000000008000000, + 0x0000000010000000, 0x00000000200006a0, 0x0000000040002048, 0x0000000080000000, + // Entry 20 - 3F + 0x0000000100000000, +} + +// regionInclusion maps region identifiers to sets of regions in regionInclusionBits, +// where each set holds all groupings that are directly connected in a region +// containment graph. +// Size: 358 bytes, 358 elements +var regionInclusion = [358]uint8{ + // Entry 0 - 3F + 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x26, 0x23, + 0x24, 0x26, 0x27, 0x22, 0x28, 0x29, 0x2a, 0x2b, + 0x26, 0x2c, 0x24, 0x23, 0x26, 0x25, 0x2a, 0x2d, + 0x2e, 0x24, 0x2f, 0x2d, 0x26, 0x30, 0x31, 0x28, + // Entry 40 - 7F + 0x26, 0x28, 0x26, 0x25, 0x31, 0x22, 0x32, 0x33, + 0x34, 0x30, 0x22, 0x27, 0x27, 0x27, 0x35, 0x2d, + 0x29, 0x28, 0x27, 0x36, 0x28, 0x22, 0x34, 0x23, + 0x21, 0x26, 0x2d, 0x26, 0x22, 0x37, 0x2e, 0x35, + 0x2a, 0x22, 0x2f, 0x38, 0x26, 0x26, 0x21, 0x39, + 0x39, 0x28, 0x38, 0x39, 0x39, 0x2f, 0x3a, 0x2f, + 0x20, 0x21, 0x38, 0x3b, 0x28, 0x3c, 0x2c, 0x21, + 0x2a, 0x35, 0x27, 0x38, 0x26, 0x24, 0x28, 0x2c, + // Entry 80 - BF + 0x2d, 0x23, 0x30, 0x2d, 0x2d, 0x26, 0x27, 0x3a, + 0x22, 0x34, 0x3c, 0x2d, 0x28, 0x36, 0x22, 0x34, + 0x3a, 0x26, 0x2e, 0x21, 0x39, 0x31, 0x38, 0x24, + 0x2c, 0x25, 0x22, 0x24, 0x25, 0x2c, 0x3a, 0x2c, + 0x26, 0x24, 0x36, 0x21, 0x2f, 0x3d, 0x31, 0x3c, + 0x2f, 0x26, 0x36, 0x36, 0x24, 0x26, 0x3d, 0x31, + 0x24, 0x26, 0x35, 0x25, 0x2d, 0x32, 0x38, 0x2a, + 0x38, 0x39, 0x39, 0x35, 0x33, 0x23, 0x26, 0x2f, + // Entry C0 - FF + 0x3c, 0x21, 0x23, 0x2d, 0x31, 0x36, 0x36, 0x3c, + 0x26, 0x2d, 0x26, 0x3a, 0x2f, 0x25, 0x2f, 0x34, + 0x31, 0x2f, 0x32, 0x3b, 0x2d, 0x2b, 0x2d, 0x21, + 0x34, 0x2a, 0x2c, 0x25, 0x21, 0x3c, 0x24, 0x29, + 0x2b, 0x24, 0x34, 0x21, 0x28, 0x29, 0x3b, 0x31, + 0x25, 0x2e, 0x30, 0x29, 0x26, 0x24, 0x3a, 0x21, + 0x3c, 0x28, 0x21, 0x24, 0x21, 0x21, 0x1f, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + // Entry 100 - 13F + 0x21, 0x21, 0x2f, 0x21, 0x2e, 0x23, 0x33, 0x2f, + 0x24, 0x3b, 0x2f, 0x39, 0x38, 0x31, 0x2d, 0x3a, + 0x2c, 0x2e, 0x2d, 0x23, 0x2d, 0x2f, 0x28, 0x2f, + 0x27, 0x33, 0x34, 0x26, 0x24, 0x32, 0x22, 0x26, + 0x27, 0x22, 0x2d, 0x31, 0x3d, 0x29, 0x31, 0x3d, + 0x39, 0x29, 0x31, 0x24, 0x26, 0x29, 0x36, 0x2f, + 0x33, 0x2f, 0x21, 0x22, 0x21, 0x30, 0x28, 0x3d, + 0x23, 0x26, 0x21, 0x28, 0x26, 0x26, 0x31, 0x3b, + // Entry 140 - 17F + 0x29, 0x21, 0x29, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x23, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x24, 0x24, 0x2f, + 0x23, 0x32, 0x2f, 0x27, 0x2f, 0x21, +} + +// regionInclusionBits is an array of bit vectors where every vector represents +// a set of region groupings. These sets are used to compute the distance +// between two regions for the purpose of language matching. +// Size: 584 bytes, 73 elements +var regionInclusionBits = [73]uint64{ + // Entry 0 - 1F + 0x0000000102400813, 0x00000000200007a3, 0x0000000000003844, 0x0000000040000808, + 0x00000000803c0011, 0x0000000020000022, 0x0000000040000844, 0x0000000020000082, + 0x0000000000000102, 0x0000000020000202, 0x0000000020000402, 0x000000004000384d, + 0x0000000000001804, 0x0000000040002804, 0x0000000000404000, 0x0000000000408000, + 0x0000000000410000, 0x0000000002020000, 0x0000000000040010, 0x0000000000080010, + 0x0000000000100010, 0x0000000000200010, 0x0000000001c1c001, 0x0000000000c00000, + 0x0000000001400000, 0x000000001e020001, 0x0000000006000000, 0x000000000a000000, + 0x0000000012000000, 0x00000000200006a2, 0x0000000040002848, 0x0000000080000010, + // Entry 20 - 3F + 0x0000000100000001, 0x0000000000000001, 0x0000000080000000, 0x0000000000020000, + 0x0000000001000000, 0x0000000000008000, 0x0000000000002000, 0x0000000000000200, + 0x0000000000000008, 0x0000000000200000, 0x0000000110000000, 0x0000000000040000, + 0x0000000008000000, 0x0000000000000020, 0x0000000104000000, 0x0000000000000080, + 0x0000000000001000, 0x0000000000010000, 0x0000000000000400, 0x0000000004000000, + 0x0000000000000040, 0x0000000010000000, 0x0000000000004000, 0x0000000101000000, + 0x0000000108000000, 0x0000000000000100, 0x0000000100020000, 0x0000000000080000, + 0x0000000000100000, 0x0000000000800000, 0x00000001ffffffff, 0x0000000122400fb3, + // Entry 40 - 5F + 0x00000001827c0813, 0x000000014240385f, 0x0000000103c1c813, 0x000000011e420813, + 0x0000000112000001, 0x0000000106000001, 0x0000000101400001, 0x000000010a000001, + 0x0000000102020001, +} + +// regionInclusionNext marks, for each entry in regionInclusionBits, the set of +// all groups that are reachable from the groups set in the respective entry. +// Size: 73 bytes, 73 elements +var regionInclusionNext = [73]uint8{ + // Entry 0 - 3F + 0x3e, 0x3f, 0x0b, 0x0b, 0x40, 0x01, 0x0b, 0x01, + 0x01, 0x01, 0x01, 0x41, 0x0b, 0x0b, 0x16, 0x16, + 0x16, 0x19, 0x04, 0x04, 0x04, 0x04, 0x42, 0x16, + 0x16, 0x43, 0x19, 0x19, 0x19, 0x01, 0x0b, 0x04, + 0x00, 0x00, 0x1f, 0x11, 0x18, 0x0f, 0x0d, 0x09, + 0x03, 0x15, 0x44, 0x12, 0x1b, 0x05, 0x45, 0x07, + 0x0c, 0x10, 0x0a, 0x1a, 0x06, 0x1c, 0x0e, 0x46, + 0x47, 0x08, 0x48, 0x13, 0x14, 0x17, 0x3e, 0x3e, + // Entry 40 - 7F + 0x3e, 0x3e, 0x3e, 0x3e, 0x43, 0x43, 0x42, 0x43, + 0x43, +} + +type parentRel struct { + lang uint16 + script uint8 + maxScript uint8 + toRegion uint16 + fromRegion []uint16 +} + +// Size: 414 bytes, 5 elements +var parents = [5]parentRel{ + 0: {lang: 0x139, script: 0x0, maxScript: 0x57, toRegion: 0x1, fromRegion: []uint16{0x1a, 0x25, 0x26, 0x2f, 0x34, 0x36, 0x3d, 0x42, 0x46, 0x48, 0x49, 0x4a, 0x50, 0x52, 0x5c, 0x5d, 0x61, 0x64, 0x6d, 0x73, 0x74, 0x75, 0x7b, 0x7c, 0x7f, 0x80, 0x81, 0x83, 0x8c, 0x8d, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9f, 0xa0, 0xa4, 0xa7, 0xa9, 0xad, 0xb1, 0xb4, 0xb5, 0xbf, 0xc6, 0xca, 0xcb, 0xcc, 0xce, 0xd0, 0xd2, 0xd5, 0xd6, 0xdd, 0xdf, 0xe0, 0xe6, 0xe7, 0xe8, 0xeb, 0xf0, 0x107, 0x109, 0x10a, 0x10b, 0x10d, 0x10e, 0x112, 0x117, 0x11b, 0x11d, 0x11f, 0x125, 0x129, 0x12c, 0x12d, 0x12f, 0x131, 0x139, 0x13c, 0x13f, 0x142, 0x161, 0x162, 0x164}}, + 1: {lang: 0x139, script: 0x0, maxScript: 0x57, toRegion: 0x1a, fromRegion: []uint16{0x2e, 0x4e, 0x60, 0x63, 0x72, 0xd9, 0x10c, 0x10f}}, + 2: {lang: 0x13e, script: 0x0, maxScript: 0x57, toRegion: 0x1f, fromRegion: []uint16{0x2c, 0x3f, 0x41, 0x48, 0x51, 0x54, 0x56, 0x59, 0x65, 0x69, 0x89, 0x8f, 0xcf, 0xd8, 0xe2, 0xe4, 0xec, 0xf1, 0x11a, 0x135, 0x136, 0x13b}}, + 3: {lang: 0x3c0, script: 0x0, maxScript: 0x57, toRegion: 0xee, fromRegion: []uint16{0x2a, 0x4e, 0x5a, 0x86, 0x8b, 0xb7, 0xc6, 0xd1, 0x118, 0x126}}, + 4: {lang: 0x529, script: 0x39, maxScript: 0x39, toRegion: 0x8d, fromRegion: []uint16{0xc6}}, +} + +// Total table size 27238 bytes (26KiB); checksum: C9BBE4D5 diff --git a/vendor/golang.org/x/text/language/tags.go b/vendor/golang.org/x/text/language/tags.go new file mode 100644 index 000000000..de30155a2 --- /dev/null +++ b/vendor/golang.org/x/text/language/tags.go @@ -0,0 +1,143 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package language + +// TODO: Various sets of commonly use tags and regions. + +// MustParse is like Parse, but panics if the given BCP 47 tag cannot be parsed. +// It simplifies safe initialization of Tag values. +func MustParse(s string) Tag { + t, err := Parse(s) + if err != nil { + panic(err) + } + return t +} + +// MustParse is like Parse, but panics if the given BCP 47 tag cannot be parsed. +// It simplifies safe initialization of Tag values. +func (c CanonType) MustParse(s string) Tag { + t, err := c.Parse(s) + if err != nil { + panic(err) + } + return t +} + +// MustParseBase is like ParseBase, but panics if the given base cannot be parsed. +// It simplifies safe initialization of Base values. +func MustParseBase(s string) Base { + b, err := ParseBase(s) + if err != nil { + panic(err) + } + return b +} + +// MustParseScript is like ParseScript, but panics if the given script cannot be +// parsed. It simplifies safe initialization of Script values. +func MustParseScript(s string) Script { + scr, err := ParseScript(s) + if err != nil { + panic(err) + } + return scr +} + +// MustParseRegion is like ParseRegion, but panics if the given region cannot be +// parsed. It simplifies safe initialization of Region values. +func MustParseRegion(s string) Region { + r, err := ParseRegion(s) + if err != nil { + panic(err) + } + return r +} + +var ( + und = Tag{} + + Und Tag = Tag{} + + Afrikaans Tag = Tag{lang: _af} // af + Amharic Tag = Tag{lang: _am} // am + Arabic Tag = Tag{lang: _ar} // ar + ModernStandardArabic Tag = Tag{lang: _ar, region: _001} // ar-001 + Azerbaijani Tag = Tag{lang: _az} // az + Bulgarian Tag = Tag{lang: _bg} // bg + Bengali Tag = Tag{lang: _bn} // bn + Catalan Tag = Tag{lang: _ca} // ca + Czech Tag = Tag{lang: _cs} // cs + Danish Tag = Tag{lang: _da} // da + German Tag = Tag{lang: _de} // de + Greek Tag = Tag{lang: _el} // el + English Tag = Tag{lang: _en} // en + AmericanEnglish Tag = Tag{lang: _en, region: _US} // en-US + BritishEnglish Tag = Tag{lang: _en, region: _GB} // en-GB + Spanish Tag = Tag{lang: _es} // es + EuropeanSpanish Tag = Tag{lang: _es, region: _ES} // es-ES + LatinAmericanSpanish Tag = Tag{lang: _es, region: _419} // es-419 + Estonian Tag = Tag{lang: _et} // et + Persian Tag = Tag{lang: _fa} // fa + Finnish Tag = Tag{lang: _fi} // fi + Filipino Tag = Tag{lang: _fil} // fil + French Tag = Tag{lang: _fr} // fr + CanadianFrench Tag = Tag{lang: _fr, region: _CA} // fr-CA + Gujarati Tag = Tag{lang: _gu} // gu + Hebrew Tag = Tag{lang: _he} // he + Hindi Tag = Tag{lang: _hi} // hi + Croatian Tag = Tag{lang: _hr} // hr + Hungarian Tag = Tag{lang: _hu} // hu + Armenian Tag = Tag{lang: _hy} // hy + Indonesian Tag = Tag{lang: _id} // id + Icelandic Tag = Tag{lang: _is} // is + Italian Tag = Tag{lang: _it} // it + Japanese Tag = Tag{lang: _ja} // ja + Georgian Tag = Tag{lang: _ka} // ka + Kazakh Tag = Tag{lang: _kk} // kk + Khmer Tag = Tag{lang: _km} // km + Kannada Tag = Tag{lang: _kn} // kn + Korean Tag = Tag{lang: _ko} // ko + Kirghiz Tag = Tag{lang: _ky} // ky + Lao Tag = Tag{lang: _lo} // lo + Lithuanian Tag = Tag{lang: _lt} // lt + Latvian Tag = Tag{lang: _lv} // lv + Macedonian Tag = Tag{lang: _mk} // mk + Malayalam Tag = Tag{lang: _ml} // ml + Mongolian Tag = Tag{lang: _mn} // mn + Marathi Tag = Tag{lang: _mr} // mr + Malay Tag = Tag{lang: _ms} // ms + Burmese Tag = Tag{lang: _my} // my + Nepali Tag = Tag{lang: _ne} // ne + Dutch Tag = Tag{lang: _nl} // nl + Norwegian Tag = Tag{lang: _no} // no + Punjabi Tag = Tag{lang: _pa} // pa + Polish Tag = Tag{lang: _pl} // pl + Portuguese Tag = Tag{lang: _pt} // pt + BrazilianPortuguese Tag = Tag{lang: _pt, region: _BR} // pt-BR + EuropeanPortuguese Tag = Tag{lang: _pt, region: _PT} // pt-PT + Romanian Tag = Tag{lang: _ro} // ro + Russian Tag = Tag{lang: _ru} // ru + Sinhala Tag = Tag{lang: _si} // si + Slovak Tag = Tag{lang: _sk} // sk + Slovenian Tag = Tag{lang: _sl} // sl + Albanian Tag = Tag{lang: _sq} // sq + Serbian Tag = Tag{lang: _sr} // sr + SerbianLatin Tag = Tag{lang: _sr, script: _Latn} // sr-Latn + Swedish Tag = Tag{lang: _sv} // sv + Swahili Tag = Tag{lang: _sw} // sw + Tamil Tag = Tag{lang: _ta} // ta + Telugu Tag = Tag{lang: _te} // te + Thai Tag = Tag{lang: _th} // th + Turkish Tag = Tag{lang: _tr} // tr + Ukrainian Tag = Tag{lang: _uk} // uk + Urdu Tag = Tag{lang: _ur} // ur + Uzbek Tag = Tag{lang: _uz} // uz + Vietnamese Tag = Tag{lang: _vi} // vi + Chinese Tag = Tag{lang: _zh} // zh + SimplifiedChinese Tag = Tag{lang: _zh, script: _Hans} // zh-Hans + TraditionalChinese Tag = Tag{lang: _zh, script: _Hant} // zh-Hant + Zulu Tag = Tag{lang: _zu} // zu +) From 8b57b21d79415c653cb5bbcdc11c43d50f41dbad Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 16 Aug 2018 22:07:02 +1000 Subject: [PATCH 37/45] add circle ci config --- .circleci/config.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..cc81391f5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,26 @@ +# Golang CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-go/ for more details +version: 2 +jobs: + build: + docker: + # specify the version + - image: circleci/golang:1.9 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + #### TEMPLATE_NOTE: go expects specific checkout path representing url + #### expecting it in the form of + #### /go/src/github.com/circleci/go-tool + #### /go/src/bitbucket.org/circleci/go-tool + working_directory: /go/src/github.com/jesseduffield/lazygit + steps: + - checkout + + # specify any bash command here prefixed with `run: ` + - run: go get -v -t -d ./... + - run: go test -v ./... From ff15d86ced7023da7bef266d40b6215eb7726676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Borbo=C3=ABn?= Date: Thu, 16 Aug 2018 14:15:55 +0200 Subject: [PATCH 38/45] =?UTF-8?q?Typo=20path=20=E2=86=92=20patch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/i18n/english.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 6e2af8e22..d67bd9053 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -68,7 +68,7 @@ func addEnglish(i18nObject *i18n.Bundle) { Other: "refresh", }, &i18n.Message{ ID: "addPatch", - Other: "add path", + Other: "add patch", }, &i18n.Message{ ID: "edit", Other: "edit", From 36eed802280e2767dec3a16f6ea8908fb08ec3b9 Mon Sep 17 00:00:00 2001 From: Dawid Dziurla Date: Thu, 16 Aug 2018 19:26:43 +0200 Subject: [PATCH 39/45] add info about release PPA --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0990db695..721dc0862 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,19 @@ brew install lazygit ### Ubuntu Packages for Ubuntu 16.04, 18.04 and 18.10 are available via [Launchpad PPA](https://launchpad.net/~lazygit-team). -They are built daily, straight from master branch. +**Release builds** + +Built from git tags. Supposed to be more stable. + +```sh +sudo add-apt-repository ppa:lazygit-team/release +sudo apt-get update +sudo apt-get install lazygit +``` + +**Daily builds** + +Built from master branch once in 24 hours (or more sometimes). ```sh sudo add-apt-repository ppa:lazygit-team/daily From 52033b32f702d1b0c0157b9dd9ecb77e8ddf82b0 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen Date: Thu, 16 Aug 2018 17:04:39 -0400 Subject: [PATCH 40/45] Use strings.Replace instead of regexp --- pkg/commands/os.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/commands/os.go b/pkg/commands/os.go index c23b48c82..08db71fbb 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -5,7 +5,7 @@ import ( "os" "os/exec" "runtime" - "regexp" + "strings" "github.com/davecgh/go-spew/spew" @@ -171,7 +171,6 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) (*e // Quote wraps a message in platform-specific quotation marks func (c *OSCommand) Quote(message string) string { - r := regexp.MustCompile("`") - message = r.ReplaceAllString(message, "\\`") + message = strings.Replace(message, "`", "\\`", -1) return c.Platform.escapedQuote + message + c.Platform.escapedQuote } From dcd3bb6bbde338715cb7ce249c88396fa2a97fe6 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 17 Aug 2018 11:13:21 +1000 Subject: [PATCH 41/45] use platform independent command to remove a file or directory --- pkg/commands/git.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 44fd57f1c..3349e4860 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -359,7 +359,7 @@ func (c *GitCommand) IsInMergeState() (bool, error) { func (c *GitCommand) RemoveFile(file File) error { // if the file isn't tracked, we assume you want to delete it if !file.Tracked { - return c.OSCommand.RunCommand("rm -rf ./" + file.Name) + return os.RemoveAll(file.Name) } // if the file is tracked, we assume you want to just check it out return c.OSCommand.RunCommand("git checkout " + file.Name) From c08e6d9999f60e7f8edb50a035be00ebd4d61cda Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 17 Aug 2018 11:14:54 +1000 Subject: [PATCH 42/45] bump version to v0.1.62 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index e26c92f2c..d694cb00f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.1.61 \ No newline at end of file +v0.1.62 \ No newline at end of file From 3b1689727a013d884e151d2c294fb42d88035e76 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 17 Aug 2018 11:15:19 +1000 Subject: [PATCH 43/45] bump version to v0.1.63 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index d694cb00f..b7816814d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.1.62 \ No newline at end of file +v0.1.63 \ No newline at end of file From 933aae7da12e0f5ee1ef8eada1e72e386cd68e51 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 17 Aug 2018 11:15:41 +1000 Subject: [PATCH 44/45] bump version to v0.1.64 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b7816814d..00a638d6c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.1.63 \ No newline at end of file +v0.1.64 \ No newline at end of file From 99d40c2f8ecdecd39df80b199f9ff1cc9eb90434 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 18 Aug 2018 11:14:51 +1000 Subject: [PATCH 45/45] run codecov report after other commands on circle ci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cc81391f5..c5f520b70 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,5 +22,5 @@ jobs: - checkout # specify any bash command here prefixed with `run: ` - - run: go get -v -t -d ./... - run: go test -v ./... + - run: bash <(curl -s https://codecov.io/bash)