package notesium import ( "bufio" "fmt" "log" "os" "path/filepath" "regexp" "strconv" "strings" "time" ) type Link struct { Filename string Title string LineNumber int } type Note struct { Filename string Title string IsLabel bool OutgoingLinks []*Link IncomingLinks []*Link Ctime time.Time Mtime time.Time Lines int Words int Chars int } var noteCache map[string]*Note var fileRegex = regexp.MustCompile(`^[0-9a-f]{8}\.md$`) var linkRegex = regexp.MustCompile(`\]\(([0-9a-f]{8}\.md)\)`) func populateCache(dir string) { if noteCache != nil { return } noteCache = make(map[string]*Note) files, err := os.ReadDir(dir) if err != nil { log.Fatalf("could not read directory: %s\n", err) } for _, file := range files { filename := file.Name() if !file.IsDir() && fileRegex.MatchString(filename) { note, err := readNote(dir, filename) if err != nil { log.Fatalf("could not read note: %s\n", err) } noteCache[filename] = note } } for _, note := range noteCache { for _, link := range note.OutgoingLinks { if targetNote, exists := noteCache[link.Filename]; exists { link.Title = targetNote.Title targetNote.IncomingLinks = append(targetNote.IncomingLinks, &Link{ Filename: note.Filename, Title: note.Title, LineNumber: link.LineNumber, }) } } } } func readNote(dir string, filename string) (*Note, error) { path := filepath.Join(dir, filename) file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("could not open file: %s", err) } defer file.Close() info, err := file.Stat() if err != nil { return nil, fmt.Errorf("could not get file info: %s", err) } mtime := info.ModTime().Truncate(time.Second) hexTime := strings.TrimSuffix(filename, ".md") unixTime, err := strconv.ParseInt(hexTime, 16, 64) if err != nil { return nil, err } ctime := time.Unix(unixTime, 0) var title string var isLabel bool var outgoingLinks []*Link var lines, words, chars int scanner := bufio.NewScanner(file) lineNumber := 0 for scanner.Scan() { lineNumber++ line := scanner.Text() if line != "" { lines++ words += len(strings.Fields(line)) chars += len(line) } if title == "" { title = strings.TrimPrefix(line, "# ") isLabel = len(strings.Fields(title)) == 1 continue } matches := linkRegex.FindAllStringSubmatch(line, -1) for _, match := range matches { outgoingLinks = append(outgoingLinks, &Link{LineNumber: lineNumber, Filename: match[1]}) } } if err := scanner.Err(); err != nil { return nil, err } if title == "" { title = "untitled" } note := &Note{ Filename: filename, Title: title, IsLabel: isLabel, OutgoingLinks: outgoingLinks, Ctime: ctime, Mtime: mtime, Lines: lines, Words: words, Chars: chars, } return note, nil }