implement 'AICRA' command | system to parse config, set defaults, infer map of sources from a folder, etc

This commit is contained in:
Adrien Marquès 2018-10-01 12:29:05 +02:00
parent c51281c731
commit b6e19c255b
11 changed files with 393 additions and 221 deletions

View File

@ -6,65 +6,38 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings"
) )
// Builds types as plugins (.so) // compile compiles the 'source' file into the 'build' path
// from the sources in the @in folder func compile(source, build string) {
// recursively and generate .so files
// into the @out folder with the same structure
func compile(in string, out string) error {
/* (1) Create build folder */ // 2. Create folder
clifmt.Align(" . create output folder") clifmt.Align(" + create output folder")
err := os.MkdirAll(out, os.ModePerm) err := os.MkdirAll(filepath.Dir(build), os.ModePerm)
if err != nil { if err != nil {
return err fmt.Printf("fail\n")
return
} }
fmt.Printf("ok\n") fmt.Printf("ok\n")
/* (2) List recursively */ // 3. Compile
types := []string{} clifmt.Align(" + compile")
err = filepath.Walk(in, func(path string, f os.FileInfo, err error) error { stdout, err := exec.Command("go",
if strings.HasSuffix(path, "/main.go") { "build", "-buildmode=plugin",
types = append(types, filepath.Base(filepath.Dir(path))) "-o", build,
} source,
return nil ).Output()
})
if err != nil { // 4. success
return err if err == nil {
fmt.Printf("ok\n")
return
} }
/* (3) Print files */ // 5. debug error
for _, name := range types { fmt.Printf("error\n")
if len(stdout) > 0 {
// 1. process output file name fmt.Printf("%s\n%s\n%s\n", clifmt.Color(31, "-=-"), stdout, clifmt.Color(31, "-=-"))
infile := filepath.Join(in, name, "main.go")
outfile := filepath.Join(out, fmt.Sprintf("%s.so", name))
clifmt.Align(fmt.Sprintf(" . compile %s", clifmt.Color(33, name)))
// 2. compile
stdout, err := exec.Command("go",
"build", "-buildmode=plugin",
"-o", outfile,
infile,
).Output()
// 3. success
if err == nil {
fmt.Printf("ok\n")
continue
}
// 4. debug error
fmt.Printf("error\n")
if len(stdout) > 0 {
fmt.Printf("%s\n%s\n%s\n", clifmt.Color(31, "-=-"), stdout, clifmt.Color(31, "-=-"))
}
} }
return nil
} }

View File

@ -1,25 +0,0 @@
package main
import (
"os"
"path/filepath"
)
// Returns an absolute path from the @path variable if already absolute
// if @path is relative, it is processed relative to the @base directory
func getAbsPath(base string, path string) (string, error) {
// already absolute
if filepath.IsAbs(path) {
return path, nil
}
// relative: join from @base dir
return filepath.Abs(filepath.Join(base, path))
}
// Returns whether a directory exists for the path @path
func dirExists(path string) bool {
stat, err := os.Stat(path)
return err == nil && stat.IsDir()
}

View File

