gonew: templates for new modules #61669
Replies: 46 comments 40 replies
-
And something like |
Beta Was this translation helpful? Give feedback.
-
Would it be reasonable to make this functionality part of the Since they both serve bootstrapping process, |
Beta Was this translation helpful? Give feedback.
-
I wrote my own template tool inspired by Python Cookiecutter that uses txtar format a few years back. https://github.com/carlmjohnson/springerle I think this makes sense as a thing to build into Go. For example, Node has a thing where npx create foo will automatically run a create-foo package (I forget the precise details of how it works). Another tool to look at is degit from the creator of Svelte, which clones a git repo while stripping out certain info. Anyhow, it’s good to hear Alan Donovan is working on GoPL2.0. 😊 |
Beta Was this translation helpful? Give feedback.
-
I think that regardless of how the new module will be generated (gonew or --template), it would be very interesting to be able to mark the code with some possible variables or notations that would be filled in by flags at the time of initialization of the new module CLI example: |
Beta Was this translation helpful? Give feedback.
-
Here's something I've been writing last couple of days: https://github.com/vedranvuk/boil |
Beta Was this translation helpful? Give feedback.
-
I wrote a similar tool a while back for Wails. Would love to see this as part of the standard tooling: https://github.com/leaanthony/gosod |
Beta Was this translation helpful? Give feedback.
This comment has been minimized.
This comment has been minimized.
-
Neat idea!
after looking at the package itself, it already supports versioning! Just the projen comment then —— have you heard of Projen before ? The TLDR is that it is a tool that eschews static one-time templates in favor of programmatic updates over the life of a project. imagine your project was bootstrapped with v1.1 of example.com/fooTemplate. With projen, you can upgrade to v1.2 in an automated fashion. This could involve adding, deleting, or editing files. I don’t think it’s necessarily something the go tool chain would support natively, but I just figured I’d mention it as it’s in the same orbit as templating, but takes the devils advocate approach of “templates are bad” (their words, not mine). just a thought! |
Beta Was this translation helpful? Give feedback.
-
Is it possible to contribute to this tool? I was thinking of having |
Beta Was this translation helpful? Give feedback.
-
We have a project called Often we are supporting old/new code patterns and assisting developers in migrating their code. {{- if .Config.Kafka.Enabled}}
{{- if .Config.Kafka.UseEventing}}
Eventing eventing.Client
{{else}}
Producer producer.Producer
Consumer consumer.ConsumerContext
{{- end}}
{{- end}}
templater config fileProjectPath: "."
Project:
ProjectID: "file-saver"
OrgID: "moovfinancial"
ProjectName: "File Saver"
Description: |
Download files from remote locations to store, handoff, and organize within Moov.
PrimaryBranch: "master"
CodeOwners: "@moovfinancial/bank-rails"
DockerCompose:
Enabled: true
SQL: true
MySQL:
Enabled: true
Port: 3306
Spanner:
Enabled: false
Kafka:
Enabled: true
UseEventing: true
Topics:
- events.wrath.v1:1:1
Templates:
GoService:
ServicePort: 8691
HealthPort: 9691
GoGithubActionsPrivate:
- Executable: file-saver API controller template// {{.GeneratedTag}}
package {{.Template.PackageID}}
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/moov-io/base/log"
"github.com/moovfinancial/errors"
"github.com/moovfinancial/go-zero-trust/v2/pkg/middleware"
"{{.ImportPath}}/pkg/api"
)
type {{.Template.ModelName}}Controller interface {
AppendRoutes(router *mux.Router) *mux.Router
}
func New{{.Template.ModelName}}Controller(logger log.Logger, service {{.Template.ModelName}}Service) {{.Template.ModelName}}Controller {
return &{{.Template.ModelID}}Controller{
logger: logger,
service: service,
}
}
type {{.Template.ModelID}}Controller struct {
logger log.Logger
service {{.Template.ModelName}}Service
}
func (c {{.Template.ModelID}}Controller) AppendRoutes(router *mux.Router) *mux.Router {
router.
Name("{{.Template.ModelName}}.create").
Methods("POST").
Path("{{.Template.BasePath}}{{.Template.ResourcePath}}").
HandlerFunc(c.create)
router.
Name("{{.Template.ModelName}}.list").
Methods("GET").
Path("{{.Template.BasePath}}{{.Template.ResourcePath}}").
HandlerFunc(c.list)
router.
Name("{{.Template.ModelName}}.get").
Methods("GET").
Path("{{.Template.BasePath}}{{.Template.ResourcePath}}/{ {{- .Template.ModelID}}ID}").
HandlerFunc(c.get)
{{if (eq .Template.NoUpdate false)}}
router.
Name("{{.Template.ModelName}}.update").
Methods("PUT").
Path("{{.Template.BasePath}}{{.Template.ResourcePath}}/{ {{- .Template.ModelID}}ID}").
HandlerFunc(c.update)
{{- end}}
{{if (eq .Template.NoDelete false)}}
router.
Name("{{.Template.ModelName}}.delete").
Methods("DELETE").
Path("{{.Template.BasePath}}{{.Template.ResourcePath}}/{ {{- .Template.ModelID}}ID}").
HandlerFunc(c.delete)
{{- end}}
return router
}
func (c *{{.Template.ModelID}}Controller) create(w http.ResponseWriter, r *http.Request) {
middleware.WithClaimsFromRequest(w, r, func(claims middleware.TrustedClaims) {
accountID := mux.Vars(r)["accountID"]
create := Create{{.Template.ModelName}}{}
if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
err = errors.Flag(errors.Wrap(err, "decoding"), errors.NotSerializable)
api.ErrorResponse(w, err, c.logger)
return
}
result, err := c.service.Create(r.Context(), claims, accountID, create)
if err != nil {
api.ErrorResponse(w, err, c.logger)
return
}
api.JsonResponse(w, result)
})
}
func (c *{{.Template.ModelID}}Controller) list(w http.ResponseWriter, r *http.Request) {
middleware.WithClaimsFromRequest(w, r, func(claims middleware.TrustedClaims) {
accountID := mux.Vars(r)["accountID"]
result, err := c.service.List(r.Context(), claims, accountID)
if err != nil {
api.ErrorResponse(w, err, c.logger)
return
}
api.JsonResponse(w, result)
})
}
func (c *{{.Template.ModelID}}Controller) get(w http.ResponseWriter, r *http.Request) {
middleware.WithClaimsFromRequest(w, r, func(claims middleware.TrustedClaims) {
params := mux.Vars(r)
accountID := params["accountID"]
{{.Template.ModelID}}ID := params["{{.Template.ModelID}}ID"]
result, err := c.service.Get(r.Context(), claims, accountID, {{.Template.ModelID}}ID)
if err != nil {
api.ErrorResponse(w, err, c.logger)
return
}
api.JsonResponse(w, result)
})
}
{{if (eq .Template.NoUpdate false)}}
func (c *{{.Template.ModelID}}Controller) update(w http.ResponseWriter, r *http.Request) {
middleware.WithClaimsFromRequest(w, r, func(claims middleware.TrustedClaims) {
params := mux.Vars(r)
accountID := params["accountID"]
{{.Template.ModelID}}ID := params["{{.Template.ModelID}}ID"]
update := Update{{.Template.ModelName}}{}
if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
err = errors.Flag(errors.Wrap(err, "decoding"), errors.NotSerializable)
api.ErrorResponse(w, err, c.logger)
return
}
result, err := c.service.Update(r.Context(), claims, accountID, {{.Template.ModelID}}ID, update)
if err != nil {
api.ErrorResponse(w, err, c.logger)
return
}
api.JsonResponse(w, result)
})
}
{{- end}}
{{if (eq .Template.NoDelete false)}}
func (c *{{.Template.ModelID}}Controller) delete(w http.ResponseWriter, r *http.Request) {
middleware.WithClaimsFromRequest(w, r, func(claims middleware.TrustedClaims) {
params := mux.Vars(r)
accountID := params["accountID"]
{{.Template.ModelID}}ID := params["{{.Template.ModelID}}ID"]
err := c.service.Delete(r.Context(), claims, accountID, {{.Template.ModelID}}ID)
if err != nil {
api.ErrorResponse(w, err, c.logger)
return
}
w.WriteHeader(http.StatusNoContent)
})
}
{{- end}} |
Beta Was this translation helpful? Give feedback.
-
One use case that I can think of is to have a standard header in every file like for example the used license. |
Beta Was this translation helpful? Give feedback.
-
Aren't you simply enabling something that you criticised here: golang-standards/project-layout#117 I'm already seeing comments online about how this will enable a global standard for all projects, etc. Which is horrifying. |
Beta Was this translation helpful? Give feedback.
-
Since this goes through the module system it will not contain files/directories that do not end up in the mod zip, correct? If there's a way to tag modules as templates it would probably be handy to use that mechanism to also specify files/directories to keep for readmes or anything that you'd want the template user to have access to but not want to embed |
Beta Was this translation helpful? Give feedback.
-
A common use for a scaffolding tool is to fill in the boilerplate for an extension to a framework where an extension can either be a third party shareable module or a project-specific subdirectory. This tool would work for either situation if it's okay for the extensions to be sub-modules or dependencies. That's certainly doable but I imagine not everyone would want to deal with that. It would be useful if there were a way to "adopt" the template as part of the current module. The easy part would be For more context, I'm specifically thinking of the sort of thing I mentioned in a previous reply: #61669 (reply in thread) |
Beta Was this translation helpful? Give feedback.
-
Thank you for bringing this discussion up. We have heard that Google uses a mono repo and it's very impressive. However, due to the lack of tools and expertise, we as an external company can't do the same. Therefore, we have to duplicate some parts of our logic for each project. To handle this, we decided to set up a separate repo and copy common parts of code, such as working with NATS and Redis, in that repo and use it in our projects. With this approach, the But some issues can't be resolved with this approach and we still need to duplicate some logic for each project. For example, if we want to support Redis in a project, we have to manually add the initialization and shutdown parts, call the monitoring parts, and add the configuration and deployment files as well. The problem gets worse when we need to refactor some parts of our common module and replace the old code with a better alternative. When I read this discussion, based on this experience, I think we are not alone and maybe this kind of issue can lead to better tools. Let me think aloud, if the
I apologize for the long description but I wanted to bring up the problem to find a better approach. |
Beta Was this translation helpful? Give feedback.
-
gonew can keep template files default permissions? this is my test template: https://github.com/zhb127/gonew-test ./helloserver/scripts/init-local-dev.sh
start local db...
start local redis...
The local development environment is initialized. create project from template: gonew github.com/zhb127/gonew-test/helloserver github.com/zhb127/demo
gonew: initialized github.com/zhb127/demo in ./demo but: cd demo
./scripts/init-local-dev.sh
zsh: permission denied: ./scripts/init-local-dev.sh |
Beta Was this translation helpful? Give feedback.
-
@rsc this could be interesting, if you can keep it simple. Previously I've used Copier, its a templating approach which, while powerful, has the limitation that the template itself is not valid code. So, all the toolings you might use for QA and doc don't work. So, in that regard, I find gonew to be very interesting. I would (like to) use this gonew to build projects based on existing patterns. Pull in a CLI implementation, then a service (and tests), perhaps a doc layout (which is compatible with our doc systems) and so on. |
Beta Was this translation helpful? Give feedback.
-
Really cool, I need to give it a try. |
Beta Was this translation helpful? Give feedback.
-
It is really good to see gonew working over this initial interaction. @rsc am I right in thinking that the modules being used to create a new module are meant to be fully functioning standalone and not templated themselves? I see translating an example project into a templated syntax to be a barrier to maintenance. I see the original module as being a fully working standalone project and not having to be adapted for the gonew tool. It feels that working over the go.mod and the directory name would give enough information to adapt references to internal imports that might be in the module. It feels like in the go module landscape all modules should rename useable and not a combination of traditional modules and templated sytntax modules. Hopefully I have been able to explain myself. In short I think gonew is a positive start 😄 |
Beta Was this translation helpful? Give feedback.
-
go new
Both of these are valuable on their own. Renaming a module obviously so. Extracting files less so, but it would be useful.† If those were separate commands then † Consider a web framework that by convention embeds all its template files on an exported Templates variable and has a naming system that lets you override a dependency's template file. Handy on its own but you usually want to start from the original template instead of from scratch. It's already in the module cache so being able to ask go to grab just that file from the module being used is simpler than going to github and finding the file at the correct version. requests for extensions so far generally fall into two categories
I think it's unlikely that templating will be included. Some basic package renaming would be nice but also seems unlikely given how awkward to specify this would be. In both cases you could use some tool after you call go new to get the desired results but you may not know that you have to so there would probably be some kind of "read me first" file though you may not know to look for it. If the file name were standardized, maybe go new could cat it before exiting. Alternately, while generally go doesn't run arbitrary code, maybe this could be the exception as it's meant to be run once by a developer. If there were a standard go tool/generate command name to run at the end it could collect any additional information and run any templating/refactoring. |
Beta Was this translation helpful? Give feedback.
This comment was marked as spam.
This comment was marked as spam.
-
Suddenly seeing this discussion, it is very interesting that our team also developed a similar tool in the past two years and used it extensively internally. This greatly contributes to improving development efficiency, especially in the case of microservice development. It's worth mentioning that we also supported caching and generating project structure based on module paths. However, the caching mechanism implemented in "gonew" is more elegant, we will also consider learning from it and optimizing our own implementation. Running this command below would generate a project in jupiter new github.com/douyu/jupiter-layout github.com/douyu/exampleapp Once gonew is merged into the official Go repository, we will immediately replace and start using it. 😄 Refer: |
Beta Was this translation helpful? Give feedback.
-
I was excited to use gonew but I had a issue that I want to share it with you. I made a quick solution for myself by cloning gonew and adding new command line flags in this repo but maybe you can think of this issue and find a better solution. |
Beta Was this translation helpful? Give feedback.
-
To echo others who have requested input parameters, it'd be great if I as the template author could tell gonew to unmarshal yaml or json provided by the template user into a struct accessible from the templates. The use case is to have the user provide values like a hostname or url. |
Beta Was this translation helpful? Give feedback.
-
A quick question/feedback as I am currently implementing a project launcher CLI which is similar to gonew. When I create a new project that will live in a workspace, seems like I need to update the go.work file before I build the project, otherwise I get:
The first line is a custom error message, the second occurs because I am trying to build a main.go into a main.wasm I probably have to check whether I'm in a workspace and if so, update the workspace programmatically, unless there is a better way? cc @bcmills |
Beta Was this translation helpful? Give feedback.
-
If the tool would not change, then I would consider renaming the tool to Without the ability to pass anything more than I think that with Still thanks for your great work 👍 |
Beta Was this translation helpful? Give feedback.
-
What about repositories which have multiple Go modules? For instance it is quite common to follow https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module. You can take a look at https://github.com/goyek/goyek which has |
Beta Was this translation helpful? Give feedback.
-
I want to be positive about experiments and contributions to the go ecosystem. I realise that we need to cater to developers of all levels and sometimes a problem that seems trivial to someone familiar with go may be a challenge to a developer just arriving from another language. That said, I don't see what problem gonew solves in its current state. Looking at the gonew source it's just replacing the old module name with the new name in source code. This is not a challenging problem to solve for someone who wants to copy an existing repo to a new namespace. When i saw the announcement I had hoped for something similar to cookiecutter, which i've been using for years to help bootstrap new projects. This has been incredibly useful to my teams in crafting and reusing project templates for go and other languages. I think the current version of gonew is effectively a conversation starter (and this discussion is the conversation!). What I hope is that gonew becomes something similar to what we're doing with cookiecutter so that we have a go native tool for bootstrapping projects and a common language for defining project templates. This would seem to be something easily built on top of text/template in conjunction with a project template specification. Is this the direction we're likely to head? |
Beta Was this translation helpful? Give feedback.
-
I think this can be useful. It can save people coding time if there are maintained standard code templates for common things like a web server, etc. similar to how some Google-internal server frameworks create the initial files. One con to keep in mind is that making things easier to write is not always the best thing. Copying over a large amount of boilerplate code over and over without understanding and real need can make things less maintainable over time (if there is not a refactoring to some dependency that is maintain, the folks who copied the code would need to maintain the copy). So it might be good to think about the ecosystem and maintainability impact of having such a tool. |
Beta Was this translation helpful? Give feedback.
-
I am looking at using this in the context of a monorepo. In this case, the templates and the destination would be in the same repo. Is there a good way to use this tool to reference a local path as the module template? Or am I over-complicating something that should just be a simple directory copy? |
Beta Was this translation helpful? Give feedback.
-
A common request we hear from users is some kind of 'go new' functionality to start a new module with some kind of basic template.
It seems like a tool along these lines should not be limited to templates provided by the Go team: it should be possible for anyone to define templates. That is, templates should just be ordinary modules. At that point the core job of 'go new' is mainly to download a template, change its module path, and drop it in a new directory for editing. There may be more of course, but that's the core.
After literally years of occasionally batting this idea around with @Sajmani, a few months ago I wrote a very trivial text-substitution-based, untested tool at rsc.io/tmp/gonew that did this and mentioned it to a few other people.
Almost immediately, I found another use: a book author emailed asking how best to make book examples available without requiring book readers to puzzle through the vagaries of cloning a git repo. The obvious answer is gonew, which lets people use only Go tools:
(In this form there's no changing the module path, just clone and drop in a new directory for editing.)
I've also found that form useful for grabbing a copy of some module I am using, to grep through code or to hack up and add to a go.work, like a very lightweight version of github.com/rogpeppe/gohack.
A couple teams at Google were also interested to try gonew for templating. The ServiceWeaver team was interested enough that they put
go install rsc.io/tmp/gonew@latest
in their getting started template docs. That both confirmed interest and scared me enough (tmp is in the name for a reason!) that I wrote a small, real, tested version at golang.org/x/tools/cmd/gonew, which I've just submitted.That tool, golang.org/x/tools/cmd/gonew, is experimental in the sense that we aren't sure what will happen next, but it is non-experimental in the sense that we promise to keep the core command line that exists today working. That way, people can confidently post scripts like ServiceWeaver now does:
I've also updated the golang.org/x/example repo to work with gonew, so here are three sample examples:
I am confident there is plenty that template authors will want from 'gonew' beyond this basic functionality. I'd like to hear more about that in the comments. We will try to identify general mechanisms that will satisfy many of the things people need.
Another interesting thing about having these kinds of templates is that, with a clear way to identify them (TBD), you can imagine good support for searching for templates on pkg.go.dev and for instantiating templates in the IDE experience using gopls. If you can think of other ways that the overall Go tooling ecosystem might be changed to incorporate the idea of templates, please post them too.
This is the very very beginning of what could turn out to be an important concept or tool in the Go ecosystem. Perhaps eventually it will graduate to 'go new'. It's a bit unusual for us to publish such a tiny idea, but the core functionality seems useful enough to be worth taking the community's attention to see how it should evolve.
Thanks for any feedback, and please keep the discussion respectful.
Beta Was this translation helpful? Give feedback.
All reactions