├── .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
--------------------------------------------------------------------------------