@ -1,9 +1,9 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"git.xdrm.io/go/aicra/internal/clifmt" "git.xdrm.io/go/aicra/internal/clifmt"
"git.xdrm.io/go/aicra/internal/meta"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -11,170 +11,84 @@ import (
func main() { func main() {
starttime := time.Now().UnixNano() starttime := time.Now()
/* (1) Flags /* 1. Load config */
---------------------------------------------------------*/ schema, err := meta.Parse("./aicra.json")
/* (1) controller path */
ctlPathFlag := flag.String("c", "controller", "Path to controllers' directory")
/* (2) types path */
typPathFlag := flag.String("t", "type", "Path to custom types' directory")
/* (3) middleware path */
midPathFlag := flag.String("m", "middleware", "Path to middlewares' directory")
flag.Parse()
/* (3) Get last arg: project path */
if len(flag.Args()) < 1 {
fmt.Printf("%s\n\n", clifmt.Warn("missing argument"))
fmt.Printf("You must provide the project folder as the last argument\n")
return
}
var projectPathFlag = flag.Arg(0)
compileTypes := true
compileControllers := true
compileMiddlewares := true
/* (2) Get absolute paths
---------------------------------------------------------*/
/* (1) Get absolute project path */
projectPath, err := filepath.Abs(projectPathFlag)
if err != nil { if err != nil {
fmt.Printf("invalid argument: project path\n") fmt.Printf("aicra.json: %s\n", err)
return
}
/* (2) Get absolute controllers' path */
cPath, err := getAbsPath(projectPath, *ctlPathFlag)
if err != nil {
fmt.Printf("invalid argument: controllers' path\n")
return return
} }
/* (3) Get absolute types' path */ /* 2. End if nothing to compile */
tPath, err := getAbsPath(projectPath, *typPathFlag) if !schema.Driver.Compiled() {
if err != nil { fmt.Printf("\n[ %s | %s ] nothing to compile\n",
fmt.Printf("invalid argument: types' path\n") clifmt.Color(32, "finished"),
time.Now().Sub(starttime),
)
return return
} }
/* (4) Get absolute middlewares' path */ /* Compile
mPath, err := getAbsPath(projectPath, *midPathFlag)
if err != nil {
fmt.Printf("invalid argument: middlwares' path\n")
return
}
// default types folder
dtPath := filepath.Join(os.Getenv("GOPATH"), "src/git.xdrm.io/go/aicra/internal/checker/default")
/* (3) Check path are existing dirs
---------------------------------------------------------*/
clifmt.Title("file check")
/* (1) Project path */
clifmt.Align(" . project root")
if !dirExists(projectPath) {
fmt.Printf("invalid\n\n")
fmt.Printf("%s invalid project folder - %s\n\n", clifmt.Warn(), clifmt.Color(36, projectPath))
fmt.Printf("You must specify an existing directory path\n")
return
}
fmt.Printf("ok\n")
/* (2) Controllers path */
clifmt.Align(" . controllers")
if !dirExists(cPath) {
compileControllers = false
fmt.Printf("missing\n")
} else {
fmt.Printf("ok\n")
}
/* (3) Middlewares path */
clifmt.Align(" . middlewares")
if !dirExists(mPath) {
compileMiddlewares = false
fmt.Printf("missing\n")
} else {
fmt.Printf("ok\n")
}
/* (4) Default types path */
clifmt.Align(" . default types")
if !dirExists(dtPath) {
fmt.Printf("missing\n")
compileTypes = false
} else {
fmt.Printf("ok\n")
}
/* (5) Types path */
clifmt.Align(" . custom types")
if !dirExists(tPath) {
fmt.Printf("missing\n")
compileTypes = false
} else {
fmt.Printf("ok\n")
}
if !compileControllers && !compileTypes && !compileMiddlewares {
fmt.Printf("\n%s\n", clifmt.Info("Nothing to compile"))
return
}
/* (4) Compile
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Create build output dir */ /* (1) Create build output dir */
buildPath := filepath.Join(projectPath, ".build") buildPath := filepath.Join(schema.Root, ".build")
clifmt.Align(" . create build folder")
err = os.MkdirAll(buildPath, os.ModePerm) err = os.MkdirAll(buildPath, os.ModePerm)
if err != nil { if err != nil {
fmt.Printf("error\n\n")
fmt.Printf("%s the directory %s cannot be created, check permissions.", clifmt.Warn(), clifmt.Color(33, buildPath)) fmt.Printf("%s the directory %s cannot be created, check permissions.", clifmt.Warn(), clifmt.Color(33, buildPath))
return return
} }
fmt.Printf("ok\n")
/* (2) Compile controllers */ /* (2) Compile Types */
if compileControllers { if len(schema.Types.Map) > 0 {
clifmt.Title("compile controllers")
err = compile(cPath, filepath.Join(projectPath, ".build/controller"))
if err != nil {
fmt.Printf("%s compilation error: %s\n", clifmt.Warn(), err)
}
}
/* (3) Compile middlewares */
if compileMiddlewares {
clifmt.Title("compile middlewares")
err = compile(mPath, filepath.Join(projectPath, ".build/middleware"))
if err != nil {
fmt.Printf("%s compilation error: %s\n", clifmt.Warn(), err)
}
}
/* (4) Compile DEFAULT types */
clifmt.Title("compile default types")
err = compile(dtPath, filepath.Join(projectPath, ".build/type"))
if err != nil {
fmt.Printf("%s compilation error: %s\n", clifmt.Warn(), err)
}
/* (5) Compile types */
if compileTypes {
clifmt.Title("compile types") clifmt.Title("compile types")
err = compile(tPath, filepath.Join(projectPath, ".build/type")) for name, upath := range schema.Types.Map {
if err != nil {
fmt.Printf("%s compilation error: %s\n", clifmt.Warn(), err) fmt.Printf(" '%s'\n", clifmt.Color(33, name))
// Get useful paths
source := schema.Driver.Source(schema.Root, schema.Types.Folder, upath)
build := schema.Driver.Build(schema.Root, schema.Types.Folder, upath)
compile(source, build)
}
}
/* (3) Compile controllers */
if len(schema.Controllers.Map) > 0 {
clifmt.Title("compile controllers")
for name, upath := range schema.Controllers.Map {
fmt.Printf(" '%s'\n", clifmt.Color(33, name))
// Get useful paths
source := schema.Driver.Source(schema.Root, schema.Controllers.Folder, upath)
build := schema.Driver.Build(schema.Root, schema.Controllers.Folder, upath)
compile(source, build)
fmt.Printf("\n")
}
}
/* (4) Compile middlewares */
if len(schema.Middlewares.Map) > 0 {
clifmt.Title("compile middlewares")
for name, upath := range schema.Middlewares.Map {
fmt.Printf(" '%s'\n", clifmt.Color(33, name))
// Get useful paths
source := schema.Driver.Source(schema.Root, schema.Middlewares.Folder, upath)
build := schema.Driver.Build(schema.Root, schema.Middlewares.Folder, upath)
compile(source, build)
} }
} }
/* (6) finished */ /* (6) finished */
fmt.Printf("\n[ %s | %.0f ms ] files are located inside the %s directory inside the project folder\n", fmt.Printf("\n[ %s | %s ] files are located inside the %s directory inside the project folder\n",
clifmt.Color(32, "finished"), clifmt.Color(32, "finished"),
float64(time.Now().UnixNano()-starttime)/1e6, time.Now().Sub(starttime),
clifmt.Color(33, ".build"), clifmt.Color(33, ".build"),
) )

View File

@ -7,12 +7,32 @@ import (
"git.xdrm.io/go/aicra/response" "git.xdrm.io/go/aicra/response"
"net/http" "net/http"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
) )
// Name returns the driver name // Name returns the driver name
func (d *Generic) Name() string { return "generic" } func (d *Generic) Name() string { return "generic" }
// Path returns the universal path from the source path
func (d Generic) Path(_root, _folder, _src string) string {
return _src
}
// Source returns the source path from the universal path
func (d Generic) Source(_root, _folder, _path string) string {
return filepath.Join(_root, _folder, _path)
}
// Build returns the build path from the universal path
func (d Generic) Build(_root, _folder, _path string) string {
return filepath.Join(_root, _folder, _path)
}
// Compiled returns whether the driver has to be build
func (d Generic) Compiled() bool { return false }
// RunController implements the Driver interface // RunController implements the Driver interface
func (d *Generic) RunController(_path []string, _method string) (func(response.Arguments) response.Response, e.Error) { func (d *Generic) RunController(_path []string, _method string) (func(response.Arguments) response.Response, e.Error) {

View File

@ -6,13 +6,32 @@ import (
e "git.xdrm.io/go/aicra/err" e "git.xdrm.io/go/aicra/err"
"git.xdrm.io/go/aicra/response" "git.xdrm.io/go/aicra/response"
"net/http" "net/http"
"plugin" "path/filepath"
"strings" "strings"
) )
// Name returns the driver name // Name returns the driver name
func (d *Import) Name() string { return "import" } func (d *Import) Name() string { return "import" }
// Path returns the universal path from the source path
func (d Import) Path(_root, _folder, _src string) string {
return strings.TrimSuffix(_src, ".go")
}
// Source returns the source path from the universal path
func (d Import) Source(_root, _folder, _path string) string {
return fmt.Sprintf("%s.go", filepath.Join(_root, _folder, _path))
}
// Build returns the build path from the universal path
func (d Import) Build(_root, _folder, _path string) string {
return filepath.Join(_root, _folder, _path)
}
// Compiled returns whether the driver has to be build
func (d Import) Compiled() bool { return false }
// RegisterController registers a new controller // RegisterController registers a new controller
func (d *Import) RegisterController(_path string, _controller Controller) error { func (d *Import) RegisterController(_path string, _controller Controller) error {
@ -62,7 +81,7 @@ func (d *Import) RegisterMiddlware(_path string, _middleware Middleware) error {
} }
// RunController implements the Driver interface // RunController implements the Driver interface
func (d *Import) RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) { func (d *Import) RunController(_path []string, _method string) (func(response.Arguments) response.Response, e.Error) {
/* (1) Build controller path */ /* (1) Build controller path */
path := strings.Join(_path, "-") path := strings.Join(_path, "-")
@ -109,5 +128,5 @@ func (d *Import) LoadMiddleware(_path string) (func(http.Request, *[]string), er
} }
/* (3) Return middleware */ /* (3) Return middleware */
return mware, nil return middleware.Inspect, nil
} }

View File

@ -5,12 +5,32 @@ import (
"git.xdrm.io/go/aicra/err" "git.xdrm.io/go/aicra/err"
"git.xdrm.io/go/aicra/response" "git.xdrm.io/go/aicra/response"
"net/http" "net/http"
"path/filepath"
"plugin" "plugin"
"strings" "strings"
) )
// Name returns the driver name // Name returns the driver name
func (d *Plugin) Name() string { return "plugin" } func (d Plugin) Name() string { return "plugin" }
// Path returns the universal path from the source path
func (d Plugin) Path(_root, _folder, _src string) string {
return filepath.Dir(_src)
}
// Source returns the source path from the universal path
func (d Plugin) Source(_root, _folder, _path string) string {
return filepath.Join(_root, _folder, _path, "main.go")
}
// Build returns the build path from the universal path
func (d Plugin) Build(_root, _folder, _path string) string {
return fmt.Sprintf("%s.so", filepath.Join(_root, ".build", _folder, _path))
}
// Compiled returns whether the driver has to be build
func (d Plugin) Compiled() bool { return true }
// RunController implements the Driver interface // RunController implements the Driver interface
func (d *Plugin) RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) { func (d *Plugin) RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) {

View File

@ -9,6 +9,11 @@ import (
// Driver defines the driver interface to load controller/middleware implementation or executables // Driver defines the driver interface to load controller/middleware implementation or executables
type Driver interface { type Driver interface {
Name() string Name() string
Path(string, string, string) string
Source(string, string, string) string
Build(string, string, string) string
Compiled() bool
RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error)
LoadMiddleware(_path string) (func(http.Request, *[]string), error) LoadMiddleware(_path string) (func(http.Request, *[]string), error)
} }

60
internal/meta/builder.go Normal file
View File

@ -0,0 +1,60 @@
package meta
import (
"fmt"
"git.xdrm.io/go/aicra/driver"
"os"
"path/filepath"
)
// format inits the map if not set
func (b *builder) format() {
if b.Map == nil {
b.Map = make(map[string]string)
}
}
// InferFromFolder fills the 'Map' by browsing recursively the
// 'Folder' field
func (b *builder) InferFromFolder(_root string, _driver driver.Driver) {
// 1. ignore if no Folder
if len(b.Folder) < 1 {
return
}
// 2. If relative Folder, join to root
rootpath := filepath.Join(_root, b.Folder)
// 3. Walk
filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
// ignore dir
if err != nil || info.IsDir() {
return nil
}
// format path
path, err = filepath.Rel(rootpath, path)
if err != nil {
return nil
}
// extract universal path from the driver
upath := _driver.Path(_root, b.Folder, path)
// format name
name := upath
if name == "ROOT" {
name = ""
}
name = fmt.Sprintf("/%s", name)
// add to map
b.Map[name] = upath
return nil
})
}

