1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

New feature: require_glob() (similar to require() but supports globs) (#804)

* Initial implementation of findFiles/globe/glob

* Fixed path, some small improvements

* filepath.Dir() calls Clean() automatically anyway

* Relative path support (like require()), renamed func

* Check file ext prefix, further comments, var renaming

* Updated static.go after merge

* Added doc for glob()

* Tiny adjustment of description of glob()

* Updated docs for possible pattern

* Reworked glob, added public-facing require_glob()

* Updated docs with examples

* Updated static.go

* go generate
This commit is contained in:
Patrik Kernstock
2020-08-19 19:00:40 +01:00
committed by GitHub
parent b01b278d18
commit 576c2bd582
4 changed files with 262 additions and 117 deletions

View File

@ -0,0 +1,39 @@
---
name: require_glob
parameters:
- path
- recursive
---
`require_glob()` can recursively load `.js` files, optionally non-recursive as well.
Possible parameters are:
- Path as string, where you would like to start including files. Mandatory. Pattern matching possible, see [GoLand path/filepath/#Match docs](https://golang.org/pkg/path/filepath/#Match).
- If being recursive. This is a boolean if the search should be recursive or not. Define either `true` or `false`. Default is `true`.
Example to load `.js` files recursively:
```
require_glob("./domains/");
```
Example to load `.js` files only in `domains/`:
```
require_glob("./domains/", false);
```
One more important thing to note: `require_glob()` is as smart as `require()` is. It loads files always relative to the JavaScript
file where it's being executed in. Let's go with an example, as it describes it better:
dnscontrol.js:
```
require("domains/index.js");
```
domains/index.js:
```
require_glob("./user1/");
```
This will now load files being present underneath `./domains/user1/` and **NOT** at below `./domains/`, as `require_glob()`
is called in the subfolder `domains/`.

View File

@ -887,3 +887,15 @@ function DKIM(arr) {
R.push(arr.slice(i, i + chunkSize));
return R;
}
// Function wrapper for glob() for recursively loading files.
// As the main function (in Go) is in our control anyway, all the values here are already sanity-checked.
// Note: glob() is only an internal undocumented helper function. So use it on your own risk.
function require_glob() {
arguments[2] = "js"; // force to only include .js files.
var files = glob.apply(null, arguments);
for (i = 0; i < files.length; i++) {
require(files[i]);
}
return files
}

View File

@ -3,12 +3,12 @@ package js
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/robertkrimen/otto" // load underscore js into vm by default
_ "github.com/robertkrimen/otto/underscore" // required by otto
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
@ -31,12 +31,13 @@ func ExecuteJavascript(file string, devMode bool) (*models.DNSConfig, error) {
}
// Record the directory path leading up to this file.
currentDirectory = filepath.Clean(filepath.Dir(file))
currentDirectory = filepath.Dir(file)
vm := otto.New()
vm.Set("require", require)
vm.Set("REV", reverse)
vm.Set("glob", listFiles) // used for require_glob()
helperJs := GetHelpers(devMode)
// run helper script to prime vm and initialize variables
@ -87,10 +88,11 @@ func require(call otto.FunctionCall) otto.Value {
// Record the old currentDirectory so that we can return there.
currentDirectoryOld := currentDirectory
// Record the directory path leading up to the file we're about to require.
currentDirectory = filepath.Clean(filepath.Dir(cleanFile))
currentDirectory = filepath.Dir(cleanFile)
printer.Debugf("requiring: %s (%s)\n", file, relFile)
data, err := ioutil.ReadFile(relFile)
// quick fix, by replacing to linux slashes, to make it work with windows paths too.
data, err := ioutil.ReadFile(filepath.ToSlash(relFile))
if err != nil {
throw(call.Otto, err.Error())
@ -116,6 +118,94 @@ func require(call otto.FunctionCall) otto.Value {
return value
}
func listFiles(call otto.FunctionCall) otto.Value {
// Check amount of arguments provided
if ! (len(call.ArgumentList) >= 1 && len(call.ArgumentList) <= 3) {
throw(call.Otto, "glob requires at least one argument: folder (string). " +
"Optional: recursive (bool) [true], fileExtension (string) [.js]")
}
// Check if provided parameters are valid
// First: Let's check dir.
if !(call.Argument(0).IsDefined() && call.Argument(0).IsString() &&
len(call.Argument(0).String()) > 0) {
throw(call.Otto, "glob: first argument needs to be a path, provided as string.")
}
dir := call.Argument(0).String() // Path where to start listing
printer.Debugf("listFiles: cd: %s, user: %s \n", currentDirectory, dir)
// now we always prepend the current directory we're working in, which is being set within
// the func ExecuteJavascript() above. So when require("domains/load_all.js") is being used,
// where glob("customer1/") is being used, we basically search for files in domains/customer1/.
dir = filepath.ToSlash(filepath.Join(currentDirectory, dir))
if _, err := os.Stat(dir); os.IsNotExist(err) {
throw(call.Otto, "glob: provided path does not exist.")
}
// Second: Recursive?
var recursive bool = true;
if call.Argument(1).IsDefined() && ! call.Argument(1).IsNull() {
if call.Argument(1).IsBoolean() {
recursive, _ = call.Argument(1).ToBoolean() // If it should be recursive
} else {
throw(call.Otto, "glob: second argument, if recursive, needs to be bool.")
}
}
// Third: File extension filter.
var fileExtension string = ".js";
if call.Argument(2).IsDefined() && ! call.Argument(2).IsNull() {
if call.Argument(2).IsString() {
fileExtension = call.Argument(2).String() // Which file extension to filter for.
if ! strings.HasPrefix(fileExtension, ".") {
// If it doesn't start with a dot, probably user forgot it and we do it instead.
fileExtension = "." + fileExtension
}
} else {
throw(call.Otto, "glob: third argument, file extension, needs to be a string. * for no filter.")
}
}
// Now we're doing the actual work: Listing files.
// Folders are ending with a slash. Can be identified later on from the user with JavaScript.
// Additionally, when more smart logic required, user can use regex in JS.
files := make([]string, 0) // init files list
dirClean := filepath.Clean(dir) // let's clean it here once, instead of over-and-over again within loop
err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
// quick fix to get it working on windows, as it returns paths with double-backslash, what usually
// require() doesn't seem to handle well. For the sake of compatibility (and because slash looks nicer),
// we simply replace "\\" to "/" using filepath.ToSlash()..
path = filepath.ToSlash(filepath.Clean(path)) // convert to slashes for directories
if ! recursive && fi.IsDir() {
// If recursive is disabled, it is a dir what we're processing, and the path is different
// than specified, we're apparently in a different folder. Therefore: Skip it.
// So: Why this way? Because Walk() is always recursive and otherwise would require a complete
// different function to handle this scenario. This way it's easier to maintain.
if path != dirClean {
return filepath.SkipDir
}
}
if fileExtension != "*" && fileExtension != filepath.Ext(path) {
// ONLY skip, when the file extension is NOT matching, or when filter is NOT disabled.
return nil
}
//dirPath := filepath.ToSlash(filepath.Dir(path)) + "/"
files = append(files, path)
return err
})
if err != nil {
throw(call.Otto, fmt.Sprintf("dirwalk failed: %v", err.Error()))
}
// let's pass the data back to the JS engine.
value, err := call.Otto.ToValue(files)
if err != nil {
throw(call.Otto, fmt.Sprintf("converting value failed: %v", err.Error()))
}
return value
}
func throw(vm *otto.Otto, str string) {
panic(vm.MakeCustomError("Error", str))
}

View File

@ -212,118 +212,122 @@ var _escData = map[string]*_escFile{
"/helpers.js": {
name: "helpers.js",
local: "pkg/js/helpers.js",
size: 25616,
size: 26104,
modtime: 0,
compressed: `
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
/3My09H4ZQAA
`,
},
}