├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── SHOULDERS.MD ├── cmd ├── build.go ├── dev.go ├── init.go ├── new.go └── root.go ├── codegen ├── clientmain.go ├── compnav.go ├── globalcss.go ├── indextemplate.go ├── makefile.go ├── routes.go ├── server.go ├── todoclient.go ├── transpiler │ ├── component.go │ ├── elem.go │ └── props.go └── wasmexec.go ├── component ├── component.go ├── component_test.go ├── process.go ├── template.go └── transpiler.go ├── examples ├── .gitignore ├── Makefile ├── app │ ├── index.html │ └── wasm_exec.js ├── assets │ ├── favicon.png │ ├── global.css │ ├── great-success.png │ ├── manifest.json │ ├── svelte-logo-192.png │ └── svelte-logo-512.png ├── client │ └── main.go ├── components │ ├── App.html │ ├── Nav.html │ ├── Todo.html │ ├── app.go │ ├── app_generated.go │ ├── markdown.go │ ├── markdown.html │ ├── markdown_generated.go │ ├── nav.go │ ├── nav_generated.go │ ├── router.go │ ├── todo.go │ └── todo_generated.go ├── models │ ├── client_generated.go │ ├── server_generated.go │ ├── todo.go │ ├── todo_client.go │ ├── todo_server.go │ └── todo_types.go ├── routes │ ├── _error.html │ ├── about.go │ ├── about.gox │ ├── about.html │ ├── about_generated.go │ ├── blog.[slug].html │ ├── blogslug_generated.go │ ├── error_generated.go │ ├── index.go │ ├── index.html │ ├── index_generated.go │ ├── todo.[id].html │ ├── todoid_generated.go │ ├── todos.html │ └── todos_generated.go └── server │ └── main.go ├── files ├── file_types.go └── files.go ├── go.mod ├── go.sum ├── main.go ├── model ├── client.go ├── model.go ├── process.go ├── server.go └── template.go └── route └── process.go /.gitignore: -------------------------------------------------------------------------------- 1 | golden/server/server 2 | /golden/server/server 3 | .idea 4 | .vs 5 | .vscode 6 | factor 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | go test ./... 4 | 5 | .PHONY: test 6 | install: 7 | go install . 8 | 9 | build: 10 | go build -o factor . 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Factor 2 | 3 | Factor is a tool for building SPA web applications in Go using Web Assembly (WASM). 4 | 5 | Factor is heavily inspired by [Svelte and Sapper](https://sapper.svelte.technology/guide#getting-started), but doesn't attempt to be a straight port, but rather take those ideas and present them in a way natural for a Go developer. 6 | 7 | ## Application Structure 8 | 9 | ``` 10 | ├ app 11 | │ ├ App.html # Main/initial view 12 | │ └ template.html # application template 13 | ├ assets 14 | │ ├ # your files here 15 | ├ components 16 | │ ├ # your files here 17 | ├ routes 18 | │ ├ # your routes here - these are pages. 19 | │ ├ _error.html 20 | │ └ index.html 21 | ``` 22 | 23 | ## Requirements 24 | 25 | WASM support is only available in Go 1.11, which is not yet officially released, so you'll need to download a pre-release: 26 | 27 | ```bash 28 | $ go get golang.org/x/build/version/go1.11beta1 29 | ``` 30 | 31 | Then, use `go1.11beta1` when building. 32 | 33 | ## Development 34 | 35 | ```console 36 | $ cd factor 37 | $ go1.11beta1 install 38 | ``` 39 | 40 | Make sure you have `$GOPATH/bin` in your executable path (i.e. `$PATH`) and your `factor` CLI is ready to go, hot off the presses. 41 | 42 | ## Notes 43 | 44 | * Factor enforces very strict HTML rules. `
` will break. Close all self-closing tags properly: `
`. Failing to do this will result in an error like `element input closed by form` in your generated Go files. 45 | -------------------------------------------------------------------------------- /SHOULDERS.MD: -------------------------------------------------------------------------------- 1 | # On the Shoulders of Giants 2 | 3 | This project wouldn't be possible without the hard work of MANY other people and projects. In no particular order: 4 | 5 | * [GopherJS](https://gopherjs.org) 6 | * [Vecty](https://github.com/gopherjs/vecty) 7 | * [gopherwasm](https://github.com/gopherjs/gopherwasm) 8 | * [dom](https://honnef.co/go/js/dom) 9 | * [go-humble](https://github.com/go-humble/router) 10 | * [html2vecty](https://jsgo.io/dave/html2vecty) 11 | * [jennifer](https://github.com/dave/jennifer) 12 | * [gorilla](http://www.gorillatoolkit.org/) 13 | * [dom](https://github.com/yml/go-js-dom) 14 | 15 | To make this project work, we had to fork many of these fine projects to support Web Assembly. Where that was necessary, the forks are at [the gowasm org on Github](https://github.com/gowasm). 16 | 17 | We're extremely grateful to Richard Musiol, Dave Brophy, Dmitri Shuralyov, Hajime Hoshi, Paul Jolly, and Stephen Gutekanst from the GopherJS community for all the hard work behind the GopherJS family of projects. -------------------------------------------------------------------------------- /cmd/build.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "log" 19 | "os" 20 | "path/filepath" 21 | 22 | "github.com/factorapp/factor/component" 23 | "github.com/factorapp/factor/model" 24 | "github.com/factorapp/factor/route" 25 | "github.com/spf13/cobra" 26 | ) 27 | 28 | // buildCmd represents the build command 29 | var buildCmd = &cobra.Command{ 30 | Use: "build", 31 | Short: "A brief description of your command", 32 | Long: `A longer description that spans multiple lines and likely contains examples 33 | and usage of using your command. For example: 34 | 35 | Cobra is a CLI library for Go that empowers applications. 36 | This application is a tool to generate the needed files 37 | to quickly create a Cobra application.`, 38 | Run: func(cmd *cobra.Command, args []string) { 39 | err := build() 40 | if err != nil { 41 | log.Println(err) 42 | return 43 | } 44 | }, 45 | } 46 | 47 | func build() error { 48 | log.Println("Building Application") 49 | cwd, err := os.Getwd() 50 | if err != nil { 51 | return err 52 | } 53 | 54 | dir := filepath.Join(cwd, "components") 55 | // err = component.ProcessAll(dir, "components") 56 | err = component.ProcessAll(dir) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | dir = filepath.Join(cwd, "routes") 62 | // err = component.ProcessAll(dir, "routes") 63 | err = route.ProcessAll(dir) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | dir = filepath.Join(cwd, "models") 69 | err = model.ProcessAll(dir) 70 | if err != nil { 71 | return err 72 | } 73 | return nil 74 | } 75 | func init() { 76 | rootCmd.AddCommand(buildCmd) 77 | 78 | // Here you will define your flags and configuration settings. 79 | 80 | // Cobra supports Persistent Flags which will work for this command 81 | // and all subcommands, e.g.: 82 | // buildCmd.PersistentFlags().String("foo", "", "A help for foo") 83 | 84 | // Cobra supports local flags which will only run when this command 85 | // is called directly, e.g.: 86 | // buildCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 87 | } 88 | -------------------------------------------------------------------------------- /cmd/dev.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "log" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | // devCmd represents the dev command 25 | var devCmd = &cobra.Command{ 26 | Use: "dev", 27 | Short: "A brief description of your command", 28 | Long: `A longer description that spans multiple lines and likely contains examples 29 | and usage of using your command. For example: 30 | 31 | Cobra is a CLI library for Go that empowers applications. 32 | This application is a tool to generate the needed files 33 | to quickly create a Cobra application.`, 34 | Run: func(cmd *cobra.Command, args []string) { 35 | cwd, err := os.Getwd() 36 | if err != nil { 37 | log.Println(err) 38 | return 39 | } 40 | log.Println("Factor Build in root:", cwd) 41 | 42 | // build first 43 | err = build() 44 | if err != nil { 45 | log.Println(err) 46 | return 47 | } 48 | 49 | }, 50 | } 51 | 52 | func init() { 53 | rootCmd.AddCommand(devCmd) 54 | 55 | // Here you will define your flags and configuration settings. 56 | 57 | // Cobra supports Persistent Flags which will work for this command 58 | // and all subcommands, e.g.: 59 | // devCmd.PersistentFlags().String("foo", "", "A help for foo") 60 | 61 | // Cobra supports local flags which will only run when this command 62 | // is called directly, e.g.: 63 | // devCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 64 | } 65 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "log" 21 | "os" 22 | "path/filepath" 23 | "text/template" 24 | 25 | "github.com/factorapp/factor/codegen" 26 | "github.com/gobuffalo/envy" 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | var appName string 31 | var directories = []string{ 32 | "app", 33 | "assets", 34 | "client", 35 | "components", 36 | "models", 37 | "routes", 38 | "server", 39 | } 40 | 41 | // initCmd represents the init command 42 | var initCmd = &cobra.Command{ 43 | Use: "init", 44 | Short: "initialize a new factor application", 45 | Long: `A longer description that spans multiple lines and likely contains examples 46 | and usage of using your command. For example: 47 | 48 | Cobra is a CLI library for Go that empowers applications. 49 | This application is a tool to generate the needed files 50 | to quickly create a Cobra application.`, 51 | Args: func(cmd *cobra.Command, args []string) error { 52 | if len(args) < 1 { 53 | return errors.New("requires at least one arg") 54 | } 55 | appName = args[0] 56 | return nil 57 | }, 58 | Run: func(cmd *cobra.Command, args []string) { 59 | cwd, err := os.Getwd() 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | // make directories under appName 65 | err = makeDirectories(cwd) 66 | if err != nil { 67 | fmt.Println("error making directories:", err) 68 | return 69 | } 70 | appPkg := filepath.Join(envy.CurrentPackage(), appName) 71 | if appPkg == "" { 72 | fmt.Println("couldn't get the current package for the app") 73 | return 74 | } 75 | fmt.Println("app package", appPkg) 76 | // put the new files there 77 | populateApp(cwd, appPkg) 78 | }, 79 | } 80 | 81 | // appPath is the location of the app under the GOPATH 82 | func populateApp(cwd, appPkg string) error { 83 | filename := "index.html" 84 | filePath := filepath.Join(cwd, appName, "app", filename) 85 | err := writeTemplate(filePath, codegen.IndexHTML) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | filename = "wasm_exec.js" 91 | filePath = filepath.Join(cwd, appName, "app", filename) 92 | err = writeTemplate(filePath, codegen.WasmJS) 93 | if err != nil { 94 | return err 95 | } 96 | filename = "global.css" 97 | filePath = filepath.Join(cwd, appName, "assets", filename) 98 | err = writeTemplate(filePath, codegen.GlobalCSS) 99 | if err != nil { 100 | return err 101 | } 102 | filename = "main.go" 103 | filePath = filepath.Join(cwd, appName, "client", filename) 104 | clientGoMain, err := codegen.ClientGoMain(appPkg) 105 | if err != nil { 106 | return err 107 | } 108 | err = writeTemplate(filePath, clientGoMain) 109 | if err != nil { 110 | return err 111 | } 112 | filename = "Nav.html" 113 | filePath = filepath.Join(cwd, appName, "components", filename) 114 | err = writeTemplate(filePath, codegen.NavComponentHTML) 115 | if err != nil { 116 | return err 117 | } 118 | filename = "nav.go" 119 | filePath = filepath.Join(cwd, appName, "components", filename) 120 | err = writeTemplate(filePath, codegen.NavComponentGo) 121 | if err != nil { 122 | return err 123 | } 124 | filename = "Index.html" 125 | filePath = filepath.Join(cwd, appName, "routes", filename) 126 | err = writeTemplate(filePath, codegen.RoutesHTML) 127 | if err != nil { 128 | return err 129 | } 130 | filename = "index.go" 131 | filePath = filepath.Join(cwd, appName, "routes", filename) 132 | err = writeTemplate(filePath, codegen.RoutesGo) 133 | if err != nil { 134 | return err 135 | } 136 | filename = "todo.go" 137 | filePath = filepath.Join(cwd, appName, "models", filename) 138 | err = writeTemplate(filePath, codegen.TodoClient) 139 | if err != nil { 140 | return err 141 | } 142 | filename = "main.go" 143 | filePath = filepath.Join(cwd, appName, "server", filename) 144 | serverGoMain, err := codegen.ServerGoMain(appPkg) 145 | if err != nil { 146 | return err 147 | } 148 | err = writeTemplate(filePath, serverGoMain) 149 | if err != nil { 150 | return err 151 | } 152 | filename = "Makefile" 153 | filePath = filepath.Join(cwd, appName, filename) 154 | err = writeTemplate(filePath, codegen.Makefile) 155 | if err != nil { 156 | return err 157 | } 158 | return err 159 | } 160 | func writeTemplate(filePath string, templateName string) error { 161 | gofile, err := os.Create(filePath) 162 | if err != nil { 163 | log.Println("ERROR", err) 164 | return err 165 | } 166 | defer gofile.Close() 167 | tpl := template.Must(template.New("component").Parse(templateName)) 168 | // TODO - get the gopath of cwd and pass it in a context below to this call 169 | err = tpl.Execute(gofile, nil) 170 | if err != nil { 171 | return err 172 | } 173 | return nil 174 | } 175 | func makeDirectories(cwd string) error { 176 | for _, dir := range directories { 177 | err := os.MkdirAll(filepath.Join(cwd, appName, dir), 0755) 178 | if err != nil { 179 | return err 180 | } 181 | } 182 | return nil 183 | } 184 | func init() { 185 | rootCmd.AddCommand(initCmd) 186 | 187 | // Here you will define your flags and configuration settings. 188 | 189 | // Cobra supports Persistent Flags which will work for this command 190 | // and all subcommands, e.g.: 191 | // initCmd.Flags().StringP("name", "n", "", "Name of your new application") 192 | 193 | // Cobra supports local flags which will only run when this command 194 | // is called directly, e.g.: 195 | // initCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 196 | } 197 | -------------------------------------------------------------------------------- /cmd/new.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // newCmd represents the new command 24 | var newCmd = &cobra.Command{ 25 | Use: "new", 26 | Short: "A brief description of your command", 27 | Long: `A longer description that spans multiple lines and likely contains examples 28 | and usage of using your command. For example: 29 | 30 | Cobra is a CLI library for Go that empowers applications. 31 | This application is a tool to generate the needed files 32 | to quickly create a Cobra application.`, 33 | Run: func(cmd *cobra.Command, args []string) { 34 | fmt.Println("new called") 35 | }, 36 | } 37 | 38 | func init() { 39 | rootCmd.AddCommand(newCmd) 40 | 41 | // Here you will define your flags and configuration settings. 42 | 43 | // Cobra supports Persistent Flags which will work for this command 44 | // and all subcommands, e.g.: 45 | // newCmd.PersistentFlags().String("foo", "", "A help for foo") 46 | 47 | // Cobra supports local flags which will only run when this command 48 | // is called directly, e.g.: 49 | // newCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 50 | } 51 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | homedir "github.com/mitchellh/go-homedir" 22 | "github.com/spf13/cobra" 23 | "github.com/spf13/viper" 24 | ) 25 | 26 | var cfgFile string 27 | 28 | // rootCmd represents the base command when called without any subcommands 29 | var rootCmd = &cobra.Command{ 30 | Use: "factor", 31 | Short: "A brief description of your application", 32 | Long: `A longer description that spans multiple lines and likely contains 33 | examples and usage of using your application. For example: 34 | 35 | Cobra is a CLI library for Go that empowers applications. 36 | This application is a tool to generate the needed files 37 | to quickly create a Cobra application.`, 38 | // Uncomment the following line if your bare application 39 | // has an action associated with it: 40 | // Run: func(cmd *cobra.Command, args []string) { }, 41 | } 42 | 43 | // Execute adds all child commands to the root command and sets flags appropriately. 44 | // This is called by main.main(). It only needs to happen once to the rootCmd. 45 | func Execute() { 46 | if err := rootCmd.Execute(); err != nil { 47 | fmt.Println(err) 48 | os.Exit(1) 49 | } 50 | } 51 | 52 | func init() { 53 | cobra.OnInitialize(initConfig) 54 | 55 | // Here you will define your flags and configuration settings. 56 | // Cobra supports persistent flags, which, if defined here, 57 | // will be global for your application. 58 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.factor.yaml)") 59 | 60 | // Cobra also supports local flags, which will only run 61 | // when this action is called directly. 62 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 63 | } 64 | 65 | // initConfig reads in config file and ENV variables if set. 66 | func initConfig() { 67 | if cfgFile != "" { 68 | // Use config file from the flag. 69 | viper.SetConfigFile(cfgFile) 70 | } else { 71 | // Find home directory. 72 | home, err := homedir.Dir() 73 | if err != nil { 74 | fmt.Println(err) 75 | os.Exit(1) 76 | } 77 | 78 | // Search config in home directory with name ".factor" (without extension). 79 | viper.AddConfigPath(home) 80 | viper.SetConfigName(".factor") 81 | } 82 | 83 | viper.AutomaticEnv() // read in environment variables that match 84 | 85 | // If a config file is found, read it in. 86 | if err := viper.ReadInConfig(); err == nil { 87 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /codegen/clientmain.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | ) 7 | 8 | // ClientGoMain returns the Go code for the main function for the wasm app 9 | func ClientGoMain(appPath string) (string, error) { 10 | b := new(bytes.Buffer) 11 | data := map[string]string{"AppPath": appPath} 12 | if err := clMainTemplate.Execute(b, data); err != nil { 13 | return "", err 14 | } 15 | return string(b.Bytes()), nil 16 | } 17 | 18 | var clMainTemplate = template.Must(template.New("cl").Parse(clMainTemplateStr)) 19 | 20 | const clMainTemplateStr = `// build +js,wasm 21 | package main 22 | 23 | import ( 24 | "{{.AppPath}}/routes" 25 | "github.com/gowasm/router" 26 | "github.com/gowasm/vecty" 27 | ) 28 | 29 | func main() { 30 | c := make(chan struct{}, 0) 31 | // Create a new Router object 32 | r := router.New() 33 | //r.ShouldInterceptLinks = true 34 | // Use HandleFunc to add routes. 35 | r.HandleFunc("/", func(context *router.Context) { 36 | 37 | // The handler for this route simply grabs the name parameter 38 | // from the map of params and says hello. 39 | vecty.SetTitle("Factor: Home") 40 | vecty.RenderBody(&routes.Index{}) 41 | }) 42 | // You must call Start in order to start listening for changes 43 | // in the url and trigger the appropriate handler function. 44 | r.Start() 45 | <-c 46 | } 47 | ` 48 | -------------------------------------------------------------------------------- /codegen/compnav.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | // NavComponentGo returns the Go code for the Nav component 4 | // 5 | // TODO: make this flexible to generate arbitrary components 6 | // see https://github.com/factorapp/factor/issues/24 7 | const NavComponentGo = `package components 8 | 9 | import ( 10 | "github.com/gowasm/vecty" 11 | ) 12 | 13 | type Nav struct { 14 | vecty.Core 15 | MyProp string ` + "`" + "vecty:" + `"Prop"` + "`" + ` 16 | CurrentPath string 17 | } 18 | ` 19 | 20 | // NavComponentHTML returns the HTML code for the Nav component 21 | const NavComponentHTML = `` 53 | -------------------------------------------------------------------------------- /codegen/globalcss.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | // GlobalCSS returns the global CSS code 4 | const GlobalCSS = ` 5 | body { 6 | padding-top: 5rem; 7 | } 8 | .starter-template { 9 | padding: 3rem 1.5rem; 10 | text-align: center; 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /codegen/indextemplate.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | // IndexHTML returns the HTML for the initial index page that serves 4 | // the wasm "shell" 5 | const IndexHTML = ` 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Factor: Full Stack Go 16 | 17 | 18 | 19 |
...
20 | 21 | 22 | 23 | 24 | 45 | 46 | 47 | 48 | ` 49 | -------------------------------------------------------------------------------- /codegen/makefile.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | // Makefile returns the Makefile code for the new app 4 | // 5 | // TODO: this won't be necessary after https://github.com/factorapp/factor/issues/11 6 | const Makefile = `wasm: 7 | GOARCH=wasm GOOS=js ~/gowasm/bin/go build -o example.wasm ./client 8 | mv example.wasm ./app/ 9 | 10 | run: wasm 11 | cd server && go build && cd .. && ./server/server` 12 | -------------------------------------------------------------------------------- /codegen/routes.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | // RoutesHTML returns the HTML code for the index route 4 | const RoutesHTML = ` 5 | 6 |
7 | 8 |
9 |

Bootstrap starter template

10 |

Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.

11 |
12 | 13 |
14 | ` 15 | 16 | // RoutesGo returns the Go code for the index route 17 | const RoutesGo = `package routes 18 | 19 | import ( 20 | "fmt" 21 | "strconv" 22 | 23 | "github.com/gowasm/vecty" 24 | ) 25 | 26 | type Index struct { 27 | vecty.Core 28 | CountText string 29 | count int 30 | } 31 | 32 | func (i *Index) OnClick(e *vecty.Event) { 33 | fmt.Println("Someone clicked on me", e.Target) 34 | i.count++ 35 | i.CountText = "Click Count: " + strconv.Itoa(i.count) 36 | 37 | vecty.Rerender(i) 38 | } 39 | ` 40 | -------------------------------------------------------------------------------- /codegen/server.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | ) 7 | 8 | // ServerGoMain returns the main.go for the server 9 | func ServerGoMain(appPath string) (string, error) { 10 | b := new(bytes.Buffer) 11 | data := map[string]string{ 12 | "AppPath": appPath, 13 | } 14 | if err := serverGoTemplate.Execute(b, data); err != nil { 15 | return "", err 16 | } 17 | return string(b.Bytes()), nil 18 | 19 | } 20 | 21 | var serverGoTemplate = template.Must(template.New("servergo").Parse(serverGoTemplateStr)) 22 | 23 | var serverGoTemplateStr = `// build !js,wasm 24 | package main 25 | 26 | import ( 27 | "log" 28 | "net/http" 29 | 30 | "github.com/gorilla/rpc/v2" 31 | "github.com/gorilla/rpc/v2/json" 32 | 33 | "{{.AppPath}}/models" 34 | ) 35 | 36 | func wasmHandler(w http.ResponseWriter, r *http.Request) { 37 | w.Header().Set("Content-Type", "application/wasm") 38 | http.ServeFile(w, r, "./app/example.wasm") 39 | } 40 | func main() { 41 | s := rpc.NewServer() 42 | s.RegisterCodec(json.NewCodec(), "application/json") 43 | s.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8") 44 | tds := new(models.TodoServer) 45 | s.RegisterService(tds, "TodoServer") 46 | http.HandleFunc("/app/example.wasm", wasmHandler) 47 | http.Handle("/rpc", s) 48 | /* cwd, err := os.Getcwd() 49 | if err != nil { 50 | panic(err) 51 | } 52 | app := filepath.Join(cwd, "app") 53 | */ 54 | 55 | http.HandleFunc("/wasm_exec.js", jsHandler) 56 | 57 | http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets")))) 58 | http.HandleFunc("/", indexHandler) 59 | log.Fatal(http.ListenAndServe(":3000", nil)) 60 | } 61 | func jsHandler(w http.ResponseWriter, r *http.Request) { 62 | http.ServeFile(w, r, "./app/wasm_exec.js") 63 | } 64 | 65 | func indexHandler(w http.ResponseWriter, r *http.Request) { 66 | http.ServeFile(w, r, "./app/index.html") 67 | } 68 | ` 69 | -------------------------------------------------------------------------------- /codegen/todoclient.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | // RoutesHTML returns the HTML code for the index route 4 | const TodoClient = `package models 5 | 6 | import ( 7 | "math/rand" 8 | "time" 9 | 10 | "github.com/satori/go.uuid" 11 | ) 12 | 13 | func init() { 14 | rand.Seed(time.Now().UnixNano()) 15 | } 16 | 17 | // Todo is a model 18 | type Todo struct { 19 | ID uuid.UUID 20 | Name string 21 | Description string 22 | Permalink string 23 | } 24 | 25 | func (t Todo) GetAge() int { 26 | return rand.Int() 27 | } 28 | ` 29 | -------------------------------------------------------------------------------- /codegen/transpiler/component.go: -------------------------------------------------------------------------------- 1 | package transpiler 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | 7 | "github.com/dave/jennifer/jen" 8 | ) 9 | 10 | func ComponentElement(appPackage, componentName string, token *xml.StartElement) *jen.Statement { 11 | var component string 12 | var qual bool 13 | vectyPackage := appPackage + "/components" 14 | // vectyFunction = component 15 | // vectyParamater = tag 16 | qual = true 17 | component = strings.TrimLeft(token.Name.Local, "components.") 18 | // I'm not sure what qual was intended to mean (it's always true now) but it looks like perhaps you're 19 | // trying to avoid using Qual if the package path == local path? If so, no need! Qual handles this 20 | // gracefully... See: https://github.com/dave/jennifer#qual 21 | if qual { 22 | baseDecl := jen.Op("&").Qual(vectyPackage, component).Values(jen.DictFunc(func(d jen.Dict) { 23 | for _, v := range token.Attr { 24 | d[jen.Id(v.Name.Local)] = jen.Lit(v.Value) 25 | } 26 | })) 27 | return baseDecl 28 | } 29 | baseDecl := jen.Op("&").Id(component).Values(jen.DictFunc(func(d jen.Dict) { 30 | for _, v := range token.Attr { 31 | d[jen.Id(v.Name.Local)] = jen.Lit(v.Value) 32 | } 33 | })) 34 | return baseDecl 35 | 36 | } 37 | -------------------------------------------------------------------------------- /codegen/transpiler/elem.go: -------------------------------------------------------------------------------- 1 | package transpiler 2 | 3 | // ElemNames is a map of the elements in a template to their Vecty names 4 | var ElemNames = map[string]string{ 5 | "a": "Anchor", 6 | "abbr": "Abbreviation", 7 | "address": "Address", 8 | "area": "Area", 9 | "article": "Article", 10 | "aside": "Aside", 11 | "audio": "Audio", 12 | "b": "Bold", 13 | "base": "Base", 14 | "bdi": "BidirectionalIsolation", 15 | "bdo": "BidirectionalOverride", 16 | "blockquote": "BlockQuote", 17 | "body": "Body", 18 | "br": "Break", 19 | "button": "Button", 20 | "canvas": "Canvas", 21 | "caption": "Caption", 22 | "cite": "Citation", 23 | "code": "Code", 24 | "col": "Column", 25 | "colgroup": "ColumnGroup", 26 | "data": "Data", 27 | "datalist": "DataList", 28 | "dd": "Description", 29 | "del": "DeletedText", 30 | "details": "Details", 31 | "dfn": "Definition", 32 | "dialog": "Dialog", 33 | "div": "Div", 34 | "dl": "DescriptionList", 35 | "dt": "DefinitionTerm", 36 | "em": "Emphasis", 37 | "embed": "Embed", 38 | "fieldset": "FieldSet", 39 | "figcaption": "FigureCaption", 40 | "figure": "Figure", 41 | "footer": "Footer", 42 | "form": "Form", 43 | "h1": "Heading1", 44 | "h2": "Heading2", 45 | "h3": "Heading3", 46 | "h4": "Heading4", 47 | "h5": "Heading5", 48 | "h6": "Heading6", 49 | "header": "Header", 50 | "hgroup": "HeadingsGroup", 51 | "hr": "HorizontalRule", 52 | "i": "Italic", 53 | "iframe": "InlineFrame", 54 | "img": "Image", 55 | "input": "Input", 56 | "ins": "InsertedText", 57 | "kbd": "KeyboardInput", 58 | "label": "Label", 59 | "legend": "Legend", 60 | "li": "ListItem", 61 | "link": "Link", 62 | "main": "Main", 63 | "map": "Map", 64 | "mark": "Mark", 65 | "meta": "Meta", 66 | "meter": "Meter", 67 | "nav": "Navigation", 68 | "noscript": "NoScript", 69 | "object": "Object", 70 | "ol": "OrderedList", 71 | "optgroup": "OptionsGroup", 72 | "option": "Option", 73 | "output": "Output", 74 | "p": "Paragraph", 75 | "param": "Parameter", 76 | "picture": "Picture", 77 | "pre": "Preformatted", 78 | "progress": "Progress", 79 | "q": "Quote", 80 | "rp": "RubyParenthesis", 81 | "rt": "RubyText", 82 | "rtc": "RubyTextContainer", 83 | "ruby": "Ruby", 84 | "s": "Strikethrough", 85 | "samp": "Sample", 86 | "script": "Script", 87 | "section": "Section", 88 | "select": "Select", 89 | "slot": "Slot", 90 | "small": "Small", 91 | "source": "Source", 92 | "span": "Span", 93 | "strong": "Strong", 94 | "style": "Style", 95 | "sub": "Subscript", 96 | "summary": "Summary", 97 | "sup": "Superscript", 98 | "table": "Table", 99 | "tbody": "TableBody", 100 | "td": "TableData", 101 | "template": "Template", 102 | "textarea": "TextArea", 103 | "tfoot": "TableFoot", 104 | "th": "TableHeader", 105 | "thead": "TableHead", 106 | "time": "Time", 107 | "title": "Title", 108 | "tr": "TableRow", 109 | "track": "Track", 110 | "u": "Underline", 111 | "ul": "UnorderedList", 112 | "var": "Variable", 113 | "video": "Video", 114 | "wbr": "WordBreakOpportunity", 115 | } 116 | -------------------------------------------------------------------------------- /codegen/transpiler/props.go: -------------------------------------------------------------------------------- 1 | package transpiler 2 | 3 | // BoolProps is a map of the boolean properties in a template to their 4 | // vecty names 5 | var BoolProps = map[string]string{ 6 | "autofocus": "Autofocus", 7 | "checked": "Checked", 8 | } 9 | 10 | // StringProps is a map of the string properties in a template to their 11 | // vecty names 12 | var StringProps = map[string]string{ 13 | "for": "For", 14 | "href": "Href", 15 | "id": "ID", 16 | "placeholder": "Placeholder", 17 | "src": "Src", 18 | "value": "Value", 19 | } 20 | 21 | // TypeProps is a map of the types in a template to their vecty names 22 | var TypeProps = map[string]string{ 23 | "button": "TypeButton", 24 | "checkbox": "TypeCheckbox", 25 | "color": "TypeColor", 26 | "date": "TypeDate", 27 | "datetime": "TypeDatetime", 28 | "datetime-local": "TypeDatetimeLocal", 29 | "email": "TypeEmail", 30 | "file": "TypeFile", 31 | "hidden": "TypeHidden", 32 | "image": "TypeImage", 33 | "month": "TypeMonth", 34 | "number": "TypeNumber", 35 | "password": "TypePassword", 36 | "radio": "TypeRadio", 37 | "range": "TypeRange", 38 | "min": "TypeMin", 39 | "max": "TypeMax", 40 | "value": "TypeValue", 41 | "step": "TypeStep", 42 | "reset": "TypeReset", 43 | "search": "TypeSearch", 44 | "submit": "TypeSubmit", 45 | "tel": "TypeTel", 46 | "text": "TypeText", 47 | "time": "TypeTime", 48 | "url": "TypeUrl", 49 | "week": "TypeWeek", 50 | } 51 | -------------------------------------------------------------------------------- /codegen/wasmexec.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | // WasmJS returns the javascript to execute the wasm binary 4 | const WasmJS = `// Copyright 2018 The Go Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | (() => { 9 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API). 10 | const isNodeJS = typeof process !== "undefined"; 11 | if (isNodeJS) { 12 | global.require = require; 13 | global.fs = require("fs"); 14 | 15 | const nodeCrypto = require("crypto"); 16 | global.crypto = { 17 | getRandomValues(b) { 18 | nodeCrypto.randomFillSync(b); 19 | }, 20 | }; 21 | 22 | global.performance = { 23 | now() { 24 | const [sec, nsec] = process.hrtime(); 25 | return sec * 1000 + nsec / 1000000; 26 | }, 27 | }; 28 | 29 | const util = require("util"); 30 | global.TextEncoder = util.TextEncoder; 31 | global.TextDecoder = util.TextDecoder; 32 | } else { 33 | window.global = window; 34 | 35 | let outputBuf = ""; 36 | global.fs = { 37 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_NONBLOCK: -1, O_SYNC: -1 }, // unused 38 | writeSync(fd, buf) { 39 | outputBuf += decoder.decode(buf); 40 | const nl = outputBuf.lastIndexOf("\n"); 41 | if (nl != -1) { 42 | console.log(outputBuf.substr(0, nl)); 43 | outputBuf = outputBuf.substr(nl + 1); 44 | } 45 | return buf.length; 46 | }, 47 | openSync(path, flags, mode) { 48 | const err = new Error("not implemented"); 49 | err.code = "ENOSYS"; 50 | throw err; 51 | }, 52 | }; 53 | } 54 | 55 | const encoder = new TextEncoder("utf-8"); 56 | const decoder = new TextDecoder("utf-8"); 57 | 58 | global.Go = class { 59 | constructor() { 60 | this.argv = ["js"]; 61 | this.env = {}; 62 | this.exit = (code) => { 63 | if (code !== 0) { 64 | console.warn("exit code:", code); 65 | } 66 | }; 67 | this._callbackTimeouts = new Map(); 68 | this._nextCallbackTimeoutID = 1; 69 | 70 | const mem = () => { 71 | // The buffer may change when requesting more memory. 72 | return new DataView(this._inst.exports.mem.buffer); 73 | } 74 | 75 | const setInt64 = (addr, v) => { 76 | mem().setUint32(addr + 0, v, true); 77 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); 78 | } 79 | 80 | const getInt64 = (addr) => { 81 | const low = mem().getUint32(addr + 0, true); 82 | const high = mem().getInt32(addr + 4, true); 83 | return low + high * 4294967296; 84 | } 85 | 86 | const loadValue = (addr) => { 87 | const f = mem().getFloat64(addr, true); 88 | if (!isNaN(f)) { 89 | return f; 90 | } 91 | 92 | const id = mem().getUint32(addr, true); 93 | return this._values[id]; 94 | } 95 | 96 | const storeValue = (addr, v) => { 97 | if (typeof v === "number") { 98 | if (isNaN(v)) { 99 | mem().setUint32(addr + 4, 0x7FF80000, true); // NaN 100 | mem().setUint32(addr, 0, true); 101 | return; 102 | } 103 | mem().setFloat64(addr, v, true); 104 | return; 105 | } 106 | 107 | mem().setUint32(addr + 4, 0x7FF80000, true); // NaN 108 | 109 | switch (v) { 110 | case undefined: 111 | mem().setUint32(addr, 1, true); 112 | return; 113 | case null: 114 | mem().setUint32(addr, 2, true); 115 | return; 116 | case true: 117 | mem().setUint32(addr, 3, true); 118 | return; 119 | case false: 120 | mem().setUint32(addr, 4, true); 121 | return; 122 | } 123 | 124 | if (typeof v === "string") { 125 | let ref = this._stringRefs.get(v); 126 | if (ref === undefined) { 127 | ref = this._values.length; 128 | this._values.push(v); 129 | this._stringRefs.set(v, ref); 130 | } 131 | mem().setUint32(addr, ref, true); 132 | return; 133 | } 134 | 135 | if (typeof v === "symbol") { 136 | let ref = this._symbolRefs.get(v); 137 | if (ref === undefined) { 138 | ref = this._values.length; 139 | this._values.push(v); 140 | this._symbolRefs.set(v, ref); 141 | } 142 | mem().setUint32(addr, ref, true); 143 | return; 144 | } 145 | 146 | let ref = v[this._refProp]; 147 | if (ref === undefined) { 148 | ref = this._values.length; 149 | this._values.push(v); 150 | v[this._refProp] = ref; 151 | } 152 | mem().setUint32(addr, ref, true); 153 | } 154 | 155 | const loadSlice = (addr) => { 156 | const array = getInt64(addr + 0); 157 | const len = getInt64(addr + 8); 158 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 159 | } 160 | 161 | const loadSliceOfValues = (addr) => { 162 | const array = getInt64(addr + 0); 163 | const len = getInt64(addr + 8); 164 | const a = new Array(len); 165 | for (let i = 0; i < len; i++) { 166 | a[i] = loadValue(array + i * 8); 167 | } 168 | return a; 169 | } 170 | 171 | const loadString = (addr) => { 172 | const saddr = getInt64(addr + 0); 173 | const len = getInt64(addr + 8); 174 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 175 | } 176 | 177 | const timeOrigin = Date.now() - performance.now(); 178 | this.importObject = { 179 | go: { 180 | // func wasmExit(code int32) 181 | "runtime.wasmExit": (sp) => { 182 | this.exited = true; 183 | this.exit(mem().getInt32(sp + 8, true)); 184 | }, 185 | 186 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 187 | "runtime.wasmWrite": (sp) => { 188 | const fd = getInt64(sp + 8); 189 | const p = getInt64(sp + 16); 190 | const n = mem().getInt32(sp + 24, true); 191 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 192 | }, 193 | 194 | // func nanotime() int64 195 | "runtime.nanotime": (sp) => { 196 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 197 | }, 198 | 199 | // func walltime() (sec int64, nsec int32) 200 | "runtime.walltime": (sp) => { 201 | const msec = (new Date).getTime(); 202 | setInt64(sp + 8, msec / 1000); 203 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); 204 | }, 205 | 206 | // func scheduleCallback(delay int64) int32 207 | "runtime.scheduleCallback": (sp) => { 208 | const id = this._nextCallbackTimeoutID; 209 | this._nextCallbackTimeoutID++; 210 | this._callbackTimeouts.set(id, setTimeout( 211 | () => { this._resolveCallbackPromise(); }, 212 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 213 | )); 214 | mem().setInt32(sp + 16, id, true); 215 | }, 216 | 217 | // func clearScheduledCallback(id int32) 218 | "runtime.clearScheduledCallback": (sp) => { 219 | const id = mem().getInt32(sp + 8, true); 220 | clearTimeout(this._callbackTimeouts.get(id)); 221 | this._callbackTimeouts.delete(id); 222 | }, 223 | 224 | // func getRandomData(r []byte) 225 | "runtime.getRandomData": (sp) => { 226 | crypto.getRandomValues(loadSlice(sp + 8)); 227 | }, 228 | 229 | // func stringVal(value string) ref 230 | "syscall/js.stringVal": (sp) => { 231 | storeValue(sp + 24, loadString(sp + 8)); 232 | }, 233 | 234 | // func valueGet(v ref, p string) ref 235 | "syscall/js.valueGet": (sp) => { 236 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16))); 237 | }, 238 | 239 | // func valueSet(v ref, p string, x ref) 240 | "syscall/js.valueSet": (sp) => { 241 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 242 | }, 243 | 244 | // func valueIndex(v ref, i int) ref 245 | "syscall/js.valueIndex": (sp) => { 246 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 247 | }, 248 | 249 | // valueSetIndex(v ref, i int, x ref) 250 | "syscall/js.valueSetIndex": (sp) => { 251 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 252 | }, 253 | 254 | // func valueCall(v ref, m string, args []ref) (ref, bool) 255 | "syscall/js.valueCall": (sp) => { 256 | try { 257 | const v = loadValue(sp + 8); 258 | const m = Reflect.get(v, loadString(sp + 16)); 259 | const args = loadSliceOfValues(sp + 32); 260 | storeValue(sp + 56, Reflect.apply(m, v, args)); 261 | mem().setUint8(sp + 64, 1); 262 | } catch (err) { 263 | storeValue(sp + 56, err); 264 | mem().setUint8(sp + 64, 0); 265 | } 266 | }, 267 | 268 | // func valueInvoke(v ref, args []ref) (ref, bool) 269 | "syscall/js.valueInvoke": (sp) => { 270 | try { 271 | const v = loadValue(sp + 8); 272 | const args = loadSliceOfValues(sp + 16); 273 | storeValue(sp + 40, Reflect.apply(v, undefined, args)); 274 | mem().setUint8(sp + 48, 1); 275 | } catch (err) { 276 | storeValue(sp + 40, err); 277 | mem().setUint8(sp + 48, 0); 278 | } 279 | }, 280 | 281 | // func valueNew(v ref, args []ref) (ref, bool) 282 | "syscall/js.valueNew": (sp) => { 283 | try { 284 | const v = loadValue(sp + 8); 285 | const args = loadSliceOfValues(sp + 16); 286 | storeValue(sp + 40, Reflect.construct(v, args)); 287 | mem().setUint8(sp + 48, 1); 288 | } catch (err) { 289 | storeValue(sp + 40, err); 290 | mem().setUint8(sp + 48, 0); 291 | } 292 | }, 293 | 294 | // func valueLength(v ref) int 295 | "syscall/js.valueLength": (sp) => { 296 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 297 | }, 298 | 299 | // valuePrepareString(v ref) (ref, int) 300 | "syscall/js.valuePrepareString": (sp) => { 301 | const str = encoder.encode(String(loadValue(sp + 8))); 302 | storeValue(sp + 16, str); 303 | setInt64(sp + 24, str.length); 304 | }, 305 | 306 | // valueLoadString(v ref, b []byte) 307 | "syscall/js.valueLoadString": (sp) => { 308 | const str = loadValue(sp + 8); 309 | loadSlice(sp + 16).set(str); 310 | }, 311 | 312 | // func valueInstanceOf(v ref, t ref) bool 313 | "syscall/js.valueInstanceOf": (sp) => { 314 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); 315 | }, 316 | 317 | "debug": (value) => { 318 | console.log(value); 319 | }, 320 | } 321 | }; 322 | } 323 | 324 | async run(instance) { 325 | this._inst = instance; 326 | this._values = [ // TODO: garbage collection 327 | NaN, 328 | undefined, 329 | null, 330 | true, 331 | false, 332 | global, 333 | this._inst.exports.mem, 334 | () => { // resolveCallbackPromise 335 | if (this.exited) { 336 | throw new Error("bad callback: Go program has already exited"); 337 | } 338 | setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous 339 | }, 340 | ]; 341 | this._stringRefs = new Map(); 342 | this._symbolRefs = new Map(); 343 | this._refProp = Symbol(); 344 | this.exited = false; 345 | 346 | const mem = new DataView(this._inst.exports.mem.buffer) 347 | 348 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 349 | let offset = 4096; 350 | 351 | const strPtr = (str) => { 352 | let ptr = offset; 353 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); 354 | offset += str.length + (8 - (str.length % 8)); 355 | return ptr; 356 | }; 357 | 358 | const argc = this.argv.length; 359 | 360 | const argvPtrs = []; 361 | this.argv.forEach((arg) => { 362 | argvPtrs.push(strPtr(arg)); 363 | }); 364 | 365 | const keys = Object.keys(this.env).sort(); 366 | argvPtrs.push(keys.length); 367 | keys.forEach((key) => { 368 | argvPtrs.push(strPtr(` + "`" + "${key}=${this.env[key]}" + "`" + `)); 369 | }); 370 | 371 | const argv = offset; 372 | argvPtrs.forEach((ptr) => { 373 | mem.setUint32(offset, ptr, true); 374 | mem.setUint32(offset + 4, 0, true); 375 | offset += 8; 376 | }); 377 | 378 | while (true) { 379 | const callbackPromise = new Promise((resolve) => { 380 | this._resolveCallbackPromise = resolve; 381 | }); 382 | this._inst.exports.run(argc, argv); 383 | if (this.exited) { 384 | break; 385 | } 386 | await callbackPromise; 387 | } 388 | } 389 | } 390 | 391 | if (isNodeJS) { 392 | if (process.argv.length < 3) { 393 | process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"); 394 | process.exit(1); 395 | } 396 | 397 | const go = new Go(); 398 | go.argv = process.argv.slice(2); 399 | go.env = process.env; 400 | go.exit = process.exit; 401 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 402 | process.on("exit", () => { // Node.js exits if no callback is pending 403 | if (!go.exited) { 404 | console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!"); 405 | process.exit(1); 406 | } 407 | }); 408 | return go.run(result.instance); 409 | }).catch((err) => { 410 | console.error(err); 411 | go.exited = true; 412 | process.exit(1); 413 | }); 414 | } 415 | })(); 416 | ` 417 | -------------------------------------------------------------------------------- /component/component.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "math/rand" 8 | "strings" 9 | "text/template" 10 | "time" 11 | 12 | "github.com/tdewolff/parse" 13 | "github.com/tdewolff/parse/css" 14 | 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | // ErrComponentNotParsed is returned when an attempt is made 19 | // to Transform() a Component before calling Parse() 20 | var ErrComponentNotParsed = errors.New("transform must be called after parse") 21 | 22 | // A Component represents a web component in an HTML file 23 | type Component struct { 24 | Name string 25 | Template string 26 | Style string 27 | Package string 28 | Imports []string 29 | parsed bool 30 | Struct bool 31 | UniqueID string 32 | } 33 | 34 | func (c *Component) WriteImports() string { 35 | return strings.Join(c.Imports, "\n\t") 36 | } 37 | func (c *Component) QuotedStyle() string { 38 | return "`" + c.Style + "`" 39 | } 40 | 41 | func (c *Component) QuotedTemplate() string { 42 | return "`" + c.Template + "`" 43 | } 44 | 45 | func (c *Component) TransformStyle() { 46 | p := css.NewParser(bytes.NewBufferString(c.Style), false) 47 | 48 | output := "" 49 | for { 50 | grammar, _, data := p.Next() 51 | data = parse.Copy(data) 52 | if grammar == css.ErrorGrammar { 53 | if err := p.Err(); err != io.EOF { 54 | for _, val := range p.Values() { 55 | data = append(data, val.Data...) 56 | } 57 | if perr, ok := err.(*parse.Error); ok && perr.Message == "unexpected token in declaration" { 58 | data = append(data, ";"...) 59 | } 60 | } else { 61 | break 62 | } 63 | } else if grammar == css.AtRuleGrammar || grammar == css.BeginAtRuleGrammar || grammar == css.QualifiedRuleGrammar || grammar == css.BeginRulesetGrammar || grammar == css.DeclarationGrammar || grammar == css.CustomPropertyGrammar { 64 | if grammar == css.DeclarationGrammar || grammar == css.CustomPropertyGrammar { 65 | data = append(data, ":"...) 66 | } 67 | for _, val := range p.Values() { 68 | data = append(data, val.Data...) 69 | } 70 | if grammar == css.BeginAtRuleGrammar || grammar == css.BeginRulesetGrammar { 71 | 72 | data = append(data, "."...) 73 | data = append(data, c.UniqueID...) 74 | data = append(data, "{"...) 75 | } else if grammar == css.AtRuleGrammar || grammar == css.DeclarationGrammar || grammar == css.CustomPropertyGrammar { 76 | data = append(data, ";"...) 77 | } else if grammar == css.QualifiedRuleGrammar { 78 | data = append(data, ","...) 79 | } 80 | } 81 | output += string(data) 82 | } 83 | 84 | c.Style = output 85 | 86 | } 87 | 88 | // Parse reads a component file like Nav.html into 89 | // a Component structure 90 | func Parse(r io.Reader, name string) (*Component, error) { 91 | var template, style string 92 | var err error 93 | 94 | // TODO: do this more cleanly, not the fast/hacky way 95 | 96 | bb, err := ioutil.ReadAll(r) 97 | if err != nil { 98 | return nil, errors.Wrap(err, "reading component template") 99 | } 100 | 101 | s := string(bb) 102 | styleStart := strings.Index(s, "", "", -1) 111 | } 112 | c := &Component{ 113 | Name: name, 114 | Template: template, 115 | Style: style, 116 | UniqueID: randSeq(10), 117 | parsed: true, 118 | } 119 | return c, err 120 | } 121 | 122 | func (c *Component) Transform(w io.Writer) error { 123 | if !c.parsed { 124 | return ErrComponentNotParsed 125 | } 126 | tpl := template.Must(template.New("component").Parse(comptpl)) 127 | err := tpl.Execute(w, c) 128 | if err != nil { 129 | return err 130 | } 131 | return nil 132 | } 133 | 134 | func removeStyleTags(s string) string { 135 | s = strings.TrimSpace(s) 136 | s = strings.Replace(s, "", "", -1) 138 | return s 139 | } 140 | func init() { 141 | rand.Seed(time.Now().UnixNano()) 142 | } 143 | 144 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 145 | 146 | func randSeq(n int) string { 147 | b := make([]rune, n) 148 | for i := range b { 149 | b[i] = letters[rand.Intn(len(letters))] 150 | } 151 | return string(b) 152 | } 153 | -------------------------------------------------------------------------------- /component/component_test.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | var goodTpl = `` 19 | var goodStyle = `` 64 | 65 | func TestParse(t *testing.T) { 66 | c := parse(t) 67 | if c.Template != goodTpl { 68 | t.Errorf("Template Mismatch") 69 | } 70 | 71 | if c.Style != removeStyleTags(goodStyle) { 72 | t.Errorf("Style Mismatch") 73 | } 74 | } 75 | func TestQuoted(t *testing.T) { 76 | c := parseComponent(t) 77 | qs := c.QuotedStyle() 78 | if strings.Compare(qs[0:1], "`") != 0 { 79 | t.Errorf("expected style to start with backtick, got: %s", qs[0:1]) 80 | } 81 | qt := c.QuotedTemplate() 82 | if strings.Compare(qt[len(qt)-1:len(qt)], "`") != 0 { 83 | t.Errorf("expected template to start with backtick, got: %s", qt[len(qt)-1:len(qt)]) 84 | } 85 | } 86 | func parseComponent(t *testing.T) *Component { 87 | 88 | good := goodTpl + "\n" + goodStyle 89 | b := bytes.NewBuffer([]byte(good)) 90 | 91 | c, e := Parse(b, "Good") 92 | if e != nil { 93 | t.Fatal("failed to parse template") 94 | } 95 | return c 96 | } 97 | 98 | func TestTransformUnparsed(t *testing.T) { 99 | c := &Component{} 100 | b := new(bytes.Buffer) 101 | err := c.Transform(b) 102 | if err != ErrComponentNotParsed { 103 | t.Error("Expected ErrComponentNotParsed on unparsed component") 104 | } 105 | } 106 | 107 | func TestTransform(t *testing.T) { 108 | c := parseComponent(t) 109 | c.Package = "mypackage" 110 | b := new(bytes.Buffer) 111 | err := c.Transform(b) 112 | if err != nil { 113 | t.Error(err) 114 | } 115 | // TODO: Compare against golden 116 | } 117 | -------------------------------------------------------------------------------- /component/process.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/factorapp/factor/files" 11 | "github.com/gobuffalo/envy" 12 | ) 13 | 14 | // ProcessAll processes components starting at base 15 | func ProcessAll(base string) error { 16 | 17 | err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error { 18 | if err != nil { 19 | return err 20 | } 21 | if !info.IsDir() && files.IsHTML(info) { 22 | f, err := os.Open(path) 23 | if err != nil { 24 | return err 25 | } 26 | //c, _ := component.Parse(f, componentName(path)) 27 | 28 | comp := files.ComponentName(path) 29 | gfn := filepath.Join(base, strings.ToLower(comp)+".go") 30 | _, err = os.Stat(gfn) 31 | var makeStruct bool 32 | if os.IsNotExist(err) { 33 | makeStruct = true 34 | } 35 | /*gofile, err := os.Create(goFileName(base, componentName(path))) 36 | if err != nil { 37 | return err 38 | } 39 | defer gofile.Close() 40 | 41 | c.Transform(gofile) 42 | */ 43 | appPkg := envy.CurrentPackage() 44 | transpiler, err := NewTranspiler(f, makeStruct, appPkg, comp, "components") 45 | if err != nil { 46 | log.Println("ERROR", err) 47 | return err 48 | } 49 | 50 | gofile, err := os.Create(files.GeneratedGoFileName(base, comp)) 51 | if err != nil { 52 | log.Println("ERROR", err) 53 | return err 54 | } 55 | defer gofile.Close() 56 | _, err = io.WriteString(gofile, transpiler.Code()) 57 | if err != nil { 58 | log.Println("ERROR", err) 59 | return err 60 | } 61 | } 62 | return nil 63 | }) 64 | 65 | if err != nil { 66 | log.Printf("error walking the path %q: %v\n", base, err) 67 | } 68 | return err 69 | } 70 | -------------------------------------------------------------------------------- /component/template.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | var comptpl = ` 4 | package components 5 | 6 | import ( 7 | {{.WriteImports}} 8 | "github.com/factorapp/factor/markup" 9 | ) 10 | 11 | {{ if .Struct }}type {{.Name}} struct { 12 | 13 | }{{end}} 14 | 15 | var {{.Name}}Template = {{.QuotedTemplate}} 16 | var {{.Name}}Styles = {{.QuotedStyle}} 17 | 18 | 19 | func (t *{{.Name}}) Render() string { 20 | return {{.Name}}Template 21 | } 22 | 23 | func init() { 24 | markup.Register(&{{.Name}}{}) 25 | } 26 | ` 27 | -------------------------------------------------------------------------------- /component/transpiler.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io/ioutil" 7 | "regexp" 8 | 9 | "errors" 10 | 11 | "bytes" 12 | "io" 13 | 14 | "strings" 15 | 16 | "encoding/xml" 17 | 18 | "github.com/aymerick/douceur/parser" 19 | "github.com/dave/jennifer/jen" 20 | "github.com/factorapp/factor/codegen/transpiler" 21 | ) 22 | 23 | var callRegexp = regexp.MustCompile(`{vecty-call:([a-zA-Z0-9_\-]+)}`) 24 | var fieldRegexp = regexp.MustCompile(`{vecty-field:([a-zA-Z0-9_\-]+})`) 25 | 26 | func NewTranspiler(r io.ReadCloser, createStruct bool, appPackage, componentName, packageName string) (*Transpiler, error) { 27 | s := &Transpiler{ 28 | reader: r, 29 | createStruct: createStruct, 30 | appPackage: appPackage, 31 | packageName: packageName, 32 | componentName: componentName, 33 | } 34 | err := s.read() 35 | if err != nil { 36 | return s, err 37 | } 38 | err = s.transcode() 39 | if err != nil { 40 | return s, err 41 | } 42 | return s, nil 43 | } 44 | 45 | type Transpiler struct { 46 | reader io.ReadCloser 47 | createStruct bool 48 | appPackage string 49 | componentName string 50 | packageName string 51 | html, code string 52 | } 53 | 54 | func (s *Transpiler) read() error { 55 | bb, err := ioutil.ReadAll(s.reader) 56 | if err != nil { 57 | return errors.New("reading component template") 58 | } 59 | s.html = string(bb) 60 | return nil 61 | } 62 | 63 | func (s *Transpiler) Code() string { 64 | return s.code 65 | } 66 | 67 | func (s *Transpiler) transcode() error { 68 | // check for valid HTML 69 | if _, err := template.New("syntaxcheck").Parse(s.html); err != nil { 70 | return err 71 | } 72 | 73 | decoder := xml.NewDecoder(bytes.NewBufferString(s.html)) 74 | 75 | EOT := errors.New("end of tag") 76 | call := jen.Options{ 77 | Close: ")", 78 | Multi: true, 79 | Open: "(", 80 | Separator: ",", 81 | } 82 | /*values := jen.Options{ 83 | Close: "}", 84 | Multi: true, 85 | Open: "{", 86 | Separator: ",", 87 | }*/ 88 | 89 | var transcode func(*xml.Decoder) ([]jen.Code, error) 90 | transcode = func(decoder *xml.Decoder) (code []jen.Code, err error) { 91 | token, err := decoder.Token() 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | switch token := token.(type) { 97 | case xml.StartElement: 98 | tag := token.Name.Local 99 | vectyFunction, ok := transpiler.ElemNames[tag] 100 | vectyPackage := "github.com/gowasm/vecty/elem" 101 | vectyParamater := "" 102 | var ce *jen.Statement 103 | if !ok { 104 | if strings.HasPrefix(token.Name.Space, "components") { 105 | // not sure if we need this? 106 | componentName := strings.TrimLeft(tag, "components.") 107 | ce = transpiler.ComponentElement(s.appPackage, componentName, &token) 108 | vectyFunction = "" 109 | //ce.Render(os.Stdout) 110 | //return ce, nil 111 | //jen.Add(ce) 112 | } else { 113 | vectyFunction = "Tag" 114 | vectyPackage = "github.com/gowasm/vecty" 115 | vectyParamater = tag 116 | } 117 | } 118 | var outer error 119 | 120 | q := jen.Qual(vectyPackage, vectyFunction).CustomFunc(call, func(g *jen.Group) { 121 | if vectyParamater != "" { 122 | g.Lit(vectyParamater) 123 | } 124 | if ce == nil && len(token.Attr) > 0 { 125 | g.Qual("github.com/gowasm/vecty", "Markup").CustomFunc(call, func(g *jen.Group) { 126 | for _, v := range token.Attr { 127 | switch { 128 | case v.Name.Local == "style": 129 | css, err := parser.ParseDeclarations(v.Value) 130 | if err != nil { 131 | outer = err 132 | return 133 | } 134 | for _, dec := range css { 135 | if dec.Important { 136 | dec.Value += "!important" 137 | } 138 | g.Qual("github.com/gowasm/vecty", "Style").Call( 139 | jen.Lit(dec.Property), 140 | jen.Lit(dec.Value), 141 | ) 142 | } 143 | case v.Name.Local == "class": 144 | g.Qual("github.com/gowasm/vecty", "Class").CallFunc(func(g *jen.Group) { 145 | classes := strings.Split(v.Value, " ") 146 | for _, class := range classes { 147 | if strings.HasPrefix(class, "{vecty-field:") { 148 | field := strings.TrimLeft(class, "{vecty-field:") 149 | field = field[:len(field)-1] 150 | g.Add(jen.Id("p").Dot(field)) 151 | } else { 152 | g.Lit(class) 153 | } 154 | } 155 | }) 156 | case strings.HasPrefix(v.Name.Local, "data-"): 157 | attribute := strings.TrimPrefix(v.Name.Local, "data-") 158 | g.Qual("github.com/gowasm/vecty", "Data").Call( 159 | jen.Lit(attribute), 160 | jen.Lit(v.Value), 161 | ) 162 | 163 | case transpiler.BoolProps[v.Name.Local] != "": 164 | value := v.Value == "true" 165 | g.Qual("github.com/gowasm/vecty/prop", transpiler.BoolProps[v.Name.Local]).Call( 166 | jen.Lit(value), 167 | ) 168 | case transpiler.StringProps[v.Name.Local] != "": 169 | if strings.HasPrefix(v.Value, "{vecty-field:") { 170 | field := strings.TrimLeft(v.Value, "{vecty-field:") 171 | field = field[:len(field)-1] 172 | g.Qual("github.com/gowasm/vecty/prop", transpiler.StringProps[v.Name.Local]).Call( 173 | jen.Id("p").Dot(field), 174 | ) 175 | } else { 176 | g.Qual("github.com/gowasm/vecty/prop", transpiler.StringProps[v.Name.Local]).Call( 177 | jen.Lit(v.Value), 178 | ) 179 | } 180 | case strings.HasPrefix(v.Name.Space, "vecty"): 181 | field := strings.TrimLeft(v.Name.Local, "on") 182 | field = strings.ToLower(field) 183 | g.Qual("github.com/gowasm/vecty/event", strings.Title(field)).Call( 184 | jen.Id("p").Dot(v.Value), 185 | ) 186 | case strings.HasPrefix(v.Name.Space, "components"): 187 | component := strings.TrimLeft(v.Name.Local, "components.") 188 | jen.Op("&").Id(component).Values() 189 | case v.Name.Local == "xmlns": 190 | g.Qual("github.com/gowasm/vecty", "Namespace").Call( 191 | jen.Lit(v.Value), 192 | ) 193 | case v.Name.Local == "type" && transpiler.TypeProps[v.Value] != "": 194 | g.Qual("github.com/gowasm/vecty/prop", "Type").Call( 195 | jen.Qual("github.com/gowasm/vecty/prop", transpiler.TypeProps[v.Value]), 196 | ) 197 | default: 198 | g.Qual("github.com/gowasm/vecty", "Attribute").Call( 199 | jen.Lit(v.Name.Local), 200 | jen.Lit(v.Value), 201 | ) 202 | } 203 | } 204 | }) 205 | } 206 | for { 207 | c, err := transcode(decoder) 208 | if err != nil { 209 | if err == EOT { 210 | break 211 | } 212 | outer = err 213 | return 214 | } 215 | if c != nil { 216 | g.Add(c...) 217 | } 218 | } 219 | }) 220 | if outer != nil { 221 | return nil, outer 222 | } 223 | if ce != nil { 224 | return []jen.Code{ce}, nil 225 | } 226 | return []jen.Code{q}, nil 227 | case xml.CharData: 228 | str := string(token) 229 | hasCall := callRegexp.MatchString(str) 230 | hasField := fieldRegexp.MatchString(str) 231 | hasSpecial := hasCall || hasField 232 | 233 | if hasSpecial { 234 | /* 235 | fieldQualifier := func(name string) (*jen.Statement, error) { 236 | 237 | fmt.Println("field qualifier:", str, name) 238 | n := strings.TrimLeft(name, "{vecty-field:") 239 | n = strings.TrimRight(n, "}") 240 | return jen.Qual("github.com/gowasm/vecty", "Text").Call( 241 | // TODO: struct qualifier 242 | jen.Id("p").Dot(n), 243 | ), nil 244 | } 245 | */ 246 | /* 247 | callQualifier := func(lhs, name, rhs string) (*jen.Statement, error) { 248 | fmt.Println("call qualifier:", str, name) 249 | fnCall := strings.TrimLeft(name, "{vecty-call:") 250 | fnCall = strings.Replace(fnCall, "}", "", -1) 251 | stmt := jen.Qual("github.com/gowasm/vecty", "Text").Call( 252 | jen.Lit(lhs), 253 | ) 254 | stmt.Qual("github.com/gowasm/vecty", "Text").Call( 255 | jen.Id("p").Dot(fnCall).Call(), 256 | ) 257 | stmt.Qual("github.com/gowasm/vecty", "Text").Call( 258 | jen.Lit(rhs), 259 | ) 260 | return stmt, nil 261 | 262 | } 263 | */ 264 | // TODO: find next index for each field and call regexp. 265 | // build up a string of text statements for static text, and 266 | // generated calls or field accesses 267 | if hasCall { 268 | 269 | /* 270 | re := regexp.MustCompile(`({vecty-call:[a-zA-Z0-9]+})`) 271 | t := "{vecty-call:Alone}thing{vecty-call:Thing}stuff{vecty-call:Stuff}other{vecty-call:Blue}" 272 | crResult := re.FindAllStringIndex(t, -1) 273 | index := 0 274 | for matchNumber, match := range crResult { 275 | fmt.Println(match) 276 | fmt.Println("Before:", t[index:match[0]]) 277 | fmt.Println("Match:", t[match[0]:match[1]]) 278 | fmt.Println(re.FindAllStringSubmatch(t, index)) 279 | if matchNumber < len(crResult)-1 { 280 | // there's another match 281 | 282 | fmt.Println(matchNumber, len(crResult), index, crResult, "There is another") 283 | fmt.Println("next Result:",crResult[matchNumber+1][0]) 284 | fmt.Println(t[match[1]:]) 285 | fmt.Println(crResult[matchNumber+1]) 286 | fmt.Println("Internal After:", t[match[1]:crResult[matchNumber+1][0]]) 287 | } 288 | 289 | fmt.Println("After:", t[match[1]:]) 290 | index = match[1] 291 | } 292 | */ 293 | var statements []jen.Code 294 | crResult := callRegexp.FindAllStringIndex(str, -1) 295 | index := 0 296 | for matchNumber, match := range crResult { 297 | var before, between, after string 298 | before = str[index:match[0]] 299 | fnCall := str[match[0]:match[1]] 300 | fnCall = strings.TrimLeft(fnCall, "{vecty-call:") 301 | fnCall = strings.Replace(fnCall, "}", "", -1) 302 | if matchNumber < len(crResult)-1 { 303 | // there's another match 304 | between = str[match[1]:crResult[matchNumber+1][0]] 305 | } 306 | after = str[match[1]:] 307 | /* 308 | g.Qual("fmt", "Sprintf").Call( 309 | jen.Lit("%s%s%s"), 310 | jen.Lit(lhs), 311 | jen.Id("p").Dot(fnCall).Call(), 312 | jen.Lit(rhs), 313 | ) 314 | */ 315 | if before != "" && !strings.Contains(before, "vecty-call") { 316 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 317 | jen.Lit(before), 318 | )) 319 | } 320 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 321 | jen.Id("p").Dot(fnCall).Call(), 322 | )) 323 | if between != "" && !strings.Contains(between, "vecty-call") { 324 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 325 | jen.Lit(between), 326 | )) 327 | } 328 | if after != "" && !strings.Contains(after, "vecty-call") { 329 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 330 | jen.Lit(after), 331 | )) 332 | } 333 | } 334 | return statements, nil 335 | 336 | } 337 | if hasField { 338 | 339 | /* 340 | re := regexp.MustCompile(`({vecty-call:[a-zA-Z0-9]+})`) 341 | t := "{vecty-call:Alone}thing{vecty-call:Thing}stuff{vecty-call:Stuff}other{vecty-call:Blue}" 342 | crResult := re.FindAllStringIndex(t, -1) 343 | index := 0 344 | for matchNumber, match := range crResult { 345 | fmt.Println(match) 346 | fmt.Println("Before:", t[index:match[0]]) 347 | fmt.Println("Match:", t[match[0]:match[1]]) 348 | fmt.Println(re.FindAllStringSubmatch(t, index)) 349 | if matchNumber < len(crResult)-1 { 350 | // there's another match 351 | 352 | fmt.Println(matchNumber, len(crResult), index, crResult, "There is another") 353 | fmt.Println("next Result:",crResult[matchNumber+1][0]) 354 | fmt.Println(t[match[1]:]) 355 | fmt.Println(crResult[matchNumber+1]) 356 | fmt.Println("Internal After:", t[match[1]:crResult[matchNumber+1][0]]) 357 | } 358 | 359 | fmt.Println("After:", t[match[1]:]) 360 | index = match[1] 361 | } 362 | */ 363 | var statements []jen.Code 364 | crResult := fieldRegexp.FindAllStringIndex(str, -1) 365 | index := 0 366 | for matchNumber, match := range crResult { 367 | var before, between, after string 368 | before = str[index:match[0]] 369 | field := str[match[0]:match[1]] 370 | field = strings.TrimLeft(field, "{vecty-field:") 371 | field = strings.Replace(field, "}", "", -1) 372 | if matchNumber < len(crResult)-1 { 373 | // there's another match 374 | between = str[match[1]:crResult[matchNumber+1][0]] 375 | } 376 | after = str[match[1]:] 377 | /* 378 | g.Qual("fmt", "Sprintf").Call( 379 | jen.Lit("%s%s%s"), 380 | jen.Lit(lhs), 381 | jen.Id("p").Dot(fnCall).Call(), 382 | jen.Lit(rhs), 383 | ) 384 | */ 385 | if before != "" && !strings.Contains(before, "vecty-field") { 386 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 387 | jen.Lit(before), 388 | )) 389 | } 390 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 391 | jen.Id("p").Dot(field), 392 | )) 393 | if between != "" && !strings.Contains(between, "vecty-field") { 394 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 395 | jen.Lit(between), 396 | )) 397 | } 398 | if after != "" && !strings.Contains(after, "vecty-field") { 399 | statements = append(statements, jen.Qual("github.com/gowasm/vecty", "Text").Call( 400 | jen.Lit(after), 401 | )) 402 | } 403 | } 404 | return statements, nil 405 | 406 | } 407 | 408 | } 409 | s := strings.TrimSpace(string(token)) 410 | if s == "" { 411 | return nil, nil 412 | } 413 | return []jen.Code{jen.Qual("github.com/gowasm/vecty", "Text").Call(jen.Lit(s))}, nil 414 | case xml.EndElement: 415 | return nil, EOT 416 | case xml.Comment: 417 | return nil, nil 418 | default: 419 | fmt.Printf("%T %#v \n", token, token) 420 | } 421 | return nil, nil 422 | } 423 | 424 | file := jen.NewFile(s.packageName) 425 | file.PackageComment("This file was created with https://github.com/factorapp/factor") 426 | file.PackageComment("using https://jsgo.io/dave/html2vecty") 427 | file.ImportNames(map[string]string{ 428 | "github.com/gowasm/vecty": "vecty", 429 | "github.com/gowasm/vecty/elem": "elem", 430 | "github.com/gowasm/vecty/prop": "prop", 431 | "github.com/gowasm/vecty/event": "event", 432 | "github.com/gowasm/vecty/style": "style", 433 | "_ github.com/factorapp/factor/examples/components": "components", 434 | }) 435 | var elements []jen.Code 436 | for { 437 | c, err := transcode(decoder) 438 | if err != nil { 439 | if err == io.EOF || err == EOT { 440 | break 441 | } 442 | s.code = fmt.Sprintf("%s", err) 443 | return nil 444 | } 445 | if c != nil { 446 | elements = append(elements, c...) 447 | } 448 | } 449 | /* 450 | func main() { 451 | vecty.RenderBody(&Page{}) 452 | } 453 | 454 | type Page struct { 455 | vecty.Core 456 | } 457 | 458 | func (*Page) Render() vecty.ComponentOrHTML { 459 | return elem.Body(...) 460 | } 461 | */ 462 | /* file.Func().Id("main").Params().Block( 463 | jen.Qual("github.com/gowasm/vecty", "RenderBody").Call( 464 | jen.Op("&").Id("Page").Values(), 465 | ), 466 | ) 467 | */ 468 | if s.createStruct { 469 | file.Type().Id(s.componentName).Struct( 470 | jen.Qual("github.com/gowasm/vecty", "Core"), 471 | ) 472 | } 473 | if s.packageName == "routes" || s.packageName == "pages" { 474 | file.Func().Params(jen.Id("p").Op("*").Id(s.componentName)).Id("Render").Params().Qual("github.com/gowasm/vecty", "ComponentOrHTML").Block( 475 | jen.Qual("github.com/gowasm/vecty", "SetTitle").Call( 476 | jen.Id("p").Dot("GetTitle").Call(), 477 | ), 478 | jen.Return( 479 | // TODO: wrap in if - only body for a "route" 480 | jen.Qual("github.com/gowasm/vecty/elem", "Body").Custom(call, elements...), 481 | ), 482 | ) 483 | } else { 484 | file.Func().Params(jen.Id("p").Op("*").Id(s.componentName)).Id("Render").Params().Qual("github.com/gowasm/vecty", "ComponentOrHTML").Block( 485 | // TODO: wrap in if - only body for a "route" 486 | // TODO: Are you sure this is right? It looks like if len(elements) > 1 this will break? 487 | jen.Return(elements...), 488 | ) 489 | } 490 | /*if len(elements) == 1 { 491 | file.Var().Id("Element").Op("=").Add(elements[0]) 492 | } else if len(elements) > 1 { 493 | file.Var().Id("Elements").Op("=").Index().Op("*").Qual("github.com/gopherjs/vecty", "HTML").Custom(values, elements...) 494 | }*/ 495 | 496 | buf := &bytes.Buffer{} 497 | if err := file.Render(buf); err != nil { 498 | s.code = fmt.Sprintf("%s", err) 499 | return nil 500 | } 501 | 502 | s.code = buf.String() 503 | return nil 504 | } 505 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | factortmp 2 | example.wasm 3 | .vs/ 4 | server-app 5 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: wasm 2 | wasm: 3 | GOROOT=~/gowasm GOARCH=wasm GOOS=js ~/gowasm/bin/go build -o example.wasm ./client 4 | 5 | .PHONY: server-app 6 | server-app: 7 | go build -o server-app ./server 8 | 9 | .PHONY: run 10 | run: wasm server-app 11 | ./server-app 12 | -------------------------------------------------------------------------------- /examples/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Factor: Full Stack Go 12 | 13 | 14 | 15 |
...
16 | 17 | 18 | 19 | 20 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/app/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | (() => { 6 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API). 7 | const isNodeJS = typeof process !== "undefined"; 8 | if (isNodeJS) { 9 | global.require = require; 10 | global.fs = require("fs"); 11 | 12 | const nodeCrypto = require("crypto"); 13 | global.crypto = { 14 | getRandomValues(b) { 15 | nodeCrypto.randomFillSync(b); 16 | }, 17 | }; 18 | 19 | global.performance = { 20 | now() { 21 | const [sec, nsec] = process.hrtime(); 22 | return sec * 1000 + nsec / 1000000; 23 | }, 24 | }; 25 | 26 | const util = require("util"); 27 | global.TextEncoder = util.TextEncoder; 28 | global.TextDecoder = util.TextDecoder; 29 | } else { 30 | window.global = window; 31 | 32 | let outputBuf = ""; 33 | global.fs = { 34 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_NONBLOCK: -1, O_SYNC: -1 }, // unused 35 | writeSync(fd, buf) { 36 | outputBuf += decoder.decode(buf); 37 | const nl = outputBuf.lastIndexOf("\n"); 38 | if (nl != -1) { 39 | console.log(outputBuf.substr(0, nl)); 40 | outputBuf = outputBuf.substr(nl + 1); 41 | } 42 | return buf.length; 43 | }, 44 | openSync(path, flags, mode) { 45 | const err = new Error("not implemented"); 46 | err.code = "ENOSYS"; 47 | throw err; 48 | }, 49 | }; 50 | } 51 | 52 | const encoder = new TextEncoder("utf-8"); 53 | const decoder = new TextDecoder("utf-8"); 54 | 55 | global.Go = class { 56 | constructor() { 57 | this.argv = ["js"]; 58 | this.env = {}; 59 | this.exit = (code) => { 60 | if (code !== 0) { 61 | console.warn("exit code:", code); 62 | } 63 | }; 64 | this._callbackTimeouts = new Map(); 65 | this._nextCallbackTimeoutID = 1; 66 | 67 | const mem = () => { 68 | // The buffer may change when requesting more memory. 69 | return new DataView(this._inst.exports.mem.buffer); 70 | } 71 | 72 | const setInt64 = (addr, v) => { 73 | mem().setUint32(addr + 0, v, true); 74 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); 75 | } 76 | 77 | const getInt64 = (addr) => { 78 | const low = mem().getUint32(addr + 0, true); 79 | const high = mem().getInt32(addr + 4, true); 80 | return low + high * 4294967296; 81 | } 82 | 83 | const loadValue = (addr) => { 84 | const f = mem().getFloat64(addr, true); 85 | if (!isNaN(f)) { 86 | return f; 87 | } 88 | 89 | const id = mem().getUint32(addr, true); 90 | return this._values[id]; 91 | } 92 | 93 | const storeValue = (addr, v) => { 94 | if (typeof v === "number") { 95 | if (isNaN(v)) { 96 | mem().setUint32(addr + 4, 0x7FF80000, true); // NaN 97 | mem().setUint32(addr, 0, true); 98 | return; 99 | } 100 | mem().setFloat64(addr, v, true); 101 | return; 102 | } 103 | 104 | mem().setUint32(addr + 4, 0x7FF80000, true); // NaN 105 | 106 | switch (v) { 107 | case undefined: 108 | mem().setUint32(addr, 1, true); 109 | return; 110 | case null: 111 | mem().setUint32(addr, 2, true); 112 | return; 113 | case true: 114 | mem().setUint32(addr, 3, true); 115 | return; 116 | case false: 117 | mem().setUint32(addr, 4, true); 118 | return; 119 | } 120 | 121 | if (typeof v === "string") { 122 | let ref = this._stringRefs.get(v); 123 | if (ref === undefined) { 124 | ref = this._values.length; 125 | this._values.push(v); 126 | this._stringRefs.set(v, ref); 127 | } 128 | mem().setUint32(addr, ref, true); 129 | return; 130 | } 131 | 132 | if (typeof v === "symbol") { 133 | let ref = this._symbolRefs.get(v); 134 | if (ref === undefined) { 135 | ref = this._values.length; 136 | this._values.push(v); 137 | this._symbolRefs.set(v, ref); 138 | } 139 | mem().setUint32(addr, ref, true); 140 | return; 141 | } 142 | 143 | let ref = v[this._refProp]; 144 | if (ref === undefined) { 145 | ref = this._values.length; 146 | this._values.push(v); 147 | v[this._refProp] = ref; 148 | } 149 | mem().setUint32(addr, ref, true); 150 | } 151 | 152 | const loadSlice = (addr) => { 153 | const array = getInt64(addr + 0); 154 | const len = getInt64(addr + 8); 155 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 156 | } 157 | 158 | const loadSliceOfValues = (addr) => { 159 | const array = getInt64(addr + 0); 160 | const len = getInt64(addr + 8); 161 | const a = new Array(len); 162 | for (let i = 0; i < len; i++) { 163 | a[i] = loadValue(array + i * 8); 164 | } 165 | return a; 166 | } 167 | 168 | const loadString = (addr) => { 169 | const saddr = getInt64(addr + 0); 170 | const len = getInt64(addr + 8); 171 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 172 | } 173 | 174 | const timeOrigin = Date.now() - performance.now(); 175 | this.importObject = { 176 | go: { 177 | // func wasmExit(code int32) 178 | "runtime.wasmExit": (sp) => { 179 | this.exited = true; 180 | this.exit(mem().getInt32(sp + 8, true)); 181 | }, 182 | 183 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 184 | "runtime.wasmWrite": (sp) => { 185 | const fd = getInt64(sp + 8); 186 | const p = getInt64(sp + 16); 187 | const n = mem().getInt32(sp + 24, true); 188 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 189 | }, 190 | 191 | // func nanotime() int64 192 | "runtime.nanotime": (sp) => { 193 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 194 | }, 195 | 196 | // func walltime() (sec int64, nsec int32) 197 | "runtime.walltime": (sp) => { 198 | const msec = (new Date).getTime(); 199 | setInt64(sp + 8, msec / 1000); 200 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); 201 | }, 202 | 203 | // func scheduleCallback(delay int64) int32 204 | "runtime.scheduleCallback": (sp) => { 205 | const id = this._nextCallbackTimeoutID; 206 | this._nextCallbackTimeoutID++; 207 | this._callbackTimeouts.set(id, setTimeout( 208 | () => { this._resolveCallbackPromise(); }, 209 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 210 | )); 211 | mem().setInt32(sp + 16, id, true); 212 | }, 213 | 214 | // func clearScheduledCallback(id int32) 215 | "runtime.clearScheduledCallback": (sp) => { 216 | const id = mem().getInt32(sp + 8, true); 217 | clearTimeout(this._callbackTimeouts.get(id)); 218 | this._callbackTimeouts.delete(id); 219 | }, 220 | 221 | // func getRandomData(r []byte) 222 | "runtime.getRandomData": (sp) => { 223 | crypto.getRandomValues(loadSlice(sp + 8)); 224 | }, 225 | 226 | // func stringVal(value string) ref 227 | "syscall/js.stringVal": (sp) => { 228 | storeValue(sp + 24, loadString(sp + 8)); 229 | }, 230 | 231 | // func valueGet(v ref, p string) ref 232 | "syscall/js.valueGet": (sp) => { 233 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16))); 234 | }, 235 | 236 | // func valueSet(v ref, p string, x ref) 237 | "syscall/js.valueSet": (sp) => { 238 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 239 | }, 240 | 241 | // func valueIndex(v ref, i int) ref 242 | "syscall/js.valueIndex": (sp) => { 243 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 244 | }, 245 | 246 | // valueSetIndex(v ref, i int, x ref) 247 | "syscall/js.valueSetIndex": (sp) => { 248 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 249 | }, 250 | 251 | // func valueCall(v ref, m string, args []ref) (ref, bool) 252 | "syscall/js.valueCall": (sp) => { 253 | try { 254 | const v = loadValue(sp + 8); 255 | const m = Reflect.get(v, loadString(sp + 16)); 256 | const args = loadSliceOfValues(sp + 32); 257 | storeValue(sp + 56, Reflect.apply(m, v, args)); 258 | mem().setUint8(sp + 64, 1); 259 | } catch (err) { 260 | storeValue(sp + 56, err); 261 | mem().setUint8(sp + 64, 0); 262 | } 263 | }, 264 | 265 | // func valueInvoke(v ref, args []ref) (ref, bool) 266 | "syscall/js.valueInvoke": (sp) => { 267 | try { 268 | const v = loadValue(sp + 8); 269 | const args = loadSliceOfValues(sp + 16); 270 | storeValue(sp + 40, Reflect.apply(v, undefined, args)); 271 | mem().setUint8(sp + 48, 1); 272 | } catch (err) { 273 | storeValue(sp + 40, err); 274 | mem().setUint8(sp + 48, 0); 275 | } 276 | }, 277 | 278 | // func valueNew(v ref, args []ref) (ref, bool) 279 | "syscall/js.valueNew": (sp) => { 280 | try { 281 | const v = loadValue(sp + 8); 282 | const args = loadSliceOfValues(sp + 16); 283 | storeValue(sp + 40, Reflect.construct(v, args)); 284 | mem().setUint8(sp + 48, 1); 285 | } catch (err) { 286 | storeValue(sp + 40, err); 287 | mem().setUint8(sp + 48, 0); 288 | } 289 | }, 290 | 291 | // func valueLength(v ref) int 292 | "syscall/js.valueLength": (sp) => { 293 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 294 | }, 295 | 296 | // valuePrepareString(v ref) (ref, int) 297 | "syscall/js.valuePrepareString": (sp) => { 298 | const str = encoder.encode(String(loadValue(sp + 8))); 299 | storeValue(sp + 16, str); 300 | setInt64(sp + 24, str.length); 301 | }, 302 | 303 | // valueLoadString(v ref, b []byte) 304 | "syscall/js.valueLoadString": (sp) => { 305 | const str = loadValue(sp + 8); 306 | loadSlice(sp + 16).set(str); 307 | }, 308 | 309 | // func valueInstanceOf(v ref, t ref) bool 310 | "syscall/js.valueInstanceOf": (sp) => { 311 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); 312 | }, 313 | 314 | "debug": (value) => { 315 | console.log(value); 316 | }, 317 | } 318 | }; 319 | } 320 | 321 | async run(instance) { 322 | this._inst = instance; 323 | this._values = [ // TODO: garbage collection 324 | NaN, 325 | undefined, 326 | null, 327 | true, 328 | false, 329 | global, 330 | this._inst.exports.mem, 331 | () => { // resolveCallbackPromise 332 | if (this.exited) { 333 | throw new Error("bad callback: Go program has already exited"); 334 | } 335 | setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous 336 | }, 337 | ]; 338 | this._stringRefs = new Map(); 339 | this._symbolRefs = new Map(); 340 | this._refProp = Symbol(); 341 | this.exited = false; 342 | 343 | const mem = new DataView(this._inst.exports.mem.buffer) 344 | 345 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 346 | let offset = 4096; 347 | 348 | const strPtr = (str) => { 349 | let ptr = offset; 350 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); 351 | offset += str.length + (8 - (str.length % 8)); 352 | return ptr; 353 | }; 354 | 355 | const argc = this.argv.length; 356 | 357 | const argvPtrs = []; 358 | this.argv.forEach((arg) => { 359 | argvPtrs.push(strPtr(arg)); 360 | }); 361 | 362 | const keys = Object.keys(this.env).sort(); 363 | argvPtrs.push(keys.length); 364 | keys.forEach((key) => { 365 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 366 | }); 367 | 368 | const argv = offset; 369 | argvPtrs.forEach((ptr) => { 370 | mem.setUint32(offset, ptr, true); 371 | mem.setUint32(offset + 4, 0, true); 372 | offset += 8; 373 | }); 374 | 375 | while (true) { 376 | const callbackPromise = new Promise((resolve) => { 377 | this._resolveCallbackPromise = resolve; 378 | }); 379 | this._inst.exports.run(argc, argv); 380 | if (this.exited) { 381 | break; 382 | } 383 | await callbackPromise; 384 | } 385 | } 386 | } 387 | 388 | if (isNodeJS) { 389 | if (process.argv.length < 3) { 390 | process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"); 391 | process.exit(1); 392 | } 393 | 394 | const go = new Go(); 395 | go.argv = process.argv.slice(2); 396 | go.env = process.env; 397 | go.exit = process.exit; 398 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 399 | process.on("exit", () => { // Node.js exits if no callback is pending 400 | if (!go.exited) { 401 | console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!"); 402 | process.exit(1); 403 | } 404 | }); 405 | return go.run(result.instance); 406 | }).catch((err) => { 407 | console.error(err); 408 | go.exited = true; 409 | process.exit(1); 410 | }); 411 | } 412 | })(); 413 | -------------------------------------------------------------------------------- /examples/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factorapp/factor/69dfdd227a1d960661c62d2546a6126d995b311b/examples/assets/favicon.png -------------------------------------------------------------------------------- /examples/assets/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /examples/assets/great-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factorapp/factor/69dfdd227a1d960661c62d2546a6126d995b311b/examples/assets/great-success.png -------------------------------------------------------------------------------- /examples/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "#ffffff", 3 | "theme_color": "#aa1e1e", 4 | "name": "TODO", 5 | "short_name": "TODO", 6 | "display": "minimal-ui", 7 | "start_url": "/", 8 | "icons": [ 9 | { 10 | "src": "svelte-logo-192.png", 11 | "sizes": "192x192", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "svelte-logo-512.png", 16 | "sizes": "512x512", 17 | "type": "image/png" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/assets/svelte-logo-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factorapp/factor/69dfdd227a1d960661c62d2546a6126d995b311b/examples/assets/svelte-logo-192.png -------------------------------------------------------------------------------- /examples/assets/svelte-logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factorapp/factor/69dfdd227a1d960661c62d2546a6126d995b311b/examples/assets/svelte-logo-512.png -------------------------------------------------------------------------------- /examples/client/main.go: -------------------------------------------------------------------------------- 1 | // build +js,wasm 2 | package main 3 | 4 | import ( 5 | "github.com/factorapp/factor/examples/routes" 6 | "github.com/gowasm/router" 7 | "github.com/gowasm/vecty" 8 | ) 9 | 10 | func main() { 11 | c := make(chan struct{}, 0) 12 | // Create a new Router object 13 | r := router.New() 14 | //r.ShouldInterceptLinks = true 15 | // Use HandleFunc to add routes. 16 | r.HandleFunc("/", func(context *router.Context) { 17 | 18 | // The handler for this route simply grabs the name parameter 19 | // from the map of params and says hello. 20 | vecty.SetTitle("Factor: Home") 21 | vecty.RenderBody(&routes.Index{}) 22 | }) 23 | r.HandleFunc("/about", func(context *router.Context) { 24 | 25 | // The handler for this route simply grabs the name parameter 26 | // from the map of params and says hello. 27 | vecty.SetTitle("Factor: About") 28 | vecty.RenderBody(&routes.About{}) 29 | }) 30 | // You must call Start in order to start listening for changes 31 | // in the url and trigger the appropriate handler function. 32 | r.Start() 33 | <-c 34 | } 35 | -------------------------------------------------------------------------------- /examples/components/App.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
Brian Was Here
4 |
5 | -------------------------------------------------------------------------------- /examples/components/Nav.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/components/Todo.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

{vecty-field:Name}

4 | {vecty-field:Description} 5 | 6 |
Active for {vecty-call:GetAge} days
7 |
8 | -------------------------------------------------------------------------------- /examples/components/app.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "github.com/factorapp/factor/examples/models" 5 | ) 6 | 7 | type App struct { 8 | //factor.Router 9 | Todos []*models.Todo 10 | Page string 11 | } 12 | -------------------------------------------------------------------------------- /examples/components/app_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package components 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | ) 9 | 10 | func (p *App) Render() vecty.ComponentOrHTML { 11 | return elem.Body( 12 | elem.Main( 13 | &Nav{ 14 | 15 | MyProp: "Red", 16 | }, 17 | ), 18 | elem.Div( 19 | vecty.Text("Brian Was Here"), 20 | ), 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /examples/components/markdown.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "github.com/gowasm/vecty" 5 | ) 6 | 7 | // PageView is our main page component. 8 | type PageView struct { 9 | vecty.Core 10 | Input string 11 | } 12 | 13 | /* 14 | // Render implements the vecty.Component interface. 15 | func (p *PageView) Render() vecty.ComponentOrHTML { 16 | return p.template() 17 | } 18 | */ 19 | 20 | // Markdown is a simple component which renders the Input markdown as sanitized 21 | // HTML into a div. 22 | type Markdown struct { 23 | vecty.Core 24 | Input string `vecty:"prop"` 25 | } 26 | 27 | /* 28 | // Render implements the vecty.Component interface. 29 | func (m *Markdown) Render() vecty.ComponentOrHTML { 30 | // Render the markdown input into HTML using Blackfriday. 31 | unsafeHTML := blackfriday.MarkdownCommon([]byte(m.Input)) 32 | 33 | // Sanitize the HTML. 34 | safeHTML := string(bluemonday.UGCPolicy().SanitizeBytes(unsafeHTML)) 35 | 36 | // Return the HTML, which we guarantee to be safe / sanitized. 37 | return elem.Div( 38 | vecty.Markup( 39 | vecty.UnsafeHTML(safeHTML), 40 | ), 41 | ) 42 | } 43 | */ 44 | -------------------------------------------------------------------------------- /examples/components/markdown.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 | -------------------------------------------------------------------------------- /examples/components/markdown_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package components 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | ) 9 | 10 | func (p *Markdown) Render() vecty.ComponentOrHTML { 11 | return elem.Body( 12 | elem.Body( 13 | elem.Div( 14 | vecty.Markup( 15 | vecty.Style("float", "right"), 16 | ), 17 | elem.TextArea( 18 | vecty.Markup( 19 | vecty.Style("font-family", "monospace"), 20 | vecty.Attribute("cols", "70"), 21 | vecty.Attribute("rows", "14"), 22 | vecty.Attribute("oninput", "texthandler"), 23 | ), 24 | 25 | vecty.Text(p.Input), 26 | ), 27 | ), 28 | ), 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /examples/components/nav.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "github.com/gowasm/vecty" 5 | ) 6 | 7 | type Nav struct { 8 | vecty.Core 9 | MyProp string `vecty:"Prop"` 10 | CurrentPath string 11 | } 12 | -------------------------------------------------------------------------------- /examples/components/nav_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package components 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | "github.com/gowasm/vecty/prop" 9 | ) 10 | 11 | func (p *Nav) Render() vecty.ComponentOrHTML { 12 | return elem.Body( 13 | elem.Navigation( 14 | vecty.Markup( 15 | vecty.Class("navbar", "navbar-expand-md", "navbar-dark", "bg-dark", "fixed-top"), 16 | ), 17 | elem.Anchor( 18 | vecty.Markup( 19 | vecty.Class("navbar-brand"), 20 | prop.Href("#"), 21 | ), 22 | vecty.Text("Navbar"), 23 | ), 24 | elem.Button( 25 | vecty.Markup( 26 | vecty.Class("navbar-toggler"), 27 | prop.Type(prop.TypeButton), 28 | vecty.Data("toggle", "collapse"), 29 | vecty.Data("target", "#navbarsExampleDefault"), 30 | vecty.Attribute("aria-controls", "navbarsExampleDefault"), 31 | vecty.Attribute("aria-expanded", "false"), 32 | vecty.Attribute("aria-label", "Toggle navigation"), 33 | ), 34 | elem.Span( 35 | vecty.Markup( 36 | vecty.Class("navbar-toggler-icon"), 37 | ), 38 | ), 39 | ), 40 | elem.Div( 41 | vecty.Markup( 42 | vecty.Class("collapse", "navbar-collapse"), 43 | prop.ID("navbarsExampleDefault"), 44 | ), 45 | elem.UnorderedList( 46 | vecty.Markup( 47 | vecty.Class("navbar-nav", "mr-auto"), 48 | ), 49 | elem.ListItem( 50 | vecty.Markup( 51 | vecty.Class("nav-item", "active"), 52 | ), 53 | elem.Anchor( 54 | vecty.Markup( 55 | vecty.Class("nav-link"), 56 | prop.Href("#"), 57 | ), 58 | vecty.Text("Home"), 59 | elem.Span( 60 | vecty.Markup( 61 | vecty.Class("sr-only"), 62 | ), 63 | vecty.Text("(current)"), 64 | ), 65 | ), 66 | ), 67 | elem.ListItem( 68 | vecty.Markup( 69 | vecty.Class("nav-item"), 70 | ), 71 | elem.Anchor( 72 | vecty.Markup( 73 | vecty.Class("nav-link"), 74 | prop.Href("#"), 75 | ), 76 | vecty.Text("Link"), 77 | ), 78 | ), 79 | elem.ListItem( 80 | vecty.Markup( 81 | vecty.Class("nav-item"), 82 | ), 83 | elem.Anchor( 84 | vecty.Markup( 85 | vecty.Class("nav-link", "disabled"), 86 | prop.Href("#"), 87 | ), 88 | vecty.Text("Disabled"), 89 | ), 90 | ), 91 | elem.ListItem( 92 | vecty.Markup( 93 | vecty.Class("nav-item", "dropdown"), 94 | ), 95 | elem.Anchor( 96 | vecty.Markup( 97 | vecty.Class("nav-link", "dropdown-toggle"), 98 | prop.Href("https://example.com"), 99 | prop.ID("dropdown01"), 100 | vecty.Data("toggle", "dropdown"), 101 | vecty.Attribute("aria-haspopup", "true"), 102 | vecty.Attribute("aria-expanded", "false"), 103 | ), 104 | vecty.Text("Dropdown"), 105 | ), 106 | elem.Div( 107 | vecty.Markup( 108 | vecty.Class("dropdown-menu"), 109 | vecty.Attribute("aria-labelledby", "dropdown01"), 110 | ), 111 | elem.Anchor( 112 | vecty.Markup( 113 | vecty.Class("dropdown-item"), 114 | prop.Href("#"), 115 | ), 116 | vecty.Text("Action"), 117 | ), 118 | elem.Anchor( 119 | vecty.Markup( 120 | vecty.Class("dropdown-item"), 121 | prop.Href("#"), 122 | ), 123 | vecty.Text("Another action"), 124 | ), 125 | elem.Anchor( 126 | vecty.Markup( 127 | vecty.Class("dropdown-item"), 128 | prop.Href("#"), 129 | ), 130 | vecty.Text("Something else here"), 131 | ), 132 | ), 133 | ), 134 | ), 135 | elem.Form( 136 | vecty.Markup( 137 | vecty.Class("form-inline", "my-2", "my-lg-0"), 138 | ), 139 | elem.Input( 140 | vecty.Markup( 141 | vecty.Class("form-control", "mr-sm-2"), 142 | prop.Type(prop.TypeText), 143 | prop.Placeholder("Search"), 144 | vecty.Attribute("aria-label", "Search"), 145 | ), 146 | ), 147 | elem.Button( 148 | vecty.Markup( 149 | vecty.Class("btn", "btn-outline-success", "my-2", "my-sm-0"), 150 | prop.Type(prop.TypeSubmit), 151 | ), 152 | vecty.Text("Search"), 153 | ), 154 | ), 155 | ), 156 | ), 157 | ) 158 | } 159 | -------------------------------------------------------------------------------- /examples/components/router.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | // import "github.com/factorapp/factor/markup" 4 | 5 | // type routeMaker = func() (markup.Componer, error) 6 | 7 | // func newRouteMaker(tag string) routeMaker { 8 | // return func() (markup.Componer, error) { 9 | // return markup.New(tag) 10 | // } 11 | // } 12 | 13 | // var routes = map[string]routeMaker{ 14 | // "/about", newRouteMaker("routes.About"), 15 | // "/blog/{slug}": newRouteMaker("routes.Blog"), 16 | // } 17 | 18 | // type Router struct { 19 | // routes map[string]routeMaker 20 | // } 21 | 22 | // func NewRouter() Router { 23 | // return Router{routes: routes} 24 | // } 25 | 26 | // func (r *Router) Route(path string) markup.Componer { 27 | // fn, ok := r.routes[path] 28 | // if !ok { 29 | // return ForOhForComp{} 30 | // } 31 | // comp, err := fn() 32 | // if err != nil { 33 | // return ForOhForComp{} 34 | // } 35 | // return comp 36 | // } 37 | 38 | // func (r *Router) Calculate() error { 39 | 40 | // // walk the routes directory 41 | 42 | // // add a routeComponent entry for each html/go combo 43 | 44 | // // something with regex 45 | 46 | // // Profit 47 | // } 48 | -------------------------------------------------------------------------------- /examples/components/todo.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import "github.com/gowasm/vecty" 4 | 5 | type Todo struct { 6 | vecty.Core 7 | Name string 8 | Description string 9 | Permalink string 10 | } 11 | 12 | func (t *Todo) GetAge() string { 13 | return "5" 14 | } 15 | -------------------------------------------------------------------------------- /examples/components/todo_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package components 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | "github.com/gowasm/vecty/prop" 9 | ) 10 | 11 | func (p *Todo) Render() vecty.ComponentOrHTML { 12 | return elem.Body( 13 | elem.Div( 14 | elem.Heading1( 15 | 16 | vecty.Text(p.Name), 17 | ), 18 | elem.Small( 19 | 20 | vecty.Text(p.Description), 21 | ), 22 | elem.Div( 23 | vecty.Text("("), 24 | elem.Anchor( 25 | vecty.Markup( 26 | prop.Href(p.Permalink), 27 | ), 28 | vecty.Text("Permalink"), 29 | ), 30 | vecty.Text(")"), 31 | ), 32 | elem.Div( 33 | 34 | vecty.Text("Active for "), 35 | vecty.Text(p.GetAge()), 36 | vecty.Text(" days"), 37 | ), 38 | ), 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /examples/models/client_generated.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "net/rpc" 5 | ) 6 | 7 | type Client struct { 8 | 9 | TodoClient 10 | 11 | } 12 | 13 | func NewClient(port int) (*Client, error) { 14 | client, err := rpc.DialHTTP("tcp", ":1234") 15 | if err != nil { 16 | return nil, err 17 | } 18 | return &Client{ 19 | 20 | TodoClient{RPC: client}, 21 | 22 | }, nil 23 | } 24 | -------------------------------------------------------------------------------- /examples/models/server_generated.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "net/rpc" 5 | "net" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | func StartRPCServer(port int) error { 11 | rpc.HandleHTTP() 12 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 13 | if err != nil { 14 | return err 15 | } 16 | return http.Serve(listener, nil) 17 | } 18 | -------------------------------------------------------------------------------- /examples/models/todo.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/satori/go.uuid" 8 | ) 9 | 10 | func init() { 11 | rand.Seed(time.Now().UnixNano()) 12 | } 13 | 14 | // Todo is a model 15 | type Todo struct { 16 | ID uuid.UUID 17 | Name string 18 | Description string 19 | Permalink string 20 | } 21 | 22 | func (t Todo) GetAge() int { 23 | return rand.Int() 24 | } 25 | -------------------------------------------------------------------------------- /examples/models/todo_client.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // this file is generated by 'factor dev' but won't be overwritten. feel free to 4 | // fill in your own client logic here! 5 | 6 | import ( 7 | "net/rpc" 8 | 9 | "github.com/satori/go.uuid" 10 | ) 11 | 12 | type TodoClient struct{ 13 | RPC *rpc.Client 14 | } 15 | 16 | func (cl *TodoClient) Get(id uuid.UUID) (*Todo, error) { 17 | req := GetTodoReq{ID: id} 18 | var res GetTodoRes 19 | if err := cl.RPC.Call("Todo.Get", req, &res); err != nil { 20 | return nil, err 21 | } 22 | return &res.Data, nil 23 | } 24 | 25 | // TODO: more 26 | -------------------------------------------------------------------------------- /examples/models/todo_server.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // this file is generated by 'factor dev' but won't be overwritten. feel free to 4 | // fill in your server implementation here! 5 | 6 | import ( 7 | "net/rpc" 8 | ) 9 | 10 | var todoServer = NewTodoServer() 11 | 12 | type TodoServer struct{} 13 | 14 | func NewTodoServer() *TodoServer { 15 | return &TodoServer{} 16 | } 17 | 18 | func (srv *TodoServer) Create(req CreateTodoReq, res *CreateTodoRes) error { 19 | // TODO 20 | return nil 21 | } 22 | 23 | func (srv *TodoServer) Get(req GetTodoReq, res *GetTodoRes) error { 24 | // TODO 25 | return nil 26 | } 27 | 28 | func (srv *TodoServer) Update(req UpdateTodoReq, res *UpdateTodoRes) error { 29 | // TODO 30 | return nil 31 | } 32 | 33 | func (srv *TodoServer) Delete(req DeleteTodoReq, res *DeleteTodoRes) error { 34 | // TODO 35 | return nil 36 | } 37 | 38 | func init() { 39 | rpc.RegisterName("Todo", todoServer) 40 | } 41 | -------------------------------------------------------------------------------- /examples/models/todo_types.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // this file is generated by 'factor dev' but won't be overwritten. feel free 4 | // to make any modifications you want in here. If you do, make sure to 5 | // check TodoServer and TodoClient because you might need to 6 | // update them too so the thing still builds. 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/satori/go.uuid" 12 | ) 13 | 14 | type CreateTodoReq struct { 15 | Ctx context.Context 16 | Data Todo 17 | } 18 | 19 | type CreateTodoRes struct { 20 | Err error 21 | } 22 | 23 | type GetTodoReq struct { 24 | ID uuid.UUID 25 | } 26 | 27 | type GetTodoRes struct { 28 | Data Todo 29 | } 30 | 31 | type UpdateTodoReq struct { 32 | ID uuid.UUID 33 | New Todo 34 | } 35 | 36 | type UpdateTodoRes struct { 37 | Err error 38 | } 39 | 40 | type DeleteTodoReq struct { 41 | ID uuid.UUID 42 | } 43 | 44 | type DeleteTodoRes struct { 45 | Err error 46 | } 47 | -------------------------------------------------------------------------------- /examples/routes/_error.html: -------------------------------------------------------------------------------- 1 |

status

2 | 3 |

error.message

4 | 5 | 27 | -------------------------------------------------------------------------------- /examples/routes/about.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import "github.com/gopherjs/vecty" 4 | import "github.com/gopherjs/vecty/elem" 5 | 6 | func asAnArg(arg *vecty.HTML) *vecty.HTML { 7 | return arg 8 | } 9 | 10 | type About struct{} 11 | 12 | func (p *About) Render() vecty.ComponentOrHTML { 13 | 14 | header := elem.Heading1(vecty.Text("Hello")) 15 | content := elem.Paragraph(vecty. 16 | 17 | // hello world I'm a comment 18 | Text("paragraphs")) 19 | 20 | return elem.Div(vecty.Markup(vecty.Class("content")), vecty.Text(header), vecty.Text(content), vecty.Text(asAnArg(elem.Paragraph(vecty.Text("help I'm caught in the web"))))) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /examples/routes/about.gox: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import "github.com/gopherjs/vecty" 4 | 5 | func asAnArg(arg *vecty.HTML) *vecty.HTML { 6 | 7 | return arg 8 | } 9 | 10 | func (p *About) Render() vecty.ComponentOrHTML{ 11 | 12 | header :=

Hello

13 | content :=

paragraphs

14 | 15 | // hello world I'm a comment 16 | return
17 | {header} 18 | {content} 19 | {asAnArg(

help I'm caught in the web

)} 20 |
21 | } 22 | -------------------------------------------------------------------------------- /examples/routes/about.html: -------------------------------------------------------------------------------- 1 |

About this site

2 | 3 |

This is the 'about' page. There's not much here.

4 | -------------------------------------------------------------------------------- /examples/routes/about_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package routes 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | ) 9 | 10 | type About struct { 11 | vecty.Core 12 | } 13 | 14 | func (p *About) Render() vecty.ComponentOrHTML { 15 | return elem.Body( 16 | elem.Heading1( 17 | vecty.Text("About this site"), 18 | ), 19 | elem.Paragraph( 20 | vecty.Text("This is the 'about' page. There's not much here."), 21 | ), 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /examples/routes/blog.[slug].html: -------------------------------------------------------------------------------- 1 | 2 |

blog

3 | 4 |

This is a blog page, will populate by parameter

5 | -------------------------------------------------------------------------------- /examples/routes/blogslug_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package routes 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | ) 9 | 10 | type Blogslug struct { 11 | vecty.Core 12 | } 13 | 14 | func (p *Blogslug) Render() vecty.ComponentOrHTML { 15 | return elem.Body( 16 | elem.Heading1( 17 | vecty.Text("blog"), 18 | ), 19 | elem.Paragraph( 20 | vecty.Text("This is a blog page, will populate by parameter"), 21 | ), 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /examples/routes/error_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package routes 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | ) 9 | 10 | type Error struct { 11 | vecty.Core 12 | } 13 | 14 | func (p *Error) Render() vecty.ComponentOrHTML { 15 | return elem.Body( 16 | elem.Heading1( 17 | vecty.Text("status"), 18 | ), 19 | elem.Paragraph( 20 | vecty.Text("error.message"), 21 | ), 22 | elem.Style( 23 | vecty.Text("h1,\n\tp {\n\t\tmargin: 0 auto;\n\t}\n\n\th1 {\n\t\tfont-size: 2.8em;\n\t\tfont-weight: 700;\n\t\tmargin: 0 0 0.5em 0;\n\t}\n\n\tp {\n\t\tmargin: 1em auto;\n\t}\n\n\t@media (min-width: 480px) {\n\t\th1 {\n\t\t\tfont-size: 4em;\n\t\t}\n\t}"), 24 | ), 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /examples/routes/index.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/gowasm/vecty" 8 | ) 9 | 10 | type Index struct { 11 | vecty.Core 12 | CountText string 13 | count int 14 | } 15 | 16 | func (i *Index) OnClick(e *vecty.Event) { 17 | fmt.Println("Someone clicked on me", e.Target) 18 | i.count++ 19 | i.CountText = "Click Count: " + strconv.Itoa(i.count) 20 | 21 | vecty.Rerender(i) 22 | } 23 | -------------------------------------------------------------------------------- /examples/routes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |

Bootstrap starter template

7 |

Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.

8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /examples/routes/index_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package routes 4 | 5 | import ( 6 | components "github.com/factorapp/factor/examples/components" 7 | "github.com/gowasm/vecty" 8 | "github.com/gowasm/vecty/elem" 9 | ) 10 | 11 | func (p *Index) Render() vecty.ComponentOrHTML { 12 | return elem.Body( 13 | elem.Body( 14 | &components.Nav{}, 15 | ), 16 | elem.Main( 17 | vecty.Markup( 18 | vecty.Attribute("role", "main"), 19 | vecty.Class("container"), 20 | ), 21 | elem.Div( 22 | vecty.Markup( 23 | vecty.Class("starter-template"), 24 | ), 25 | elem.Heading1( 26 | vecty.Text("Bootstrap starter template"), 27 | ), 28 | elem.Paragraph( 29 | vecty.Markup( 30 | vecty.Class("lead"), 31 | ), 32 | vecty.Text("Use this document as a way to quickly start any new project."), 33 | elem.Break(), 34 | vecty.Text("All you get is this text and a mostly barebones HTML document."), 35 | ), 36 | ), 37 | ), 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /examples/routes/todo.[id].html: -------------------------------------------------------------------------------- 1 | 2 |

Todo

3 | 4 |

I'm not sure exactly how this'll work yet. Haven't run factor dev against it yet...

5 | -------------------------------------------------------------------------------- /examples/routes/todoid_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package routes 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | ) 9 | 10 | type Todoid struct { 11 | vecty.Core 12 | } 13 | 14 | func (p *Todoid) Render() vecty.ComponentOrHTML { 15 | return elem.Body( 16 | elem.Heading1( 17 | vecty.Text("Todo"), 18 | ), 19 | elem.Paragraph( 20 | vecty.Text("I'm not sure exactly how this'll work yet. Haven't run factor dev against it yet..."), 21 | ), 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /examples/routes/todos.html: -------------------------------------------------------------------------------- 1 |
2 | {{ range .Todos }} 3 | 7 | {{ .TodoComponent . }} 8 | {{ end }} 9 |
10 | -------------------------------------------------------------------------------- /examples/routes/todos_generated.go: -------------------------------------------------------------------------------- 1 | // This file was created with https://github.com/factorapp/factor 2 | // using https://jsgo.io/dave/html2vecty 3 | package routes 4 | 5 | import ( 6 | "github.com/gowasm/vecty" 7 | "github.com/gowasm/vecty/elem" 8 | ) 9 | 10 | type Todos struct { 11 | vecty.Core 12 | } 13 | 14 | func (p *Todos) Render() vecty.ComponentOrHTML { 15 | return elem.Body( 16 | elem.Div( 17 | vecty.Markup( 18 | vecty.Class("todos"), 19 | ), 20 | vecty.Text("{{ range .Todos }}"), 21 | vecty.Text("{{ .TodoComponent . }}\n {{ end }}"), 22 | ), 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /examples/server/main.go: -------------------------------------------------------------------------------- 1 | // build !js,wasm 2 | package main 3 | 4 | import ( 5 | "log" 6 | "net/http" 7 | 8 | "github.com/gorilla/rpc/v2" 9 | "github.com/gorilla/rpc/v2/json" 10 | 11 | "github.com/factorapp/factor/examples/models" 12 | ) 13 | 14 | func wasmHandler(w http.ResponseWriter, r *http.Request) { 15 | w.Header().Set("Content-Type", "application/wasm") 16 | http.ServeFile(w, r, "./app/example.wasm") 17 | } 18 | func main() { 19 | s := rpc.NewServer() 20 | s.RegisterCodec(json.NewCodec(), "application/json") 21 | s.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8") 22 | tds := new(models.TodoServer) 23 | s.RegisterService(tds, "TodoServer") 24 | http.HandleFunc("/app/example.wasm", wasmHandler) 25 | http.Handle("/rpc", s) 26 | /* cwd, err := os.Getcwd() 27 | if err != nil { 28 | panic(err) 29 | } 30 | app := filepath.Join(cwd, "app") 31 | */ 32 | 33 | http.HandleFunc("/wasm_exec.js", jsHandler) 34 | 35 | http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets")))) 36 | http.HandleFunc("/", indexHandler) 37 | log.Fatal(http.ListenAndServe(":3000", nil)) 38 | } 39 | func jsHandler(w http.ResponseWriter, r *http.Request) { 40 | http.ServeFile(w, r, "./app/wasm_exec.js") 41 | } 42 | 43 | func indexHandler(w http.ResponseWriter, r *http.Request) { 44 | http.ServeFile(w, r, "./app/index.html") 45 | } 46 | -------------------------------------------------------------------------------- /files/file_types.go: -------------------------------------------------------------------------------- 1 | package files 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | var reg = regexp.MustCompile("[^a-zA-Z0-9]+") 11 | 12 | func IsHTML(info os.FileInfo) bool { 13 | return filepath.Ext(info.Name()) == ".html" || filepath.Ext(info.Name()) == ".ghtml" 14 | } 15 | 16 | func GeneratedGoFileName(base, name string) string { 17 | return filepath.Join(base, strings.ToLower(name)+"_generated.go") 18 | } 19 | func ComponentName(path string) string { 20 | base := filepath.Base(path) 21 | base = strings.Replace(base, filepath.Ext(path), "", -1) 22 | return strings.Title(reg.ReplaceAllString(base, "")) 23 | } 24 | 25 | func RouteName(path string) string { 26 | return ComponentName(path) 27 | } 28 | -------------------------------------------------------------------------------- /files/files.go: -------------------------------------------------------------------------------- 1 | package files 2 | 3 | import ( 4 | "path/filepath" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | var fileNameRegexp = regexp.MustCompile("[^a-zA-Z0-9]+") 10 | 11 | func SanitizedName(path string) string { 12 | base := filepath.Base(path) 13 | base = strings.Replace(base, filepath.Ext(path), "", -1) 14 | return strings.Title(fileNameRegexp.ReplaceAllString(base, "")) 15 | } 16 | 17 | func GeneratedName(path string) string { 18 | n := SanitizedName(path) 19 | return n + "_generated.go" 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/factorapp/factor 2 | 3 | require ( 4 | github.com/BurntSushi/toml v0.3.0 // indirect 5 | github.com/aymerick/douceur v0.2.0 6 | github.com/dave/jennifer v1.0.2 7 | github.com/davecgh/go-spew v1.1.0 // indirect 8 | github.com/fsnotify/fsnotify v1.4.7 // indirect 9 | github.com/go-humble/detect v0.1.2 // indirect 10 | github.com/go-humble/router v0.5.0 // indirect 11 | github.com/gobuffalo/envy v1.6.3 12 | github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect 13 | github.com/gopherjs/gopherwasm v0.0.0-20180715123310-0765e455aaf6 // indirect 14 | github.com/gopherjs/vecty v0.0.0-20180525005238-a3bd138280bf 15 | github.com/gorilla/css v1.0.0 // indirect 16 | github.com/gorilla/rpc v1.1.0 17 | github.com/gowasm/router v0.5.0 18 | github.com/gowasm/vecty v0.0.0-20180708230244-0151905da086 19 | github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect 20 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 21 | github.com/joho/godotenv v1.2.0 // indirect 22 | github.com/kr/pretty v0.1.0 // indirect 23 | github.com/magiconair/properties v1.8.0 // indirect 24 | github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9 25 | github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 // indirect 26 | github.com/pelletier/go-toml v1.2.0 // indirect 27 | github.com/pkg/errors v0.8.0 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/satori/go.uuid v1.2.0 30 | github.com/spf13/afero v1.1.1 // indirect 31 | github.com/spf13/cast v1.2.0 // indirect 32 | github.com/spf13/cobra v0.0.3 33 | github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec // indirect 34 | github.com/spf13/pflag v1.0.1 // indirect 35 | github.com/spf13/viper v1.0.2 36 | github.com/stretchr/testify v1.2.2 // indirect 37 | github.com/tdewolff/buffer v1.1.0 // indirect 38 | github.com/tdewolff/parse v0.0.0-20180607042416-d739d6fccb09 39 | github.com/tdewolff/test v0.0.0-20171106182207-265427085153 // indirect 40 | golang.org/x/sys v0.0.0-20180715085529-ac767d655b30 // indirect 41 | golang.org/x/text v0.3.0 // indirect 42 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 43 | gopkg.in/yaml.v2 v2.2.1 // indirect 44 | honnef.co/go/js/dom v0.0.0-20180323154144-6da835bec70f // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= 2 | github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 4 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 5 | github.com/dave/jennifer v1.0.2 h1:ixSwWgh8HCIJN9GlVNvdbKHrD/qfh5Mvd4ZCaFAJbr8= 6 | github.com/dave/jennifer v1.0.2/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 10 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 11 | github.com/go-humble/detect v0.1.2 h1:mitrDomAvFJiH2D4LNwlRDj5d/3woOATAxKfIsoMP0I= 12 | github.com/go-humble/detect v0.1.2/go.mod h1:pVrYDy9oO+niVnRn94ljzppsRGpVs0l2+xTDW6u1nAQ= 13 | github.com/go-humble/router v0.5.0 h1:tVzuDGj2b8PDjyT/x/saIxgir+qfRdFGEkLuhpAECbU= 14 | github.com/go-humble/router v0.5.0/go.mod h1:skbvzVQASVDieX3rvgRsHnd7oe6JBr98BxAPeheHioM= 15 | github.com/gobuffalo/envy v1.6.3 h1:I9iyNACF0Tovfta7iqLrUAXFHYBDBWveQrjpEv2XeWs= 16 | github.com/gobuffalo/envy v1.6.3/go.mod h1:gOxUQY+OEwqH1a2m25Sqax1GIhj31tPNOIdFzj8QThs= 17 | github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ= 18 | github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 19 | github.com/gopherjs/gopherwasm v0.0.0-20180715123310-0765e455aaf6 h1:jlhhn5+kvyFKC+diq6kYPdVgkemQMVea3cRRUSGRZBw= 20 | github.com/gopherjs/gopherwasm v0.0.0-20180715123310-0765e455aaf6/go.mod h1:p5NWZjUtSXtYIzAvkf5nz1PKQ1ZP2kf/mb4apd1qN24= 21 | github.com/gopherjs/vecty v0.0.0-20180525005238-a3bd138280bf h1:J7WjKCdpxMhXDzFPP6d41YonnCnPi1JkYOFxIPoH1E0= 22 | github.com/gopherjs/vecty v0.0.0-20180525005238-a3bd138280bf/go.mod h1:5YRDuy7i7SgM0kL+0b5oq+jfDTqPyzV3TQbcDkBvcoE= 23 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 24 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 25 | github.com/gorilla/rpc v1.1.0 h1:marKfvVP0Gpd/jHlVBKCQ8RAoUPdX7K1Nuh6l1BNh7A= 26 | github.com/gorilla/rpc v1.1.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= 27 | github.com/gowasm/router v0.5.0 h1:y77vpo2QRJGpi/a072YIHw+wdAI0U4M9z0UCznW7mxs= 28 | github.com/gowasm/router v0.5.0/go.mod h1:QxUQWSLbwUpnH+eYpsp4L/cmuxMjK93YOePIyd2tav4= 29 | github.com/gowasm/vecty v0.0.0-20180708230244-0151905da086 h1:yCAW35FtKqarvfMi/1uAdTs47la6HtNF7Onl6w1+A8Q= 30 | github.com/gowasm/vecty v0.0.0-20180708230244-0151905da086/go.mod h1:otcQ+kDw3rABBAlJh8bQ1+OtW2kjmJzXo2cXYyFtkj4= 31 | github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= 32 | github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= 33 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 34 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 35 | github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc= 36 | github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 37 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 38 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 39 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 40 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 41 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 42 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= 43 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 44 | github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9 h1:Y94YB7jrsihrbGSqRNMwRWJ2/dCxr0hdC2oPRohkx0A= 45 | github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 46 | github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun9knAVAR8tg/aHJEr5DgtcbqyvzacK+CDCaI= 47 | github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 48 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 49 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 50 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 51 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 52 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 54 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 55 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 56 | github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I= 57 | github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 58 | github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= 59 | github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= 60 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 61 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 62 | github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig= 63 | github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 64 | github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= 65 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 66 | github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso= 67 | github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= 68 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 69 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 70 | github.com/tdewolff/buffer v1.1.0 h1:NpNuBqn78WinZjWlJvr0SvCiNKOObr+bIC6VL7QZZIE= 71 | github.com/tdewolff/buffer v1.1.0/go.mod h1:2aVmbfts5BiODsIH0lFp+FX5BiqjSS7P7+cKqieIAb0= 72 | github.com/tdewolff/parse v0.0.0-20180607042416-d739d6fccb09 h1:pHcEtlNu1Bf5P32lo80YMtGn5FewhZXbfE45EhlBe7s= 73 | github.com/tdewolff/parse v0.0.0-20180607042416-d739d6fccb09/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ= 74 | github.com/tdewolff/parse v1.1.0 h1:tMjj9GCK8zzwjWyxdZ4pabzdWO1VG+G3bvCnG6aUIyQ= 75 | github.com/tdewolff/parse v1.1.0/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ= 76 | github.com/tdewolff/test v0.0.0-20171106182207-265427085153 h1:B1Z2txQ2QI9nsWELeEvGBAdNhMylGMSCCypjsLJh/Mw= 77 | github.com/tdewolff/test v0.0.0-20171106182207-265427085153/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4= 78 | golang.org/x/sys v0.0.0-20180715085529-ac767d655b30 h1:4bYUqrXBoiI7UFQeibUwFhvcHfaEeL75O3lOcZa964o= 79 | golang.org/x/sys v0.0.0-20180715085529-ac767d655b30/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 80 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 81 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 83 | gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 84 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 85 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 86 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 87 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 88 | honnef.co/go/js/dom v0.0.0-20180323154144-6da835bec70f h1:8wvPTUK0UjW0bwvb0Q2mdhzSf78P6dXQI6mE9v+jJvI= 89 | honnef.co/go/js/dom v0.0.0-20180323154144-6da835bec70f/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= 90 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "github.com/factorapp/factor/cmd" 18 | 19 | func main() { 20 | cmd.Execute() 21 | } 22 | -------------------------------------------------------------------------------- /model/client.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | func WriteClientFile(wr io.Writer, names []string) error { 8 | return clientsTpl.Execute(wr, map[string]interface{}{ 9 | "Clients": names, 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "strings" 7 | 8 | "github.com/factorapp/factor/files" 9 | ) 10 | 11 | // Model represents a model 12 | type Model struct { 13 | fullPath string 14 | lowerName string 15 | } 16 | 17 | func (m Model) ServerName() string { 18 | return strings.Title(m.lowerName) + "Server" 19 | } 20 | 21 | func (m Model) ClientName() string { 22 | return strings.Title(m.lowerName) + "Client" 23 | } 24 | 25 | func (m Model) TypesFilename() string { 26 | return m.lowerName + "_types.go" 27 | } 28 | 29 | func (m Model) ServerFilename() string { 30 | return m.lowerName + "_server.go" 31 | } 32 | 33 | func (m Model) ClientFilename() string { 34 | return m.lowerName + "_client.go" 35 | } 36 | 37 | func (m Model) Write(typesW io.Writer, clientW io.Writer, serverW io.Writer) error { 38 | data := map[string]interface{}{ 39 | "UpperName": strings.Title(m.lowerName), 40 | "LowerName": m.lowerName, 41 | } 42 | if err := modelTypesTpl.Execute(typesW, data); err != nil { 43 | return err 44 | } 45 | if err := modelServerTpl.Execute(serverW, data); err != nil { 46 | return err 47 | } 48 | if err := modelClientTpl.Execute(clientW, data); err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | func New(fullPath string, info os.FileInfo) Model { 55 | return Model{ 56 | fullPath: fullPath, 57 | lowerName: strings.ToLower(files.SanitizedName(info.Name())), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /model/process.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | // ProcessAll processes models starting at base 11 | func ProcessAll(base string) error { 12 | servers := []string{} 13 | clients := []string{} 14 | err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error { 15 | if err != nil { 16 | return err 17 | } 18 | if info.IsDir() { 19 | return nil 20 | } 21 | if !isModel(info) { 22 | return nil 23 | } 24 | model := New(path, info) 25 | 26 | typesFilename := filepath.Join(base, model.TypesFilename()) 27 | log.Printf("generating %s", typesFilename) 28 | typesFd, err := os.Create(typesFilename) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | serverFilename := filepath.Join(base, model.ServerFilename()) 34 | log.Printf("generating %s", serverFilename) 35 | serverFd, err := os.Create(serverFilename) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | clientFilename := filepath.Join(base, model.ClientFilename()) 41 | log.Printf("generating %s", clientFilename) 42 | clientFd, err := os.Create(clientFilename) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | if err := model.Write(typesFd, clientFd, serverFd); err != nil { 48 | return err 49 | } 50 | servers = append(servers, model.ServerName()) 51 | clients = append(clients, model.ClientName()) 52 | return nil 53 | }) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | log.Printf("writing client file") 59 | clientFd, err := os.Create(filepath.Join(base, "client_generated.go")) 60 | if err != nil { 61 | return err 62 | } 63 | if err := WriteClientFile(clientFd, clients); err != nil { 64 | return err 65 | } 66 | 67 | log.Printf("writing server file") 68 | serverFd, err := os.Create(filepath.Join(base, "server_generated.go")) 69 | if err != nil { 70 | return err 71 | } 72 | if err := WriteServerFile(serverFd); err != nil { 73 | return err 74 | } 75 | log.Printf("generated servers and clients: %s", servers) 76 | return nil 77 | } 78 | 79 | func isModel(info os.FileInfo) bool { 80 | // ignore generated files 81 | if strings.HasSuffix(info.Name(), "_generated.go") || 82 | strings.HasSuffix(info.Name(), "_server.go") || 83 | strings.HasSuffix(info.Name(), "_types.go") || 84 | strings.HasSuffix(info.Name(), "_client.go") { 85 | return false 86 | } 87 | // assume all other go files are models 88 | return filepath.Ext(info.Name()) == ".go" 89 | } 90 | -------------------------------------------------------------------------------- /model/server.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | func WriteServerFile(wr io.Writer) error { 8 | return serversTpl.Execute(wr, nil) 9 | } 10 | -------------------------------------------------------------------------------- /model/template.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "log" 5 | "text/template" 6 | ) 7 | 8 | var modelTypesTpl *template.Template 9 | var modelServerTpl *template.Template 10 | var modelClientTpl *template.Template 11 | var serversTpl *template.Template 12 | var clientsTpl *template.Template 13 | 14 | func init() { 15 | var err error 16 | modelTypesTpl, err = template.New("modelTypes").Parse(`package models 17 | 18 | // this file is generated by 'factor dev' but won't be overwritten. feel free 19 | // to make any modifications you want in here. If you do, make sure to 20 | // check {{.UpperName}}Server and {{.UpperName}}Client because you might need to 21 | // update them too so the thing still builds. 22 | 23 | import ( 24 | "context" 25 | 26 | "github.com/satori/go.uuid" 27 | ) 28 | 29 | type Create{{.UpperName}}Req struct { 30 | Ctx context.Context 31 | Data {{.UpperName}} 32 | } 33 | 34 | type Create{{.UpperName}}Res struct { 35 | Err error 36 | } 37 | 38 | type Get{{.UpperName}}Req struct { 39 | ID uuid.UUID 40 | } 41 | 42 | type Get{{.UpperName}}Res struct { 43 | Data {{.UpperName}} 44 | } 45 | 46 | type Update{{.UpperName}}Req struct { 47 | ID uuid.UUID 48 | New {{.UpperName}} 49 | } 50 | 51 | type Update{{.UpperName}}Res struct { 52 | Err error 53 | } 54 | 55 | type Delete{{.UpperName}}Req struct { 56 | ID uuid.UUID 57 | } 58 | 59 | type Delete{{.UpperName}}Res struct { 60 | Err error 61 | } 62 | `) 63 | if err != nil { 64 | log.Fatalf("parsing types template: %s", err) 65 | } 66 | 67 | modelServerTpl, err = template.New("modelServer").Parse(`package models 68 | 69 | // this file is generated by 'factor dev' but won't be overwritten. feel free to 70 | // fill in your server implementation here! 71 | 72 | import ( 73 | "net/rpc" 74 | ) 75 | 76 | var {{.LowerName}}Server = New{{.UpperName}}Server() 77 | 78 | type {{.UpperName}}Server struct{} 79 | 80 | func New{{.UpperName}}Server() *{{.UpperName}}Server { 81 | return &{{.UpperName}}Server{} 82 | } 83 | 84 | func (srv *{{.UpperName}}Server) Create(req Create{{.UpperName}}Req, res *Create{{.UpperName}}Res) error { 85 | // TODO 86 | return nil 87 | } 88 | 89 | func (srv *{{.UpperName}}Server) Get(req Get{{.UpperName}}Req, res *Get{{.UpperName}}Res) error { 90 | // TODO 91 | return nil 92 | } 93 | 94 | func (srv *{{.UpperName}}Server) Update(req Update{{.UpperName}}Req, res *Update{{.UpperName}}Res) error { 95 | // TODO 96 | return nil 97 | } 98 | 99 | func (srv *{{.UpperName}}Server) Delete(req Delete{{.UpperName}}Req, res *Delete{{.UpperName}}Res) error { 100 | // TODO 101 | return nil 102 | } 103 | 104 | func init() { 105 | rpc.RegisterName("{{.UpperName}}", {{.LowerName}}Server) 106 | } 107 | `) 108 | if err != nil { 109 | log.Fatalf("parsing server template: %s", err) 110 | } 111 | 112 | modelClientTpl, err = template.New("modelClient").Parse(`package models 113 | 114 | // this file is generated by 'factor dev' but won't be overwritten. feel free to 115 | // fill in your own client logic here! 116 | 117 | import ( 118 | "net/rpc" 119 | 120 | "github.com/satori/go.uuid" 121 | ) 122 | 123 | type {{.UpperName}}Client struct{ 124 | RPC *rpc.Client 125 | } 126 | 127 | func (cl *{{.UpperName}}Client) Get(id uuid.UUID) (*{{.UpperName}}, error) { 128 | req := Get{{.UpperName}}Req{ID: id} 129 | var res Get{{.UpperName}}Res 130 | if err := cl.RPC.Call("{{.UpperName}}.Get", req, &res); err != nil { 131 | return nil, err 132 | } 133 | return &res.Data, nil 134 | } 135 | 136 | // TODO: more 137 | `) 138 | if err != nil { 139 | log.Fatalf("parsing client template: %s", err) 140 | } 141 | 142 | serversTpl, err = template.New("servers").Parse(`package models 143 | 144 | import ( 145 | "net/rpc" 146 | "net" 147 | "fmt" 148 | "net/http" 149 | ) 150 | 151 | func StartRPCServer(port int) error { 152 | rpc.HandleHTTP() 153 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 154 | if err != nil { 155 | return err 156 | } 157 | return http.Serve(listener, nil) 158 | } 159 | `) 160 | if err != nil { 161 | log.Fatalf("parsing servers file: %s", err) 162 | } 163 | clientsTpl, err = template.New("clients").Parse(`package models 164 | 165 | import ( 166 | "net/rpc" 167 | ) 168 | 169 | type Client struct { 170 | {{range $cl := .Clients}} 171 | {{$cl}} 172 | {{end}} 173 | } 174 | 175 | func NewClient(port int) (*Client, error) { 176 | client, err := rpc.DialHTTP("tcp", ":1234") 177 | if err != nil { 178 | return nil, err 179 | } 180 | return &Client{ 181 | {{range $cl := .Clients}} 182 | {{$cl}}{RPC: client}, 183 | {{end}} 184 | }, nil 185 | } 186 | `) 187 | if err != nil { 188 | log.Fatalf("parsing clients file: %s", err) 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /route/process.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/factorapp/factor/component" 11 | "github.com/factorapp/factor/files" 12 | "github.com/gobuffalo/envy" 13 | ) 14 | 15 | // ProcessAll processes components starting at base 16 | func ProcessAll(base string) error { 17 | 18 | err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error { 19 | if err != nil { 20 | return err 21 | } 22 | if !info.IsDir() && files.IsHTML(info) { 23 | f, err := os.Open(path) 24 | if err != nil { 25 | return err 26 | } 27 | //c, _ := component.Parse(f, componentName(path)) 28 | 29 | route := files.RouteName(path) 30 | gfn := filepath.Join(base, strings.ToLower(route)+".go") 31 | _, err = os.Stat(gfn) 32 | var makeStruct bool 33 | if os.IsNotExist(err) { 34 | makeStruct = true 35 | } 36 | /*gofile, err := os.Create(goFileName(base, componentName(path))) 37 | if err != nil { 38 | return err 39 | } 40 | defer gofile.Close() 41 | 42 | c.Transform(gofile) 43 | */ 44 | appPkg := envy.CurrentPackage() 45 | transpiler, err := component.NewTranspiler(f, makeStruct, appPkg, route, "routes") 46 | if err != nil { 47 | log.Println("ERROR", err) 48 | return err 49 | } 50 | 51 | gofile, err := os.Create(files.GeneratedGoFileName(base, route)) 52 | if err != nil { 53 | log.Println("ERROR", err) 54 | return err 55 | } 56 | defer gofile.Close() 57 | _, err = io.WriteString(gofile, transpiler.Code()) 58 | if err != nil { 59 | log.Println("ERROR", err) 60 | return err 61 | } 62 | } 63 | return nil 64 | }) 65 | 66 | if err != nil { 67 | log.Printf("error walking the path %q: %v\n", base, err) 68 | } 69 | return err 70 | } 71 | --------------------------------------------------------------------------------