func (mfs *MemFS) Update(node *Node, newInfo os.FileInfo) {
	if node == nil {
		return
	}
	err := node.Update(newInfo, mfs.Opts.MaxFileSize)
	if err != nil {
		log.Printf("Update: %s: %s", node.SysPath, err)
	}
}
Update the node content and information in memory based on new file
information.
This method only check if the node name is equal with file name, but it's
not checking whether the node is part of memfs (node is parent or have the
same Root node).
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
}
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
}