func (mfs *MemFS) Search(words []string, snippetLen int) (results []SearchResult) {
	if len(words) == 0 {
		return nil
	}
	if snippetLen <= 0 {
		snippetLen = 60
	}
	var (
		logp = "Search"
		err  error
	)
	tokens := libstrings.ToBytes(words)
	for x := 0; x < len(tokens); x++ {
		tokens[x] = bytes.ToLower(tokens[x])
	}
	nodes := mfs.PathNodes.Nodes()
	for _, node := range nodes {
		if node.mode.IsDir() {
			continue
		}
		if !strings.HasPrefix(node.ContentType, "text/") {
			continue
		}
		if len(node.lowerv) == 0 {
			_, err = node.Decode()
			if err != nil {
				log.Printf("%s: %s", logp, err)
				continue
			}
			if strings.HasPrefix(node.ContentType, "text/html") {
				node.plainv = sanitize.HTML(node.plainv)
			}
			node.lowerv = bytes.ToLower(node.plainv)
		}
		result := SearchResult{
			Path: node.Path,
		}
		var allIndexes []int
		for _, token := range tokens {
			indexes := libbytes.WordIndexes(node.lowerv, token)
			allIndexes = append(allIndexes, indexes...)
		}
		if len(allIndexes) == 0 {
			continue
		}
		allIndexes = libints.MergeByDistance(allIndexes, nil, snippetLen)
		snippets := libbytes.SnippetByIndexes(node.lowerv, allIndexes, snippetLen)
		for _, snippet := range snippets {
			result.Snippets = append(result.Snippets, string(snippet))
		}
		results = append(results, result)
	}
	return results
}
Search one or more strings in each content of files.
package memfs
import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"
	libbytes "github.com/shuLhan/share/lib/bytes"
	libints "github.com/shuLhan/share/lib/ints"
	"github.com/shuLhan/share/lib/sanitize"
	libstrings "github.com/shuLhan/share/lib/strings"
)
const (
	EncodingGzip = "gzip"
	defContentType = "text/plain"  
)
type MemFS struct {
	http.FileSystem
	PathNodes *PathNode
	Root      *Node
	Opts      *Options
	incRE     []*regexp.Regexp
	excRE     []*regexp.Regexp
}
func Merge(params ...*MemFS) (merged *MemFS) {
	merged = &MemFS{
		PathNodes: NewPathNode(),
		Root: &Node{
			SysPath: "..",
			Path:    "/",
			mode:    2147484141,
		},
		Opts: &Options{},
	}
	merged.PathNodes.Set("/", merged.Root)
	for _, mfs := range params {
		for _, child := range mfs.Root.Childs {
			gotNode := merged.PathNodes.Get(child.Path)
			if gotNode != nil {
				continue
			}
			merged.Root.AddChild(child)
		}
		paths := mfs.PathNodes.Paths()
		for _, path := range paths {
			if path == "/" {
				continue
			}
			gotNode := merged.PathNodes.Get(path)
			if gotNode == nil {
				merged.PathNodes.Set(path, mfs.PathNodes.Get(path))
			}
		}
	}
	return merged
}
func New(opts *Options) (mfs *MemFS, err error) {
	logp := "New"
	if opts == nil {
		opts = &Options{}
	}
	opts.init()
	mfs = &MemFS{
		PathNodes: NewPathNode(),
		Opts:      opts,
	}
	for _, inc := range opts.Includes {
		re, err := regexp.Compile(inc)
		if err != nil {
			return nil, fmt.Errorf("%s: %w", logp, err)
		}
		mfs.incRE = append(mfs.incRE, re)
	}
	for _, exc := range opts.Excludes {
		re, err := regexp.Compile(exc)
		if err != nil {
			return nil, fmt.Errorf("%s: %w", logp, err)
		}
		mfs.excRE = append(mfs.excRE, re)
	}
	err = mfs.mount()
	if err != nil {
		return nil, fmt.Errorf("%s: %w", logp, err)
	}
	return mfs, nil
}
func (mfs *MemFS) AddChild(parent *Node, fi os.FileInfo) (child *Node, err error) {
	sysPath := filepath.Join(parent.SysPath, fi.Name())
	if !mfs.isIncluded(sysPath, fi.Mode()) {
		return nil, nil
	}
	child, err = parent.addChild(sysPath, fi, mfs.Opts.MaxFileSize)
	if err != nil {
		log.Printf("AddChild %s: %s", fi.Name(), err.Error())
		return nil, nil
	}
	mfs.PathNodes.Set(child.Path, child)
	return child, nil
}
func (mfs *MemFS) AddFile(internalPath, externalPath string) (node *Node, err error) {
	if len(internalPath) == 0 {
		return nil, nil
	}
	logp := "AddFile"
	fi, err := os.Stat(externalPath)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", logp, err)
	}
	var parent *Node
	internalPath = filepath.ToSlash(filepath.Clean(internalPath))
	paths := strings.Split(internalPath, "/")
	base := paths[len(paths)-1]
	paths = paths[:len(paths)-1]
	path := ""
	for _, p := range paths {
		path = filepath.Join(path, p)
		node, _ := mfs.Get(path)
		if node != nil {
			parent = node
			continue
		}
		node = &Node{
			SysPath: path,
			Path:    path,
			name:    p,
			mode:    os.ModeDir,
			Parent:  parent,
		}
		node.generateFuncName(path)
		if parent == nil {
			mfs.Root.Childs = append(mfs.Root.Childs, node)
		} else {
			parent.Childs = append(parent.Childs, node)
		}
		mfs.PathNodes.Set(node.Path, node)
		parent = node
	}
	path = filepath.Join(path, base)
	node = &Node{
		SysPath: externalPath,
		Path:    path,
		name:    base,
		modTime: fi.ModTime(),
		mode:    fi.Mode(),
		size:    fi.Size(),
		Parent:  parent,
	}
	node.generateFuncName(path)
	err = node.updateContent(mfs.Opts.MaxFileSize)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", logp, err)
	}
	err = node.updateContentType()
	if err != nil {
		return nil, fmt.Errorf("%s: %w", logp, err)
	}
	parent.Childs = append(parent.Childs, node)
	mfs.PathNodes.Set(node.Path, node)
	return node, nil
}
func (mfs *MemFS) ContentEncode(encoding string) (err error) {
	var (
		logp    = "ContentEncode"
		buf     bytes.Buffer
		encoder io.WriteCloser
	)
	encoding = strings.ToLower(encoding)
	switch encoding {
	case EncodingGzip:
		encoder = gzip.NewWriter(&buf)
	default:
		return fmt.Errorf("%s: invalid encoding %q", logp, encoding)
	}
	nodes := mfs.PathNodes.Nodes()
	for _, node := range nodes {
		if node.mode.IsDir() || len(node.Content) == 0 {
			continue
		}
		_, err = encoder.Write(node.Content)
		if err != nil {
			return fmt.Errorf("%s: %w", logp, err)
		}
		err = encoder.Close()
		if err != nil {
			return fmt.Errorf("%s: %w", logp, err)
		}
		node.Content = make([]byte, buf.Len())
		copy(node.Content, buf.Bytes())
		node.ContentEncoding = encoding
		node.size = int64(len(node.Content))
		buf.Reset()
		if encoding == EncodingGzip {
			gziper := encoder.(*gzip.Writer)
			gziper.Reset(&buf)
		}
	}
	return nil
}
func (mfs *MemFS) Get(path string) (node *Node, err error) {
	logp := "Get"
	node = mfs.PathNodes.Get(path)
	if node == nil {
		if mfs.Opts.Development {
			node, err = mfs.refresh(path)
			if err != nil {
				return nil, fmt.Errorf("%s: %s: %w", logp, path, err)
			}
			return node, nil
		}
		return nil, os.ErrNotExist
	}
	if mfs.Opts.Development {
		err = node.Update(nil, mfs.Opts.MaxFileSize)
		if err != nil {
			return nil, fmt.Errorf("%s: %s: %w", logp, path, err)
		}
	}
	return node, nil
}
func (mfs *MemFS) ListNames() (paths []string) {
	paths = mfs.PathNodes.Paths()
	return paths
}
func (mfs *MemFS) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer
	mfs.Root.packAsJson(&buf, 0)
	return buf.Bytes(), nil
}
func (mfs *MemFS) MustGet(path string) (node *Node) {
	node, _ = mfs.Get(path)
	return node
}
func (mfs *MemFS) Open(path string) (http.File, error) {
	return mfs.Get(path)
}
func (mfs *MemFS) RemoveChild(parent *Node, child *Node) (removed *Node) {
	removed = parent.removeChild(child)
	if removed != nil {
		mfs.PathNodes.Delete(removed.Path)
	}
	return
}