crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.47k stars 1.62k forks source link

Revisit `crystal init` #8844

Open straight-shoota opened 4 years ago

straight-shoota commented 4 years ago

crystal init is a tool for easily setting up a Crystal project. It's obviously opinionated and won't serve everyone's needs. But it can still be improved to fit more use cases and help more people get stared with a new project.

Since its inception in #534, there have only been little changes to the general behaviour of crystal init. A few details where fixed and improved since then.

A few things I'd like to set for debate:

Wizard

A great addition towards usability would be to provide an interactive wizard which guides the user through configuration instead of asking to provide everything as command line options. The rationale here is that crystal init is generally used pretty rarely (most people don't start a new project every other day), but especially often by beginners (for their first steps with crystal). So it should be easy to use and not require to be familiar with every detail about it. Right now, you need to be aware that crystal init expects at least two arguments to specify the project type (app or library) and name. The choice of type might already be unexpected and overwhelming for new users. The idea would be to ask the user about everything that's not specified in the command line and guide them through the process. crystal init is most commonly used interactively anyway, and for non-interactive usage it should be possible to specify everything on the command line. Example flow:

$ crystal init
What's the name of your project? foo
Is it intended as a library for others to depend on? [Y/n/?] y
    create  foo/.gitignore
    create  foo/.editorconfig
    create  foo/LICENSE
    create  foo/README.md
    create  foo/.travis.yml
    create  foo/shard.yml
    create  foo/src/foo.cr
    create  foo/spec/spec_helper.cr
    create  foo/spec/foo_spec.cr
Initialized empty Git repository in /data/projects/foo/.git/

License

I don't like that crystal init imposes the MIT license on its users. Of course, that can easily be removed and replaced. And in general it's a good idea to promote adding a license to a project, and making it an open source license. Still, having that MIT license in place doesn't have any value if the author doesn't mean it. Simply placing it removes any thought about choosing an appropriate license. That's usually not the main concern when starting a new project, but you should at least put some thought into that. And one might even question the legal effect of an autogenerated license application.

So I'd prefer to be more agnostic in that regard. IMO it's a bad practice to assume and impose any intention on licensing. It's too important. My idea would be to provide templates for several common open source licenses ({A,L}GL, Apache) and let the user choose either by CLI option or interactive question (if not option).

Makefile

I like to use a Makefile for my shards which is responsible for managing all tasks for development, testing and building. And we also use it in most crystal-lang repos. It's a great tool to improve workflow. It also serves as a documentation because you don't need to know which commands are necessary for specific tasks. This becomes even more important when more tools than just crystal and shards are involved. It would be a great enhancement (at least for me ^_^) if a good default Makefile template was automatically initialized by crystal init.

A generalized example: https://gist.github.com/straight-shoota/275685fcb8187062208c0871318c4a23

CI

Currently, a .travis-ci.yml is initialized by default. There should be other options as well (circle-ci, gitlab-ci, github actions). I doubt it would be useful to install all of them by default (because you likely won't use more than one or two providers anyway), but rather let the user select. We also have great guides for travis and circle now at https://crystal-lang.org/reference/guides/continuous_integration.html. Still it's great to have it autogenerated. The template could be enhanced by some featured mentioned in the guides (for example testing against latest). Also a link to the respective guide could be mentioned in the config file.

Incremental additions

Another useful feature would be to update an existing project folder. Especially when individual files can be optionally selected, it would be great to have simple method to add some optional files later to an existing project. For example, a circle-ci config file in a project that was only initialized with travis config. This would also be a way to upgrade the default configuration from a previous installment of the crystal init program. When crystal init is run against an existing project this could work interactively and ask about individual files. Currently, you can only overwrite or skip all existing files.

WDYT? Any other suggestions are welcome.

sudo-nice commented 4 years ago

Default git is an issue too.

sudo-nice commented 4 years ago

Personally, I like that give me all you got principle as it follows now, because it's very easy to delete unwanted config files, but it would take some effort to obtain them in case of need.

Do we need a wizard? Personally, I think it doesn't worth the complexity, because if crystal init stays the way it is now, it's really easy to choose between app and lib and give a project name.

What if you deleted some file, but found yourself in need of it after a while? Just run new crystal init somewhere else and copy the needed file(s) over.

License file? Make it empty. That way a user will be reminded of it, but woun't feel forced to use exact license.

Makefile? It's a very personal thing, it would be hard to come up with a pattern that suits the most.

j8r commented 4 years ago

Having an initializer directly by default in the language itself is a problem. The idea is nice, but it should be a different project from the language:

I think it can perfectly be a community project, with a modular approach to meet everyone's need.

straight-shoota commented 4 years ago

@j8r I guess technically it could be split from the compiler. But it's important to distribute crystal init with the compiler and stdlib, because it follows a batteries included approach that's very welcoming to new users. Wanna start using Crystal? Just install it and run crystal init to setup a default project. There should be no need to refer to some other application you need to install first to get this IMO very basic feature.

It might be option though, to move crystal init to shards init. But IMO it's good as it stands now. shards init deals with what's specific to shards' dependency manangement and crystal init sets up a full Crystal project. The difference here is that shards is actually not exclusive to Crystal, it can be used for other languages, too.

straight-shoota commented 4 years ago

@sudo-nice There's a perfectly valid reason for initializing a git repository: It's necessary if you want your project to be available in the shards ecosystem, because shards builds on top of git for dependency retrieval.

License file? Make it empty.

That would be really bad user experience. It would undoubtedly be better to have no license file at all instead of an empty one.

Makefile? It's a very personal thing, it would be hard to come up with a pattern that suits the most.

I disagree. It may be opinionated, but that's inevitable. It should be very much possible to compile a set of default recipies that's good for most use cases, at least as a start. See my example. What do you think doesn't fit there?

Just run new crystal init somewhere else and copy the needed file(s) over.

Sure that works. But it's way nicer if it would just be an option to do this in place, and also take the existing configuration into account, instead of needing to call crystal init with the same parameters as originally.

asterite commented 4 years ago

@j8r crystal init is something that people mention in blog posts as one of the things they really like about Crystal. Wanna start? Just call crystal init and you get the basic structure of a project. I use it all the timee.

j8r commented 4 years ago

It could be crystal-init @asterite , and included by default in the distribution. I don't see any harm. Look at crystal deps moved to shards, the result would be the same.

sudo-nice commented 4 years ago

@straight-shoota

There's a perfectly valid reason for initializing a git repository

I have no problem with git at all, and I like the way it is now, but if some kind of wizard is to take place one day, it might be good to consider options other than git.

It would undoubtedly be better to have no license file at all instead of an empty one.

You might be right. Then which options do we have?

  1. Have no license file at all.
  2. Have an empty license file.
  3. Have a file with some predefined license (like now).
  4. Have a wizard with some options to choose.

Pts. 1-3 are pretty good because they are really easy to maintain. But p.4 will require relatively large amount of fork and still there is no guarantee to cover all the possible demands:

Seems too complicated.

What do you think doesn't fit there?

I have no complains about the Makefile you linked, it looks pretty good, but I won't use it because it's not of my taste. Is it better to have a default Makefile rather than not having one? I guess, yes, because it's really easy to delete or overwrite one, when you know what you're doing, but for a newbie it might be a start.

But it's way nicer if it would just be an option to do this in place, and also take the existing configuration into account, instead of needing to call crystal init with the same parameters as originally.

Sounds really good, but how often does one need it?

asterite commented 4 years ago

@j8r separating the tool from the compiler is irrelevant to the discussion of improving crystal init

straight-shoota commented 4 years ago

Pts. 1-3 are pretty good because they are really easy to maintain. But p.4 will require relatively large amount of fork and still there is no guarantee to cover all the possible demands:

I don't see a huge issue with maintenance. Once it's set up properly, there shouldn't be much work required, assuming we'll stick with a minimal set of OSS licenses (everything else wouldn't make any sense).

  • which license to include?

Probably the big three AGPLv3, LGPLv3, Apache. Maybe MIT, although as I understand it, Apache is mostly similar and a bit more explicit, so there's little reason to choose MIT over Apache (except for length of license text, but that shouldn't matter really).

  • which one should be default?

Maybe none? It could be required to select one explicitly, that's not a big deal.

  • how to include my own license?

You select no license for crystal init and paste your custom license manually.

  • are several license revisions to be supported?

What would be the purpose of that?

Sounds really good, but how often does one need it?

Currently, that's not pretty useful. But when there's support for a bunch of ci providers, for example, it would be great to have a way to easily initialize additional ones when necessary. This makes it really easy to only carry what you need.

j8r commented 4 years ago

@asterite I think it is, because there won't be any issue of having a full-featured project. Here we are talking about compromises, because of size limitation by keeping it inside the compiler.

@straight-shoota We can include ISC, it's simpler than MIT and also the default in the NPM ecosystem.

Sija commented 4 years ago
  • which license to include?

I'd go for the top 10, including MIT - since it is popular, no matter our opinion on similarities.

straight-shoota commented 4 years ago

Here we are talking about compromises, because of size limitation by keeping it inside the compiler.

No, we're not. All limitations are about keeping the tool as much focused as possible and they apply whether it's a compiler component or not.

I won't go into more detail regarding which licenses to include. Those specifics need to be figured out later.

watzon commented 4 years ago

I like the way Nim's nimble handles the license issue. It specifically asks you in the wizard which license you want. We could have the top option in the list be "No license" so that it doesn't show any favoritism towards a specific one, but I love having license generation built in.

naqvis commented 4 years ago

I like the ideas and thoughts put together, but have different opinion only on Makefile as that would require different domain knowledge and make things look Cish style (IMHO). Crystal community always love to refer to how things work in Ruby, in Ruby they have Rake , should Crystal have something Cake? My opinion would be to have build system written using same language that is crystal, as that is the norm in other languages like Python setup.py etc.

watzon commented 4 years ago

@naqvis personally I like the way nim handles things with nimble, and how Zig handles things with their own build system. Zig especially has a whole build system defined that's much more powerful than what gnu make provides. It would be cool to have the same functionality in Crystal, but I have a feeling it's not something we're likely to get pre-v1 without someone outside of the core team taking time to implement it.

straight-shoota commented 4 years ago

My opinion would be to have build system written using same language that is crystal, as that is the norm in other languages like Python setup.py etc.

I'm not sure how much of a norm that is and it probably mostly applies to interpreted languages. I'm not aware of many build systems where the recipies are based on compiled languages. Even for interpreted languages, I'm highly skeptical of the benefits of everyone doing there own thing when a general purpose build system would fit for all. And make is the undisputed standard system.

Still, I wouldn't make this feature the most important topic in this discussion. I mostly brought it up as an example of future enhancements and to broaden the ideas on what crystal init could do.

naqvis commented 4 years ago

I'm not sure how much of a norm that is and it probably mostly applies to interpreted languages. I'm not aware of many build systems where the recipies are based on compiled languages. Even for interpreted languages, I'm highly skeptical of the benefits of everyone doing there own thing when a general purpose build system would fit for all. And make is the undisputed standard system.

Rust, OCaml, Nim, Zig and list goes on and none of them qualify as interpreted languages.

Still, I wouldn't make this feature the most important topic in this discussion. I mostly brought it up as an example of future enhancements and to broaden the ideas on what crystal init could do.

As stated in my initial post, I highly appreciate and thanks for the thoughts/idea put together and all of them definitely make sense. I only have different opinion on the build system.

aleandros commented 4 years ago

Hi, I know I'm late to the discussion but what do you guys think of delegating the project structure with crystal init to the community by simply allowing a template as a url to command.

I summarized it in this forum topic but in case you don't want to click:

What would you guys think about allowing crystal init to receive as an argument a URL with a project template? of course, some files in the project would require to be ECR templates so the project name is properly inserted. As a bonus, there could be a flag like template-arguments in which the user could pass a list of key value pairs with parameters for the project.

Ultimately, it could look like:

$ crystal init myapp --url=http://github.com/coolframework/template --template-arguments db_engine=postgres

Of course you could question how is this useful or why is it required. I believe there are several advantages:

  1. I like to think that crystal best practices would tend to evolve faster than the language. Or at least there could be useful setup that can be shared with others (I thought about this when adding GitHub Actions for publishing a CLI App I created, and thought there should be a simple way to share that with others).

  2. Organizations could leverage this by providing their users with pre-approved project templates for say, micro services, that serve some standard.

  3. It gives framework creators the leverage for creating boilerplate without creating that infrastructure themselves (think about rails, in which you need rails for setting up a rails project in which rails is included as a dependency).

I believe this removes the burden of the core team for constantly updating the default project template (even if this is not a frequent activity, someone has to do it) and provides other advantages without adding too much complexity to the init command.

j8r commented 3 years ago

Related forum post, which has a poll: https://forum.crystal-lang.org/t/poll-how-do-you-use-crystal-init/2921.

TL;DR: roughly half the people would like additional features, and roughly half are fine with the tool as-is.

Fryguy commented 3 years ago

Didn't see it mentioned before, but this sounds a lot like what bundle gem in Ruby-land already does, and we can take insipration from there - https://bundler.io/man/bundle-gem.1.html

m-o-e commented 1 year ago

in Ruby they have Rake , should Crystal have something Cake?

IMHO not a wheel worth re-inventing.

The variety of build tools across languages is a bug, not a feature.

In some languages merely figuring out how to list or run the tasks can already be an exercise in frustration. (npm run, yarn run, gulp, or what's the flavor of the day here?)

And it only goes downhill when questions like "how to pass arguments?" arise or when the tool barfs up some obscure internal error.

Instead of inventing yet another tool to do "largely the same just with yet another syntax that no one is familiar with", I'd much prefer if Crystal just standardizes on Make, as @straight-shoota suggested.

Make may be old and crummy, but it's ubiquitous, nearly everyone has either already used it or can learn the basics in 5 minutes.