diff --git a/cmd/aicra/compile.go b/cmd/aicra/compile.go index 0310a68..addb757 100644 --- a/cmd/aicra/compile.go +++ b/cmd/aicra/compile.go @@ -6,65 +6,38 @@ import ( "os" "os/exec" "path/filepath" - "strings" ) -// Builds types as plugins (.so) -// from the sources in the @in folder -// recursively and generate .so files -// into the @out folder with the same structure -func compile(in string, out string) error { +// compile compiles the 'source' file into the 'build' path +func compile(source, build string) { - /* (1) Create build folder */ - clifmt.Align(" . create output folder") - err := os.MkdirAll(out, os.ModePerm) + // 2. Create folder + clifmt.Align(" + create output folder") + err := os.MkdirAll(filepath.Dir(build), os.ModePerm) if err != nil { - return err + fmt.Printf("fail\n") + return } fmt.Printf("ok\n") - /* (2) List recursively */ - types := []string{} - err = filepath.Walk(in, func(path string, f os.FileInfo, err error) error { - if strings.HasSuffix(path, "/main.go") { - types = append(types, filepath.Base(filepath.Dir(path))) - } - return nil - }) + // 3. Compile + clifmt.Align(" + compile") + stdout, err := exec.Command("go", + "build", "-buildmode=plugin", + "-o", build, + source, + ).Output() - if err != nil { - return err + // 4. success + if err == nil { + fmt.Printf("ok\n") + return } - /* (3) Print files */ - for _, name := range types { - - // 1. process output file name - 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, "-=-")) - } - + // 5. 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 } diff --git a/cmd/aicra/files.go b/cmd/aicra/files.go deleted file mode 100644 index 68d6f57..0000000 --- a/cmd/aicra/files.go +++ /dev/null @@ -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() -} diff --git a/cmd/aicra/main.go b/cmd/aicra/main.go index b80e9ce..033f400 100644 --- a/cmd/aicra/main.go +++ b/cmd/aicra/main.go @@ -1,9 +1,9 @@ package main import ( - "flag" "fmt" "git.xdrm.io/go/aicra/internal/clifmt" + "git.xdrm.io/go/aicra/internal/meta" "os" "path/filepath" "time" @@ -11,170 +11,84 @@ import ( func main() { - starttime := time.Now().UnixNano() + starttime := time.Now() - /* (1) Flags - ---------------------------------------------------------*/ - /* (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) + /* 1. Load config */ + schema, err := meta.Parse("./aicra.json") if err != nil { - fmt.Printf("invalid argument: project path\n") - return - } - /* (2) Get absolute controllers' path */ - cPath, err := getAbsPath(projectPath, *ctlPathFlag) - if err != nil { - fmt.Printf("invalid argument: controllers' path\n") + fmt.Printf("aicra.json: %s\n", err) return } - /* (3) Get absolute types' path */ - tPath, err := getAbsPath(projectPath, *typPathFlag) - if err != nil { - fmt.Printf("invalid argument: types' path\n") + /* 2. End if nothing to compile */ + if !schema.Driver.Compiled() { + fmt.Printf("\n[ %s | %s ] nothing to compile\n", + clifmt.Color(32, "finished"), + time.Now().Sub(starttime), + ) return } - /* (4) Get absolute middlewares' path */ - 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 + /* Compile ---------------------------------------------------------*/ /* (1) Create build output dir */ - buildPath := filepath.Join(projectPath, ".build") - clifmt.Align(" . create build folder") + buildPath := filepath.Join(schema.Root, ".build") err = os.MkdirAll(buildPath, os.ModePerm) 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)) return } - fmt.Printf("ok\n") - /* (2) Compile controllers */ - if compileControllers { - 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 { + /* (2) Compile Types */ + if len(schema.Types.Map) > 0 { clifmt.Title("compile types") - err = compile(tPath, filepath.Join(projectPath, ".build/type")) - if err != nil { - fmt.Printf("%s compilation error: %s\n", clifmt.Warn(), err) + for name, upath := range schema.Types.Map { + + 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 */ - 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"), - float64(time.Now().UnixNano()-starttime)/1e6, + time.Now().Sub(starttime), clifmt.Color(33, ".build"), ) diff --git a/driver/generic.go b/driver/generic.go index 2b86ebd..b124461 100644 --- a/driver/generic.go +++ b/driver/generic.go @@ -7,12 +7,32 @@ import ( "git.xdrm.io/go/aicra/response" "net/http" "os/exec" + "path/filepath" "strings" ) // Name returns the driver name 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 func (d *Generic) RunController(_path []string, _method string) (func(response.Arguments) response.Response, e.Error) { diff --git a/driver/import.go b/driver/import.go index f8b715c..52d0417 100644 --- a/driver/import.go +++ b/driver/import.go @@ -6,13 +6,32 @@ import ( e "git.xdrm.io/go/aicra/err" "git.xdrm.io/go/aicra/response" "net/http" - "plugin" + "path/filepath" "strings" ) // Name returns the driver name 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 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 -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 */ path := strings.Join(_path, "-") @@ -109,5 +128,5 @@ func (d *Import) LoadMiddleware(_path string) (func(http.Request, *[]string), er } /* (3) Return middleware */ - return mware, nil + return middleware.Inspect, nil } diff --git a/driver/plugin.go b/driver/plugin.go index 3a343aa..272497e 100644 --- a/driver/plugin.go +++ b/driver/plugin.go @@ -5,12 +5,32 @@ import ( "git.xdrm.io/go/aicra/err" "git.xdrm.io/go/aicra/response" "net/http" + "path/filepath" "plugin" "strings" ) // 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 func (d *Plugin) RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) { diff --git a/driver/types.go b/driver/types.go index 61aeb0a..7062e25 100644 --- a/driver/types.go +++ b/driver/types.go @@ -9,6 +9,11 @@ import ( // Driver defines the driver interface to load controller/middleware implementation or executables type Driver interface { 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) LoadMiddleware(_path string) (func(http.Request, *[]string), error) } diff --git a/internal/meta/builder.go b/internal/meta/builder.go new file mode 100644 index 0000000..28d791a --- /dev/null +++ b/internal/meta/builder.go @@ -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 + }) + +} diff --git a/internal/meta/defaults.go b/internal/meta/defaults.go new file mode 100644 index 0000000..d820aa5 --- /dev/null +++ b/internal/meta/defaults.go @@ -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, + }, +} diff --git a/internal/meta/parser.go b/internal/meta/parser.go new file mode 100644 index 0000000..14c054d --- /dev/null +++ b/internal/meta/parser.go @@ -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) + +} diff --git a/internal/meta/types.go b/internal/meta/types.go new file mode 100644 index 0000000..8e4c72a --- /dev/null +++ b/internal/meta/types.go @@ -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"` +}