Skip to content
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

Rewrite bin/init_exercise.py in Rust #660

Merged
merged 80 commits into from
Oct 20, 2018

Conversation

ZapAnton
Copy link
Contributor

The purpose of this PR is to replace the functionality of the existing bin/init_exersice.py script with the Rust crate exercise and to add additional functionality as per #641.

It is opened as In Progress in order for maintainers to track any undesirable decisions/functionality in the new crate, since it is a medium-complexity project, so disagreements may arise.

In addition to the future suggestions the current work plan looks like this:

Major Tasks

  • Reach an agreement on the new crate's API/commands
  • Implement configure subcommand
  • Implement update subcommand
  • Update the docs to mention the usage of the new crate

Minor Tasks

  • Add any missing comments/fields from the canonical-data.json to the generated test suite
  • Improve the crate's functions documentation
  • Prettify the generated test suite when the maplit crate is used. Currently it is an unreadable mess and rustfmt does not help
  • General refactoring. I am not the most prominent rustcean out there, so my solutions for the problem may not be the most optimal

Note that as per #641 (comment) the crate name is changed from init_exercise to exercise, so the prefix in the commit messages is changed from init_exercise: to exercise: accordingly

If this PR is merged, closes #641

@ZapAnton
Copy link
Contributor Author

Questions regarding the update subcommand:

As I understand it, the desirable algorithm is to get the latest canonical-data.json for the exercise and to apply any diffs to the existing test suite:

  • If a new case/property is added to the canonical-data, add the appropriate functions to the existing test suite - no problems here
  • If the existing case is updated, add a new function to the test suite and do not touch the existing function. Question: How to track the updates to the cases? We cannot use the existing test suite, as the person who implements the exercise is not bound to follow the format of the generated test suite and may use the format of their own. Should we track the changes via git?
  • If the existing case is deleted from the canonical-data.json, what should be done with the existing test functions?

@coriolinus
Copy link
Member

coriolinus commented Sep 18, 2018 via email


echo "Copying exercise crate from $EXERCISE_CRATE_PATH/target/release/exercise into $BIN_DIR_PATH"

cp "$EXERCISE_CRATE_PATH/target/release/exercise" "$BIN_DIR_PATH"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail on a Windows machine if the user is running this through cygwin: the binary in that case would be exercise.exe.

@ZapAnton
Copy link
Contributor Author

Some questions regarding usage notes:

  1. update inserts a //NEW line, which I am inclined against keeping. This is somewhat debatable, though.

I have included the line as per your comment:

if the generated test case differs from the existing test case, update
the name of the generated case with some suffix and then emit them both.

The line will change to //UPDATED if the test case was already present in the test suite.
Is this behavior undesirable?

  1. update inserts new process_x_case at the end of the file. I'd prefer it to be inserted at the beginning, before any tests.

By 'before any tests' do you mean the tests generated with the update subcommand, or the already present test suite?
Because if it is the later, I think it is not the best behavior, since we ungroup the changes made to the test suite and scatter them over the whole file, which could be inconvenient for the contributor who applies the changes. And, since there is no standard format for the test suite, it will be hard to guess where to insert the function, which could breaks the existing formatting and is also inconvenient .

  1. when generating process_X_case functions, the current implementation causes the compiler to complain about unused variables. We should, instead of generating comments describing what it might look like, just generate the example directly. I.e. using the abbreviate case:

Since we will be implementing the process_x_function and the current track policy is to always have a non-empty stub file, should we also generate stub template?

  1. If we will be tracking the changes made to the utility via Travis script should some sort of versioning be included?

  2. When generating or updating the test suite, the utility tries to invoke rustfmt on the modified file, which includes the search of the rustfmt using the which command and, if rustfmt is found, applying the formatting. Because of the which command usage, this process is Unix-specifix and will not work on Windows. Would it be enough, if I replace the which command with searching the PATH environment variable?

@ZapAnton
Copy link
Contributor Author

Additional note about the test suite format:

