-
-
Save benjacoblee/6244cfd2a21be2cc9ea8c71d3f51b81b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"cmp" | |
"fmt" | |
"log" | |
"os" | |
"os/exec" | |
"slices" | |
"strconv" | |
"text/template" | |
"time" | |
"github.com/urfave/cli/v2" | |
) | |
const TilDir = "TIL_DIR" | |
const TemplPath = "TIL_TMPL_PATH" | |
const DailyNoteDFormat = "2006-01-02" | |
const DirNotSetErr = "environment variable TIL_DIR needs to be set" | |
const EditorNotSetErr = "environment variable TIL_EDITOR needs to be set" | |
const CreateDirErr = "directory not created" | |
const InputNotNumberErr = "input has to be a number" | |
const EntryNotExistErr = "entry doesn't exist" | |
func main() { | |
app := &cli.App{ | |
Name: "TIL", | |
Usage: "a small CLI app to take notes", | |
Commands: []*cli.Command{ | |
{ | |
Name: "open", | |
Aliases: []string{"o"}, | |
Usage: "open daily note in configured editor", | |
Action: func(cCtx *cli.Context) error { | |
editor, ok := os.LookupEnv("TIL_EDITOR") | |
if !ok { | |
fmt.Printf("Error: %s\n", EditorNotSetErr) | |
os.Exit(0) | |
} | |
openDailyNote(editor, tilPath(todayPath())) | |
return nil | |
}, | |
}, | |
{ | |
Name: "append", | |
Aliases: []string{"a"}, | |
Usage: "append to daily note", | |
Action: func(cCtx *cli.Context) error { | |
var ( | |
tilPath = tilPath(todayPath()) | |
content = cCtx.Args().First() | |
f, err = os.OpenFile(tilPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
) | |
if err != nil { | |
fmt.Println(err.Error()) | |
} | |
contentToWrite := fmt.Sprintf("%s\n", content) | |
_, wErr := f.Write([]byte(contentToWrite)) | |
if wErr != nil { | |
fmt.Println(err.Error()) | |
} | |
fmt.Printf("Wrote \"%s\" to daily note ✏️\n", content) | |
return nil | |
}, | |
}, | |
{ | |
Name: "read", | |
Aliases: []string{"r"}, | |
Usage: "read a daily note from a list of daily notes", | |
Action: func(cCtx *cli.Context) error { | |
entries, _ := getDirEntries() | |
input := "" | |
for i, entry := range entries { | |
fmt.Printf("%d. %s\n", i+1, entry) | |
} | |
fmt.Println("0. Exit") | |
fmt.Scanln(&input) | |
idx, err := strconv.Atoi(input) | |
if err != nil { | |
fmt.Printf("Error: %s\n", InputNotNumberErr) | |
os.Exit(0) | |
} | |
if idx == 0 { | |
os.Exit(0) | |
} | |
idx-- | |
if idx < 0 || idx > len(entries)-1 { | |
fmt.Printf("Error: %s\n", EntryNotExistErr) | |
os.Exit(0) | |
} | |
editor, ok := os.LookupEnv("TIL_EDITOR") | |
tilPath := tilPath(entries[idx]) | |
if !ok { | |
fmt.Printf("Error: %s\n", EditorNotSetErr) | |
os.Exit(0) | |
} | |
openDailyNote(editor, tilPath) | |
return nil | |
}, | |
}, | |
}, | |
} | |
if err := app.Run(os.Args); err != nil { | |
log.Fatal(err) | |
} | |
} | |
func openDailyNote(editor, tilPath string) { | |
createTemplateIfNotExists(tilPath) | |
cmd := exec.Command(editor, tilPath) | |
cmd.Stdin = os.Stdin | |
cmd.Stdout = os.Stdout | |
cmd.Stderr = os.Stderr | |
err := cmd.Run() | |
if err != nil { | |
fmt.Printf("Error: %s", err.Error()) | |
} | |
} | |
func createTemplateIfNotExists(tilPath string) { | |
fExists, err := fileOrDirExists(tilPath) | |
if err != nil { | |
fmt.Println(err) | |
} | |
if !fExists { | |
tmplFile := os.Getenv(TemplPath) | |
tmpl, err := template.ParseFiles(tmplFile) | |
if err != nil { | |
fmt.Println(err) | |
} | |
f, _ := os.Create(tilPath) | |
defer f.Close() | |
tmpl.Execute(f, map[string]interface{}{ | |
"today": todayFmt(), | |
}) | |
} | |
} | |
func getDirEntries() ([]string, error) { | |
tilDir, ok := os.LookupEnv(TilDir) | |
entries := []string{} | |
if !ok { | |
fmt.Printf("Error: %s\n", DirNotSetErr) | |
os.Exit(0) | |
} | |
res, err := os.ReadDir(tilDir) | |
for _, entry := range res { | |
entries = append(entries, entry.Name()) | |
} | |
slices.SortFunc(entries, func(a, b string) int { | |
return cmp.Compare(b, a) | |
}) | |
return entries, err | |
} | |
func fileOrDirExists(path string) (bool, error) { | |
_, err := os.Stat(path) | |
if err == nil { | |
return true, nil | |
} | |
if os.IsNotExist(err) { | |
return false, nil | |
} | |
return false, err | |
} | |
func tilPath(path string) string { | |
dirName, ok := os.LookupEnv(TilDir) | |
if !ok { | |
fmt.Printf("Error: %s\n", DirNotSetErr) | |
os.Exit(0) | |
} | |
dirExists, dErr := fileOrDirExists(dirName) | |
if dErr != nil { | |
fmt.Println(dErr) | |
} | |
if !dirExists { | |
mdErr := os.Mkdir(dirName, os.ModePerm) | |
if mdErr != nil { | |
fmt.Printf("Error: %s\n", CreateDirErr) | |
os.Exit(0) | |
} | |
} | |
return fmt.Sprintf("%s/%s", dirName, path) | |
} | |
func todayPath() string { | |
return fmt.Sprintf("%s.md", todayFmt()) | |
} | |
func todayFmt() string { | |
return time.Now().Format(DailyNoteDFormat) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment