-
-
Notifications
You must be signed in to change notification settings - Fork 402
Bootstrap practice exercises #774
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bootstrap practice exercises #774
Conversation
Thank you for contributing to Based on the files changed in this PR, it would be good to pay attention to the following details when reviewing the PR:
Automated comment created by PR Commenter 🤖. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I run it on a few existing exercises and left some (hopefully realistic) improvement ideas based on the output.
This is excellent 🎉. I guess with this script you'll churn through all the remaining "new practice exercise" issues in no time. I have to create more 😅.
bin/bootstrap_practice_exercise.exs
Outdated
""" | ||
@doc \"\"\" | ||
insert function description here | ||
\"\"\" | ||
@spec #{property}(#{Enum.map_join(variables, ", ", fn | ||
{var, nil} -> "#{var} :: String.t()" | ||
{var, sub_vars} -> "#{var} :: %{#{Enum.map_join(sub_vars, " : String.t(), ", fn v -> v end)} : String.t()}" | ||
end)}) :: {:ok, String.t()} | {:error, String.t()} | ||
def #{property}(#{Enum.map_join(variables, ", ", fn {var, _} -> var end)}) do | ||
end | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it better to make them obviously wrong?
Maybe it's better to somehow mark them as requiring manual changes? Maybe something like this? (and similar in other places that also have most-likely-wrong defaults)
""" | |
@doc \"\"\" | |
insert function description here | |
\"\"\" | |
@spec #{property}(#{Enum.map_join(variables, ", ", fn | |
{var, nil} -> "#{var} :: String.t()" | |
{var, sub_vars} -> "#{var} :: %{#{Enum.map_join(sub_vars, " : String.t(), ", fn v -> v end)} : String.t()}" | |
end)}) :: {:ok, String.t()} | {:error, String.t()} | |
def #{property}(#{Enum.map_join(variables, ", ", fn {var, _} -> var end)}) do | |
end | |
""" | |
""" | |
@doc \"\"\" | |
TODO: insert function description here | |
\"\"\" | |
@spec #{property}(#{Enum.map_join(variables, ", ", fn | |
{var, nil} -> "#{var} :: TODO.t()" | |
{var, sub_vars} -> "#{var} :: %{#{Enum.map_join(sub_vars, " : TODO.t(), ", fn v -> v end)} : TODO.t()}" | |
end)}) :: TODO.t() | |
def #{property}(#{Enum.map_join(variables, ", ", fn {var, _} -> var end)}) do | |
end | |
""" |
bin/bootstrap_practice_exercise.exs
Outdated
test \"#{description}\" do | ||
#{Generate.print_comments(c, ["description", "property", "input", "expected", "uuid"])} | ||
#{print_input(input)} | ||
output = #{module}.#{Generate.to_snake_case(property)}(#{print_variables(input)}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good if changing to snake case happened as soon as possible, right after fetching and decoding the JSON, and not when printing the test case. For example, you forgot to use it when printing the stub 😉 so for an exercise like allergies
(https://github.com/exercism/problem-specifications/blob/e2779fc84ff07cb5bd8ff89f38e78af6d17b5056/exercises/allergies/canonical-data.json#L14), you get a stub like this:
def allergicTo(item, score) do
end
but a function call in tests like this:
output = Allergies.allergic_to(item, score)
It's also necessary to change properties to snake case, for example: https://github.com/exercism/problem-specifications/blob/daf620d47ed905409564dec5fa9610664e294bde/exercises/all-your-base/canonical-data.json#L20-L22
bin/bootstrap_practice_exercise.exs
Outdated
def print_expected(%{"error" => err}), do: "{:error, #{inspect(err)}}" | ||
def print_expected(expected), do: "{:ok, #{inspect(expected)}}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible (and not too difficult) to make this more intelligent, and only use {:ok, value}
if any error case exists in the problem specification for this property, and otherwise just use the plain value? There are a lot of exercises without any error handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done :)
] | ||
""" | ||
|
||
Mix.Generator.create_file("exercises/practice/#{exercise}/.formatter.exs", format) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had no idea about this module 😍 awesome
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is quite nice. And actually it wouldn't be much more work to make this script into a Mix.Task
. It would be kind of cool: mix ex.practice.gen darts
.
I'd like to look over this one before merge, but might need to wait a couple days if that's okay. I have all next week off, so should be able to catch up on this stuff. :) |
bin/bootstrap_practice_exercise.exs
Outdated
|
||
path = "exercises/practice/#{exercise}/test/#{exercise_path}_test.exs" | ||
Mix.Generator.create_file(path, test_file) | ||
System.cmd("mix", ["format", path]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know if there is a way to call mix format
from within a .exs
script without having to use System.cmd
? I searched for it, but couldn't find it.
Although maybe it's best to run it this way so that it includes the format in .formatter.exs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every mix task is just a module with a run
function, so it should be possible to run Mix.Tasks.Format.run([])
. It should read .formatter.exs
from the current directory anyway.
mix format documentation: https://hexdocs.pm/mix/master/Mix.Tasks.Format.html
and implementation: https://github.com/elixir-lang/elixir/blob/2a4312412be39303af08ccd0cef81b5bbcd55eac/lib/mix/lib/mix/tasks/format.ex#L1
I compiled a list of all the exercises we don't have yet (separating the ones with and without tests), do you want me to share it in an issue? Or do you prefer individual ones? |
bin/bootstrap_practice_exercise.exs
Outdated
def to_snake_case(lower_camel_case) do | ||
lower_camel_case | ||
|> String.split(~r/(?=[A-Z\d])/) | ||
|> Enum.map_join("_", &String.downcase/1) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use Macro.underscore
for this if we are using it just for identifiers
defmodule #{module}.MixProject do | ||
use Mix.Project | ||
|
||
def project do | ||
[ | ||
app: :#{exercise_snake_case}, | ||
version: "0.1.0", | ||
# elixir: "~> 1.8", | ||
start_permanent: Mix.env() == :prod, | ||
deps: deps() | ||
] | ||
end | ||
|
||
# Run "mix help compile.app" to learn about applications. | ||
def application do | ||
[ | ||
extra_applications: [:logger] | ||
] | ||
end | ||
|
||
# Run "mix help deps" to learn about dependencies. | ||
defp deps do | ||
[ | ||
# {:dep_from_hexpm, "~> 0.3.0"}, | ||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} | ||
] | ||
end | ||
end | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: if we can't run mix format on this file, can we indent here 2 spaces for this binding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the end we run mix format on everything, it should take care of this one too..
I'm not sure what you mean, I ran mix format on the file.
Do you want to indent the triple quoted block? mix format puts it back there.
ExUnit.start() | ||
ExUnit.configure(exclude: :pending, trace: true) | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, same indent as mentioned above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the indentation looks good already 🤔 meaning: level 0, there's no indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few minor comments, cool stuff!
There is a dashboard for this already: https://tracks.exercism.io/elixir/main/unimplemented Sadly it doesn't mention which exercises don't have tests defined, but there's only a few left unimplemented so we can manage. Normally only maintainers deal with exercise selection, but since you're working faster than I can review or write new issues, I want to share with you my thoughts on implementing those missing exercises:
The rest can be implemented and your PRs with them will be very welcome if you still have the time and energy 🙂 |
I'm going to make a call that all the feedback has been considered and applied, and now it's merge time 🎉 |
This script will generate all files (minus config.json and tests.toml) for a practice exercise. Of course, the tests need a bunch of manual input, but usually at the level of search and replace, so it's quite valuable.
I tried to be a little bit clever about some things (for example @SPEC generation for the lib file) but I hope that contributors understand that they should not trust these guesses too much. Is it better to make them obviously wrong?
Please test that script by running
elixir bin/boostrap_practice_exercise.exs darts
(or whichever exercise) and inspect the generated files. I'm sure there is room for improvement.