summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2022-01-04 10:46:14 +1100
committerJesse Duffield <jessedduffield@gmail.com>2022-01-04 11:12:04 +1100
commite8a1a4ffc0ab0e10ee0ad43042dd5a2fd2438353 (patch)
tree6f645fc04e22ba08f10010cbec0ef3d27a670311 /pkg
parent8e66d2761ee6580981d6d465e9d970c6a80d3e65 (diff)
add cheatsheet check script
Diffstat (limited to 'pkg')
-rw-r--r--pkg/cheatsheet/check.go79
-rw-r--r--pkg/cheatsheet/generate.go265
2 files changed, 344 insertions, 0 deletions
diff --git a/pkg/cheatsheet/check.go b/pkg/cheatsheet/check.go
new file mode 100644
index 000000000..03f65d910
--- /dev/null
+++ b/pkg/cheatsheet/check.go
@@ -0,0 +1,79 @@
+package cheatsheet
+
+import (
+ "fmt"
+ "io/fs"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ "github.com/pmezard/go-difflib/difflib"
+)
+
+func Check() {
+ dir := GetDir()
+ tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
+ err := os.RemoveAll(tmpDir)
+ if err != nil {
+ log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err)
+ }
+ err = os.Mkdir(tmpDir, 0700)
+ if err != nil {
+ log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err)
+ }
+
+ generateAtDir(tmpDir)
+ defer os.RemoveAll(tmpDir)
+
+ actualContent := obtainContent(dir)
+ expectedContent := obtainContent(tmpDir)
+
+ if expectedContent == "" {
+ log.Fatal("empty expected content")
+ }
+
+ if actualContent != expectedContent {
+ err := difflib.WriteUnifiedDiff(os.Stdout, difflib.UnifiedDiff{
+ A: difflib.SplitLines(expectedContent),
+ B: difflib.SplitLines(actualContent),
+ FromFile: "Expected",
+ FromDate: "",
+ ToFile: "Actual",
+ ToDate: "",
+ Context: 1,
+ })
+ if err != nil {
+ log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err)
+ }
+ fmt.Printf("\nCheatsheets are out of date. Please run `%s` at the project root and commit the changes\n", CommandToRun())
+ os.Exit(1)
+ }
+
+ fmt.Println("\nCheatsheets are up to date")
+}
+
+func obtainContent(dir string) string {
+ re := regexp.MustCompile(`Keybindings_\w+\.md$`)
+
+ content := ""
+ err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+ if re.MatchString(path) {
+ bytes, err := ioutil.ReadFile(path)
+ if err != nil {
+ log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err)
+ }
+ content += fmt.Sprintf("\n%s\n\n", filepath.Base(path))
+ content += string(bytes)
+ }
+
+ return nil
+ })
+
+ if err != nil {
+ log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err)
+ }
+
+ return content
+}
diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go
new file mode 100644
index 000000000..513049f23
--- /dev/null
+++ b/pkg/cheatsheet/generate.go
@@ -0,0 +1,265 @@
+// This "script" generates a file called Keybindings_{{.LANG}}.md
+// in current working directory.
+//
+// The content of this generated file is a keybindings cheatsheet.
+//
+// To generate cheatsheet in english run:
+// go run scripts/generate_cheatsheet.go
+
+package cheatsheet
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "sort"
+
+ "github.com/jesseduffield/lazygit/pkg/app"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui"
+ "github.com/jesseduffield/lazygit/pkg/i18n"
+ "github.com/jesseduffield/lazygit/pkg/integration"
+)
+
+type bindingSection struct {
+ title string
+ bindings []*gui.Binding
+}
+
+func CommandToRun() string {
+ return "go run scripts/cheatsheet/main.go generate"
+}
+
+func GetDir() string {
+ return integration.GetRootDirectory() + "/docs/keybindings"
+}
+
+func generateAtDir(cheatsheetDir string) {
+ os.Setenv("LANG", "en")
+
+ translationSetsByLang := i18n.GetTranslationSets()
+ mConfig := config.NewDummyAppConfig()
+
+ for lang := range translationSetsByLang {
+ os.Setenv("LC_ALL", lang)
+ mApp, _ := app.NewApp(mConfig, "")
+ path := cheatsheetDir + "/Keybindings_" + lang + ".md"
+ file, err := os.Create(path)
+ if err != nil {
+ panic(err)
+ }
+
+ bindingSections := getBindingSections(mApp)
+ content := formatSections(mApp.Tr, bindingSections)
+ content = fmt.Sprintf("# This file is auto-generated. To update, make the changes in the "+
+ "pkg/i18n directory and then run `%s` from the project root.\n\n%s", CommandToRun(), content)
+ writeString(file, content)
+ }
+}
+
+func Generate() {
+ generateAtDir(GetDir())
+}
+
+func writeString(file *os.File, str string) {
+ _, err := file.WriteString(str)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func localisedTitle(mApp *app.App, str string) string {
+ tr := mApp.Tr
+
+ contextTitleMap := map[string]string{
+ "global": tr.GlobalTitle,
+ "navigation": tr.NavigationTitle,
+ "branches": tr.BranchesTitle,
+ "localBranches": tr.LocalBranchesTitle,
+ "files": tr.FilesTitle,
+ "status": tr.StatusTitle,
+ "submodules": tr.SubmodulesTitle,
+ "subCommits": tr.SubCommitsTitle,
+ "remoteBranches": tr.RemoteBranchesTitle,
+ "remotes": tr.RemotesTitle,
+ "reflogCommits": tr.ReflogCommitsTitle,
+ "tags": tr.TagsTitle,
+ "commitFiles": tr.CommitFilesTitle,
+ "commitMessage": tr.CommitMessageTitle,
+ "commits": tr.CommitsTitle,
+ "confirmation": tr.ConfirmationTitle,
+ "credentials": tr.CredentialsTitle,
+ "information": tr.InformationTitle,
+ "main": tr.MainTitle,
+ "patchBuilding": tr.PatchBuildingTitle,
+ "merging": tr.MergingTitle,
+ "normal": tr.NormalTitle,
+ "staging": tr.StagingTitle,
+ "menu": tr.MenuTitle,
+ "search": tr.SearchTitle,
+ "secondary": tr.SecondaryTitle,
+ "stash": tr.StashTitle,
+ "suggestions": tr.SuggestionsCheatsheetTitle,
+ "extras": tr.ExtrasTitle,
+ }
+
+ title, ok := contextTitleMap[str]
+ if !ok {
+ panic(fmt.Sprintf("title not found for %s", str))
+ }
+
+ return title
+}
+
+func formatTitle(title string) string {
+ return fmt.Sprintf("\n## %s\n\n", title)
+}
+
+func formatBinding(binding *gui.Binding) string {
+ if binding.Alternative != "" {
+ return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative)
+ }
+ return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", gui.GetKeyDisplay(binding.Key), binding.Description)
+}
+
+func getBindingSections(mApp *app.App) []*bindingSection {
+ bindingSections := []*bindingSection{}
+
+ bindings := mApp.Gui.GetInitialKeybindings()
+
+ type contextAndViewType struct {
+ subtitle string
+ title string
+ }
+
+ contextAndViewBindingMap := map[contextAndViewType][]*gui.Binding{}
+
+outer:
+ for _, binding := range bindings {
+ if binding.Tag == "navigation" {
+ key := contextAndViewType{subtitle: "", title: "navigation"}
+ existing := contextAndViewBindingMap[key]
+ if existing == nil {
+ contextAndViewBindingMap[key] = []*gui.Binding{binding}
+ } else {
+ for _, navBinding := range contextAndViewBindingMap[key] {
+ if navBinding.Description == binding.Description {
+ continue outer
+ }
+ }
+ contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding)
+ }
+
+ continue outer
+ }
+
+ contexts := []string{}
+ if len(binding.Contexts) == 0 {
+ contexts = append(contexts, "")
+ } else {
+ contexts = append(contexts, binding.Contexts...)
+ }
+
+ for _, context := range contexts {
+ key := contextAndViewType{subtitle: context, title: binding.ViewName}
+ existing := contextAndViewBindingMap[key]
+ if existing == nil {
+ contextAndViewBindingMap[key] = []*gui.Binding{binding}
+ } else {
+ contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding)
+ }
+ }
+ }
+
+ type groupedBindingsType struct {
+ contextAndView contextAndViewType
+ bindings []*gui.Binding
+ }
+
+ groupedBindings := make([]groupedBindingsType, len(contextAndViewBindingMap))
+
+ for contextAndView, contextBindings := range contextAndViewBindingMap {
+ groupedBindings = append(groupedBindings, groupedBindingsType{contextAndView: contextAndView, bindings: contextBindings})
+ }
+
+ sort.Slice(groupedBindings, func(i, j int) bool {
+ first := groupedBindings[i].contextAndView
+ second := groupedBindings[j].contextAndView
+ if first.title == "" {
+ return true
+ }
+ if second.title == "" {
+ return false
+ }
+ if first.title == "navigation" {
+ return true
+ }
+ if second.title == "navigation" {
+ return false
+ }
+ return first.title < second.title || (first.title == second.title && first.subtitle < second.subtitle)
+ })
+
+ for _, group := range groupedBindings {
+ contextAndView := group.contextAndView
+ contextBindings := group.bindings
+ mApp.Log.Info("viewname: " + contextAndView.title + ", context: " + contextAndView.subtitle)
+ viewName := contextAndView.title
+ if viewName == "" {
+ viewName = "global"
+ }
+ translatedView := localisedTitle(mApp, viewName)
+ var title string
+ if contextAndView.subtitle == "" {
+ addendum := " " + mApp.Tr.Panel
+ if viewName == "global" || viewName == "navigation" {
+ addendum = ""
+ }
+ title = fmt.Sprintf("%s%s", translatedView, addendum)
+ } else {
+ translatedContextName := localisedTitle(mApp, contextAndView.subtitle)
+ title = fmt.Sprintf("%s %s (%s)", translatedView, mApp.Tr.Panel, translatedContextName)
+ }
+
+ for _, binding := range contextBindings {
+ bindingSections = addBinding(title, bindingSections, binding)
+ }
+ }
+
+ return bindingSections
+}
+
+func addBinding(title string, bindingSections []*bindingSection, binding *gui.Binding) []*bindingSection {
+ if binding.Description == "" && binding.Alternative == "" {
+ return bindingSections
+ }
+
+ for _, section := range bindingSections {
+ if title == section.title {
+ section.bindings = append(section.bindings, binding)
+ return bindingSections
+ }
+ }
+
+ section := &bindingSection{
+ title: title,
+ bindings: []*gui.Binding{binding},
+ }
+
+ return append(bindingSections, section)
+}
+
+func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string {
+ content := fmt.Sprintf("# Lazygit %s\n", tr.Keybindings)
+
+ for _, section := range bindingSections {
+ content += formatTitle(section.title)
+ content += "<pre>\n"
+ for _, binding := range section.bindings {
+ content += formatBinding(binding)
+ }
+ content += "</pre>\n"
+ }
+
+ return content
+}