In the update subcommand, I expect the existing test cases to have names like test_description_from_the_canonical_data.
Since there is no standard for formatting and naming the test cases, not all exercises adhere to the above rule.
For example if we try to invoke the update subcommand on the leap exercise we will end up with the whole test suite copied.
Should I left this as it is, and if not should some sort of test naming policy be applied to the track?

@ZapAnton
Copy link
Contributor Author

The Travis script has been added and it seems like it works, but it takes about 5 minutes to complete, which is quite a lot.
Should I change the cargo build --release command with the cargo check command in this script?

@coriolinus
Copy link
Member

  • re: inserting //NEW: I see where the confusion came from. I spoke unclearly. My intent was this:

    • if the generated name for a particular test is test_description_from_the_canonical_data, and there already exists a test named test_description_from_the_canonical_data, then check if the text of the test is equal to the newly generated test text:
      • if the text of the test is equal to the generated test text, then nothing has changed, so we leave the old test in place and do not write the new test
      • if the text of the test is not equal to the generated test text, then something has changed. We can't expect an automatic tool to know which to keep, so we should keep the existing test, change the name by appending a suffix, and insert the generated test. The format I expect is test_description_from_the_canonical_data_N, where N is the lowest integer > 1 which does not generate a name collision
    • if the generated name for a particular test is test_description_from_the_canonical_data, and there already exists a test named description_in_other_words, an automatic tool can't be expected to notice that the test exists already, so simply retain the existing test and also insert the generated test. The tool user or the PR reviewer are the ones who should catch those cases.
  • given the rules above, we need neither //NEW nor //UPDATED.

  • re: test file layout. In my opinion, the test file is easiest for a human to read and understand if it contains the following sections in sequence:

    • test module doccomment
    • imports
    • process_X_case implementations
    • tests

    I understand that maintaining this format will break up the inserted lines into multiple chunks. That's fine with me. There are plenty of diff tools which make the work of finding insertions quite easy.

    From the exercise tool's point of view, the rules above require parsing the test file to determine which tests exist, and what bytes they contain, anyway. Given all that information, I think it should not be difficult to insert new process_X_case implementations at the end of the appropriate section.

  • I do understand that parsing the existing test file is a non-trivial task; implementing these layout and naming rules will not be required to merge this PR. After merging this PR, I'll create a bunch of issues for items which didn't make it into the PR; at that point, I'll probably start work on implementing the rules above myself.

  • re: generating a stub template. We could think about that for future work, but I think it's beyond the scope of this initial PR. It would be tricky to get right.

  • re: versioning the script: I don't know exactly what you mean

  • re: searching the PATH instead of using which: yes, that's a good idea. I'd be happy to see it in this PR, but it could also be an issue after merging this. I don't think that windows support is a big enough issue for this to block merging this PR.

  • re: replacing cargo build --release with cargo check in travis script: yes, that's a good idea

@ZapAnton
Copy link
Contributor Author

re: versioning the script: I don't know exactly what you mean

What I wanted to say is that from the start the exercise crate had the 0.1.0 version. If we are including the script to check for crate's modifications, should we somehow track and change the crate's version?

@coriolinus
Copy link
Member

I see. No, I think it's probably best to manually update the crate's version. I'd be very reluctant to give Travis push access to this repo.

@ZapAnton
Copy link
Contributor Author

Then, right before the merge, I will give the crate the 1.0.0 version, to emphasize it's relative completness

@coriolinus
Copy link
Member

coriolinus commented Oct 19, 2018 via email

Copy link
Member

@coriolinus coriolinus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @ZapAnton, for this utility! This may be the biggest PR yet merged to this repo in one go.

There are still quite a few areas which could be improved, but this already more feature-complete than the init_exercise.py script which it replaces.

I confirm that the three critical points I listed above have all been fixed. I've listed some minor changes and fixes below. When the time comes to merge this, I will also go through and add a bunch of issues, itemizing the remaining improvement points that I've found here.

This PR has been open for long enough that I do not intend to wait for other maintainer feedback. I will merge it as soon as you say it's ready, @ZapAnton.

