├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── install.sh ├── notes.go └── notes_demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # This gitignore is generated using https://github.com/prdpx7/GiG/ 2 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 3 | *.o 4 | *.a 5 | *.so 6 | notes 7 | # Folders 8 | _obj 9 | _test 10 | MD5SUMS 11 | SHA512SUMS 12 | *.tar.gz 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | notes-cli 26 | *.exe 27 | *.test 28 | *.prof 29 | 30 | # Output of the go coverage tool, specifically when used with LiteIDE 31 | *.out 32 | 33 | # external packages folder 34 | vendor/ 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pradeep Khileri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # notes-cli 2 | > A simple CLI app to take notes on markdown files 3 | 4 | ## Installation 5 | ``` 6 | git clone https://github.com/prdpx7/notes-cli.git 7 | 8 | cd notes-cli/ 9 | 10 | go build 11 | 12 | chmod +x ./notes-cli 13 | 14 | sudo cp ./notes-cli /usr/local/bin/notes 15 | ``` 16 | 17 | ## Usage 18 | ``` 19 | notes - A simple note-taking app 20 | Usage: notes [OPTIONS] 21 | Options: 22 | write - write in markdown file automatically created with today's date stamp 23 | read - browse all notes in terminal file browser 24 | sync - upload all markdown files to your github as Private Gist 25 | Example: 26 | notes write 27 | notes read 28 | notes sync 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/prdpx7/notes-cli 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/briandowns/spinner v1.10.0 7 | github.com/fatih/color v1.9.0 // indirect 8 | github.com/golang/protobuf v1.4.0 // indirect 9 | github.com/google/go-github v17.0.0+incompatible 10 | github.com/google/go-querystring v1.0.0 // indirect 11 | github.com/mattn/go-colorable v0.1.6 // indirect 12 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect 13 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d 14 | golang.org/x/sys v0.0.0-20200427175716-29b57079015a // indirect 15 | google.golang.org/appengine v1.6.6 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/briandowns/spinner v1.10.0 h1:753NIJC2NHmPyVoPVWS+wh9eDx5umqe2U+JgX+KoTag= 3 | github.com/briandowns/spinner v1.10.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= 4 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 5 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 6 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 7 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 8 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 9 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 10 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 12 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 13 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 14 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 15 | github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= 16 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 17 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 18 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 19 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 20 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= 22 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 23 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 24 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 25 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 26 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 27 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 28 | github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= 29 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 30 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 31 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 32 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 33 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 34 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 35 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 36 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 37 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= 38 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 39 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 40 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= 41 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 42 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= 43 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 44 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 45 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 46 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= 47 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20200427175716-29b57079015a h1:08u6b1caTT9MQY4wSbmsd4Ulm6DmgNYnbImBuZjGJow= 53 | golang.org/x/sys v0.0.0-20200427175716-29b57079015a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 55 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 56 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 57 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 58 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 59 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 60 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 61 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= 62 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 63 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 64 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 65 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 66 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 67 | google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= 68 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 69 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "git clone notes-cli" 4 | git clone https://github.com/prdpx7/notes-cli.git 5 | 6 | echo "cd notes-cli/" 7 | cd notes-cli/ 8 | 9 | echo "go build" 10 | go build 11 | 12 | echo "chmod +x ./notes-cli" 13 | chmod +x ./notes-cli 14 | 15 | echo "Need root privilage to copy notes into /usr/local/bin/" 16 | sudo cp ./notes-cli /usr/local/bin/notes 17 | 18 | echo "Done!" 19 | -------------------------------------------------------------------------------- /notes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "regexp" 13 | "time" 14 | 15 | "github.com/briandowns/spinner" 16 | "github.com/google/go-github/github" 17 | "golang.org/x/oauth2" 18 | ) 19 | 20 | const notesFilePathPrefix = "daily_notes_" 21 | const notesGistDescription = "Daily Notes via notes app" 22 | 23 | // GistNotesMap - Offline mapping of local notes files with respective remote gist-ids 24 | type GistNotesMap struct { 25 | GistID string `json:"gist_id"` 26 | Filename string `json:"filename"` 27 | } 28 | 29 | func getOrCreateNotesDir() string { 30 | homeDir, err := os.UserHomeDir() 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | dirPath := filepath.Join(homeDir, ".notes") 35 | if _, err := os.Stat(dirPath); os.IsNotExist(err) { 36 | err := os.Mkdir(dirPath, 0700) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | return dirPath 42 | } 43 | func getOrCreateNotesDataDir() string { 44 | dirPath := getOrCreateNotesDir() 45 | dataDirPath := filepath.Join(dirPath, "data") 46 | if _, err := os.Stat(dataDirPath); os.IsNotExist(err) { 47 | err := os.Mkdir(dataDirPath, 0700) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | } 52 | return dataDirPath 53 | } 54 | 55 | func getOrCreateNotesConfigDir() string { 56 | dirPath := getOrCreateNotesDir() 57 | configDirPath := filepath.Join(dirPath, "config") 58 | if _, err := os.Stat(configDirPath); os.IsNotExist(err) { 59 | err := os.Mkdir(configDirPath, 0700) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | } 64 | return configDirPath 65 | } 66 | 67 | func getOrCreateLocalGistStore() string { 68 | configDirPath := getOrCreateNotesConfigDir() 69 | localGistStore := filepath.Join(configDirPath, "gist_store.json") 70 | _, err := os.Open(localGistStore) 71 | if err != nil { 72 | os.Create(localGistStore) 73 | } 74 | return localGistStore 75 | } 76 | 77 | func getGithubPersonalToken() (token string) { 78 | token, exists := os.LookupEnv("GITHUB_CREATE_GIST_TOKEN") 79 | if exists == false { 80 | fmt.Println(`No GITHUB_CREATE_GIST_TOKEN found..! 81 | You can get your personal token from https://github.com/settings/tokens/new?scopes=gist&description=notes-cli 82 | Paste the following line in your ~/.bashrc or ~/.zshrc 83 | export GITHUB_CREATE_GIST_TOKEN=''`, 84 | ) 85 | os.Exit(1) 86 | } 87 | return token 88 | } 89 | 90 | func getAllLocalNotesFiles() []string { 91 | dataDirPath := getOrCreateNotesDataDir() 92 | markdownsFiles, err := filepath.Glob(filepath.Join(dataDirPath, "*.md")) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | return markdownsFiles 97 | } 98 | 99 | func getGists(token string) []*github.Gist { 100 | ctx := context.Background() 101 | ts := oauth2.StaticTokenSource( 102 | &oauth2.Token{AccessToken: token}, 103 | ) 104 | tc := oauth2.NewClient(ctx, ts) 105 | 106 | client := github.NewClient(tc) 107 | var allGists []*github.Gist 108 | for { 109 | opt := &github.GistListOptions{ListOptions: github.ListOptions{PerPage: 100}} 110 | gists, resp, err := client.Gists.List(ctx, "", opt) 111 | if err != nil { 112 | log.Fatal(err) 113 | } 114 | allGists = append(allGists, gists...) 115 | if resp.NextPage == 0 { 116 | break 117 | } 118 | opt.Page = resp.NextPage 119 | } 120 | return allGists 121 | } 122 | 123 | func getOrCreateGist(token string, markdownFilePath string, GistID string) string { 124 | ctx := context.Background() 125 | ts := oauth2.StaticTokenSource( 126 | &oauth2.Token{AccessToken: token}, 127 | ) 128 | tc := oauth2.NewClient(ctx, ts) 129 | 130 | client := github.NewClient(tc) 131 | markdownFileName := filepath.Base(markdownFilePath) 132 | markdownFileContentByte, err := ioutil.ReadFile(markdownFilePath) 133 | if err != nil { 134 | log.Fatal(err) 135 | } 136 | markdownFileContent := string(markdownFileContentByte) 137 | tmpGistFile := github.GistFile{Filename: &markdownFileName, Content: &markdownFileContent} 138 | var tmpFilesObj = map[github.GistFilename]github.GistFile{ 139 | github.GistFilename(markdownFileName): tmpGistFile, 140 | } 141 | var gistVisibilityToPublic = false 142 | // create gist 143 | gistDescription := notesGistDescription 144 | if GistID == "" { 145 | tmpGistObj := github.Gist{ 146 | Files: tmpFilesObj, 147 | Public: &gistVisibilityToPublic, 148 | Description: &gistDescription, 149 | } 150 | gistResponse, _, err := client.Gists.Create(ctx, &tmpGistObj) 151 | if err != nil { 152 | log.Fatal(err) 153 | } 154 | return *gistResponse.ID 155 | } 156 | tmpGistObj := github.Gist{ 157 | Files: tmpFilesObj, 158 | Public: &gistVisibilityToPublic, 159 | Description: &gistDescription, ID: &GistID, 160 | } 161 | gistResponse, _, err := client.Gists.Edit(ctx, GistID, &tmpGistObj) 162 | if err != nil { 163 | log.Fatal(err) 164 | } 165 | return *gistResponse.ID 166 | } 167 | 168 | func doDownSync() { 169 | // download all gists with `daily_notes` prefix 170 | // TODO 171 | } 172 | 173 | func doUpSync() { 174 | // upload local gists 175 | token := getGithubPersonalToken() 176 | localGistStorePath := getOrCreateLocalGistStore() 177 | gistStoreFile, _ := ioutil.ReadFile(localGistStorePath) 178 | data := []GistNotesMap{} 179 | _ = json.Unmarshal([]byte(gistStoreFile), &data) 180 | 181 | localMarkdownsFiles := getAllLocalNotesFiles() 182 | 183 | var filesToBeSynced []GistNotesMap 184 | for i := 0; i < len(localMarkdownsFiles); i++ { 185 | var fileFoundInStore = false 186 | for j := 0; j < len(data); j++ { 187 | if data[j].Filename == localMarkdownsFiles[i] { 188 | filesToBeSynced = append(filesToBeSynced, data[j]) 189 | fileFoundInStore = true 190 | break 191 | } 192 | } 193 | if fileFoundInStore == false { 194 | filesToBeSynced = append(filesToBeSynced, GistNotesMap{"", localMarkdownsFiles[i]}) 195 | } 196 | } 197 | for i := 0; i < len(filesToBeSynced); i++ { 198 | filesToBeSynced[i].GistID = getOrCreateGist(token, filesToBeSynced[i].Filename, filesToBeSynced[i].GistID) 199 | } 200 | 201 | syncedFileData, _ := json.MarshalIndent(filesToBeSynced, "", " ") 202 | 203 | _ = ioutil.WriteFile(localGistStorePath, syncedFileData, 0644) 204 | } 205 | 206 | func performSync() { 207 | fmt.Println("Syncing your gists....") 208 | loader := spinner.New(spinner.CharSets[36], 100*time.Millisecond) 209 | loader.Start() 210 | doUpSync() 211 | doDownSync() 212 | loader.Stop() 213 | fmt.Println("Done!") 214 | } 215 | 216 | func runEditor(cmd *exec.Cmd) error { 217 | cmd.Stdin = os.Stdin 218 | cmd.Stdout = os.Stdout 219 | err := cmd.Run() 220 | return err 221 | } 222 | 223 | func isVimEditor(editor string) bool { 224 | re := regexp.MustCompile("(g|n|neo)?vim") 225 | 226 | if re.Match([]byte(editor)) { 227 | return true 228 | } 229 | return false 230 | } 231 | 232 | func getWorkingTextEditorWithFileBrowsingSupport() string { 233 | commands := [...]string{"vim", "vi", "nvim", "emacs"} 234 | prefixPath := [...]string{"/usr/bin/", "/usr/local/bin/"} 235 | for i := 0; i < len(commands); i++ { 236 | for j := 0; j < len(prefixPath); j++ { 237 | cmdFilePath := prefixPath[j] + commands[i] 238 | if _, err := os.Stat(cmdFilePath); !os.IsNotExist(err) { 239 | return cmdFilePath 240 | } 241 | } 242 | } 243 | fmt.Println("Unable to find terminal based filebrowser like vim, emacs, nvim, vi etc..!") 244 | log.Fatal("Please install any of the above software to browse notes in terminal") 245 | return "" 246 | } 247 | 248 | func getEditorCommand(editor string, mode string) *exec.Cmd { 249 | currentTime := time.Now() 250 | var filename string 251 | dataDirPath := getOrCreateNotesDataDir() 252 | if mode == "write" { 253 | filename = filepath.Join(dataDirPath, notesFilePathPrefix+currentTime.Format("2006_01_02")+".md") 254 | } else { 255 | filename = dataDirPath 256 | } 257 | 258 | if isVimEditor(editor) && mode == "write" { 259 | cmd := exec.Command(editor, "+normal Go", filename) 260 | return cmd 261 | } 262 | return exec.Command(editor, filename) 263 | } 264 | 265 | func showUsage() { 266 | helpMessage := `notes - A simple note-taking app 267 | Usage: notes [OPTIONS] 268 | Options: 269 | write - write in markdown file created with today's date stamp 270 | read - browse all notes in terminal file browser 271 | sync - upload all markdown files to your github as Private Gist 272 | Example: 273 | notes write 274 | notes read 275 | notes sync 276 | ` 277 | fmt.Println(helpMessage) 278 | } 279 | 280 | func main() { 281 | if len(os.Args) == 1 { 282 | showUsage() 283 | return 284 | } 285 | mode := os.Args[1] 286 | editor, exists := os.LookupEnv("EDITOR") 287 | if exists == false { 288 | fmt.Println("EDITOR not set in your enviornment!") 289 | fmt.Println("edit your env(~/.bashrc etc) and write export EDITOR='vim'") 290 | } 291 | if mode == "write" { 292 | cmd := getEditorCommand(editor, mode) 293 | runEditor(cmd) 294 | } else if mode == "read" { 295 | editor := getWorkingTextEditorWithFileBrowsingSupport() 296 | cmd := getEditorCommand(editor, mode) 297 | runEditor(cmd) 298 | } else if mode == "sync" { 299 | performSync() 300 | } else { 301 | showUsage() 302 | } 303 | 304 | } 305 | -------------------------------------------------------------------------------- /notes_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prdpx7/notes-cli/9eaa8b432375ff17ca7e2a677b1c0b4d7a8b8ee5/notes_demo.gif --------------------------------------------------------------------------------