cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / mcp / list.go
list.go 2.8K
1package mcp23import (4 "io/fs"5 "os"6 "path/filepath"7 "strings"89 "src.rest/cat/012/internal/confine"10 "src.rest/cat/012/internal/fileio"11)1213// describe without a path lists the workbooks open to the server: the14// default one, then the .012 files and files 012 imports in each root,15// not looking in hidden folders or node_modules, nor deeper than16// listDepth, and stopping at maxListedFiles files or maxListedDirs17// folders looked in, so a root as wide as a home folder answers18// quickly.19const (20 maxListedFiles = 50021 maxListedDirs = 200022 listDepth = 423)2425// listedFile is a workbook describe lists.26type listedFile struct {27 Path string `json:"path" jsonschema:"what to pass as path"`28 Format string `json:"format" jsonschema:"012, or the format it's imported from (read only)"`29 Size int64 `json:"size"`30 Modified string `json:"modified" jsonschema:"when it last changed, RFC 3339"`31}3233// lister gathers the files of a listing.34type lister struct {35 roots []confine.Root36 skip string // the default workbook, listed first37 files []listedFile38 dirs int39 stopped bool40}4142// listFiles lists the workbooks in roots, and the default one; more is43// set when there were more than it lists.44func (s *Server) listFiles(roots []confine.Root) (files []listedFile, more bool) {45 l := &lister{roots: roots, skip: s.opts.Default}46 if d := s.opts.Default; d != "" {47 if info, err := os.Stat(d); err == nil {48 l.add(d, info)49 }50 }51 for _, r := range roots {52 if l.stopped {53 break54 }55 _ = filepath.WalkDir(r.Dir(), func(p string, e fs.DirEntry, err error) error { return l.visit(r, p, e, err) })56 }57 return l.files, l.stopped58}5960func (l *lister) add(path string, info fs.FileInfo) {61 format := "012"62 if own, _ := isWorkbook(path); !own {63 k, _ := fileio.KindOf(path)64 format = k.String()65 }66 l.files = append(l.files, listedFile{Path: displayName(l.roots, path), Format: format, Size: info.Size(),67 Modified: info.ModTime().UTC().Format("2006-01-02T15:04:05Z")})68}6970// visit is the walk's step: a folder to look in or not, or a file to71// list or not.72func (l *lister) visit(r confine.Root, p string, e fs.DirEntry, err error) error {73 if err != nil || p == r.Dir() {74 return nil75 }76 if e.IsDir() {77 rel, _ := filepath.Rel(r.Dir(), p)78 l.dirs++79 switch {80 case l.dirs > maxListedDirs:81 l.stopped = true82 return filepath.SkipAll83 case strings.HasPrefix(e.Name(), ".") || e.Name() == "node_modules" || strings.Count(rel, string(filepath.Separator)) >= listDepth-1:84 return filepath.SkipDir85 }86 return nil87 }88 if _, ok := isWorkbook(p); !ok || !e.Type().IsRegular() || strings.HasPrefix(e.Name(), ".") || p == l.skip {89 return nil90 }91 if len(l.files) == maxListedFiles {92 l.stopped = true93 return filepath.SkipAll94 }95 if info, err := e.Info(); err == nil {96 l.add(p, info)97 }98 return nil99}