Optionally, you may also delete bin/init_exercise.py as part of this PR.

[package]
name = "exercise"
version = "0.1.0"
authors = ["ZapAnton <[email protected]>"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this line, as noted in the notes from usage.

Suggested change
authors = ["ZapAnton <[email protected]>"]


let exercises_path = Path::new(&track_root).join("exercises");

for entry in exercises_path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we check whether the exercises path exists directly, instead of needing to iterate over the entire path?

pub fn update_exercise(exercise_name: &str, use_maplit: bool) {
if !utils::exercise_exists(exercise_name) {
println!(
"Exercise with the name '{}' does not exists. Aborting",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: exists -> exist.

Suggested change
"Exercise with the name '{}' does not exists. Aborting",
"Exercise with the name '{}' does not exist. Aborting.",

let insert_index = exercises_with_similar_difficulty[insert_index].0;

let prompt = if insert_index == 0 {
format!("{} is the easiest exercise on the track.", exercise_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only true if *difficulty == 1.

Suggested change
format!("{} is the easiest exercise on the track.", exercise_name)
format!("{} is the easiest exercise of difficulty {}.", exercise_name, *difficulty)

let prompt = if insert_index == 0 {
format!("{} is the easiest exercise on the track.", exercise_name)
} else if insert_index == exercises.len() - 1 {
format!("{} is the hardest exercise on the track.", exercise_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only true if difficulty == 10.

Suggested change
format!("{} is the hardest exercise on the track.", exercise_name)
format!("{} is the hardest exercise of difficulty {}.", exercise_name, *difficulty)

};
use utils;

static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking this file will not be generated by Cargo

Suggested change
static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo
static GITIGNORE_CONTENT: &'static str = "# Generated by exercism rust track exercise tool

false
}

// Update the version of the specified exersice in the Cargo.toml file according to the passed canonical data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Suggested change
// Update the version of the specified exersice in the Cargo.toml file according to the passed canonical data
// Update the version of the specified exercise in the Cargo.toml file according to the passed canonical data

};
use toml::Value as TomlValue;

pub fn get_track_root() -> String {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a good place to use lazy_static! to initialize this value exactly once, and then keep it. By my count this command gets called in eight different places in this code; since the result will never change, we don't need to rerun the subcommand every time.

{} \n\
",
exercise_name,
"https://github.com/exercism/rust/tree/master/util/exercise",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be inlined.

",
exercise_name,
"https://github.com/exercism/rust/tree/master/util/exercise",
format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be inlined; it's a little silly to embed a format!() string in the args of another format!() string.

@ZapAnton
Copy link
Contributor Author

ZapAnton commented Oct 19, 2018

The removal of the bin/init_exercise.py has triggered the brach conflict. Should I rebase against master?

@ZapAnton
Copy link
Contributor Author

Ignoring the conflict with the Python script, I think I have addressed all the recent points.

@coriolinus if the last batch of commits does not contain any shorcomings and you are fine with moving the remaining usage and formatting notes into separate issues, I think this PR is ready to be merged.

@coriolinus coriolinus merged commit 45599b8 into exercism:master Oct 20, 2018
@ZapAnton ZapAnton deleted the riir_init_exercise branch October 20, 2018 14:31
ErikSchierboom pushed a commit to ErikSchierboom/rust that referenced this pull request Jan 26, 2021
* extract conceps from leap

* Update languages/reference/exercise-concepts/leap.md

Co-Authored-By: Peter Goodspeed-Niklaus <[email protected]>

Co-authored-by: Peter Goodspeed-Niklaus <[email protected]>
ErikSchierboom pushed a commit to ErikSchierboom/rust that referenced this pull request Jan 27, 2021
* extract conceps from leap

* Update languages/reference/exercise-concepts/leap.md

Co-Authored-By: Peter Goodspeed-Niklaus <[email protected]>

Co-authored-by: Peter Goodspeed-Niklaus <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replace init_exercise.py
2 participants