23
internal/meta/defaults.go Normal file
View File

@ -0,0 +1,23 @@
package meta
// Default contains the default values when ommited in json
var Default = Schema{
Host: "0.0.0.0",
Port: 80,
DriverName: "",
Types: &builder{
IgnoreBuiltIn: false,
Folder: "",
Map: nil,
},
Controllers: &builder{
IgnoreBuiltIn: true,
Folder: "",
Map: nil,
},
Middlewares: &builder{
IgnoreBuiltIn: true,
Folder: "",
Map: nil,
},
}

109
internal/meta/parser.go Normal file
View File

@ -0,0 +1,109 @@
package meta
import (
"encoding/json"
"errors"
"git.xdrm.io/go/aicra/driver"
"os"
"path/filepath"
"strings"
)
// Parse extracts a Meta from a json config file (aicra.json)
func Parse(_path string) (*Schema, error) {
/* 1. ppen file */
file, err := os.Open(_path)
if err != nil {
return nil, errors.New("cannot open file")
}
defer file.Close()
/* 2. Init receiver dataset */
receiver := &Schema{}
/* 3. Decode json */
decoder := json.NewDecoder(file)
err = decoder.Decode(receiver)
if err != nil {
return nil, err
}
/* 4. Error on invalid driver */
receiver.DriverName = strings.ToLower(receiver.DriverName)
switch receiver.DriverName {
case "generic":
receiver.Driver = &driver.Generic{}
case "plugin":
receiver.Driver = &driver.Plugin{}
case "import":
receiver.Driver = &driver.Import{}
default:
return nil, errors.New("invalid driver; choose from 'generic', 'plugin', and 'import'")
}
/* 5. Fail if type map is set */
if receiver.Types.Map != nil {
return nil, errors.New("types must not feature the 'map'")
}
/* 6. Fail on absolute folders */
if len(receiver.Types.Folder) > 0 && filepath.IsAbs(receiver.Types.Folder) {
return nil, errors.New("types folder must be relative to root")
}
if len(receiver.Controllers.Folder) > 0 && filepath.IsAbs(receiver.Controllers.Folder) {
return nil, errors.New("controllers folder must be relative to root")
}
if len(receiver.Middlewares.Folder) > 0 && filepath.IsAbs(receiver.Middlewares.Folder) {
return nil, errors.New("middlewares folder must be relative to root")
}
/* 7. Format result (default values, etc) */
receiver.setDefaults()
return receiver, nil
}
// setDefaults sets defaults values and checks for missing data
func (m *Schema) setDefaults() {
// 1. extract absolute root folder
absroot, err := filepath.Abs(m.Root)
if err == nil {
m.Root = absroot
}
// 2. host
if len(m.Host) < 1 {
m.Host = Default.Host
}
// 3. port
if m.Port == 0 {
m.Port = Default.Port
}
// 4. Empty builders
if m.Types == nil {
m.Types = Default.Types
}
if m.Controllers == nil {
m.Controllers = Default.Controllers
}
if m.Middlewares == nil {
m.Middlewares = Default.Middlewares
}
// 5. Init map if not set
m.Types.format()
m.Controllers.format()
m.Middlewares.format()
// 6. Infer Maps from Folders
m.Types.InferFromFolder(m.Root, m.Driver)
m.Controllers.InferFromFolder(m.Root, m.Driver)
m.Middlewares.InferFromFolder(m.Root, m.Driver)
}

