Shareable Git-powered notebooks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

223 lines
4.0 KiB

package main
import (
"bufio"
"bytes"
"io"
"regexp"
"sort"
"strings"
)
const optionsReaderSize = 512
var (
startsWithCheckbox = regexp.MustCompile(`^\[[ *xX]].*`)
noteHeader = []byte("[//]: # (")
)
type listItem struct {
Indent int
Text string
}
type note struct {
ID string
Label string
ModifiedAt int64
ModifiedBy string
Body string
}
func optionsReader(r io.Reader) []string {
lr := io.LimitReader(r, 9)
hdr := make([]byte, 9)
n, err := lr.Read(hdr)
if (err != nil && err != io.EOF) || n != 9 {
return nil
}
if !bytes.Equal(hdr, noteHeader) {
return nil
}
var (
opts []string
buf = make([]byte, optionsReaderSize)
opt []byte
)
ReadOptions:
for {
read, err := r.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return nil
}
for _, chr := range buf[0:read] {
switch chr {
case ',':
if opt != nil {
opts = append(opts, string(opt))
opt = nil
}
case ')':
break ReadOptions
case '\n':
return nil
default:
opt = append(opt, chr)
}
}
}
if opt != nil {
opts = append(opts, string(opt))
}
return opts
}
func options(body string) []string {
if len(body) <= 10 || body[0:9] != "[//]: # (" {
return nil
}
endOptions := strings.IndexRune(body, ')')
if endOptions == -1 {
return nil
}
opts := strings.Split(body[9:endOptions], ",")
options := make([]string, len(opts))
for _, a := range opts {
options = append(options, strings.ToLower(strings.TrimSpace(a)))
}
return options
}
func printListItems(list []*listItem, bullet string, capitalize bool) string {
var indents []int
for _, item := range list {
indents = append(indents, item.Indent)
}
indents = uniqueInts(indents)
sort.Ints(indents)
offsets := make(map[int]int, len(indents))
for i, indent := range indents {
offsets[indent] = i
}
var (
listItems = make([][]string, len(list))
previousItems []string
theseItems []string
)
for i, item := range list {
if offsets[item.Indent] > 0 {
for _, previtem := range previousItems[0:offsets[item.Indent]] {
theseItems = append(theseItems, previtem)
}
}
theseItems = append(theseItems, item.Text)
listItems[i] = theseItems
previousItems = theseItems
theseItems = nil
}
sort.Slice(listItems, func(i, j int) bool {
a := listItems[i]
b := listItems[j]
lena := len(a)
lenb := len(b)
for i := 0; i < lena; i++ {
if i > lenb-1 {
return false
}
if a[i] != b[i] {
return stripLeadingCheckbox(a[i]) < stripLeadingCheckbox(b[i])
}
}
return true
})
out := ""
var (
item string
itemTrimmed string
chkoffset int
lenitems int
)
for _, items := range listItems {
lenitems = len(items)
for i := 0; i < indents[lenitems-1]; i++ {
out += " "
}
out += bullet + " "
if capitalize {
item = items[lenitems-1]
chkoffset = 0
if startsWithCheckbox.MatchString(item) {
chkoffset = strings.IndexRune(item, ']')
if len(item) > chkoffset+1 {
itemTrimmed = strings.TrimSpace(item[chkoffset+2:])
item = item[0:chkoffset+1] + " " + strings.ToUpper(itemTrimmed[0:1]) + itemTrimmed[1:]
}
}
out += item
} else {
out += items[lenitems-1]
}
out += "\n"
}
return out
}
func sortListItems(body string, capitalize bool) string {
var (
line string
newBody string
list []*listItem
bullet string
)
scanner := bufio.NewScanner(strings.NewReader(body))
for scanner.Scan() {
line = scanner.Text()
thisIndent := strings.IndexAny(line, "*-")
if thisIndent == -1 || len(line) == (thisIndent+1) || line[thisIndent+1:thisIndent+2] != " " {
if list != nil {
newBody += printListItems(list, bullet, capitalize)
list = nil
}
newBody += line + "\n"
} else {
if bullet == "" {
bullet = line[thisIndent : thisIndent+1]
}
list = append(list, &listItem{Indent: thisIndent, Text: strings.TrimSpace(line[thisIndent+2:])})
}
}
if list != nil {
newBody += printListItems(list, bullet, capitalize)
}
return newBody
}