diff --git a/.travis.yml b/.travis.yml index 85831b6..9ef87dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ branches: go: - 1.8 - - 1.8.x + - 1.9 - tip go_import_path: aahframework.org/tools.v0/aah diff --git a/README.md b/README.md index 88a89da..4687119 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ Home of all aah framework CLI tools. ## aah CLI Tool [![Build Status](https://travis-ci.org/go-aah/tools.svg?branch=master)](https://travis-ci.org/go-aah/tools) [![Go Report Card](https://goreportcard.com/badge/aahframework.org/tools.v0)](https://goreportcard.com/report/aahframework.org/tools.v0/aah) [![Powered by Go](https://img.shields.io/badge/powered_by-go-blue.svg)](https://golang.org) - [![Version](https://img.shields.io/badge/version-0.7-blue.svg)](https://github.com/go-aah/tools/releases/latest) + [![Version](https://img.shields.io/badge/version-0.8-blue.svg)](https://github.com/go-aah/tools/releases/latest) [![License](https://img.shields.io/github/license/go-aah/tools.svg)](LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@aahframework-55acee.svg)](https://twitter.com/aahframework) -***Release [v0.7](https://github.com/go-aah/tools/releases/latest) tagged on Aug 01, 2017*** +***Release [v0.8](https://github.com/go-aah/tools/releases/latest) tagged on Sep 01, 2017*** aah framework - A scalable, performant, rapid development Web framework for Go. diff --git a/aah/aah.go b/aah/aah.go index 4e0506c..7380b50 100644 --- a/aah/aah.go +++ b/aah/aah.go @@ -7,6 +7,7 @@ package main import ( "errors" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -26,17 +27,11 @@ import ( "aahframework.org/router.v0" "aahframework.org/security.v0" "aahframework.org/test.v0" + "aahframework.org/valpar.v0" "aahframework.org/view.v0" ) -// Version no. of aah framework CLI tool -const Version = "0.7" - const ( - header = `––––––––––––––––––––––––––––––––––––––––––––––––––––– - aah framework v%s - https://aahframework.org -––––––––––––––––––––––––––––––––––––––––––––––––––––– -` aahImportPath = "aahframework.org/aah.v0" aahCLIImportPath = "aahframework.org/tools.v0/aah" permRWXRXRX = 0755 @@ -111,7 +106,6 @@ func main() { sort.Sort(cli.FlagsByName(app.Flags)) _ = app.Run(os.Args) - return } //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ @@ -119,15 +113,38 @@ func main() { //___________________________________ func printHeader(c *cli.Context) error { - if isWindowsOS() { - fmt.Fprintf(c.App.Writer, header, aah.Version) - } else { - fmt.Fprintf(c.App.Writer, fmt.Sprintf("\033[1;32m%v\033[0m", header), aah.Version) + hdr := fmt.Sprintf("aah framework v%s - https://aahframework.org", aah.Version) + improveRpt := "# Report improvements/bugs at https://github.com/go-aah/aah/issues #" + cnt := len(improveRpt) + sp := (cnt - len(hdr)) / 2 + + if !isWindowsOS() { + fmt.Fprintf(c.App.Writer, "\033[1;32m") + } + + printChr(c.App.Writer, "–", cnt) + fmt.Fprintf(c.App.Writer, "\n") + printChr(c.App.Writer, " ", sp) + fmt.Fprintf(c.App.Writer, hdr) + printChr(c.App.Writer, " ", sp) + fmt.Fprintf(c.App.Writer, "\n") + printChr(c.App.Writer, "–", cnt) + fmt.Fprintf(c.App.Writer, "\n") + + if !isWindowsOS() { + fmt.Fprintf(c.App.Writer, "\033[0m") } - fmt.Fprintf(c.App.Writer, "# Report improvements/bugs at https://github.com/go-aah/aah/issues\n\n") + + fmt.Fprintf(c.App.Writer, improveRpt+"\n\n") return nil } +func printChr(w io.Writer, chr string, cnt int) { + for idx := 0; idx < cnt; idx++ { + fmt.Fprintf(w, chr) + } +} + func init() { cli.HelpFlag = cli.BoolFlag{ Name: "h, help", @@ -151,8 +168,8 @@ func init() { "security v" + security.Version}, ", ")) fmt.Fprintf(c.App.Writer, "\t%-17s %s\n", "", strings.Join( []string{"i18n v" + i18n.Version, "view v" + view.Version, - "log v" + log.Version, "test v" + test.Version, "aruntime v" + aruntime.Version, - }, ", ")) + "log v" + log.Version, "test v" + test.Version, + "aruntime v" + aruntime.Version, "valpar v" + valpar.Version}, ", ")) fmt.Println() fmt.Fprintf(c.App.Writer, "\t%-17s %s\n", fmt.Sprintf("go[%s/%s]", runtime.GOOS, runtime.GOARCH), runtime.Version()[2:]) diff --git a/aah/app-template/aah.project.atmpl b/aah/app-template/aah.project.atmpl index eca3c6b..59beac8 100644 --- a/aah/app-template/aah.project.atmpl +++ b/aah/app-template/aah.project.atmpl @@ -1,8 +1,8 @@ -######################################## +############################################################## # {{ .AppName }} - aah framework project # # Note: Add it to version control -######################################## +############################################################## # Build section is used during aah application compile and build command. build { diff --git a/aah/app-template/app/controllers/app.go.atmpl b/aah/app-template/app/controllers/app.go.atmpl index 0f9e893..d1b6ede 100644 --- a/aah/app-template/app/controllers/app.go.atmpl +++ b/aah/app-template/app/controllers/app.go.atmpl @@ -5,13 +5,13 @@ import ( "{{.AppImportPath}}/app/models" ) -// App struct application controller -type App struct { +// AppController struct application controller +type AppController struct { *aah.Context } // Index method is application {{ if eq .AppType "web" -}}home page.{{ else }}root API endpoint.{{- end }} -func (a *App) Index() { +func (a *AppController) Index() { {{- if eq .AppType "web" }} data := aah.Data{ "Greet": models.Greet{ diff --git a/aah/app-template/config/aah.conf.atmpl b/aah/app-template/config/aah.conf.atmpl index ad38237..478579e 100644 --- a/aah/app-template/config/aah.conf.atmpl +++ b/aah/app-template/config/aah.conf.atmpl @@ -12,6 +12,11 @@ name = "{{ .AppName }}" # Friendly description of application desc = "aah framework {{ .AppType }} application" +# Configure file path of application PID file to be written. +# Ensure application has appropriate permission and directory exists. +# Default value is `/.pid` +#pid_file = "/path/to/pidfile.pid" + # ----------------------------------------------------------------- # Server configuration - HTTP # Doc: https://docs.aahframework.org/app-config.html#section-server @@ -25,6 +30,10 @@ server { # Default value is 8080. #port = "" + # Header value written as `Server` HTTP header. + # If you do not want to include `Server` header, comment it out. + header = "aah-go-server" + # Valid time units are "s = seconds", "m = minutes" timeout { # Mapped to `http.Server.ReadTimeout`, is the maximum duration for reading @@ -138,7 +147,6 @@ server { # Doc: https://docs.aahframework.org/app-config.html#section-request # ------------------------------------------------------------------ request { - # aah framework encourages to have unique `Request Id` for each incoming # request, it helps in traceability. If request has already `X-Request-Id` # HTTP header then it does not generate one. @@ -150,10 +158,62 @@ request { #header = "X-Request-Id" } + # Max request body size for all incoming HTTP requests except `MultipartForm`. + # Also you can override this size for individual route on specific cases + # in `routes.conf` if need be. + # Default value is `5mb`. + #max_body_size = "5mb" + # Default value is `32mb`, choose your value based on your use case #multipart_size = "32mb" + + # aah provides `Content Negotiation` feature for the incoming HTTP request. + # Read more about implementation and RFC details here GitHub #75. + # Perfect for REST API, also can be used for web application too if needed. + content_negotiation { + # To enable Content Negotiation for the application. + # Default value is `false`. + #enable = true + + # For example: Client sends Content-Type header as `application/xml`. + # However server only supports JSON payload as request body. + # Then server responds with 415 Unsupported Media Type. + # Default value is empty list and disabled. + #accepted = ["application/json", "text/json"] + + # For example: Client sends Accept header as `application/xml`. + # However server only supports serving JSON i.e. `application/json`. + # Then server responds with 406 Not Acceptable. + # Default value is empty list and disabled. + #offered = ["application/json", "text/json"] + } + + # Auto Bind configuration used to bind request parameters to controller + # action parameters. + auto_bind { + # Priority is used to select the bind source priority. + # P -> Path Parameter + # F -> Form Parameter + # Q -> Query Parameter + # + # For example: Let's say you have a controller action named `OrderInfo` and its has + # parameter called `orderId`. So framework tries to parse and bind based + # on the priority. The `orderId` present in `Path` and `Form`, framework + # binds the value from `Path`. Typically recommended to have unique names + # in the request parameter though :) + # Path -> Form -> Query + # If not found then it returns with default Go zero value. + # + # Default value is `PFQ`. + #priority = "PFQ" + + # Tag Name is used for bind values to struct exported fields. + # Default value is `bind`. + #tag_name = "bind" + } } +{{ if eq .AppType "web" -}} # --------------------------------------------------------------- # i18n configuration # Doc: https://docs.aahframework.org/app-config.html#section-i18n @@ -176,18 +236,22 @@ i18n { # Default value is `lang`. #query = "locale" } -} +}{{ end }} # ----------------------------------------------------------------- # Format configuration # Doc: https://docs.aahframework.org/app-config.html#section-format # ----------------------------------------------------------------- format { - # Default value is `2006-01-02`. - #date = "2006-01-02" - - # Default value is `2006-01-02 15:04:05`. - #datetime = "2006-01-02 15:04:05" + # Time format for auto parse and bind. aah tries to parse the + # time value in the order they defined till it gets success + # otherwise returns the error. + time = [ + "2006-01-02T15:04:05Z07:00", + "2006-01-02T15:04:05Z", + "2006-01-02 15:04:05", + "2006-01-02" + ] } # ------------------------------------------------------------------ diff --git a/aah/app-template/config/routes.conf.atmpl b/aah/app-template/config/routes.conf.atmpl index 9d36923..2bb0998 100644 --- a/aah/app-template/config/routes.conf.atmpl +++ b/aah/app-template/config/routes.conf.atmpl @@ -44,15 +44,6 @@ domains { # Default value is empty string. #default_auth = "" - # Mapping your custom `NotFound` implementation. It is when no matching - # route is found. If it is not set framework default is called. This is optional one. - # Create your controller and action method with param called `isStatic bool`. - # Framework tells you whether route is `static route` or `application route`. - #not_found { - # controller = "App" - # action = "NotFound" - #} - {{ if eq .AppType "web" -}} #---------------------------------------------------------------------------- # Static Routes Configuration @@ -119,7 +110,7 @@ domains { # The controller to be called for mapped URL path. # * `controller` attribute supports with or without package prefix. For e.g.: `v1/User` or `User` # * `controller` attribute supports both naming conventions. For e.g.: `User` or `UserController` - controller = "App" + controller = "AppController" # The action/method name in the controller to be called for mapped URL path. # Default values are mapped based on `HTTP` method. Refer doc for more info. @@ -142,6 +133,15 @@ domains { # `auth` attribute as `anonymous`. # Default value is empty string. #auth = "" + + # Max request body size for this route. If its happen to be `MultipartForm` + # then this value ignored since `request.multipart_size` config from `aah.conf` + # is applied. + # + # If this value is not provided then global `request.max_body_size` config + # from `aah.conf` is applied. So use it for specific cases. + # No default value, global value is applied. + #max_body_size = "5mb" } } # end - routes diff --git a/aah/app-template/config/security.conf.atmpl b/aah/app-template/config/security.conf.atmpl index 89c8ab2..afeecb7 100644 --- a/aah/app-template/config/security.conf.atmpl +++ b/aah/app-template/config/security.conf.atmpl @@ -230,4 +230,165 @@ security { #cleanup_interval = "30m" {{- end }} } + + # --------------------------------------------------------------------------- + # HTTP Secure Header(s) + # Application security headers with many safe defaults. + # Doc: https://docs.aahframework.org/security-config.html#section-http-header + # + # Tip: Quick way to verify secure headers - https://securityheaders.io + # --------------------------------------------------------------------------- + http_header { + # X-XSS-Protection + # Designed to enable the cross-site scripting (XSS) filter built into modern + # web browsers. This is usually enabled by default, but using this header + # will enforce it. + # + # Learn more: + # https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xxxsp + # https://www.keycdn.com/blog/x-xss-protection/ + # + # Encouraged to make use of header `Content-Security-Policy` with enhanced + # policy to reduce XSS risk along with header `X-XSS-Protection`. + # Default values is `1; mode=block`. + #xxssp = "1; mode=block" + + # X-Content-Type-Options + # Prevent Content Sniffing or MIME sniffing. + # + # Learn more: + # https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xcto + # https://en.wikipedia.org/wiki/Content_sniffing + # Default value is `nosniff`. + #xcto = "nosniff" + + # X-Frame-Options + # Prevents Clickjacking. + # + # Learn more: + # https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xfo + # https://www.keycdn.com/blog/x-frame-options/ + # Default value is `SAMEORIGIN`. + #xfo = "SAMEORIGIN" + + # Referrer-Policy + # This header governs which referrer information, sent in the Referer header, should + # be included with requests made. + # Referrer Policy has been a W3C Candidate Recommendation since 26 January 2017. + # + # Learn more: + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + # https://scotthelme.co.uk/a-new-security-header-referrer-policy/ + # https://www.w3.org/TR/referrer-policy/ + # Default value is `no-referrer-when-downgrade`. + #rp = "no-referrer-when-downgrade" + + # Strict-Transport-Security (STS, aka HSTS) + # STS header that lets a web site tell browsers that it should only be communicated + # with using HTTPS, instead of using HTTP. + # + # Learn more: + # https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts + # https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security + # + # Note: Framework checks that application uses SSL on startup then applies + # this header. Otherwise it does not apply. + sts { + # The time, in seconds, that the browser should remember that this site + # is only to be accessed using HTTPS. Valid time units are + # "s -> seconds", "m -> minutes", "h - hours". + # Default value is `30 days` in hours. + #max_age = "720h" + + # If enabled the STS rule applies to all of the site's subdomains as well. + # Default value is `false`. + #include_subdomains = true + + # Before enabling preload option, please read about pros and cons from above links. + # Default value is `false`. + #preload = false + } + + # Content-Security-Policy (CSP) + # Provides a rich set of policy directives that enable fairly granular control + # over the resources that a page is allowed. Prevents XSS risks. + # + # Learn more: + # https://content-security-policy.com/ + # https://developers.google.com/web/fundamentals/security/csp/ + # https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#csp + # + # Read above references and define your policy. + # + # Note: It is highly recommended to verify your policy directives in report + # only mode before enabling this header. Since its highly controls how your + # page is rendered. + # + # No default values, you have to provide it. + csp { + # Set of directives to govern the resources load on a page. + #directives = "" + + # By default, violation reports aren't sent. To enable violation reporting, + # you need to specify the report-uri policy directive. + report_uri = "" + + # Puts your `Content-Security-Policy` in report only mode, so that you can verify + # and then set `csp_report_only` value to false. + # Don't forget to set the `report-uri` for validation. + report_only = true + } + + # Public-Key-Pins PKP (aka HPKP) + # This header prevents the Man-in-the-Middle Attack (MITM) with forged certificates. + # + # Learn more: + # https://scotthelme.co.uk/hpkp-http-public-key-pinning/ + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning + # Read above references and define your keys. + # + # Note: + # - HPKP has the potential to lock out users for a long time if used incorrectly! + # The use of backup certificates and/or pinning the CA certificate is recommended. + # - It is highly recommended to verify your policy directives in report only mode + # before enabling this header + # - It is highly recommended to verify your PKP in report only mode before enabling this header. + # No default values, you have to provide it. + pkp { + # The Base64 encoded Subject Public Key Information (SPKI) fingerprint. + # These values gets added as `pin-sha256=; ...`. + #keys = [ + #"X3pGTSOuJeEVw989IJ/cEtXUEmy52zs1TZQrU06KUKg=", + #"MHJYVThihUrJcxW6wcqyOISTXIsInsdj3xK8QrZbHec=" + #] + + # The time that the browser should remember that this site is only to be + # accessed using one of the defined keys. + # Valid time units are "s -> seconds", "m -> minutes", "h - hours". + max_age = "720h" + + # If enabled the PKP keys applies to all of the site's subdomains as well. + # Default value is `false`. + include_subdomains = false + + # By default, Pin validation failure reports aren't sent. To enable Pin validation + # failure reporting, you need to specify the report-uri. + report_uri = "" + + # Puts your `Public-Key-Pins` in report only mode, so that you can verify + # and then set `pkp_report_only` value to false. + # Don't forget to set the `report-uri` for validation. + report_only = true + } + + # X-Permitted-Cross-Domain-Policies + # Restrict Adobe Flash Player's or PDF documents access via crossdomain.xml, + # and this header. + # + # Learn more: + # https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xpcdp + # https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html + # Default value is `master-only`. + #xpcdp = "master-only" + } } diff --git a/aah/app-template/i18n/messages.en.atmpl b/aah/app-template/i18n/messages.en.atmpl index 0da643e..6a82d11 100644 --- a/aah/app-template/i18n/messages.en.atmpl +++ b/aah/app-template/i18n/messages.en.atmpl @@ -5,6 +5,8 @@ # https://docs.aahframework.org/i18n.html ############################################# +# This structure is example purpose +# So choose your suitable structure for your application label { pages { app { diff --git a/aah/app-template/static/css/aah.css b/aah/app-template/static/css/aah.css index 5fc7e1f..fd463e9 100644 --- a/aah/app-template/static/css/aah.css +++ b/aah/app-template/static/css/aah.css @@ -2,10 +2,9 @@ Minimal aah framework application template CSS. Based on your need choose your CSS framework. */ -@import url('https://fonts.googleapis.com/css?family=Open+Sans'); html { - font-family: 'Open Sans', sans-serif; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } diff --git a/aah/app-template/views/common/error_footer.html b/aah/app-template/views/common/error_footer.html new file mode 100644 index 0000000..b605728 --- /dev/null +++ b/aah/app-template/views/common/error_footer.html @@ -0,0 +1,2 @@ + + diff --git a/aah/app-template/views/common/error_header.html b/aah/app-template/views/common/error_header.html new file mode 100644 index 0000000..88e05a8 --- /dev/null +++ b/aah/app-template/views/common/error_header.html @@ -0,0 +1,36 @@ + + + + + + + {{ .Error.Code }} {{ .Error.Message }} + + + + diff --git a/aah/app-template/views/errors/404.html b/aah/app-template/views/errors/404.html new file mode 100644 index 0000000..2e03a27 --- /dev/null +++ b/aah/app-template/views/errors/404.html @@ -0,0 +1,9 @@ +{{ import "error_header.html" . -}} +
{{ with .Error }} +
+
+ {{ .Code }} {{ .Message }} +
+
{{ end }} +
+{{ import "error_footer.html" . -}} diff --git a/aah/app-template/views/errors/500.html b/aah/app-template/views/errors/500.html new file mode 100644 index 0000000..2e03a27 --- /dev/null +++ b/aah/app-template/views/errors/500.html @@ -0,0 +1,9 @@ +{{ import "error_header.html" . -}} +
{{ with .Error }} +
+
+ {{ .Code }} {{ .Message }} +
+
{{ end }} +
+{{ import "error_footer.html" . -}} diff --git a/aah/ast.go b/aah/ast.go index a573b03..3aec816 100644 --- a/aah/ast.go +++ b/aah/ast.go @@ -46,6 +46,10 @@ var ( "uint8": true, "uintptr": true, } + + errInvalidActionParam = errors.New("aah: invalid action parameter") + errInterfaceActionParam = errors.New("aah: 'interface{}' is not supported in the action parameter") + errMapActionParam = errors.New("aah: 'map' is not supported in the action parameter") ) type ( @@ -195,33 +199,18 @@ func (prg *program) Process() { // Each source file for name, file := range pkgInfo.Pkg.Files { pkgInfo.Files = append(pkgInfo.Files, filepath.Base(name)) - var fileImports map[string]string + fileImports := make(map[string]string) - // collecting imports for _, decl := range file.Decls { - if genDecl, ok := decl.(*ast.GenDecl); ok { - if isImportTok(genDecl) { - fileImports = pkgInfo.processImports(genDecl) - } - } - } + // Processing imports + pkgInfo.processImports(decl, fileImports) - // collecting types - for _, decl := range file.Decls { - if genDecl, ok := decl.(*ast.GenDecl); ok { - if isTypeTok(genDecl) { - pkgInfo.processTypes(genDecl, fileImports) - } - } - } + // Processing types + pkgInfo.processTypes(decl, fileImports) - // collecting methods - for _, decl := range file.Decls { - if funcDecl, ok := decl.(*ast.FuncDecl); ok { - findMethods(pkgInfo, prg.RegisteredActions, funcDecl, fileImports) - } + // Processing methods + processMethods(pkgInfo, prg.RegisteredActions, decl, fileImports) } - } } } @@ -272,23 +261,36 @@ func (prg *program) FindTypeByEmbeddedType(qualifiedTypeName string) []*typeInfo func (prg *program) CreateImportPaths(types []*typeInfo) map[string]string { importPaths := map[string]string{} for _, t := range types { - importPath := filepath.ToSlash(t.ImportPath) - if _, found := importPaths[importPath]; !found { - cnt := 0 - pkgAlias := t.PackageName() - - for isPkgAliasExists(importPaths, pkgAlias) { - pkgAlias = fmt.Sprintf("%s%d", t.PackageName(), cnt) - cnt++ + createAlias(t.PackageName(), t.ImportPath, importPaths) + for _, m := range t.Methods { + for _, p := range m.Parameters { + if !p.Type.IsBuiltIn { + createAlias(p.Type.PackageName, p.ImportPath, importPaths) + } } - - importPaths[importPath] = pkgAlias } } return importPaths } +func createAlias(packageName, importPath string, importPaths map[string]string) { + importPath = filepath.ToSlash(importPath) + if _, found := importPaths[importPath]; !found { + cnt := 0 + pkgAlias := packageName + + for isPkgAliasExists(importPaths, pkgAlias) { + pkgAlias = fmt.Sprintf("%s%d", packageName, cnt) + cnt++ + } + + if !ess.IsStrEmpty(pkgAlias) && !ess.IsStrEmpty(importPath) { + importPaths[importPath] = pkgAlias + } + } +} + //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ // PackageInfo methods //___________________________________ @@ -298,55 +300,65 @@ func (p *packageInfo) Name() string { return filepath.Base(p.ImportPath) } -func (p *packageInfo) processTypes(decl *ast.GenDecl, imports map[string]string) { - spec := decl.Specs[0].(*ast.TypeSpec) +func (p *packageInfo) processTypes(decl ast.Decl, imports map[string]string) { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || !isTypeTok(genDecl) || len(genDecl.Specs) == 0 { + return + } + + spec := genDecl.Specs[0].(*ast.TypeSpec) + st, ok := spec.Type.(*ast.StructType) + if !ok { + // Not a struct type + return + } + typeName := spec.Name.Name ty := &typeInfo{ Name: typeName, ImportPath: filepath.ToSlash(p.ImportPath), - Methods: []*methodInfo{}, - EmbeddedTypes: []*typeInfo{}, + Methods: make([]*methodInfo, 0), + EmbeddedTypes: make([]*typeInfo, 0), } - // struct type - st, ok := spec.Type.(*ast.StructType) - if ok { - // finding embedded type(s) and it's package - for _, field := range st.Fields.List { - // If field.Names is set, it's not an embedded type. - if field.Names != nil && len(field.Names) > 0 { - continue - } + for _, field := range st.Fields.List { + // If field.Names is set, it's not an embedded type. + if field.Names != nil && len(field.Names) > 0 { + continue + } - fPkgName, fTypeName := parseStructFieldExpr(field.Type) - if ess.IsStrEmpty(fTypeName) { - continue - } + fPkgName, fTypeName := parseStructFieldExpr(field.Type) + if ess.IsStrEmpty(fTypeName) { + continue + } - // Find the import path for embedded type. If it was referenced without - // a package name, use the current package import path otherwise - // get the import path by package name. - var eTypeImportPath string - if ess.IsStrEmpty(fPkgName) { - eTypeImportPath = ty.ImportPath - } else { - var found bool - if eTypeImportPath, found = imports[fPkgName]; !found { - log.Errorf("AST: Unable to find import path for %s.%s", fPkgName, fTypeName) - continue - } + // Find the import path for embedded type. If it was referenced without + // a package name, use the current package import path otherwise + // get the import path by package name. + var eTypeImportPath string + if ess.IsStrEmpty(fPkgName) { + eTypeImportPath = ty.ImportPath + } else { + var found bool + if eTypeImportPath, found = imports[fPkgName]; !found { + log.Errorf("AST: Unable to find import path for %s.%s", fPkgName, fTypeName) + continue } - - ty.EmbeddedTypes = append(ty.EmbeddedTypes, &typeInfo{Name: fTypeName, ImportPath: eTypeImportPath}) } + + ty.EmbeddedTypes = append(ty.EmbeddedTypes, &typeInfo{Name: fTypeName, ImportPath: eTypeImportPath}) } p.Types[typeName] = ty } -func (p *packageInfo) processImports(decl *ast.GenDecl) map[string]string { - imports := map[string]string{} - for _, dspec := range decl.Specs { +func (p *packageInfo) processImports(decl ast.Decl, imports map[string]string) { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || !isImportTok(genDecl) { + return + } + + for _, dspec := range genDecl.Specs { spec := dspec.(*ast.ImportSpec) var pkgAlias string if spec.Name != nil { @@ -374,8 +386,6 @@ func (p *packageInfo) processImports(decl *ast.GenDecl) map[string]string { imports[pkgAlias] = importPath } - - return imports } //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ @@ -465,12 +475,14 @@ func isPkgAliasExists(importPaths map[string]string, pkgAlias string) bool { return found } -func findMethods(pkg *packageInfo, routeMethods map[string]map[string]uint8, fn *ast.FuncDecl, imports map[string]string) { - // do not process if - - // 1. does not have receiver (only methods) - // 2. method is not exported/public - // 3. method returns result - if fn.Recv == nil || !fn.Name.IsExported() || +func processMethods(pkg *packageInfo, routeMethods map[string]map[string]uint8, decl ast.Decl, imports map[string]string) { + fn, ok := decl.(*ast.FuncDecl) + + // Do not process if these met: + // 1. does not have receiver, it means package function/method + // 2. method is not exported + // 3. method returns result + if !ok || fn.Recv == nil || !fn.Name.IsExported() || fn.Type.Results != nil { return } @@ -498,8 +510,9 @@ func findMethods(pkg *packageInfo, routeMethods map[string]map[string]uint8, fn for _, fieldName := range field.Names { te, err := parseParamFieldExpr(pkg.Name(), field.Type) if err != nil { - log.Errorf("AST: Unable to parse parameter '%s' on action '%s.%s', ignoring it", fieldName.Name, controllerName, actionName) - return + log.Errorf("AST: %s, please fix the parameter '%s' on action '%s.%s'; "+ + "otherwise your action may not work properly", err, fieldName.Name, controllerName, actionName) + continue } var importPath string @@ -519,12 +532,13 @@ func findMethods(pkg *packageInfo, routeMethods map[string]map[string]uint8, fn } if ty := pkg.Types[controllerName]; ty == nil { - log.Errorf("AST: Type[%s] not found in package: %s", controllerName, pkg.ImportPath) + pos := pkg.Fset.Position(decl.Pos()) + filename := stripGoPath(pos.Filename) + log.Errorf("AST: Method '%s' has incorrect struct recevier '%s' on file [%s] at line #%d", + actionName, controllerName, filename, pos.Line) } else { ty.Methods = append(ty.Methods, method) } - - return } func isInterceptorActioName(actionName string) bool { @@ -598,7 +612,11 @@ func parseParamFieldExpr(pkgName string, expr ast.Expr) (*typeExpr, error) { case *ast.Ellipsis: e, err := parseParamFieldExpr(pkgName, t.Elt) return &typeExpr{Expr: "[]" + e.Expr, PackageName: e.PackageName, PackageIndex: e.PackageIndex + uint8(2)}, err + case *ast.InterfaceType: + return nil, errInterfaceActionParam + case *ast.MapType: + return nil, errMapActionParam } - return nil, errors.New("not a valid fieldname/parameter name") + return nil, errInvalidActionParam } diff --git a/aah/new.go b/aah/new.go index 4036cd8..1186bc8 100644 --- a/aah/new.go +++ b/aah/new.go @@ -214,10 +214,10 @@ func createAahApp(appDir, appType string, data map[string]interface{}) error { // config processSection(appDir, appTemplatePath, "config", data) - // i18n - processSection(appDir, appTemplatePath, "i18n", data) - if typeWeb == appType { + // i18n + processSection(appDir, appTemplatePath, "i18n", data) + // static processSection(appDir, appTemplatePath, "static", data) diff --git a/aah/run.go b/aah/run.go index 92d0cf5..a527a6b 100644 --- a/aah/run.go +++ b/aah/run.go @@ -114,7 +114,7 @@ func runAction(c *cli.Context) error { appStartArgs = append(appStartArgs, "-config", configPath) } - envProfile := firstNonEmpty(c.String("e"), c.String("envprofile"), "dev") + envProfile := firstNonEmpty(c.String("e"), c.String("envprofile")) if !ess.IsStrEmpty(envProfile) { appStartArgs = append(appStartArgs, "-profile", envProfile) } @@ -125,11 +125,15 @@ func runAction(c *cli.Context) error { fatalf("aah project file error: %s", err) } + if ess.IsStrEmpty(envProfile) { + envProfile = aah.AppProfile() + } + // Hot-Reload is applicable only to `dev` environment profile. if projectCfg.BoolDefault("hot_reload.enable", true) && envProfile == "dev" { log.Infof("Hot-Reload enabled for environment profile: %s", aah.AppProfile()) - address := firstNonEmpty(aah.AppHTTPAddress(), "localhost") + address := firstNonEmpty(aah.AppHTTPAddress(), "") proxyPort := findAvailablePort() scheme := "http" if aah.AppIsSSLEnabled() { @@ -254,14 +258,15 @@ func (p *proxy) RefreshWatcher() { func (p *proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { if p.ChangedOrError { log.Info("Application file change(s) detected") + p.ChangedOrError = false p.Watcher.Close() p.Stop() if err := p.CompileAndStart(); err != nil { log.Error(err) fmt.Fprintln(w, err.Error()) + p.ChangedOrError = true return } - p.ChangedOrError = false } p.Server.ServeHTTP(w, r) } @@ -391,6 +396,11 @@ func (p *process) Stop() { } } } + } else { + proc, err := os.FindProcess(p.cmd.Process.Pid) + if err == nil { + _ = proc.Kill() + } } } @@ -407,7 +417,7 @@ func (p *process) processWait() <-chan bool { // notifyWriter methods //___________________________________ -func (nw notifyWriter) Write(b []byte) (n int, err error) { +func (nw *notifyWriter) Write(b []byte) (n int, err error) { if nw.notify != nil && bytes.Contains(b, nw.checkBytes) { nw.notify <- true nw.notify = nil diff --git a/aah/version.go b/aah/version.go new file mode 100644 index 0000000..b5974ad --- /dev/null +++ b/aah/version.go @@ -0,0 +1,8 @@ +// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm) +// go-aah/tools/aah source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package main + +// Version no. of aah framework CLI tool +const Version = "0.8"