Skip to content

Instantly share code, notes, and snippets.

@benjacoblee
Created November 1, 2023 13:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benjacoblee/6244cfd2a21be2cc9ea8c71d3f51b81b to your computer and use it in GitHub Desktop.
Save benjacoblee/6244cfd2a21be2cc9ea8c71d3f51b81b to your computer and use it in GitHub Desktop.
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