54
internal/meta/types.go Normal file
View File

@ -0,0 +1,54 @@
package meta
import (
"git.xdrm.io/go/aicra/driver"
)
type builder struct {
// IgnoreBuiltIn tells whether or not to ignore the built-in components
IgnoreBuiltIn bool `json:"default,ommitempty"`
// Folder is used to infer the 'Map' object
Folder string `json:"folder,ommitempty"`
// Map defines the association path=>file
Map map[string]string `json:"map,ommitempty"`
}
// Schema represents an AICRA configuration (not the API, the server, drivers, etc)
type Schema struct {
// Root is root of the project structure
Root string `json:"root"`
// Host is the hostname to listen to (default is 0.0.0.0)
Host string `json:"host,ommitempty"`
// Port is the port to listen to (default is 80)
Port uint16 `json:"port,ommitempty"`
// DriverName is the driver used to load the controllers and middlewares
// (default is 'plugin')
DriverName string `json:"driver"`
Driver driver.Driver
// Types defines :
// - the type folder
// - each type by 'name => path'
// - whether to load the built-in types
//
// types are ommited if not set (no default)
Types *builder `json:"types,ommitempty"`
// Controllers defines :
// - the controller folder (as a single string)
// - each controller by 'name => path' (as a map)
//
// (default is .build/controller)
Controllers *builder `json:"controllers,ommitempty"`
// Middlewares defines :
// - the middleware folder (as a single string)
// - each middleware by 'name => path' (as a map)
//
// (default is .build/middleware)
Middlewares *builder `json:"middlewares,ommitempty"`
}