├── .clang-format ├── .github └── workflows │ └── go.yml ├── .gitignore ├── .gitmodules ├── README.md ├── cmd └── uhh │ ├── cli.go │ ├── config.go │ └── main.go ├── config.go ├── config_test.go ├── constants.go ├── git.go ├── go.mod ├── go.sum ├── snippets.go └── uhh.go /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # Disable autoformatting by contributors with clang-format 3 | DisableFormat: true 4 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: [ ubuntu-latest ] 14 | 15 | name: Build 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | 19 | - name: Set up Go 1.x 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: ^1.13 23 | id: go 24 | 25 | - name: Check out code into the Go module directory 26 | uses: actions/checkout@v2 27 | 28 | - name: Get dependencies 29 | run: | 30 | go get -v -t -d ./... 31 | if [ -f Gopkg.toml ]; then 32 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 33 | dep ensure 34 | fi 35 | - name: Build 36 | run: go build -v . 37 | 38 | - name: Test 39 | run: go test -v . 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/uhh/uhh 2 | cmd/uhh2/uhh2 3 | .clangd 4 | .zshrc.bak 5 | .ycm_extra_conf.py 6 | .vscode 7 | 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/libgit2"] 2 | path = 3rdparty/libgit2 3 | url = https://github.com/libgit2/libgit2.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uhh 2 | When you keep forgetting those sweet sweet sweet sweet commands. 3 | 4 | # Requirements 5 | golang 6 | 7 | # TODO 8 | * help menu 9 | * clean up this raggity ass readme 10 | * multi-repo support 11 | 12 | # How to build 13 | TODO: 14 | 15 | ### Usage 16 | 17 | #### Directories 18 | Uhh looks at the environment variables `UHH_DIR` and `XDG_CONFIG_HOME` to determine 19 | where it should put its files on its first run. `UHH_DIR` should be an already 20 | existing folder where you wish all of Uhh's resources to be put, while 21 | `XDG_CONFIG_HOME` should be the parent folder where Uhh will make a folder for 22 | itself called `uhh`. 23 | 24 | If neither of these are defined, Uhh defaults to `$HOME/.config/uhh`. 25 | 26 | #### Basic Usage 27 | `uhh [options] {cmd/token} {searching tokens}` 28 | 29 | ``` 30 | uhh uhh = help 31 | uhh wat = help 32 | uhh huh = help 33 | Prints the help menu 34 | 35 | uhh list = list 36 | uhh what = list 37 | list all the categories 38 | 39 | uhh update 40 | from repo: git pull origin master 41 | 42 | uhh delete {cmd} {tokens for searching} 43 | How does this work? 44 | list categories, you put in number 45 | list items in category, you put in number 46 | are you sure? 47 | 48 | uhh add 49 | cat: onteuhnoteuhnoteuh 50 | one-line desc: onetuhonteuhonetonetuhonetuh 51 | cmd: eontuhoentuhonteuc:w 52 | 53 | uhh sync 54 | Syncs the repo (git commit -m 'ontehunoteh' && git push origin master) 55 | 56 | uhh tcpdump 57 | "One line explanation" 58 | tcpdump -.... 59 | 60 | uhh -F 61 | "onteuhone" 62 | perf -F 63 | ``` 64 | 65 | 66 | -------------------------------------------------------------------------------- /cmd/uhh/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/theprimeagen/uhh" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | type uhhCli struct { 13 | backend *uhh.Uhh 14 | } 15 | 16 | func newUhhCli(backend *uhh.Uhh) *uhhCli { 17 | return &uhhCli{ 18 | backend: backend, 19 | } 20 | } 21 | 22 | func (ucli *uhhCli) addRepoHandler(c *cli.Context) error { 23 | var repoUrl string 24 | if c.Args().Present() { 25 | repoUrl = c.Args().First() 26 | fmt.Printf("Using: %s\n", repoUrl) 27 | } else { 28 | fmt.Printf("Please enter the repo to add\n") 29 | repoUrl = readTermLine() 30 | } 31 | 32 | fmt.Printf("About to clone repo: %s\n", repoUrl) 33 | return ucli.backend.AddRepo(repoUrl) 34 | } 35 | 36 | func (ucli *uhhCli) syncHandler(c *cli.Context) error { 37 | return ucli.backend.Sync() 38 | } 39 | 40 | func (ucli *uhhCli) findHandler(c *cli.Context) error { 41 | 42 | if c.Args().Present() { 43 | results, err := ucli.backend.Find(c.Args().First(), c.Args().Tail()) 44 | if err != nil { 45 | return fmt.Errorf("Error from find: %w", err) 46 | } 47 | 48 | if len(results.Commands) == 0 { 49 | return nil 50 | } 51 | 52 | suffix := "\n" 53 | if isStdoutPiped() { 54 | suffix = "" 55 | } 56 | fmt.Printf("%s%s", strings.Join(results.Commands, "\n"), suffix) 57 | 58 | return nil 59 | } 60 | 61 | cli.ShowAppHelp(c) 62 | 63 | return nil 64 | } 65 | 66 | func (ucli *uhhCli) addHandler(c *cli.Context) error { 67 | var tag string 68 | if c.Args().Present() { 69 | tag = c.Args().First() 70 | } else { 71 | fmt.Println("tag name") 72 | tag = readTermLine() 73 | } 74 | 75 | fmt.Println("please enter command") 76 | cmd := readTermLine() 77 | 78 | fmt.Println("please enter space delimited search terms") 79 | jointTerms := readTermLine() 80 | 81 | err := ucli.backend.Add(tag, cmd, jointTerms) 82 | 83 | if err != nil { 84 | return fmt.Errorf("unable to add command: %w", err) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (ucli *uhhCli) deleteHandler(c *cli.Context) error { 91 | 92 | if !c.Args().Present() { 93 | return fmt.Errorf("Must provide tag and optional search terms to delete.") 94 | } 95 | 96 | results, err := ucli.backend.Find(c.Args().First(), c.Args().Tail()) 97 | 98 | if err != nil { 99 | return err 100 | } 101 | 102 | if len(results.Commands) == 0 { 103 | return fmt.Errorf("Unable to find any commands to delete") 104 | } 105 | 106 | fmt.Printf("About to delete %d items.\n", len(results.Commands)) 107 | for i, value := range results.Commands { 108 | fmt.Printf("%d: %s\n", i, value) 109 | } 110 | 111 | fmt.Printf("Confirm delete (y/N): ") 112 | line := readTermLine() 113 | 114 | if len(line) != 1 || line[0] != 'y' && line[0] != 'Y' { 115 | return nil 116 | } 117 | 118 | ucli.backend.Delete(results) 119 | 120 | return nil 121 | } 122 | 123 | func isStdoutPiped() bool { 124 | info, err := os.Stdout.Stat() 125 | if err != nil { 126 | return false 127 | } 128 | return info.Mode()&os.ModeCharDevice == 0 129 | } 130 | -------------------------------------------------------------------------------- /cmd/uhh/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/theprimeagen/uhh" 8 | ) 9 | 10 | func getConfig() (*uhh.Config, bool) { 11 | cfg, err := uhh.ReadUserConfig() 12 | 13 | created := false 14 | 15 | if err == uhh.ErrCfgNotFound { 16 | 17 | cfg = readNewConfig() 18 | created = cfg.Repo() != "" 19 | path, err := uhh.DefaultConfigPath() 20 | if err != nil { 21 | log.Fatal("getting default path for config failed", err) 22 | } 23 | 24 | cfg.Save(path) 25 | } 26 | 27 | return cfg, created 28 | } 29 | 30 | func readNewConfig() *uhh.Config { 31 | fmt.Println("Before you get started on Uhh, do you have a repo you would like to save your commands too?") 32 | fmt.Println("Please enter your repo meow (empty = no): ") 33 | 34 | repoURL := readTermLine() 35 | 36 | defaultCfg, err := uhh.DefaultConfigPath() 37 | if err != nil { 38 | log.Fatal("unable to get default config path:", err) 39 | } 40 | 41 | return uhh.CreateConfig(defaultCfg, repoURL) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/uhh/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "github.com/theprimeagen/uhh" 11 | "github.com/urfave/cli" 12 | "golang.org/x/crypto/ssh/terminal" 13 | ) 14 | 15 | func main() { 16 | cfg, created := getConfig() 17 | 18 | uhh := uhh.New(cfg) 19 | ucli := newUhhCli(uhh) 20 | 21 | if created { 22 | err := uhh.Clone() 23 | 24 | if err != nil { 25 | log.Fatalf("%+v\n", err) 26 | } 27 | } 28 | 29 | app := &cli.App{ 30 | Name: "uhh", 31 | Usage: "find commands from your repo", 32 | Action: ucli.findHandler, 33 | Commands: []cli.Command{ 34 | {Name: "sync", Action: ucli.syncHandler}, 35 | {Name: "add", Action: ucli.addHandler}, 36 | {Name: "add-repo", Action: ucli.addRepoHandler}, 37 | {Name: "delete", Action: ucli.deleteHandler}, 38 | }, 39 | } 40 | 41 | err := app.Run(os.Args) 42 | 43 | if err != nil { 44 | fmt.Printf("%+v\n", err) 45 | os.Exit(1) 46 | } 47 | } 48 | 49 | func readTermLine() string { 50 | oldState, err := terminal.MakeRaw(0) 51 | if err != nil { 52 | log.Fatal("could not change state of terminal") 53 | } 54 | defer terminal.Restore(0, oldState) 55 | t := terminal.NewTerminal(os.Stdin, ">") 56 | 57 | line, err := t.ReadLine() 58 | if err != nil { 59 | if err != io.EOF { 60 | fmt.Fprintf(os.Stderr, "unable readline: %+v\n", err) 61 | } 62 | os.Exit(1) 63 | } 64 | 65 | return strings.TrimSpace(line) 66 | } 67 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package uhh 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "strconv" 10 | ) 11 | 12 | const repoKey = "repo" 13 | 14 | var ( 15 | ErrCfgNotFound = fmt.Errorf("config file not found") 16 | ErrRepoCfgNotFound = fmt.Errorf("repo url not found in config") 17 | ) 18 | 19 | type ConfigSpec struct { 20 | Repo *string `json:"repo"` 21 | ReadRepos []string `json:"readRepos"` 22 | SyncOnAdd bool `json:"syncOnAdd"` 23 | SyncOnAfterTime bool `json:"syncAfterTime"` 24 | } 25 | 26 | type Config struct { 27 | basePath string 28 | configPath string 29 | localRepoPath string 30 | vals *ConfigSpec 31 | } 32 | 33 | func DefaultConfigPath() (string, error) { 34 | cfgDir, err := os.UserConfigDir() 35 | if err != nil { 36 | return "", fmt.Errorf("can'd find the default home dir: %w", err) 37 | } 38 | 39 | basePath := path.Join(cfgDir, "uhh") 40 | 41 | err = os.MkdirAll(cfgDir, os.ModePerm) 42 | 43 | if err != nil { 44 | return "", fmt.Errorf("unable to create uhh config dir '%s': %w", basePath, err) 45 | } 46 | 47 | return path.Join(basePath, ".config"), nil 48 | } 49 | 50 | func ReadUserConfig() (*Config, error) { 51 | cfgPath, err := DefaultConfigPath() 52 | 53 | if err != nil { 54 | return nil, fmt.Errorf("unable to get default config path: %w", err) 55 | } 56 | return ReadConfig(cfgPath) 57 | } 58 | 59 | func ReadConfig(configPath string) (*Config, error) { 60 | rawConfig, err := ioutil.ReadFile(configPath) 61 | if os.IsNotExist(err) { 62 | return nil, ErrCfgNotFound 63 | } 64 | 65 | values, err := parseConfig(rawConfig) 66 | basePath := path.Dir(configPath) 67 | localRepoPath := path.Join(basePath, "repo") 68 | 69 | if values.ReadRepos == nil { 70 | values.ReadRepos = []string{} 71 | } 72 | 73 | return &Config{ 74 | basePath: basePath, 75 | configPath: configPath, 76 | localRepoPath: localRepoPath, 77 | vals: values, 78 | }, nil 79 | } 80 | 81 | func parseConfig(cfg []byte) (*ConfigSpec, error) { 82 | var spec ConfigSpec 83 | err := json.Unmarshal(cfg, &spec) 84 | 85 | return &spec, err 86 | } 87 | 88 | func CreateConfig(configPath, repo string) *Config { 89 | basePath := path.Dir(configPath) 90 | localRepoPath := path.Join(basePath, "repo") 91 | 92 | return &Config{ 93 | configPath: configPath, 94 | basePath: basePath, 95 | localRepoPath: localRepoPath, 96 | vals: &ConfigSpec{&repo, []string{}, false, false}, 97 | } 98 | } 99 | 100 | func (c *Config) GetReadRepoPath(item int) string { 101 | return path.Join(c.basePath, strconv.Itoa(item)) 102 | } 103 | 104 | func (c *Config) ReadRepos() []string { 105 | return c.vals.ReadRepos 106 | } 107 | 108 | func (c *Config) Repo() string { 109 | if c.vals.Repo == nil { 110 | return "" 111 | } 112 | return *c.vals.Repo 113 | } 114 | 115 | func (c *Config) LocalRepoPath() string { 116 | return c.localRepoPath 117 | } 118 | 119 | func (c *Config) Path() string { 120 | return c.configPath 121 | } 122 | 123 | func (c *Config) Save(path string) error { 124 | 125 | // marshal config 126 | configJSON, err := json.MarshalIndent(&c.vals, "", " ") 127 | if err != nil { 128 | return err 129 | } 130 | 131 | // write updates to file 132 | return ioutil.WriteFile(path, configJSON, os.ModePerm) 133 | } 134 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package uhh 2 | 3 | /* 4 | import ( 5 | "io/ioutil" 6 | "os" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestReadConfig(t *testing.T) { 12 | cfgStr := ` 13 | repo=https://github.com/theprimagen/uhh 14 | ` 15 | f, closer, err := createTempCfg(cfgStr) 16 | defer closer() 17 | if err != nil { 18 | t.Error("unable to create temp file", err) 19 | } 20 | cfg, err := ReadConfig(f.Name()) 21 | 22 | if err != nil { 23 | t.Error("un expected error") 24 | } 25 | 26 | if cfg.Repo() != "https://github.com/theprimagen/uhh" { 27 | t.Error("repo method returned unexpected value") 28 | } 29 | } 30 | 31 | func TestReadConfigWithoutRepo(t *testing.T) { 32 | cfgStr := ` 33 | git=https://github.com/theprimagen/uhh 34 | ` 35 | f, closer, err := createTempCfg(cfgStr) 36 | defer closer() 37 | if err != nil { 38 | t.Error("unable to create temp file", err) 39 | } 40 | 41 | cfg, err := ReadConfig(f.Name()) 42 | if err != ErrRepoCfgNotFound { 43 | t.Error("expected reading config to fail with repo key not found but got", cfg, err) 44 | } 45 | } 46 | 47 | func createTempCfg(str string) (*os.File, func(), error) { 48 | f, err := ioutil.TempFile("", "without-repo") 49 | if err != nil { 50 | return nil, func() {}, err 51 | } 52 | 53 | f.WriteString(str) 54 | 55 | return f, func() { os.Remove(f.Name()) }, err 56 | } 57 | func TestConfigParser(t *testing.T) { 58 | inputs := []struct { 59 | name string 60 | raw string 61 | expected map[string]string 62 | }{ 63 | {"positive test", "repo=https://github.com/theprimeagen/uhh-cfg", map[string]string{"repo": "https://github.com/theprimeagen/uhh-cfg"}}, 64 | {"empty string parse", "", map[string]string{}}, 65 | {"no equals sign", "this is a useless line", map[string]string{}}, 66 | {"multiple equals signs", "woah=dude=where=is=my=car", map[string]string{"woah": "dude=where=is=my=car"}}, 67 | {"space is ignored", "key = value", map[string]string{"key": "value"}}, 68 | } 69 | 70 | for i := range inputs { 71 | tcase := inputs[i] 72 | t.Run(tcase.name, func(t *testing.T) { 73 | result, err := parseConfig(tcase.raw) 74 | if !reflect.DeepEqual(tcase.expected, result) { 75 | t.Errorf("parse result did not match for %s: expected: %+v, got: %+v", tcase.name, tcase.expected, result) 76 | } 77 | }) 78 | } 79 | } 80 | */ 81 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package uhh 2 | 3 | var actions = []string{ 4 | "add", 5 | "delete", 6 | "help", 7 | "huh", 8 | "uhh", 9 | "list", 10 | } 11 | -------------------------------------------------------------------------------- /git.go: -------------------------------------------------------------------------------- 1 | package uhh 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "time" 7 | ) 8 | 9 | func gitFetch(c *Config) bool { 10 | cmd := exec.Command("git", "-C", c.LocalRepoPath(), "fetch") 11 | cmd.Stdout = os.Stderr 12 | cmd.Stderr = os.Stderr 13 | cmd.Stdin = os.Stdin 14 | cmd.Run() 15 | return cmd.ProcessState.Success() 16 | 17 | } 18 | 19 | func gitPush(c *Config) bool { 20 | cmd := exec.Command("git", 21 | "-C", 22 | c.LocalRepoPath(), 23 | "push", 24 | ) 25 | 26 | cmd.Stdout = os.Stderr 27 | cmd.Stderr = os.Stderr 28 | cmd.Stdin = os.Stdin 29 | cmd.Run() 30 | 31 | return cmd.ProcessState.Success() 32 | } 33 | 34 | func gitAdd(c *Config) bool { 35 | cmd := exec.Command("git", "add", ".") 36 | cmd.Dir = c.LocalRepoPath() 37 | 38 | cmd.Stdout = os.Stderr 39 | cmd.Stderr = os.Stderr 40 | cmd.Stdin = os.Stdin 41 | cmd.Run() 42 | 43 | return cmd.ProcessState.Success() 44 | } 45 | 46 | func gitCommit(c *Config) bool { 47 | cmd := exec.Command("git", 48 | "commit", 49 | "-m", 50 | "updated at "+time.Now().String(), 51 | ) 52 | 53 | cmd.Dir = c.LocalRepoPath() 54 | cmd.Stdout = os.Stderr 55 | cmd.Stderr = os.Stderr 56 | cmd.Stdin = os.Stdin 57 | cmd.Run() 58 | 59 | return cmd.ProcessState.Success() 60 | } 61 | 62 | func gitPull(pathToRepo string) bool { 63 | cmd := exec.Command("git", 64 | "-C", 65 | pathToRepo, 66 | "pull", 67 | "--rebase", 68 | ) 69 | cmd.Stdout = os.Stderr 70 | cmd.Stderr = os.Stderr 71 | cmd.Stdin = os.Stdin 72 | cmd.Run() 73 | return cmd.ProcessState.Success() 74 | } 75 | 76 | func gitClone(repo string, path string) bool { 77 | cmd := exec.Command("git", "clone", repo, path) 78 | cmd.Stdout = os.Stderr 79 | cmd.Stderr = os.Stderr 80 | cmd.Stdin = os.Stdin 81 | cmd.Run() 82 | return cmd.ProcessState.Success() 83 | } 84 | 85 | func gitStashPush(c *Config) bool { 86 | cmd := exec.Command("git", 87 | "-C", 88 | c.LocalRepoPath(), 89 | "stash", 90 | ) 91 | 92 | cmd.Stdout = os.Stderr 93 | cmd.Stderr = os.Stderr 94 | cmd.Stdin = os.Stdin 95 | cmd.Run() 96 | 97 | return cmd.ProcessState.Success() 98 | } 99 | 100 | func gitStashPop(c *Config) bool { 101 | cmd := exec.Command("git", 102 | "-C", 103 | c.LocalRepoPath(), 104 | "stash", 105 | "pop", 106 | ) 107 | 108 | cmd.Stdout = os.Stderr 109 | cmd.Stderr = os.Stderr 110 | cmd.Stdin = os.Stdin 111 | cmd.Run() 112 | 113 | return cmd.ProcessState.Success() 114 | } 115 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/theprimeagen/uhh 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/urfave/cli v1.22.4 7 | golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 6 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 7 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 8 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 9 | github.com/theprimeagen/uhh v0.0.0-20200416004948-b30e4ca91c21 h1:VnpA3TdnwnN7feIdiSETNPcKxeg1g97KH93WTDBnTQo= 10 | github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= 11 | github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 12 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 13 | golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= 14 | golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 15 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 16 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 17 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 18 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 22 | -------------------------------------------------------------------------------- /snippets.go: -------------------------------------------------------------------------------- 1 | package uhh 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "path" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | type Snippet struct { 12 | line int 13 | tag string 14 | cmd string 15 | searchTerms string 16 | } 17 | 18 | type Snippets []*Snippet 19 | 20 | func (sns Snippets) Diff(other Snippets) Snippets { 21 | dummy := struct{}{} 22 | 23 | lookupTable := make(map[Snippet]struct{}, len(sns)) 24 | for _, s := range other { 25 | lookupTable[*s] = dummy 26 | } 27 | 28 | result := []*Snippet{} 29 | 30 | for _, s := range sns { 31 | if _, ok := lookupTable[*s]; !ok { 32 | result = append(result, s) 33 | } 34 | } 35 | return result 36 | } 37 | 38 | func ReadSnippetsFromDir(loc string) (Snippets, error) { 39 | files, err := ioutil.ReadDir(loc) 40 | if err != nil { 41 | return nil, fmt.Errorf("unable to read dir: %w", err) 42 | } 43 | result := []*Snippet{} 44 | for _, f := range files { 45 | if f.IsDir() { 46 | // we only read files :) 47 | continue 48 | } 49 | snippets, err := ReadSnippetsFromFile(path.Join(loc, f.Name())) 50 | if err != nil { 51 | fmt.Println("unable to read file", f.Name()) 52 | continue 53 | } 54 | result = append(result, snippets...) 55 | } 56 | 57 | return result, nil 58 | } 59 | 60 | func ReadSnippetsFromFile(file string) (Snippets, error) { 61 | _, tag := filepath.Split(file) 62 | 63 | contents, err := ioutil.ReadFile(file) 64 | if err != nil { 65 | return nil, fmt.Errorf("unable to read file: %w", err) 66 | } 67 | 68 | lines := strings.Split(strings.TrimSpace(string(contents)), "\n") 69 | 70 | if len(lines)%2 != 0 { 71 | return nil, fmt.Errorf("invalid tag files, dones not contain expected amout of lines: %d", len(lines)) 72 | } 73 | 74 | result := []*Snippet{} 75 | for i := 1; i < len(lines); i = i + 2 { 76 | command := lines[i-1] 77 | searchTerms := lines[i] 78 | 79 | result = append(result, &Snippet{ 80 | line: i, 81 | tag: tag, 82 | cmd: command, 83 | searchTerms: searchTerms, 84 | }) 85 | } 86 | return result, nil 87 | } 88 | -------------------------------------------------------------------------------- /uhh.go: -------------------------------------------------------------------------------- 1 | package uhh 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type Uhh struct { 13 | config *Config 14 | } 15 | 16 | type FindResults struct { 17 | Tag string 18 | Commands []string 19 | Lines []int 20 | } 21 | 22 | func contains(arr []string, str string) bool { 23 | for _, a := range arr { 24 | if a == str { 25 | return true 26 | } 27 | } 28 | return false 29 | } 30 | 31 | func (u *Uhh) Clone() error { 32 | if !gitClone(u.config.Repo(), u.config.LocalRepoPath()) { 33 | return fmt.Errorf("Unable to clone") 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func (u *Uhh) AddRepo(repoUrl string) error { 40 | idx := len(u.config.vals.ReadRepos) 41 | repoPath := u.config.GetReadRepoPath(idx) 42 | 43 | if !gitClone(repoUrl, repoPath) { 44 | return fmt.Errorf("Unable to clone") 45 | } 46 | 47 | u.config.vals.ReadRepos = append(u.config.vals.ReadRepos, strconv.Itoa(idx)) 48 | 49 | path, err := DefaultConfigPath() 50 | 51 | if err != nil { 52 | os.RemoveAll(repoPath) 53 | return fmt.Errorf("%w", err) 54 | } 55 | 56 | err = u.config.Save(path) 57 | 58 | if err != nil { 59 | os.RemoveAll(repoPath) 60 | return fmt.Errorf("%w", err) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func (u *Uhh) SetRepo() error { 67 | return fmt.Errorf("Definitely not a JavaException here: NotImplemented") 68 | } 69 | 70 | func (u *Uhh) Sync() error { 71 | origSnippets, err := ReadSnippetsFromDir(u.config.LocalRepoPath()) 72 | if err != nil { 73 | return fmt.Errorf("unable to read snippets from dir: %w", err) 74 | } 75 | if !(gitAdd(u.config) && gitStashPush(u.config)) { 76 | return fmt.Errorf("unable to stash files") 77 | } 78 | if !gitPull(u.config.LocalRepoPath()) { 79 | gitStashPop(u.config) 80 | return fmt.Errorf("unable fetch changes") 81 | } 82 | 83 | newSnippets, _ := ReadSnippetsFromDir(u.config.LocalRepoPath()) 84 | 85 | diff := origSnippets.Diff(newSnippets) 86 | 87 | for _, d := range diff { 88 | u.Add(d.tag, d.cmd, d.searchTerms) 89 | } 90 | if !(gitAdd(u.config) && gitCommit(u.config)) { 91 | return fmt.Errorf("Unable to commit") 92 | } 93 | 94 | if !gitPush(u.config) { 95 | return fmt.Errorf("Unable to commit") 96 | } 97 | 98 | for i := range u.config.ReadRepos() { 99 | if !gitPull(u.config.GetReadRepoPath(i)) { 100 | return fmt.Errorf("Unable to pull from repo %d", i) 101 | } 102 | } 103 | 104 | return nil 105 | } 106 | 107 | func find(tag, tagPath string, searchTerms []string) (*FindResults, error) { 108 | sns, err := ReadSnippetsFromFile(tagPath) 109 | if err != nil { 110 | fmt.Println("error:", err) 111 | return &FindResults{}, nil 112 | } 113 | 114 | findResults := &FindResults{ 115 | Tag: tag, 116 | } 117 | 118 | for _, s := range sns { 119 | contains := len(s.searchTerms) == 0 120 | 121 | for j := 0; j < len(searchTerms) && !contains; j++ { 122 | contains = strings.Contains(s.searchTerms, searchTerms[j]) 123 | } 124 | 125 | if contains { 126 | findResults.Lines = append(findResults.Lines, s.line) 127 | findResults.Commands = append(findResults.Commands, s.cmd) 128 | } 129 | } 130 | 131 | return findResults, nil 132 | } 133 | 134 | func (u *Uhh) Find(tag string, searchTerms []string) (*FindResults, error) { 135 | tagPath := path.Join(u.config.LocalRepoPath(), tag) 136 | results, err := find(tag, tagPath, searchTerms) 137 | 138 | if err != nil { 139 | return nil, fmt.Errorf("%w\n", err) 140 | } 141 | 142 | for i := range u.config.ReadRepos() { 143 | tagPath = path.Join(u.config.GetReadRepoPath(i), tag) 144 | findResults, err := find(tag, tagPath, searchTerms) 145 | 146 | if err != nil { 147 | return nil, fmt.Errorf("%w\n", err) 148 | } 149 | results.Lines = append(results.Lines, findResults.Lines...) 150 | results.Commands = append(results.Commands, findResults.Commands...) 151 | 152 | } 153 | 154 | return results, nil 155 | } 156 | 157 | func New(cfg *Config) *Uhh { 158 | return &Uhh{cfg} 159 | } 160 | 161 | func (u *Uhh) Add(tag string, cmd string, searchTerms string) error { 162 | 163 | if contains(actions, tag) { 164 | return fmt.Errorf("You cannot have a tag as a reserverd word: %+v\n", actions) 165 | } 166 | 167 | if u.config.vals.SyncOnAdd { 168 | if !gitPull(u.config.LocalRepoPath()) { 169 | return fmt.Errorf("Unable to pull from repo") 170 | } 171 | } 172 | 173 | tagPath := path.Join(u.config.LocalRepoPath(), tag) 174 | contents, err := ioutil.ReadFile(tagPath) 175 | 176 | if err != nil { 177 | contents = []byte{} 178 | } 179 | 180 | newContents := string(contents) + fmt.Sprintf("%s\n%s\n", cmd, searchTerms) 181 | err = ioutil.WriteFile(tagPath, []byte(newContents), os.ModePerm) 182 | 183 | if err != nil { 184 | return fmt.Errorf("Unable to write the command and search terms: %w\n", err) 185 | } 186 | 187 | if u.config.vals.SyncOnAdd { 188 | if !gitAdd(u.config) || !gitCommit(u.config) { 189 | return fmt.Errorf("Unable to commit") 190 | } 191 | if !gitPush(u.config) { 192 | return fmt.Errorf("Unable push") 193 | } 194 | } 195 | 196 | return nil 197 | } 198 | 199 | func (u *Uhh) Delete(results *FindResults) error { 200 | tagPath := path.Join(u.config.LocalRepoPath(), results.Tag) 201 | tagFileContents, err := ioutil.ReadFile(tagPath) 202 | 203 | if err != nil { 204 | return fmt.Errorf("Unable to read %s: %w", tagPath, err) 205 | } 206 | 207 | tagFile := strings.Split(string(tagFileContents), "\n") 208 | for i := len(results.Lines) - 1; i >= 0; i-- { 209 | tagFile = append(tagFile[0:i], tagFile[i+2:]...) 210 | } 211 | 212 | err = ioutil.WriteFile( 213 | tagPath, []byte(strings.Join(tagFile, "\n")), os.ModePerm) 214 | 215 | if err != nil { 216 | return fmt.Errorf("Unable to write file with new tagContents: %w\n", err) 217 | } 218 | 219 | return nil 220 | } 221 | --------------------------------------------------------------------------------