summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2024-12-03 00:17:34 +0700
committerShulhan <ms@kilabit.info>2024-12-03 00:19:34 +0700
commit904296da2f93b1376d7e2f5395ad11053ec7ab59 (patch)
tree373031611f8011b4d2e518b9bb411ab29f244381
parenta465d718b8ab7d8530e59586a60b11349f1ccc04 (diff)
downloadpakakeh.go-904296da2f93b1376d7e2f5395ad11053ec7ab59.tar.xz
lib/memfs: move compiled regex to their options
This changes move the following fields and methods from MemFS, * incRE and excRE fields to Options, * isIncluded and isExclude methods to Options, * watchRE field to WatchOptions, * isWatched method to WatchOptions. The reason is to allow other type that use Options or WatchOptions to use isIncluded, isExclude, or isWatched methods.
-rw-r--r--lib/memfs/memfs.go116
-rw-r--r--lib/memfs/memfs_test.go147
-rw-r--r--lib/memfs/options.go70
-rw-r--r--lib/memfs/options_test.go174
-rw-r--r--lib/memfs/watch_options.go38
5 files changed, 295 insertions, 250 deletions
diff --git a/lib/memfs/memfs.go b/lib/memfs/memfs.go
index 3d90a8b0..9b3ec668 100644
--- a/lib/memfs/memfs.go
+++ b/lib/memfs/memfs.go
@@ -13,7 +13,6 @@ import (
"net/http"
"os"
"path/filepath"
- "regexp"
"sort"
"strings"
"time"
@@ -36,10 +35,7 @@ type MemFS struct {
Root *Node
Opts *Options
dw *DirWatcher
-
- watchRE []*regexp.Regexp
- incRE []*regexp.Regexp
- excRE []*regexp.Regexp
+ watchopts *WatchOptions
// subfs contains another MemFS instances.
// During Get, it will evaluated in order.
@@ -75,10 +71,10 @@ func (mfs *MemFS) AddChild(parent *Node, fi os.FileInfo) (child *Node, err error
sysPath = filepath.Join(parent.SysPath, fi.Name())
)
- if mfs.isExcluded(sysPath) {
+ if mfs.Opts.isExcluded(sysPath) {
return nil, nil
}
- if mfs.isWatched(sysPath) {
+ if mfs.watchopts != nil && mfs.watchopts.isWatched(sysPath) {
child, err = parent.addChild(sysPath, fi, mfs.Opts.MaxFileSize)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
@@ -89,7 +85,7 @@ func (mfs *MemFS) AddChild(parent *Node, fi os.FileInfo) (child *Node, err error
mfs.PathNodes.Set(child.Path, child)
}
- if !mfs.isIncluded(sysPath, fi) {
+ if !mfs.Opts.isIncluded(sysPath, fi) {
if child != nil {
// The path being watched, but not included.
// Set the generate function name to empty, to prevent
@@ -268,34 +264,18 @@ func (mfs *MemFS) Get(path string) (node *Node, err error) {
// This method provided to initialize MemFS if its Options is set directly,
// not through New() function.
func (mfs *MemFS) Init() (err error) {
- var (
- logp = "Init"
- v string
- re *regexp.Regexp
- )
+ var logp = `Init`
if mfs.Opts == nil {
mfs.Opts = &Options{}
}
- mfs.Opts.init()
-
if mfs.PathNodes == nil {
mfs.PathNodes = NewPathNode()
}
- for _, v = range mfs.Opts.Includes {
- re, err = regexp.Compile(v)
- if err != nil {
- return fmt.Errorf("%s: %w", logp, err)
- }
- mfs.incRE = append(mfs.incRE, re)
- }
- for _, v = range mfs.Opts.Excludes {
- re, err = regexp.Compile(v)
- if err != nil {
- return fmt.Errorf("%s: %w", logp, err)
- }
- mfs.excRE = append(mfs.excRE, re)
+ err = mfs.Opts.init()
+ if err != nil {
+ return fmt.Errorf(`%s: %w`, logp, err)
}
err = mfs.mount()
@@ -471,62 +451,6 @@ func (mfs *MemFS) createRoot() error {
return nil
}
-// isExcluded will return true if the system path is excluded from being
-// watched or included.
-func (mfs *MemFS) isExcluded(sysPath string) bool {
- var (
- re *regexp.Regexp
- )
- for _, re = range mfs.excRE {
- if re.MatchString(sysPath) {
- return true
- }
- }
- return false
-}
-
-// isIncluded will return true if the system path is filtered to be included,
-// pass the list of Includes regexp or no filter defined.
-func (mfs *MemFS) isIncluded(sysPath string, fi os.FileInfo) bool {
- var (
- re *regexp.Regexp
- err error
- )
-
- if len(mfs.incRE) == 0 {
- // No filter defined, default to always included.
- return true
- }
- for _, re = range mfs.incRE {
- if re.MatchString(sysPath) {
- return true
- }
- }
- if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
- // File is symlink, get the real FileInfo to check if its
- // directory or not.
- fi, err = os.Stat(sysPath)
- if err != nil {
- return false
- }
- }
-
- return fi.IsDir()
-}
-
-// isWatched will return true if the system path is filtered to be watched.
-func (mfs *MemFS) isWatched(sysPath string) bool {
- var (
- re *regexp.Regexp
- )
- for _, re = range mfs.watchRE {
- if re.MatchString(sysPath) {
- return true
- }
- }
- return false
-}
-
// mount the directory recursively into the memory as root directory.
// For example, if we mount directory "/tmp" and "/tmp" contains file "a", to
// access file "a" we call Get("/a"), not Get("/tmp/a").
@@ -700,30 +624,22 @@ func (mfs *MemFS) resetAllModTime(t time.Time) {
//
// The returned DirWatcher is ready to use.
// To stop watching for update call the StopWatch.
-func (mfs *MemFS) Watch(opts WatchOptions) (dw *DirWatcher, err error) {
- var (
- logp = "Watch"
+func (mfs *MemFS) Watch(watchopts WatchOptions) (dw *DirWatcher, err error) {
+ var logp = `Watch`
- re *regexp.Regexp
- v string
- )
+ err = watchopts.init()
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+ mfs.watchopts = &watchopts
if mfs.dw != nil {
return mfs.dw, nil
}
- mfs.watchRE = nil
- for _, v = range opts.Watches {
- re, err = regexp.Compile(v)
- if err != nil {
- return nil, fmt.Errorf("%s: %w", logp, err)
- }
- mfs.watchRE = append(mfs.watchRE, re)
- }
-
mfs.dw = &DirWatcher{
fs: mfs,
- Delay: opts.Delay,
+ Delay: watchopts.Delay,
Options: *mfs.Opts,
}
diff --git a/lib/memfs/memfs_test.go b/lib/memfs/memfs_test.go
index 9b82121b..fb97ae1b 100644
--- a/lib/memfs/memfs_test.go
+++ b/lib/memfs/memfs_test.go
@@ -705,153 +705,6 @@ func TestMemFS_RemoveChild(t *testing.T) {
}
}
-func TestMemFS_isIncluded(t *testing.T) {
- cases := []struct {
- desc string
- inc []string
- exc []string
- sysPath []string
- exp []bool
- }{{
- desc: "With empty includes and excludes",
- sysPath: []string{
- filepath.Join(_testWD, "/testdata"),
- filepath.Join(_testWD, "/testdata/index.html"),
- },
- exp: []bool{
- true,
- true,
- },
- }, {
- desc: "With excludes only",
- exc: []string{
- `.*/exclude`,
- `.*\.html$`,
- },
- sysPath: []string{
- filepath.Join(_testWD, "/testdata"),
- filepath.Join(_testWD, "/testdata/exclude"),
- filepath.Join(_testWD, "/testdata/exclude/dir"),
- filepath.Join(_testWD, "/testdata/include"),
- filepath.Join(_testWD, "/testdata"),
- filepath.Join(_testWD, "/testdata/index.html"),
- filepath.Join(_testWD, "/testdata/index.css"),
- },
- exp: []bool{
- true,
- false,
- false,
- true,
- true,
- false,
- true,
- },
- }, {
- desc: "With includes only",
- inc: []string{
- ".*/include",
- `.*\.html$`,
- },
- sysPath: []string{
- filepath.Join(_testWD, "/testdata"),
- filepath.Join(_testWD, "/testdata/include"),
- filepath.Join(_testWD, "/testdata/include/dir"),
- filepath.Join(_testWD, "/testdata"),
- filepath.Join(_testWD, "/testdata/index.html"),
- filepath.Join(_testWD, "/testdata/index.css"),
- },
- exp: []bool{
- true,
- true,
- true,
- true,
- true,
- false,
- },
- }, {
- desc: "With excludes and includes",
- exc: []string{
- `.*/exclude`,
- `.*\.js$`,
- },
- inc: []string{
- `.*/include`,
- `.*\.(css|html)$`,
- },
- sysPath: []string{
- filepath.Join(_testWD, "/testdata"),
- filepath.Join(_testWD, "/testdata/index.html"),
- filepath.Join(_testWD, "/testdata/index.css"),
-
- filepath.Join(_testWD, "/testdata/exclude"),
- filepath.Join(_testWD, "/testdata/exclude/dir"),
- filepath.Join(_testWD, "/testdata/exclude/index-link.css"),
- filepath.Join(_testWD, "/testdata/exclude/index-link.html"),
- filepath.Join(_testWD, "/testdata/exclude/index-link.js"),
-
- filepath.Join(_testWD, "/testdata/include"),
- filepath.Join(_testWD, "/testdata/include/dir"),
- filepath.Join(_testWD, "/testdata/include/index.css"),
- filepath.Join(_testWD, "/testdata/include/index.html"),
- filepath.Join(_testWD, "/testdata/include/index.js"),
- },
- exp: []bool{
- true,
- true,
- true,
-
- false,
- false,
- false,
- false,
- false,
-
- true,
- true,
- true,
- true,
- false,
- },
- }}
-
- var (
- opts *Options
- mfs *MemFS
- fi os.FileInfo
- sysPath string
- err error
- x int
- got bool
- )
- for _, c := range cases {
- t.Log(c.desc)
-
- opts = &Options{
- Includes: c.inc,
- Excludes: c.exc,
- }
- mfs, err = New(opts)
- if err != nil {
- t.Fatal(err)
- }
-
- for x, sysPath = range c.sysPath {
- fi, err = os.Stat(sysPath)
- if err != nil {
- t.Fatal(err)
- }
-
- got = mfs.isExcluded(sysPath)
- if got {
- test.Assert(t, sysPath, !c.exp[x], got)
- } else {
- got = mfs.isIncluded(sysPath, fi)
- test.Assert(t, sysPath, c.exp[x], got)
- }
- }
- }
-}
-
func TestScanDir(t *testing.T) {
opts := Options{
Root: "testdata/",
diff --git a/lib/memfs/options.go b/lib/memfs/options.go
index 0b415f5c..3f304f0c 100644
--- a/lib/memfs/options.go
+++ b/lib/memfs/options.go
@@ -4,7 +4,11 @@
package memfs
-import "strings"
+import (
+ "os"
+ "regexp"
+ "strings"
+)
const (
defaultMaxFileSize = 1024 * 1024 * 5
@@ -27,6 +31,9 @@ type Options struct {
Includes []string
Excludes []string
+ incRE []*regexp.Regexp
+ excRE []*regexp.Regexp
+
// MaxFileSize define maximum file size that can be stored on memory.
// The default value is 5 MB.
// If its value is negative, the content of file will not be mapped to
@@ -46,12 +53,71 @@ type Options struct {
}
// init initialize the options with default value.
-func (opts *Options) init() {
+func (opts *Options) init() (err error) {
if opts.MaxFileSize == 0 {
opts.MaxFileSize = defaultMaxFileSize
}
+
opts.Root = strings.TrimSuffix(opts.Root, `/`)
if len(opts.Root) == 0 {
opts.Root = `.`
}
+
+ var (
+ v string
+ re *regexp.Regexp
+ )
+ for _, v = range opts.Includes {
+ re, err = regexp.Compile(v)
+ if err != nil {
+ return err
+ }
+ opts.incRE = append(opts.incRE, re)
+ }
+ for _, v = range opts.Excludes {
+ re, err = regexp.Compile(v)
+ if err != nil {
+ return err
+ }
+ opts.excRE = append(opts.excRE, re)
+ }
+ return nil
+}
+
+// isExcluded return true if the sysPath is match with one of regex in
+// Excludes.
+func (opts *Options) isExcluded(sysPath string) bool {
+ var re *regexp.Regexp
+ for _, re = range opts.excRE {
+ if re.MatchString(sysPath) {
+ return true
+ }
+ }
+ return false
+}
+
+// isIncluded return true if the sysPath is pass the list of Includes
+// regexp, or no filter defined.
+func (opts *Options) isIncluded(sysPath string, fi os.FileInfo) bool {
+ if len(opts.incRE) == 0 {
+ // No filter defined, default to always included.
+ return true
+ }
+ var re *regexp.Regexp
+ for _, re = range opts.incRE {
+ if re.MatchString(sysPath) {
+ return true
+ }
+ }
+ if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+ // File is symlink, get the real FileInfo to check if its
+ // directory or not.
+ var err error
+ fi, err = os.Stat(sysPath)
+ if err != nil {
+ return false
+ }
+ }
+
+ return fi.IsDir()
}
diff --git a/lib/memfs/options_test.go b/lib/memfs/options_test.go
new file mode 100644
index 00000000..6dcb14a7
--- /dev/null
+++ b/lib/memfs/options_test.go
@@ -0,0 +1,174 @@
+// Copyright 2024, Shulhan <ms@kilabit.info>. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package memfs
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "git.sr.ht/~shulhan/pakakeh.go/lib/test"
+)
+
+func TestOptionsIsIncluded(t *testing.T) {
+ type testData struct {
+ sysPath string
+ exp bool
+ }
+
+ var cases = []struct {
+ data []testData
+ desc string
+ opts Options
+ }{{
+ desc: `With empty includes and excludes`,
+ data: []testData{{
+ sysPath: filepath.Join(_testWD, `/testdata`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/index.html`),
+ exp: true,
+ }},
+ }, {
+ desc: `With excludes only`,
+ opts: Options{
+ Excludes: []string{
+ `.*/exclude`,
+ `.*\.html$`,
+ },
+ },
+ data: []testData{{
+ sysPath: filepath.Join(_testWD, `/testdata`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/exclude`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/exclude/dir`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/index.html`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/index.css`),
+ exp: true,
+ }},
+ }, {
+ desc: `With includes only`,
+ opts: Options{
+ Includes: []string{
+ `.*/include`,
+ `.*\.html$`,
+ },
+ },
+ data: []testData{{
+ sysPath: filepath.Join(_testWD, `/testdata`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include/dir`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/index.html`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/index.css`),
+ exp: false,
+ }},
+ }, {
+ desc: `With excludes and includes`,
+ opts: Options{
+ Excludes: []string{
+ `.*/exclude`,
+ `.*\.js$`,
+ },
+ Includes: []string{
+ `.*/include`,
+ `.*\.(css|html)$`,
+ },
+ },
+ data: []testData{{
+ sysPath: filepath.Join(_testWD, `/testdata`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/index.html`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/index.css`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/exclude`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/exclude/dir`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/exclude/index-link.css`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/exclude/index-link.html`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/exclude/index-link.js`),
+ exp: false,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include/dir`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include/index.css`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include/index.html`),
+ exp: true,
+ }, {
+ sysPath: filepath.Join(_testWD, `/testdata/include/index.js`),
+ exp: false,
+ }},
+ }}
+
+ var (
+ fi os.FileInfo
+ tdata testData
+ err error
+ got bool
+ )
+ for _, c := range cases {
+ t.Log(c.desc)
+
+ err = c.opts.init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, tdata = range c.data {
+ fi, err = os.Stat(tdata.sysPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ got = c.opts.isExcluded(tdata.sysPath)
+ if got {
+ test.Assert(t, tdata.sysPath, !tdata.exp, got)
+ } else {
+ got = c.opts.isIncluded(tdata.sysPath, fi)
+ test.Assert(t, tdata.sysPath, tdata.exp, got)
+ }
+ }
+ }
+}
diff --git a/lib/memfs/watch_options.go b/lib/memfs/watch_options.go
index 119ea145..412e8843 100644
--- a/lib/memfs/watch_options.go
+++ b/lib/memfs/watch_options.go
@@ -4,7 +4,10 @@
package memfs
-import "time"
+import (
+ "regexp"
+ "time"
+)
// WatchOptions define an options for the MemFS Watch method.
type WatchOptions struct {
@@ -14,9 +17,42 @@ type WatchOptions struct {
// watched.
Watches []string
+ watchRE []*regexp.Regexp
+
// Delay define the duration when the new changes will be checked from
// system.
// This field set the DirWatcher.Delay returned from Watch().
// This field is optional, default is 5 seconds.
Delay time.Duration
}
+
+func (watchopts *WatchOptions) init() (err error) {
+ if watchopts.Delay < 100*time.Millisecond {
+ watchopts.Delay = defWatchDelay
+ }
+
+ var (
+ v string
+ re *regexp.Regexp
+ )
+ watchopts.watchRE = nil
+ for _, v = range watchopts.Watches {
+ re, err = regexp.Compile(v)
+ if err != nil {
+ return err
+ }
+ watchopts.watchRE = append(watchopts.watchRE, re)
+ }
+ return nil
+}
+
+// isWatched return true if the sysPath is filtered to be watched.
+func (watchopts *WatchOptions) isWatched(sysPath string) bool {
+ var re *regexp.Regexp
+ for _, re = range watchopts.watchRE {
+ if re.MatchString(sysPath) {
+ return true
+ }
+ }
+ return false
+}