From c0da7d50bf961899a81a0e3516b8bf058396d34b Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 29 Aug 2018 17:54:11 +0300 Subject: [PATCH 01/80] init_exercise: Generated the crate with 'cargo new' --- init_exercise/Cargo.lock | 4 ++++ init_exercise/Cargo.toml | 6 ++++++ init_exercise/src/main.rs | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 init_exercise/Cargo.lock create mode 100644 init_exercise/Cargo.toml create mode 100644 init_exercise/src/main.rs diff --git a/init_exercise/Cargo.lock b/init_exercise/Cargo.lock new file mode 100644 index 000000000..a6d581bf8 --- /dev/null +++ b/init_exercise/Cargo.lock @@ -0,0 +1,4 @@ +[[package]] +name = "init_exercise" +version = "0.1.0" + diff --git a/init_exercise/Cargo.toml b/init_exercise/Cargo.toml new file mode 100644 index 000000000..393dad706 --- /dev/null +++ b/init_exercise/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "init_exercise" +version = "0.1.0" +authors = ["ZapAnton "] + +[dependencies] diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/init_exercise/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 14520c35e21d71355f499a92339dfa3d5b781612 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 29 Aug 2018 18:35:50 +0300 Subject: [PATCH 02/80] init_exercise: Added command-line arguments via clap --- init_exercise/Cargo.lock | 126 ++++++++++++++++++++++++++++++++++++++ init_exercise/Cargo.toml | 2 + init_exercise/src/main.rs | 24 +++++++- 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/init_exercise/Cargo.lock b/init_exercise/Cargo.lock index a6d581bf8..af01e721c 100644 --- a/init_exercise/Cargo.lock +++ b/init_exercise/Cargo.lock @@ -1,4 +1,130 @@ +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "init_exercise" version = "0.1.0" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_syscall" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +[metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/init_exercise/Cargo.toml b/init_exercise/Cargo.toml index 393dad706..b27e4dfd0 100644 --- a/init_exercise/Cargo.toml +++ b/init_exercise/Cargo.toml @@ -2,5 +2,7 @@ name = "init_exercise" version = "0.1.0" authors = ["ZapAnton "] +description = "An utility for creating or updating the exercises on the Exercism Rust track" [dependencies] +clap = "2.32.0" diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index e7a11a969..0592b99b7 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -1,3 +1,25 @@ +extern crate clap; + +use clap::{App, Arg, SubCommand}; + fn main() { - println!("Hello, world!"); + let _matches = App::new(env!("CARGO_PKG_NAME")) + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .subcommand( + SubCommand::with_name("generate") + .about("Generates new exercise") + .arg(Arg::with_name("exercise_name").help("The name of the generated exercise")) + .arg(Arg::with_name("dont_update_config").long("dont-update-config").short("d").help( + "If set, the command will not update config.json after generating the exercise", + )) + .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the generated test suite")), + ) + .subcommand( + SubCommand::with_name("update") + .about("Updates the specified exercise") + .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")), + ) + .get_matches(); } From 4d28d6951671b591ad90068fc399487f1d99d26d Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 30 Aug 2018 11:03:27 +0300 Subject: [PATCH 03/80] init_exercise: Added configure command --- init_exercise/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index 0592b99b7..f261a96e0 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -21,5 +21,10 @@ fn main() { .about("Updates the specified exercise") .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")), ) + .subcommand( + SubCommand::with_name("configure") + .about("Edits config.json for the specified exercise") + .arg(Arg::with_name("exercise_name").help("The name of the configured exercise")), + ) .get_matches(); } From 311fe16e8c854edccaf744d007e950cf6354a243 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 30 Aug 2018 11:09:17 +0300 Subject: [PATCH 04/80] init_exercise: Replaced the dont-update-config flag with the no-configure flag --- init_exercise/src/main.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index f261a96e0..5239a1cad 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -11,15 +11,18 @@ fn main() { SubCommand::with_name("generate") .about("Generates new exercise") .arg(Arg::with_name("exercise_name").help("The name of the generated exercise")) - .arg(Arg::with_name("dont_update_config").long("dont-update-config").short("d").help( - "If set, the command will not update config.json after generating the exercise", + .arg(Arg::with_name("no_configure").long("no-configure").short("n").help( + "If set, the command will not edit config.json after generating the exercise", )) .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the generated test suite")), ) .subcommand( SubCommand::with_name("update") .about("Updates the specified exercise") - .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")), + .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")) + .arg(Arg::with_name("no_configure").long("no-configure").short("n").help( + "If set, the command will not edit config.json after updating the exercise", + )) ) .subcommand( SubCommand::with_name("configure") From 8056b7b94163d48dd1897838aaebf1c7edee4257 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 30 Aug 2018 11:45:44 +0300 Subject: [PATCH 05/80] init_exercise: Added init_app and process_matches functions --- init_exercise/src/main.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index 5239a1cad..99718134b 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -1,9 +1,11 @@ extern crate clap; -use clap::{App, Arg, SubCommand}; +use clap::{App, Arg, ArgMatches, SubCommand}; -fn main() { - let _matches = App::new(env!("CARGO_PKG_NAME")) +// Creates a new CLI app with appropriate matches +// and returns the initialized matches. +fn init_app<'a>() -> ArgMatches<'a> { + App::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS")) .about(env!("CARGO_PKG_DESCRIPTION")) @@ -29,5 +31,25 @@ fn main() { .about("Edits config.json for the specified exercise") .arg(Arg::with_name("exercise_name").help("The name of the configured exercise")), ) - .get_matches(); + .get_matches() +} + +// Determine which subcommand was used +// and call the appropriate function. +fn process_matches(matches: &ArgMatches) { + match matches.subcommand() { + ("generate", Some(generate_matches)) => println!("Generate!"), + ("update", Some(update_matches)) => println!("Update!"), + ("configure", Some(configure_matches)) => println!("Configure!"), + ("", None) => { + println!("No subcommand was used.\nUse init_exercise --help to learn about the possible subcommands.") + } + _ => unreachable!(), + } +} + +fn main() { + let matches = init_app(); + + process_matches(&matches); } From 45449e44c75bfb1cff086b39f62c68dc60b7ab16 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 30 Aug 2018 11:52:32 +0300 Subject: [PATCH 06/80] init_exercise: Added cmd module --- init_exercise/src/cmd/configure.rs | 0 init_exercise/src/cmd/generate.rs | 0 init_exercise/src/cmd/mod.rs | 3 +++ init_exercise/src/cmd/update.rs | 0 init_exercise/src/main.rs | 2 ++ 5 files changed, 5 insertions(+) create mode 100644 init_exercise/src/cmd/configure.rs create mode 100644 init_exercise/src/cmd/generate.rs create mode 100644 init_exercise/src/cmd/mod.rs create mode 100644 init_exercise/src/cmd/update.rs diff --git a/init_exercise/src/cmd/configure.rs b/init_exercise/src/cmd/configure.rs new file mode 100644 index 000000000..e69de29bb diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs new file mode 100644 index 000000000..e69de29bb diff --git a/init_exercise/src/cmd/mod.rs b/init_exercise/src/cmd/mod.rs new file mode 100644 index 000000000..cb0a1f9c3 --- /dev/null +++ b/init_exercise/src/cmd/mod.rs @@ -0,0 +1,3 @@ +pub mod configure; +pub mod generate; +pub mod update; diff --git a/init_exercise/src/cmd/update.rs b/init_exercise/src/cmd/update.rs new file mode 100644 index 000000000..e69de29bb diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index 99718134b..a1d20db51 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -1,6 +1,8 @@ extern crate clap; +mod cmd; use clap::{App, Arg, ArgMatches, SubCommand}; +use cmd::{configure, generate, update}; // Creates a new CLI app with appropriate matches // and returns the initialized matches. From bfadb09dbf31b26fefcabd49a8aff80b096e762c Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 30 Aug 2018 14:28:45 +0300 Subject: [PATCH 07/80] init_exercise: Added process_matches to the generate module --- init_exercise/src/cmd/generate.rs | 6 ++++++ init_exercise/src/main.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index e69de29bb..1d844e537 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -0,0 +1,6 @@ +/// This module contains source for the `generate` command. + +use clap::ArgMatches; + +pub fn process_matches(matches: &ArgMatches) { +} diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index a1d20db51..0cc6b3909 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -40,7 +40,7 @@ fn init_app<'a>() -> ArgMatches<'a> { // and call the appropriate function. fn process_matches(matches: &ArgMatches) { match matches.subcommand() { - ("generate", Some(generate_matches)) => println!("Generate!"), + ("generate", Some(generate_matches)) => generate::process_matches(&generate_matches), ("update", Some(update_matches)) => println!("Update!"), ("configure", Some(configure_matches)) => println!("Configure!"), ("", None) => { From ac3961d458d0bec8fd7cb45aea3d7d9af1b8adb6 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 31 Aug 2018 20:30:44 +0300 Subject: [PATCH 08/80] init_exercise: Added exercise template generation --- init_exercise/src/cmd/generate.rs | 83 ++++++++++++++++++++++++++++++- init_exercise/src/main.rs | 2 +- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 1d844e537..9e88c298e 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -1,6 +1,87 @@ /// This module contains source for the `generate` command. - use clap::ArgMatches; +use std::{fs::OpenOptions, io::Write, path::Path, process::Command}; + +static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo +# will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock +"; + +// Generate a new exercise with specified name and flags +fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) { + let rev_parse_output = Command::new("git") + .arg("rev-parse") + .arg("--show-toplevel") + .output() + .expect("Failed to get the path to the track repo."); + + let track_root = String::from_utf8(rev_parse_output.stdout).unwrap(); + + let exercise_path = Path::new(&track_root.trim()) + .join("exercises") + .join(exercise_name); + + if exercise_path.exists() { + panic!( + "Exercise with the name {} already exists. Aborting", + exercise_name + ); + } + + println!( + "Generating a new exercise at the following path: {}", + exercise_path.to_str().unwrap() + ); + + let _cargo_new_output = Command::new("cargo") + .arg("new") + .arg("--lib") + .arg(exercise_path.to_str().unwrap()) + .output() + .expect("Failed to generate a new exercise via 'cargo new' command"); + + ::std::fs::write(exercise_path.join(".gitignore"), GITIGNORE_CONTENT) + .expect("Failed to create .gitignore file"); + + if use_maplit { + let mut cargo_toml_file = OpenOptions::new() + .append(true) + .open(exercise_path.join("Cargo.toml")) + .unwrap(); + + cargo_toml_file + .write(b"maplit = \"1.0.1\"") + .expect("Failed to add maplit dependency to the Cargo.toml"); + } + + ::std::fs::create_dir(exercise_path.join("tests")) + .expect("Failed to create the tests directory"); + + let mut test_file = OpenOptions::new() + .write(true) + .open( + exercise_path + .join("tests") + .join(format!("{}.rs", exercise_name)), + ) + .unwrap(); + + if use_maplit { + test_file.write(b"#[macro_use] extern crate maplit;"); + } +} pub fn process_matches(matches: &ArgMatches) { + let exercise_name = matches.value_of("exercise_name").unwrap(); + + let run_configure = !matches.is_present("no_configure"); + + let use_maplit = matches.is_present("use_maplit"); + + generate_exercise(exercise_name, run_configure, use_maplit); } diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index 0cc6b3909..cbb2a0344 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -14,7 +14,7 @@ fn init_app<'a>() -> ArgMatches<'a> { .subcommand( SubCommand::with_name("generate") .about("Generates new exercise") - .arg(Arg::with_name("exercise_name").help("The name of the generated exercise")) + .arg(Arg::with_name("exercise_name").required(true).help("The name of the generated exercise")) .arg(Arg::with_name("no_configure").long("no-configure").short("n").help( "If set, the command will not edit config.json after generating the exercise", )) From f6eced665ea54074151738eb3017f3fe9ffe7411 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sun, 2 Sep 2018 18:07:26 +0300 Subject: [PATCH 09/80] init_exercise: Added test suite and example.rs generation --- init_exercise/src/cmd/generate.rs | 45 ++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 9e88c298e..828e15fdd 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -1,6 +1,11 @@ /// This module contains source for the `generate` command. use clap::ArgMatches; -use std::{fs::OpenOptions, io::Write, path::Path, process::Command}; +use std::{ + fs::{File, OpenOptions}, + io::Write, + path::Path, + process::Command, +}; static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo # will have compiled files and executables @@ -12,6 +17,18 @@ static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo Cargo.lock "; +static EXAMPLE_RS_CONTENT: &'static str = "//! Example implementation +//! +//! - Implement the solution to your exercise here. +//! - Put the stubs for any tested functions in `src/lib.rs`, +//! whose variable names are `_` and +//! whose contents are `unimplemented!()`. +//! - If your example implementation has dependencies, copy +//! `Cargo.toml` into `Cargo-example.toml` and then make +//! any modifications necessary to the latter so your example will run. +//! - Test your example by running `../../bin/test-exercise` +"; + // Generate a new exercise with specified name and flags fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) { let rev_parse_output = Command::new("git") @@ -62,18 +79,26 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) ::std::fs::create_dir(exercise_path.join("tests")) .expect("Failed to create the tests directory"); - let mut test_file = OpenOptions::new() - .write(true) - .open( - exercise_path - .join("tests") - .join(format!("{}.rs", exercise_name)), - ) - .unwrap(); + let mut test_file = File::create( + exercise_path + .join("tests") + .join(format!("{}.rs", exercise_name)), + ).expect("Failed to create test suite file"); if use_maplit { - test_file.write(b"#[macro_use] extern crate maplit;"); + test_file.write(b"#[macro_use]\nextern crate maplit;\n"); } + + test_file + .write(&format!("extern crate {};\n", exercise_name.replace("-", "_")).into_bytes()) + .unwrap(); + + test_file + .write(&format!("use {}::*;\n\n", exercise_name.replace("-", "_")).into_bytes()) + .unwrap(); + + ::std::fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT) + .expect("Failed to create example.rs file"); } pub fn process_matches(matches: &ArgMatches) { From 11faf1279ba285a93adc9334a701528b5b0a538d Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 5 Sep 2018 12:51:16 +0300 Subject: [PATCH 10/80] init_exercise: Added get_canonical_data function --- init_exercise/Cargo.lock | 1140 ++++++++++++++++++++++++++++- init_exercise/Cargo.toml | 2 + init_exercise/src/cmd/generate.rs | 29 + init_exercise/src/main.rs | 3 + 4 files changed, 1149 insertions(+), 25 deletions(-) diff --git a/init_exercise/Cargo.lock b/init_exercise/Cargo.lock index af01e721c..a2e84281c 100644 --- a/init_exercise/Cargo.lock +++ b/init_exercise/Cargo.lock @@ -1,3 +1,8 @@ +[[package]] +name = "adler32" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ansi_term" version = "0.11.0" @@ -6,6 +11,14 @@ dependencies = [ "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "arrayvec" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "atty" version = "0.2.11" @@ -16,11 +29,54 @@ dependencies = [ "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "build_const" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "clap" version = "2.32.0" @@ -35,96 +91,1130 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "dtoa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "encoding_rs" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-cpupool" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hyper" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "want 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper-tls" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "init_exercise" version = "0.1.0" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lazycell" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "redox_syscall" -version = "0.1.40" +name = "libflate" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "redox_termios" -version = "0.1.1" +name = "lock_api" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "strsim" -version = "0.7.0" +name = "log" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "termion" -version = "1.5.1" +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memoffset" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mime" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mime_guess" +version = "2.0.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "mime 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "phf 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_codegen 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "textwrap" -version = "0.10.0" +name = "mio-uds" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "unicode-width" +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "native-tls" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "vec_map" -version = "0.8.1" +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "winapi" -version = "0.3.5" +name = "num_cpus" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "openssl" +version = "0.9.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.9.35" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "owning_ref" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "phf" +version = "0.7.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_shared 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_codegen" +version = "0.7.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_generator 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_generator" +version = "0.7.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_shared 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_shared" +version = "0.7.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_syscall" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "relay" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "reqwest" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libflate 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.76 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "safemem" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "schannel" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scoped-tls" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "security-framework" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework-sys" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_json" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.76 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_urlencoded" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.76 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slab" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-fs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-udp 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-uds 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-codec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-executor" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-fs" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-service" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-threadpool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-timer" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tls" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-udp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-uds" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "try-lock" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicase" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "url" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "uuid" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vcpkg" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "want" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [metadata] +"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "85415d2594767338a74a30c1d370b2f3262ec1b4ed2d7bba5b3faf4de40467d9" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" +"checksum bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e178b8e0e239e844b083d5a0d4a156b2654e67f9f80144d48398fcd736a24fb8" +"checksum cc 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "c37f0efaa4b9b001fa6f02d4b644dee4af97d3414df07c51e3e4f015f3a3e131" +"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" +"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" +"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +"checksum crossbeam-deque 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3486aefc4c0487b9cb52372c97df0a48b8c249514af1ee99703bf70d2f2ceda1" +"checksum crossbeam-epoch 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30fecfcac6abfef8771151f8be4abc9e4edc112c2bcb233314cafde2680536e9" +"checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" +"checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" +"checksum encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2a91912d6f37c6a8fef8a2316a862542d036f13c923ad518b5aca7bcaac7544c" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "884dbe32a6ae4cd7da5c6db9b78114449df9953b8d490c9d7e1b51720b922c62" +"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +"checksum httparse 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7b6288d7db100340ca12873fd4d08ad1b8f206a9457798dfb17c018a33fee540" +"checksum hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)" = "34a590ca09d341e94cddf8e5af0bbccde205d5fbc2fa3c09dd67c7f85cea59d7" +"checksum hyper-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffb1bd5e518d3065840ab315dbbf44e4420e5f7d80e2cb93fa6ffffc50522378" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" +"checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum libflate 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7d4b4c7aff5bac19b956f693d0ea0eade8066deb092186ae954fa6ba14daab98" +"checksum lock_api 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "949826a5ccf18c1b3a7c3d57692778d21768b79e46eb9dd07bfc4c2160036c54" +"checksum log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cba860f648db8e6f269df990180c2217f333472b4a6e901e97446858487971e2" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum mime 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "4b082692d3f6cf41b453af73839ce3dfc212c4411cbb2441dff80a716e38bd79" +"checksum mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30de2e4613efcba1ec63d8133f344076952090c122992a903359be5a4f99c3ed" +"checksum mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)" = "4fcfcb32d63961fb6f367bfd5d21e4600b92cd310f71f9dca25acae196eb1560" +"checksum mio-uds 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "84c7b5caa3a118a6e34dbac36504503b1e8dc5835e833306b9d6af0e05929f79" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" +"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" +"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" +"checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985" +"checksum openssl-sys 0.9.35 (registry+https://github.com/rust-lang/crates.io-index)" = "912f301a749394e1025d9dcddef6106ddee9252620e6d0a0e5f8d0681de9b129" +"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" +"checksum parking_lot_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06a2b6aae052309c2fd2161ef58f5067bc17bb758377a0de9d4b279d603fdd8a" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum phf 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "cec29da322b242f4c3098852c77a0ca261c9c01b806cae85a5572a1eb94db9a6" +"checksum phf_codegen 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "7d187f00cd98d5afbcd8898f6cf181743a449162aeb329dcd2f3849009e605ad" +"checksum phf_generator 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "03dc191feb9b08b0dc1330d6549b795b9d81aec19efe6b4a45aec8d4caee0c4b" +"checksum phf_shared 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "b539898d22d4273ded07f64a05737649dc69095d92cb87c7097ec68e3f150b93" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" +"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" +"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" +"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum reqwest 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "738769ec83daf6c1929dc9dae7d69ed3779b55ae5c356e989dcd3aa677d8486e" +"checksum ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7153dd96dade874ab973e098cb62fcdbb89a03682e46b144fd09550998d4a4a7" +"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" +"checksum schannel 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "dc1fabf2a7b6483a141426e1afd09ad543520a77ac49bd03c286e7696ccfd77f" +"checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" +"checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" +"checksum serde 1.0.76 (registry+https://github.com/rust-lang/crates.io-index)" = "d00c69ae39089576cddfd235556e3b21bf41c2d80018063cb5ab8a1183c917fd" +"checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae" +"checksum serde_urlencoded 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aaed41d9fb1e2f587201b863356590c90c1157495d811430a0c0325fe8169650" +"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +"checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d" +"checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum tokio 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fbb6a6e9db2702097bfdfddcb09841211ad423b86c75b5ddaca1d62842ac492c" +"checksum tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "881e9645b81c2ce95fcb799ded2c29ffb9f25ef5bef909089a420e5961dd8ccb" +"checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" +"checksum tokio-current-thread 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fdfb899688ac16f618076bd09215edbfda0fd5dfecb375b6942636cb31fa8a7" +"checksum tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "84823b932d566bc3c6aa644df4ca36cb38593c50b7db06011fd4e12e31e4047e" +"checksum tokio-fs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5cbe4ca6e71cb0b62a66e4e6f53a8c06a6eefe46cc5f665ad6f274c9906f135" +"checksum tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6cc2de7725863c86ac71b0b9068476fec50834f055a243558ef1655bbd34cb" +"checksum tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4bfbaf9f260635649ec26b6fb4aded03887295ffcd999f6e43fd2c4758f758ea" +"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" +"checksum tokio-tcp 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4c329b47f071eb8a746040465fa751bd95e4716e98daef6a9b4e434c17d565" +"checksum tokio-threadpool 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a5758cecb6e0633cea5d563ac07c975e04961690b946b04fd84e7d6445a8f6af" +"checksum tokio-timer 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d03fa701f9578a01b7014f106b47f0a363b4727a7f3f75d666e312ab7acbbf1c" +"checksum tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772f4b04e560117fe3b0a53e490c16ddc8ba6ec437015d91fa385564996ed913" +"checksum tokio-udp 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "da941144b816d0dcda4db3a1ba87596e4df5e860a72b70783fe435891f80601c" +"checksum tokio-uds 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "424c1ed15a0132251813ccea50640b224c809d6ceafb88154c1a8775873a0e89" +"checksum try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2" +"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +"checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6" +"checksum uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e1436e58182935dcd9ce0add9ea0b558e8a87befe01c1a301e6020aeb0876363" +"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum want 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a05d9d966753fa4b5c8db73fcab5eed4549cfe0e1e4e66911e5564a0085c35d1" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/init_exercise/Cargo.toml b/init_exercise/Cargo.toml index b27e4dfd0..dfd474af5 100644 --- a/init_exercise/Cargo.toml +++ b/init_exercise/Cargo.toml @@ -6,3 +6,5 @@ description = "An utility for creating or updating the exercises on the Exercism [dependencies] clap = "2.32.0" +reqwest = "0.8.8" +serde_json = "1.0.26" diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 828e15fdd..b9fe845a2 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -1,5 +1,7 @@ /// This module contains source for the `generate` command. use clap::ArgMatches; +use reqwest::{self, StatusCode}; +use serde_json::Value; use std::{ fs::{File, OpenOptions}, io::Write, @@ -29,6 +31,24 @@ static EXAMPLE_RS_CONTENT: &'static str = "//! Example implementation //! - Test your example by running `../../bin/test-exercise` "; +// Try to get the canonical data for the exercise of the given name +fn get_canonical_data(exercise_name: &str) -> Option { + let url = format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name); + + let mut response = + reqwest::get(&url).expect("Failed to make HTTP request for the canonical data."); + + if response.status() != StatusCode::Ok { + return None; + } else { + return Some( + response + .json() + .expect("Failed to parse the JSON canonical-data response"), + ); + } +} + // Generate a new exercise with specified name and flags fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) { let rev_parse_output = Command::new("git") @@ -99,6 +119,15 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) ::std::fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT) .expect("Failed to create example.rs file"); + + if let Some(data) = get_canonical_data(exercise_name) { + println!("Canonical data: {}", data); + } else { + println!( + "No canonical data for exercise '{}' found. Generating standard exercise template.", + exercise_name + ); + } } pub fn process_matches(matches: &ArgMatches) { diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index cbb2a0344..f7959663b 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -1,4 +1,7 @@ extern crate clap; +extern crate reqwest; +extern crate serde_json; + mod cmd; use clap::{App, Arg, ArgMatches, SubCommand}; From 9b6181857291f3f8f727b1971d985df97556986a Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 5 Sep 2018 13:32:07 +0300 Subject: [PATCH 11/80] init_exercise: Added generate_standard_exercise_template function --- init_exercise/src/cmd/generate.rs | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index b9fe845a2..1b317f9ac 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -49,6 +49,36 @@ fn get_canonical_data(exercise_name: &str) -> Option { } } +// Generate .meta directory and it's contents without using the canonical data +fn generate_standard_exercise_template(exercise_name: &str, exercise_path: &Path) { + ::std::fs::create_dir(exercise_path.join(".meta")) + .expect("Failed to create the .meta directory"); + + ::std::fs::write( + exercise_path.join(".meta").join("description.md"), + "Describe your exercise here.\n\nDon't forget that `README.md` is automatically generated; update this within `.meta/description.md`.", + ).expect("Failed to create .meta/description.md file"); + + ::std::fs::write( + exercise_path.join(".meta").join("metadata.yml"), + format!( + "---\nblurb: \"{}\"\nsource: \"\"\nsource_url: \"\"", + exercise_name + ), + ).expect("Failed to create .meta/metadata.yml file"); + + let mut tests_file = OpenOptions::new() + .append(true) + .open( + exercise_path + .join("tests") + .join(format!("{}.rs", exercise_name)), + ) + .unwrap(); + + tests_file.write(b"// Add your tests here").unwrap(); +} + // Generate a new exercise with specified name and flags fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) { let rev_parse_output = Command::new("git") @@ -125,8 +155,10 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) } else { println!( "No canonical data for exercise '{}' found. Generating standard exercise template.", - exercise_name + &exercise_name ); + + generate_standard_exercise_template(&exercise_name, &exercise_path); } } From bd002b94c952c0988d1f0bee6e38024048d82022 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sun, 9 Sep 2018 12:45:54 +0300 Subject: [PATCH 12/80] init_exercise: Renamed generate_standard_exercise_template to generate_default_meta --- init_exercise/src/cmd/generate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 1b317f9ac..7d44183e5 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -50,7 +50,7 @@ fn get_canonical_data(exercise_name: &str) -> Option { } // Generate .meta directory and it's contents without using the canonical data -fn generate_standard_exercise_template(exercise_name: &str, exercise_path: &Path) { +fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { ::std::fs::create_dir(exercise_path.join(".meta")) .expect("Failed to create the .meta directory"); @@ -158,7 +158,7 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) &exercise_name ); - generate_standard_exercise_template(&exercise_name, &exercise_path); + generate_default_meta(&exercise_name, &exercise_path); } } From 907c4b1dff9011f2c4a3b37b1108bb3c8c1d7dd9 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sun, 9 Sep 2018 15:38:17 +0300 Subject: [PATCH 13/80] init_exercise: Added update_cargo_toml function --- init_exercise/Cargo.lock | 10 ++++++ init_exercise/Cargo.toml | 1 + init_exercise/src/cmd/generate.rs | 51 ++++++++++++++++++++++++++++--- init_exercise/src/main.rs | 1 + 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/init_exercise/Cargo.lock b/init_exercise/Cargo.lock index a2e84281c..942e37ba0 100644 --- a/init_exercise/Cargo.lock +++ b/init_exercise/Cargo.lock @@ -266,6 +266,7 @@ dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -956,6 +957,14 @@ dependencies = [ "tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.76 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "try-lock" version = "0.1.0" @@ -1198,6 +1207,7 @@ dependencies = [ "checksum tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772f4b04e560117fe3b0a53e490c16ddc8ba6ec437015d91fa385564996ed913" "checksum tokio-udp 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "da941144b816d0dcda4db3a1ba87596e4df5e860a72b70783fe435891f80601c" "checksum tokio-uds 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "424c1ed15a0132251813ccea50640b224c809d6ceafb88154c1a8775873a0e89" +"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" "checksum try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2" "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" "checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a" diff --git a/init_exercise/Cargo.toml b/init_exercise/Cargo.toml index dfd474af5..351125ee1 100644 --- a/init_exercise/Cargo.toml +++ b/init_exercise/Cargo.toml @@ -8,3 +8,4 @@ description = "An utility for creating or updating the exercises on the Exercism clap = "2.32.0" reqwest = "0.8.8" serde_json = "1.0.26" +toml = "0.4" diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 7d44183e5..cfe037d92 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -1,13 +1,14 @@ /// This module contains source for the `generate` command. use clap::ArgMatches; use reqwest::{self, StatusCode}; -use serde_json::Value; +use serde_json::Value as JsonValue; use std::{ fs::{File, OpenOptions}, io::Write, path::Path, process::Command, }; +use toml::Value as TomlValue; static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo # will have compiled files and executables @@ -32,7 +33,7 @@ static EXAMPLE_RS_CONTENT: &'static str = "//! Example implementation "; // Try to get the canonical data for the exercise of the given name -fn get_canonical_data(exercise_name: &str) -> Option { +fn get_canonical_data(exercise_name: &str) -> Option { let url = format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name); let mut response = @@ -79,6 +80,41 @@ fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { tests_file.write(b"// Add your tests here").unwrap(); } +// Update Cargo.toml of the generated exercise according to the fetched canonical data +fn update_cargo_toml(exercise_name: &str, exercise_path: &Path, canonical_data: &JsonValue) { + let cargo_toml_content = ::std::fs::read_to_string(exercise_path.join("Cargo.toml")) + .expect("Error reading Cargo.toml"); + + let mut cargo_toml: TomlValue = cargo_toml_content.parse().unwrap(); + + { + let mut package_table = (&mut cargo_toml["package"]).as_table_mut().unwrap(); + + package_table.insert( + "version".to_string(), + TomlValue::String(canonical_data["version"].as_str().unwrap().to_string()), + ); + + package_table.insert( + "name".to_string(), + TomlValue::String(exercise_name.replace("-", "_")), + ); + } + + ::std::fs::write(exercise_path.join("Cargo.toml"), cargo_toml.to_string()) + .expect("Failed to update Cargo.toml file"); +} + +// Generate test suite using the canonical data +fn generate_tests_from_canonical_data( + exercise_name: &str, + exercise_path: &Path, + canonical_data: &JsonValue, + use_maplit: bool, +) { + update_cargo_toml(exercise_name, exercise_path, canonical_data); +} + // Generate a new exercise with specified name and flags fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) { let rev_parse_output = Command::new("git") @@ -150,8 +186,15 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) ::std::fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT) .expect("Failed to create example.rs file"); - if let Some(data) = get_canonical_data(exercise_name) { - println!("Canonical data: {}", data); + if let Some(canonical_data) = get_canonical_data(exercise_name) { + println!("Generating tests from canonical data"); + + generate_tests_from_canonical_data( + &exercise_name, + &exercise_path, + &canonical_data, + use_maplit, + ); } else { println!( "No canonical data for exercise '{}' found. Generating standard exercise template.", diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index f7959663b..8d5a86eed 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -1,6 +1,7 @@ extern crate clap; extern crate reqwest; extern crate serde_json; +extern crate toml; mod cmd; From fb4ceaab23c37af1a044c553cd947a794bb446dd Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Mon, 10 Sep 2018 08:06:26 +0300 Subject: [PATCH 14/80] init_exercise: Added prepend text to the test suite --- init_exercise/src/cmd/generate.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index cfe037d92..5202b5b3c 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -113,6 +113,27 @@ fn generate_tests_from_canonical_data( use_maplit: bool, ) { update_cargo_toml(exercise_name, exercise_path, canonical_data); + + let tests_path = exercise_path + .join("tests") + .join(format!("{}.rs", exercise_name)); + + let tests_content = + ::std::fs::read_to_string(&tests_path).expect("Failed to read existing tests content."); + + let updated_tests_content = format!( + "//! Tests for {} \n\ + //! \n\ + //! Generated by [utility][utility] using [canonical data][canonical_data] \n\ + //! \n\ + //! [utility]: {} \n\ + //! [canonical_data]: {} \n\ + \n\ + {} \n\ + ", exercise_name, env!("CARGO_PKG_NAME"), format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name), tests_content); + + ::std::fs::write(&tests_path, updated_tests_content) + .expect("Failed to update the content of the test suite"); } // Generate a new exercise with specified name and flags From f08f7e2f8c44efccd4c01013169c350d5d038678 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 12 Sep 2018 19:26:29 +0300 Subject: [PATCH 15/80] init_exercise: Added generation of the property functions to the test suite --- init_exercise/src/cmd/generate.rs | 90 ++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 14 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 5202b5b3c..f9ac70937 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -3,7 +3,8 @@ use clap::ArgMatches; use reqwest::{self, StatusCode}; use serde_json::Value as JsonValue; use std::{ - fs::{File, OpenOptions}, + collections::HashMap, + fs::{self, File, OpenOptions}, io::Write, path::Path, process::Command, @@ -52,15 +53,14 @@ fn get_canonical_data(exercise_name: &str) -> Option { // Generate .meta directory and it's contents without using the canonical data fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { - ::std::fs::create_dir(exercise_path.join(".meta")) - .expect("Failed to create the .meta directory"); + fs::create_dir(exercise_path.join(".meta")).expect("Failed to create the .meta directory"); - ::std::fs::write( + fs::write( exercise_path.join(".meta").join("description.md"), "Describe your exercise here.\n\nDon't forget that `README.md` is automatically generated; update this within `.meta/description.md`.", ).expect("Failed to create .meta/description.md file"); - ::std::fs::write( + fs::write( exercise_path.join(".meta").join("metadata.yml"), format!( "---\nblurb: \"{}\"\nsource: \"\"\nsource_url: \"\"", @@ -82,8 +82,8 @@ fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { // Update Cargo.toml of the generated exercise according to the fetched canonical data fn update_cargo_toml(exercise_name: &str, exercise_path: &Path, canonical_data: &JsonValue) { - let cargo_toml_content = ::std::fs::read_to_string(exercise_path.join("Cargo.toml")) - .expect("Error reading Cargo.toml"); + let cargo_toml_content = + fs::read_to_string(exercise_path.join("Cargo.toml")).expect("Error reading Cargo.toml"); let mut cargo_toml: TomlValue = cargo_toml_content.parse().unwrap(); @@ -101,10 +101,50 @@ fn update_cargo_toml(exercise_name: &str, exercise_path: &Path, canonical_data: ); } - ::std::fs::write(exercise_path.join("Cargo.toml"), cargo_toml.to_string()) + fs::write(exercise_path.join("Cargo.toml"), cargo_toml.to_string()) .expect("Failed to update Cargo.toml file"); } +fn generate_property_body<'a>( + property_functions: &mut HashMap<&'a str, String>, + case: &'a JsonValue, +) { + if let Some(property) = case.get("property") { + let property = property.as_str().unwrap(); + + if property_functions.contains_key(property) { + return; + } + + let property_function_body = format!( + "\ + /// Process a single test case for the property `{property}`\n\ + ///\n\ + /// All cases for the `{property}` property are implemented\n\ + /// in terms of this function.\n\ + /// \n\ + /// Note that you'll need to both name the expected transform which\n\ + /// the student needs to write, and name the types of the inputs and outputs.\n\ + /// While rustc _may_ be able to handle things properly given a working example,\n\ + /// students will face confusing errors if the `I` and `O` types are not concrete.\n\ + /// \n\ + fn process_{property_lower}_case(input: I, expected: O) {{\n\ + // typical implementation:\n\ + // assert_eq!(\n\ + // student_{property_lower}_func(input),\n\ + // expected\n\ + // )\n unimplemented!()\n\ + }}\n\ + \n\ + ", + property = property, + property_lower = property.to_lowercase().replace("-", "_") + ); + + property_functions.insert(property, property_function_body); + } +} + // Generate test suite using the canonical data fn generate_tests_from_canonical_data( exercise_name: &str, @@ -119,7 +159,7 @@ fn generate_tests_from_canonical_data( .join(format!("{}.rs", exercise_name)); let tests_content = - ::std::fs::read_to_string(&tests_path).expect("Failed to read existing tests content."); + fs::read_to_string(&tests_path).expect("Failed to read existing tests content."); let updated_tests_content = format!( "//! Tests for {} \n\ @@ -132,8 +172,31 @@ fn generate_tests_from_canonical_data( {} \n\ ", exercise_name, env!("CARGO_PKG_NAME"), format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name), tests_content); - ::std::fs::write(&tests_path, updated_tests_content) + fs::write(&tests_path, updated_tests_content) .expect("Failed to update the content of the test suite"); + + let mut property_functions: HashMap<&str, String> = HashMap::new(); + + let cases = canonical_data.get("cases").unwrap(); + + for case in cases.as_array().unwrap().into_iter() { + if let Some(sub_cases) = case.get("cases") { + for sub_case in sub_cases.as_array().unwrap().into_iter() { + generate_property_body(&mut property_functions, &sub_case); + } + } else { + generate_property_body(&mut property_functions, &case); + } + } + + let mut tests_file = OpenOptions::new().append(true).open(tests_path).unwrap(); + + for (property, property_body) in property_functions.iter() { + tests_file.write(property_body.as_bytes()).expect(&format!( + "Failed to add {} property function to the tests file", + property + )); + } } // Generate a new exercise with specified name and flags @@ -169,7 +232,7 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) .output() .expect("Failed to generate a new exercise via 'cargo new' command"); - ::std::fs::write(exercise_path.join(".gitignore"), GITIGNORE_CONTENT) + fs::write(exercise_path.join(".gitignore"), GITIGNORE_CONTENT) .expect("Failed to create .gitignore file"); if use_maplit { @@ -183,8 +246,7 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) .expect("Failed to add maplit dependency to the Cargo.toml"); } - ::std::fs::create_dir(exercise_path.join("tests")) - .expect("Failed to create the tests directory"); + fs::create_dir(exercise_path.join("tests")).expect("Failed to create the tests directory"); let mut test_file = File::create( exercise_path @@ -204,7 +266,7 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) .write(&format!("use {}::*;\n\n", exercise_name.replace("-", "_")).into_bytes()) .unwrap(); - ::std::fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT) + fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT) .expect("Failed to create example.rs file"); if let Some(canonical_data) = get_canonical_data(exercise_name) { From a97e032ae8db3cb7fa5c01c85638237770451d49 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Mon, 17 Sep 2018 10:27:13 +0300 Subject: [PATCH 16/80] init_exercise: Applied suggestions from clippy --- init_exercise/src/cmd/generate.rs | 45 +++++++++++++++++-------------- init_exercise/src/main.rs | 6 ++--- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index f9ac70937..1c8b2222a 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -41,13 +41,13 @@ fn get_canonical_data(exercise_name: &str) -> Option { reqwest::get(&url).expect("Failed to make HTTP request for the canonical data."); if response.status() != StatusCode::Ok { - return None; + None } else { - return Some( + Some( response .json() .expect("Failed to parse the JSON canonical-data response"), - ); + ) } } @@ -74,10 +74,9 @@ fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { exercise_path .join("tests") .join(format!("{}.rs", exercise_name)), - ) - .unwrap(); + ).unwrap(); - tests_file.write(b"// Add your tests here").unwrap(); + tests_file.write_all(b"// Add your tests here").unwrap(); } // Update Cargo.toml of the generated exercise according to the fetched canonical data @@ -88,7 +87,7 @@ fn update_cargo_toml(exercise_name: &str, exercise_path: &Path, canonical_data: let mut cargo_toml: TomlValue = cargo_toml_content.parse().unwrap(); { - let mut package_table = (&mut cargo_toml["package"]).as_table_mut().unwrap(); + let package_table = (&mut cargo_toml["package"]).as_table_mut().unwrap(); package_table.insert( "version".to_string(), @@ -150,7 +149,7 @@ fn generate_tests_from_canonical_data( exercise_name: &str, exercise_path: &Path, canonical_data: &JsonValue, - use_maplit: bool, + _use_maplit: bool, ) { update_cargo_toml(exercise_name, exercise_path, canonical_data); @@ -179,9 +178,9 @@ fn generate_tests_from_canonical_data( let cases = canonical_data.get("cases").unwrap(); - for case in cases.as_array().unwrap().into_iter() { + for case in cases.as_array().unwrap().iter() { if let Some(sub_cases) = case.get("cases") { - for sub_case in sub_cases.as_array().unwrap().into_iter() { + for sub_case in sub_cases.as_array().unwrap().iter() { generate_property_body(&mut property_functions, &sub_case); } } else { @@ -191,16 +190,20 @@ fn generate_tests_from_canonical_data( let mut tests_file = OpenOptions::new().append(true).open(tests_path).unwrap(); - for (property, property_body) in property_functions.iter() { - tests_file.write(property_body.as_bytes()).expect(&format!( - "Failed to add {} property function to the tests file", - property - )); + for (property, property_body) in &property_functions { + tests_file + .write_all(property_body.as_bytes()) + .unwrap_or_else(|_| { + panic!( + "Failed to add {} property function to the tests file", + property + ) + }); } } // Generate a new exercise with specified name and flags -fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) { +fn generate_exercise(exercise_name: &str, _run_configure: bool, use_maplit: bool) { let rev_parse_output = Command::new("git") .arg("rev-parse") .arg("--show-toplevel") @@ -242,7 +245,7 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) .unwrap(); cargo_toml_file - .write(b"maplit = \"1.0.1\"") + .write_all(b"maplit = \"1.0.1\"") .expect("Failed to add maplit dependency to the Cargo.toml"); } @@ -255,15 +258,17 @@ fn generate_exercise(exercise_name: &str, run_configure: bool, use_maplit: bool) ).expect("Failed to create test suite file"); if use_maplit { - test_file.write(b"#[macro_use]\nextern crate maplit;\n"); + test_file + .write_all(b"#[macro_use]\nextern crate maplit;\n") + .expect("Failed to append maplit crate to the test file."); } test_file - .write(&format!("extern crate {};\n", exercise_name.replace("-", "_")).into_bytes()) + .write_all(&format!("extern crate {};\n", exercise_name.replace("-", "_")).into_bytes()) .unwrap(); test_file - .write(&format!("use {}::*;\n\n", exercise_name.replace("-", "_")).into_bytes()) + .write_all(&format!("use {}::*;\n\n", exercise_name.replace("-", "_")).into_bytes()) .unwrap(); fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT) diff --git a/init_exercise/src/main.rs b/init_exercise/src/main.rs index 8d5a86eed..6837f3f89 100644 --- a/init_exercise/src/main.rs +++ b/init_exercise/src/main.rs @@ -6,7 +6,7 @@ extern crate toml; mod cmd; use clap::{App, Arg, ArgMatches, SubCommand}; -use cmd::{configure, generate, update}; +use cmd::{generate}; // Creates a new CLI app with appropriate matches // and returns the initialized matches. @@ -45,8 +45,8 @@ fn init_app<'a>() -> ArgMatches<'a> { fn process_matches(matches: &ArgMatches) { match matches.subcommand() { ("generate", Some(generate_matches)) => generate::process_matches(&generate_matches), - ("update", Some(update_matches)) => println!("Update!"), - ("configure", Some(configure_matches)) => println!("Configure!"), + ("update", Some(_update_matches)) => println!("Update!"), + ("configure", Some(_configure_matches)) => println!("Configure!"), ("", None) => { println!("No subcommand was used.\nUse init_exercise --help to learn about the possible subcommands.") } From bc83644c2a8a9282ee550fefce2457902b3b691e Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Mon, 17 Sep 2018 16:42:24 +0300 Subject: [PATCH 17/80] init_exercise: Added test suite functions generation --- init_exercise/src/cmd/generate.rs | 128 +++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 1c8b2222a..6df827103 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -144,12 +144,108 @@ fn generate_property_body<'a>( } } +// Depending on the type of the item variable, +// transform item into corresponding Rust literal +fn into_literal(item: &JsonValue, use_maplit: bool) -> String { + if item.is_string() { + format!("\"{}\"", item.as_str().unwrap()) + } else if item.is_array() { + format!( + "vec![{}]", + item.as_array() + .unwrap() + .iter() + .map(|item| into_literal(item, use_maplit)) + .collect::>() + .join(", ") + ) + } else if item.is_number() || item.is_boolean() || item.is_null() { + format!("{}", item) + } else if !use_maplit { + let key_values = item + .as_object() + .unwrap() + .iter() + .map(|(key, value)| { + format!( + "hm.insert(\"{}\", {});", + key, + into_literal(value, use_maplit) + ) + }).collect::(); + + format!( + "{{let mut hm = ::std::collections::HashMap::new(); {} hm}}", + key_values + ) + } else { + let key_values = item + .as_object() + .unwrap() + .iter() + .map(|(key, value)| format!("\"{}\"=>{}", key, into_literal(value, use_maplit))) + .collect::>() + .join(","); + + format!("hashmap!{{{}}}", key_values) + } +} + +fn generate_test_function(case: &JsonValue, use_maplit: bool) -> String { + let description = case.get("description").unwrap(); + + let description_formatted = description + .as_str() + .unwrap() + .to_lowercase() + .replace(" ", "_"); + + let property = case.get("property").unwrap().as_str().unwrap(); + + let comments = if let Some(comments) = case.get("comments") { + if comments.is_array() { + comments + .as_array() + .unwrap() + .iter() + .map(|line| format!("/// {}", line)) + .collect::() + } else { + format!("/// {}\n", comments.as_str().unwrap()) + } + } else { + "".to_string() + }; + + let input = into_literal(case.get("input").unwrap(), use_maplit); + + let expected = into_literal(case.get("expected").unwrap(), use_maplit); + + format!( + "#[test]\n\ + #[ignore]\n\ + /// {description}\n\ + {comments} + fn test_{description_formatted}() {{\n\ + process_{property}_case({input}, {expected});\n\ + }}\n\ + \n\ + ", + description = description, + description_formatted = description_formatted, + property = property, + comments = comments, + input = input, + expected = expected + ) +} + // Generate test suite using the canonical data fn generate_tests_from_canonical_data( exercise_name: &str, exercise_path: &Path, canonical_data: &JsonValue, - _use_maplit: bool, + use_maplit: bool, ) { update_cargo_toml(exercise_name, exercise_path, canonical_data); @@ -176,19 +272,31 @@ fn generate_tests_from_canonical_data( let mut property_functions: HashMap<&str, String> = HashMap::new(); + let mut test_functions: Vec = Vec::new(); + let cases = canonical_data.get("cases").unwrap(); for case in cases.as_array().unwrap().iter() { if let Some(sub_cases) = case.get("cases") { for sub_case in sub_cases.as_array().unwrap().iter() { generate_property_body(&mut property_functions, &sub_case); + + test_functions.push(generate_test_function(&sub_case, use_maplit)); } } else { generate_property_body(&mut property_functions, &case); + + test_functions.push(generate_test_function(&case, use_maplit)); } } - let mut tests_file = OpenOptions::new().append(true).open(tests_path).unwrap(); + if !test_functions.is_empty() { + let first_test_function = test_functions.remove(0).replace("#[ignore]\n", ""); + + test_functions.insert(0, first_test_function); + } + + let mut tests_file = OpenOptions::new().append(true).open(&tests_path).unwrap(); for (property, property_body) in &property_functions { tests_file @@ -200,6 +308,22 @@ fn generate_tests_from_canonical_data( ) }); } + + tests_file + .write_all(test_functions.join("\n\n").as_bytes()) + .unwrap_or_else(|_| panic!("Failed to add test functions to the test file")); + + if let Ok(which_output) = Command::new("which").arg("rustfmt").output() { + if !String::from_utf8_lossy(&which_output.stdout) + .trim() + .is_empty() + { + Command::new("rustfmt") + .arg(&tests_path) + .output() + .expect("Failed to run rustfmt command on the test suite file"); + } + } } // Generate a new exercise with specified name and flags From c1eba7a78b0e23a7d5aa2fed169f33c4be0a40dd Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Mon, 17 Sep 2018 18:00:52 +0300 Subject: [PATCH 18/80] init_exercise: Added generate_readme function --- init_exercise/src/cmd/generate.rs | 60 +++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index 6df827103..f2bc63d89 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -313,6 +313,7 @@ fn generate_tests_from_canonical_data( .write_all(test_functions.join("\n\n").as_bytes()) .unwrap_or_else(|_| panic!("Failed to add test functions to the test file")); + // FIXME: The algorithm is Unix-specific and will always fail on Windows. General solution required if let Ok(which_output) = Command::new("which").arg("rustfmt").output() { if !String::from_utf8_lossy(&which_output.stdout) .trim() @@ -326,6 +327,57 @@ fn generate_tests_from_canonical_data( } } +// Run bin/configlet generate command to generate README for the exercise +fn generate_readme(exercise_name: &str, track_root: &str) { + let bin_path = Path::new(track_root).join("bin"); + + let configlet_name = if bin_path.join("configlet").exists() { + "configlet" + } else if bin_path.join("configlet.exe").exists() { + "configlet.exe" + } else { + // FIXME: Uses bash script that would not work on Windows. + // RIIR is preferred. + let fetch_output = Command::new("sh") + .arg(bin_path.join("fetch-configlet.sh")) + .output() + .expect("Failed to run fetch-configlet script"); + + println!("OUTPUT: {}", String::from_utf8_lossy(&fetch_output.stdout)); + + if bin_path.join("configlet").exists() { + "configlet" + } else if bin_path.join("configlet.exe").exists() { + "configlet.exe" + } else { + panic!("Could not locate configlet. Aborting"); + } + }; + + let problem_specifications_path = Path::new(track_root) + .join("..") + .join("problem-specifications"); + + if !problem_specifications_path.exists() { + Command::new("git") + .arg("clone") + .arg("https://github.com/exercism/problem-specifications.git") + .arg(&problem_specifications_path) + .output() + .expect("Failed to clone problem-specifications repo"); + } + + Command::new(&bin_path.join(configlet_name)) + .arg("generate") + .arg(".") + .arg("--only") + .arg(&exercise_name) + .arg("--spec-path") + .arg(&problem_specifications_path) + .output() + .expect("Failed to run configlet generate command"); +} + // Generate a new exercise with specified name and flags fn generate_exercise(exercise_name: &str, _run_configure: bool, use_maplit: bool) { let rev_parse_output = Command::new("git") @@ -336,9 +388,9 @@ fn generate_exercise(exercise_name: &str, _run_configure: bool, use_maplit: bool let track_root = String::from_utf8(rev_parse_output.stdout).unwrap(); - let exercise_path = Path::new(&track_root.trim()) - .join("exercises") - .join(exercise_name); + let track_root = track_root.trim(); + + let exercise_path = Path::new(&track_root).join("exercises").join(exercise_name); if exercise_path.exists() { panic!( @@ -415,6 +467,8 @@ fn generate_exercise(exercise_name: &str, _run_configure: bool, use_maplit: bool generate_default_meta(&exercise_name, &exercise_path); } + + generate_readme(&exercise_name, &track_root); } pub fn process_matches(matches: &ArgMatches) { From 13f4778ce70b940e8d8d49e4945d5e8ef77eaed8 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Tue, 18 Sep 2018 11:53:37 +0300 Subject: [PATCH 19/80] init_exercise: Configured std::process::Command calls to wait until the process is finished running --- init_exercise/src/cmd/generate.rs | 51 ++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/init_exercise/src/cmd/generate.rs b/init_exercise/src/cmd/generate.rs index f2bc63d89..9afbf5204 100644 --- a/init_exercise/src/cmd/generate.rs +++ b/init_exercise/src/cmd/generate.rs @@ -7,7 +7,7 @@ use std::{ fs::{self, File, OpenOptions}, io::Write, path::Path, - process::Command, + process::{Command, Stdio}, }; use toml::Value as TomlValue; @@ -331,26 +331,32 @@ fn generate_tests_from_canonical_data( fn generate_readme(exercise_name: &str, track_root: &str) { let bin_path = Path::new(track_root).join("bin"); - let configlet_name = if bin_path.join("configlet").exists() { - "configlet" - } else if bin_path.join("configlet.exe").exists() { - "configlet.exe" + let configlet_name_unix = "configlet"; + + let configlet_name_windows = "configlet.exe"; + + let configlet_name = if bin_path.join(configlet_name_unix).exists() { + configlet_name_unix + } else if bin_path.join(configlet_name_windows).exists() { + configlet_name_windows } else { + println!("Configlet not found in the bin directory. Running bin/fetch-configlet."); + // FIXME: Uses bash script that would not work on Windows. // RIIR is preferred. - let fetch_output = Command::new("sh") - .arg(bin_path.join("fetch-configlet.sh")) + Command::new("bash") + .current_dir(track_root) + .stdout(Stdio::inherit()) + .arg(bin_path.join("fetch-configlet")) .output() .expect("Failed to run fetch-configlet script"); - println!("OUTPUT: {}", String::from_utf8_lossy(&fetch_output.stdout)); - - if bin_path.join("configlet").exists() { - "configlet" - } else if bin_path.join("configlet.exe").exists() { - "configlet.exe" + if bin_path.join(configlet_name_unix).exists() { + configlet_name_unix + } else if bin_path.join(configlet_name_windows).exists() { + configlet_name_windows } else { - panic!("Could not locate configlet. Aborting"); + panic!("Could not locate configlet after running bin/fetch-configlet. Aborting"); } }; @@ -359,15 +365,30 @@ fn generate_readme(exercise_name: &str, track_root: &str) { .join("problem-specifications"); if !problem_specifications_path.exists() { + let problem_specifications_url = "https://github.com/exercism/problem-specifications.git"; + println!( + "problem-specifications repository not found. Cloning the repository from {}", + problem_specifications_url + ); + Command::new("git") + .current_dir(track_root) + .stdout(Stdio::inherit()) .arg("clone") - .arg("https://github.com/exercism/problem-specifications.git") + .arg(problem_specifications_url) .arg(&problem_specifications_path) .output() .expect("Failed to clone problem-specifications repo"); } + println!( + "Generating README for {} via 'bin/configlet generate'", + exercise_name + ); + Command::new(&bin_path.join(configlet_name)) + .current_dir(track_root) + .stdout(Stdio::inherit()) .arg("generate") .arg(".") .arg("--only") From 3812526880cb5683ae56f55bb76ee7fa174a30b9 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Tue, 18 Sep 2018 11:57:15 +0300 Subject: [PATCH 20/80] init_exercise: Renamed 'init_exercise' crate to 'exercise' crate --- {init_exercise => exercise}/Cargo.lock | 20 +++++++++---------- {init_exercise => exercise}/Cargo.toml | 2 +- .../src/cmd/configure.rs | 0 .../src/cmd/generate.rs | 0 {init_exercise => exercise}/src/cmd/mod.rs | 0 {init_exercise => exercise}/src/cmd/update.rs | 0 {init_exercise => exercise}/src/main.rs | 0 7 files changed, 11 insertions(+), 11 deletions(-) rename {init_exercise => exercise}/Cargo.lock (99%) rename {init_exercise => exercise}/Cargo.toml (91%) rename {init_exercise => exercise}/src/cmd/configure.rs (100%) rename {init_exercise => exercise}/src/cmd/generate.rs (100%) rename {init_exercise => exercise}/src/cmd/mod.rs (100%) rename {init_exercise => exercise}/src/cmd/update.rs (100%) rename {init_exercise => exercise}/src/main.rs (100%) diff --git a/init_exercise/Cargo.lock b/exercise/Cargo.lock similarity index 99% rename from init_exercise/Cargo.lock rename to exercise/Cargo.lock index 942e37ba0..b67cf0711 100644 --- a/init_exercise/Cargo.lock +++ b/exercise/Cargo.lock @@ -164,6 +164,16 @@ dependencies = [ "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "exercise" +version = "0.1.0" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -259,16 +269,6 @@ dependencies = [ "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "init_exercise" -version = "0.1.0" -dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "iovec" version = "0.1.2" diff --git a/init_exercise/Cargo.toml b/exercise/Cargo.toml similarity index 91% rename from init_exercise/Cargo.toml rename to exercise/Cargo.toml index 351125ee1..b532077b5 100644 --- a/init_exercise/Cargo.toml +++ b/exercise/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "init_exercise" +name = "exercise" version = "0.1.0" authors = ["ZapAnton "] description = "An utility for creating or updating the exercises on the Exercism Rust track" diff --git a/init_exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs similarity index 100% rename from init_exercise/src/cmd/configure.rs rename to exercise/src/cmd/configure.rs diff --git a/init_exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs similarity index 100% rename from init_exercise/src/cmd/generate.rs rename to exercise/src/cmd/generate.rs diff --git a/init_exercise/src/cmd/mod.rs b/exercise/src/cmd/mod.rs similarity index 100% rename from init_exercise/src/cmd/mod.rs rename to exercise/src/cmd/mod.rs diff --git a/init_exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs similarity index 100% rename from init_exercise/src/cmd/update.rs rename to exercise/src/cmd/update.rs diff --git a/init_exercise/src/main.rs b/exercise/src/main.rs similarity index 100% rename from init_exercise/src/main.rs rename to exercise/src/main.rs From 1c259701cc4a0e7a53fec1dd5cec593c7049260c Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Tue, 18 Sep 2018 12:13:15 +0300 Subject: [PATCH 21/80] exercise: Added shell script for building exercise crate --- .gitignore | 4 +++- bin/build_exercise_crate.sh | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100755 bin/build_exercise_crate.sh diff --git a/.gitignore b/.gitignore index adebe4f36..67cf67b83 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ tmp bin/configlet bin/configlet.exe -exercises/*/Cargo.lock \ No newline at end of file +bin/exercise +bin/exercise.exe +exercises/*/Cargo.lock diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh new file mode 100755 index 000000000..753ed6e49 --- /dev/null +++ b/bin/build_exercise_crate.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Compile the 'exercise' crate and put it in the 'bin/' folder + +TRACK_ROOT="$(git rev-parse --show-toplevel)" + +EXERCISE_CRATE_PATH="$TRACK_ROOT/exercise" + +BIN_DIR_PATH="$TRACK_ROOT/bin" + +echo $TRACK_ROOT + +echo $EXERCISE_CRATE_PATH + +echo $BIN_DIR_PATH + +( + cd "$EXERCISE_CRATE_PATH" + + echo "Building exercise crate" + + cargo build --release + + 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" +) From e3e39f7b4f5da34ce8ec8f3ae9c7620159818ac9 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 3 Oct 2018 11:01:18 +0300 Subject: [PATCH 22/80] exercise: Updated build_exercise_crate.sh script to look for Windows builds --- bin/build_exercise_crate.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh index 753ed6e49..ce47879e4 100755 --- a/bin/build_exercise_crate.sh +++ b/bin/build_exercise_crate.sh @@ -20,7 +20,13 @@ echo $BIN_DIR_PATH cargo build --release - echo "Copying exercise crate from $EXERCISE_CRATE_PATH/target/release/exercise into $BIN_DIR_PATH" + RELEASE_PATH="$EXERCISE_CRATE_PATH/target/release/exercise" - cp "$EXERCISE_CRATE_PATH/target/release/exercise" "$BIN_DIR_PATH" + if [ -f "$RELEASE_PATH/exercise.exe" ]; then + RELEASE_PATH="$RELEASE_PATH.exe" + fi + + echo "Copying exercise crate from $RELEASE_PATH into $BIN_DIR_PATH" + + cp "$RELEASE_PATH" "$BIN_DIR_PATH" ) From e531e70179876c032c3f1da6d24ebf7abf1b442f Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 3 Oct 2018 11:02:32 +0300 Subject: [PATCH 23/80] exercise: Removed redundant echos from build_exercise_crate.sh script --- bin/build_exercise_crate.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh index ce47879e4..c579ba928 100755 --- a/bin/build_exercise_crate.sh +++ b/bin/build_exercise_crate.sh @@ -7,12 +7,6 @@ EXERCISE_CRATE_PATH="$TRACK_ROOT/exercise" BIN_DIR_PATH="$TRACK_ROOT/bin" -echo $TRACK_ROOT - -echo $EXERCISE_CRATE_PATH - -echo $BIN_DIR_PATH - ( cd "$EXERCISE_CRATE_PATH" From 831d4cb2fd1f48d52250f9477c3f66caaf5c4f29 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 3 Oct 2018 11:14:02 +0300 Subject: [PATCH 24/80] exercise: Removed run_configure argument from the generate_exercise function --- exercise/src/cmd/generate.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 9afbf5204..00b6bd3a3 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -400,7 +400,7 @@ fn generate_readme(exercise_name: &str, track_root: &str) { } // Generate a new exercise with specified name and flags -fn generate_exercise(exercise_name: &str, _run_configure: bool, use_maplit: bool) { +fn generate_exercise(exercise_name: &str, use_maplit: bool) { let rev_parse_output = Command::new("git") .arg("rev-parse") .arg("--show-toplevel") @@ -495,9 +495,9 @@ fn generate_exercise(exercise_name: &str, _run_configure: bool, use_maplit: bool pub fn process_matches(matches: &ArgMatches) { let exercise_name = matches.value_of("exercise_name").unwrap(); - let run_configure = !matches.is_present("no_configure"); + let _run_configure = !matches.is_present("no_configure"); let use_maplit = matches.is_present("use_maplit"); - generate_exercise(exercise_name, run_configure, use_maplit); + generate_exercise(exercise_name, use_maplit); } From 1400be93bc43068f5042363eeb857b403208a745 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 3 Oct 2018 11:36:03 +0300 Subject: [PATCH 25/80] exercise: Added configure_exercise call to the generate subcommand --- exercise/src/cmd/configure.rs | 6 ++++++ exercise/src/cmd/generate.rs | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index e69de29bb..b97a28c9b 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -0,0 +1,6 @@ +pub fn configure_exercise(exercise_name: &str) { + println!( + "Configuring config.json for the {} exercise.", + exercise_name + ); +} diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 00b6bd3a3..3245c4ee0 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -1,5 +1,6 @@ /// This module contains source for the `generate` command. use clap::ArgMatches; +use cmd::configure; use reqwest::{self, StatusCode}; use serde_json::Value as JsonValue; use std::{ @@ -495,9 +496,13 @@ fn generate_exercise(exercise_name: &str, use_maplit: bool) { pub fn process_matches(matches: &ArgMatches) { let exercise_name = matches.value_of("exercise_name").unwrap(); - let _run_configure = !matches.is_present("no_configure"); + let run_configure = !matches.is_present("no_configure"); let use_maplit = matches.is_present("use_maplit"); generate_exercise(exercise_name, use_maplit); + + if run_configure { + configure::configure_exercise(exercise_name); + } } From fe7962236ba53d79a446f365b088310988d33b45 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 3 Oct 2018 12:32:17 +0300 Subject: [PATCH 26/80] exercise: Added utils module --- exercise/src/cmd/configure.rs | 4 ++++ exercise/src/cmd/generate.rs | 11 ++--------- exercise/src/main.rs | 3 ++- exercise/src/utils.rs | 13 +++++++++++++ 4 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 exercise/src/utils.rs diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index b97a28c9b..c0f94c7eb 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -1,6 +1,10 @@ +use utils; + pub fn configure_exercise(exercise_name: &str) { println!( "Configuring config.json for the {} exercise.", exercise_name ); + + let _track_root = utils::get_track_root(); } diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 3245c4ee0..257383ff7 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -11,6 +11,7 @@ use std::{ process::{Command, Stdio}, }; use toml::Value as TomlValue; +use utils; static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo # will have compiled files and executables @@ -402,15 +403,7 @@ fn generate_readme(exercise_name: &str, track_root: &str) { // Generate a new exercise with specified name and flags fn generate_exercise(exercise_name: &str, use_maplit: bool) { - let rev_parse_output = Command::new("git") - .arg("rev-parse") - .arg("--show-toplevel") - .output() - .expect("Failed to get the path to the track repo."); - - let track_root = String::from_utf8(rev_parse_output.stdout).unwrap(); - - let track_root = track_root.trim(); + let track_root = utils::get_track_root(); let exercise_path = Path::new(&track_root).join("exercises").join(exercise_name); diff --git a/exercise/src/main.rs b/exercise/src/main.rs index 6837f3f89..15e26f7ff 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -4,9 +4,10 @@ extern crate serde_json; extern crate toml; mod cmd; +mod utils; use clap::{App, Arg, ArgMatches, SubCommand}; -use cmd::{generate}; +use cmd::generate; // Creates a new CLI app with appropriate matches // and returns the initialized matches. diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs new file mode 100644 index 000000000..5f2e33915 --- /dev/null +++ b/exercise/src/utils.rs @@ -0,0 +1,13 @@ +use std::process::Command; + +pub fn get_track_root() -> String { + let rev_parse_output = Command::new("git") + .arg("rev-parse") + .arg("--show-toplevel") + .output() + .expect("Failed to get the path to the track repo."); + + let track_root = String::from_utf8(rev_parse_output.stdout).unwrap(); + + track_root.trim().to_string() +} From fab6d41e1b57eeae18926df0119981fb9e7f68fe Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 3 Oct 2018 18:35:27 +0300 Subject: [PATCH 27/80] exercise: Implemented the user configuration reading for the configure subcommand --- exercise/Cargo.lock | 10 +++ exercise/Cargo.toml | 1 + exercise/src/cmd/configure.rs | 115 +++++++++++++++++++++++++++++++++- exercise/src/main.rs | 2 + 4 files changed, 127 insertions(+), 1 deletion(-) diff --git a/exercise/Cargo.lock b/exercise/Cargo.lock index b67cf0711..a5ee70577 100644 --- a/exercise/Cargo.lock +++ b/exercise/Cargo.lock @@ -172,6 +172,7 @@ dependencies = [ "reqwest 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1031,6 +1032,14 @@ dependencies = [ "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "uuid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vcpkg" version = "0.2.6" @@ -1217,6 +1226,7 @@ dependencies = [ "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6" "checksum uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e1436e58182935dcd9ce0add9ea0b558e8a87befe01c1a301e6020aeb0876363" +"checksum uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dab5c5526c5caa3d106653401a267fed923e7046f35895ffcb5ca42db64942e6" "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" diff --git a/exercise/Cargo.toml b/exercise/Cargo.toml index b532077b5..eb865dc50 100644 --- a/exercise/Cargo.toml +++ b/exercise/Cargo.toml @@ -9,3 +9,4 @@ clap = "2.32.0" reqwest = "0.8.8" serde_json = "1.0.26" toml = "0.4" +uuid = { version = "0.7", features = ["v4"] } diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index c0f94c7eb..fa5534ce7 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -1,4 +1,96 @@ +use serde_json::{self, Value}; +use std::{ + fs, + io::{stdin, stdout, Write}, + path::Path, +}; use utils; +use uuid::Uuid; + +fn get_user_input(prompt: &str) -> String { + print!("{}", prompt); + + let mut buffer = String::new(); + + stdout().flush().unwrap(); + + stdin() + .read_line(&mut buffer) + .expect("Failed to get user input."); + + buffer.trim().to_string() +} + +fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { + let uuid = Uuid::new_v4().to_hyphenated().to_string(); + + let core = false; + + let unlocked_by = loop { + let user_input = get_user_input("Exercise slug which unlocks this (blank for None): "); + if user_input.is_empty() { + // TODO: As of Exercism V2, only core exercises can have "unlocked_by" + // field set to null. Perhaps "hello-world" could be used here? + break None; + } else if !config_content["exercises"] + .as_array() + .unwrap() + .iter() + .any(|value| value["slug"] == user_input) + { + println!("{} is not an existing exercise slug", user_input); + + continue; + } else { + break Some(user_input); + }; + }; + + let difficulty = loop { + let user_input = get_user_input("Difficulty for this exercise(1, 4, 7, 10): "); + + if let Ok(difficulty) = user_input.parse::() { + if ![1, 4, 7, 10].contains(&difficulty) { + println!("Difficulty should be 1, 4, 7 or 10, not '{}'.", difficulty); + + continue; + } + + break difficulty; + } else { + println!("Difficulty should be a number, not '{}'.", user_input); + + continue; + } + }; + + let topics = loop { + let user_input = get_user_input("List of topics for this exercise, comma-separated: "); + + let topics = user_input + .split(',') + .map(|topic| topic.trim().to_string()) + .filter(|topic| !topic.is_empty()) + .collect::>(); + + if topics.is_empty() { + println!("Must enter at least one topic"); + + continue; + } + + break topics; + }; + + json!({ + "slug": exercise_name, + "uuid": uuid, + "core": core, + "unlocked_by": unlocked_by, + "difficulty": difficulty, + "topics": topics + }) +} pub fn configure_exercise(exercise_name: &str) { println!( @@ -6,5 +98,26 @@ pub fn configure_exercise(exercise_name: &str) { exercise_name ); - let _track_root = utils::get_track_root(); + let track_root = utils::get_track_root(); + + let config_path = Path::new(&track_root).join("config.json"); + + let config_content_string = fs::read_to_string(config_path) + .expect("Failed to read the contents of the config.json file"); + + let config_content: Value = serde_json::from_str(&config_content_string).unwrap(); + + let _user_config = loop { + let user_config = get_user_config(exercise_name, &config_content); + + let user_input = get_user_input(&format!( + "You have configured the {} exercise as follows:\n{}\nIs this correct? (y/N):", + exercise_name, + serde_json::to_string_pretty(&user_config).unwrap() + )); + + if user_input.to_lowercase().starts_with('y') { + break user_config; + } + }; } diff --git a/exercise/src/main.rs b/exercise/src/main.rs index 15e26f7ff..cab1a6093 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -1,7 +1,9 @@ extern crate clap; extern crate reqwest; +#[macro_use] extern crate serde_json; extern crate toml; +extern crate uuid; mod cmd; mod utils; From 2a158977269c8e0233b704a66d61c00dc154bc51 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 4 Oct 2018 01:31:36 +0300 Subject: [PATCH 28/80] exercise: Implemented the writing of the updated configuration to the config.json --- exercise/src/cmd/configure.rs | 72 +++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index fa5534ce7..7c2a225fa 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -36,7 +36,7 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { .as_array() .unwrap() .iter() - .any(|value| value["slug"] == user_input) + .any(|exercise| exercise["slug"] == user_input) { println!("{} is not an existing exercise slug", user_input); @@ -92,6 +92,63 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { }) } +fn update_config_content(exercise_name: &str, config_content: &mut Value, user_config: Value) { + let config_exercises = config_content["exercises"].as_array_mut().unwrap(); + + let insert_index = { + let exercises_with_similar_difficulty = config_exercises + .iter() + .enumerate() + .filter(|(_, exercise)| exercise["difficulty"] == user_config["difficulty"]) + .map(|(index, exercise)| (index, exercise["slug"].as_str().unwrap())) + .collect::>(); + + let mut start_index = 0; + + let mut end_index = exercises_with_similar_difficulty.len() - 1; + + let insert_index = loop { + if start_index == end_index { + break start_index; + } + + let middle_index = start_index + ((end_index - start_index) / 2); + + let user_input = get_user_input(&format!( + "Is {} easier then {}? (y/N): ", + exercise_name, exercises_with_similar_difficulty[middle_index].1 + )); + + if user_input.to_lowercase().starts_with('y') { + end_index = middle_index; + } else { + start_index = middle_index + 1; + } + }; + + exercises_with_similar_difficulty[insert_index].0 + }; + + let prompt = if insert_index == 0 { + format!("{} is the easiest exercise on the track.", exercise_name) + } else if insert_index == config_exercises.len() - 1 { + format!("{} is the hardest exercise on the track.", exercise_name) + } else { + format!( + "{} is placed between {} and {} exercises in difficulty.", + exercise_name, + config_exercises[insert_index - 1]["slug"].as_str().unwrap(), + config_exercises[insert_index]["slug"].as_str().unwrap(), + ) + }; + + println!("You have configured that {}", prompt); + + config_exercises.insert(insert_index, user_config); +} + +// TODO: Add a check for the existing exercise configuration +// TODO: Add configlet fmt call pub fn configure_exercise(exercise_name: &str) { println!( "Configuring config.json for the {} exercise.", @@ -102,12 +159,12 @@ pub fn configure_exercise(exercise_name: &str) { let config_path = Path::new(&track_root).join("config.json"); - let config_content_string = fs::read_to_string(config_path) + let config_content_string = fs::read_to_string(&config_path) .expect("Failed to read the contents of the config.json file"); - let config_content: Value = serde_json::from_str(&config_content_string).unwrap(); + let mut config_content: Value = serde_json::from_str(&config_content_string).unwrap(); - let _user_config = loop { + let user_config = loop { let user_config = get_user_config(exercise_name, &config_content); let user_input = get_user_input(&format!( @@ -120,4 +177,11 @@ pub fn configure_exercise(exercise_name: &str) { break user_config; } }; + + update_config_content(exercise_name, &mut config_content, user_config); + + fs::write( + &config_path, + serde_json::to_string_pretty(&config_content).unwrap(), + ).expect("Failed to write the updated track configuration to the config.json file"); } From da04d25f10f38c87535d1625f7149b6e469f0a5d Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 4 Oct 2018 01:42:08 +0300 Subject: [PATCH 29/80] exercise: Added run_configlate_command to the utils module --- exercise/src/utils.rs | 48 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index 5f2e33915..8f66dfa47 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -1,4 +1,7 @@ -use std::process::Command; +use std::{ + path::Path, + process::{Command, Stdio}, +}; pub fn get_track_root() -> String { let rev_parse_output = Command::new("git") @@ -11,3 +14,46 @@ pub fn get_track_root() -> String { track_root.trim().to_string() } + +pub fn run_configlate_command(command: &str, args: &[&str]) { + let track_root = get_track_root(); + + let bin_path = Path::new(&track_root).join("bin"); + + let configlet_name_unix = "configlet"; + + let configlet_name_windows = "configlet.exe"; + + let configlet_name = if bin_path.join(configlet_name_unix).exists() { + configlet_name_unix + } else if bin_path.join(configlet_name_windows).exists() { + configlet_name_windows + } else { + println!("Configlet not found in the bin directory. Running bin/fetch-configlet."); + + // FIXME: Uses bash script that would not work on Windows. + // RIIR is preferred. + Command::new("bash") + .current_dir(&track_root) + .stdout(Stdio::inherit()) + .arg(bin_path.join("fetch-configlet")) + .output() + .expect("Failed to run fetch-configlet script"); + + if bin_path.join(configlet_name_unix).exists() { + configlet_name_unix + } else if bin_path.join(configlet_name_windows).exists() { + configlet_name_windows + } else { + panic!("Could not locate configlet after running bin/fetch-configlet. Aborting"); + } + }; + + Command::new(&bin_path.join(configlet_name)) + .current_dir(&track_root) + .stdout(Stdio::inherit()) + .arg(command) + .args(args) + .output() + .expect("Failed to run configlet generate command"); +} From b4cd1cded3bf629dc49d3ec8d3a48a51c01c55de Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 4 Oct 2018 01:56:14 +0300 Subject: [PATCH 30/80] exercise: Replaced the configlet call code with the run_configlet_command function call --- exercise/src/cmd/generate.rs | 58 ++++++++---------------------------- 1 file changed, 13 insertions(+), 45 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 257383ff7..f1fdc414f 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -331,36 +331,10 @@ fn generate_tests_from_canonical_data( // Run bin/configlet generate command to generate README for the exercise fn generate_readme(exercise_name: &str, track_root: &str) { - let bin_path = Path::new(track_root).join("bin"); - - let configlet_name_unix = "configlet"; - - let configlet_name_windows = "configlet.exe"; - - let configlet_name = if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - println!("Configlet not found in the bin directory. Running bin/fetch-configlet."); - - // FIXME: Uses bash script that would not work on Windows. - // RIIR is preferred. - Command::new("bash") - .current_dir(track_root) - .stdout(Stdio::inherit()) - .arg(bin_path.join("fetch-configlet")) - .output() - .expect("Failed to run fetch-configlet script"); - - if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - panic!("Could not locate configlet after running bin/fetch-configlet. Aborting"); - } - }; + println!( + "Generating README for {} via 'bin/configlet generate'", + exercise_name + ); let problem_specifications_path = Path::new(track_root) .join("..") @@ -383,22 +357,16 @@ fn generate_readme(exercise_name: &str, track_root: &str) { .expect("Failed to clone problem-specifications repo"); } - println!( - "Generating README for {} via 'bin/configlet generate'", - exercise_name + utils::run_configlate_command( + "generate", + &[ + ".", + "--only", + exercise_name, + "--spec-path", + problem_specifications_path.to_str().unwrap(), + ], ); - - Command::new(&bin_path.join(configlet_name)) - .current_dir(track_root) - .stdout(Stdio::inherit()) - .arg("generate") - .arg(".") - .arg("--only") - .arg(&exercise_name) - .arg("--spec-path") - .arg(&problem_specifications_path) - .output() - .expect("Failed to run configlet generate command"); } // Generate a new exercise with specified name and flags From cb1328c6e0db778f9db2081585fa69d4f92dd590 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 4 Oct 2018 01:59:49 +0300 Subject: [PATCH 31/80] exercise: Added configlet fmt call to the configure_exercise funtion --- exercise/src/cmd/configure.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index 7c2a225fa..2c113fbc8 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -148,7 +148,6 @@ fn update_config_content(exercise_name: &str, config_content: &mut Value, user_c } // TODO: Add a check for the existing exercise configuration -// TODO: Add configlet fmt call pub fn configure_exercise(exercise_name: &str) { println!( "Configuring config.json for the {} exercise.", @@ -184,4 +183,8 @@ pub fn configure_exercise(exercise_name: &str) { &config_path, serde_json::to_string_pretty(&config_content).unwrap(), ).expect("Failed to write the updated track configuration to the config.json file"); + + println!("Formatting the config.json file via 'bin/configlet fmt'"); + + utils::run_configlate_command("fmt", &["."]); } From 0de9ed6601018cf5bf650d870ada85afa111d067 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 4 Oct 2018 02:07:39 +0300 Subject: [PATCH 32/80] exercise: Replace 'null' with 'hello-world' as default unlocked_by field value --- exercise/src/cmd/configure.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index 2c113fbc8..c3d54017e 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -29,9 +29,7 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { let unlocked_by = loop { let user_input = get_user_input("Exercise slug which unlocks this (blank for None): "); if user_input.is_empty() { - // TODO: As of Exercism V2, only core exercises can have "unlocked_by" - // field set to null. Perhaps "hello-world" could be used here? - break None; + break "hello-world".to_string(); } else if !config_content["exercises"] .as_array() .unwrap() @@ -42,7 +40,7 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { continue; } else { - break Some(user_input); + break user_input; }; }; From 151a1fb0dd702968b3259999aba733381872a2ff Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 4 Oct 2018 02:17:04 +0300 Subject: [PATCH 33/80] exercise: Added configure_exercise call for the configure subcommand --- exercise/src/main.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/exercise/src/main.rs b/exercise/src/main.rs index cab1a6093..9111e73df 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -9,7 +9,7 @@ mod cmd; mod utils; use clap::{App, Arg, ArgMatches, SubCommand}; -use cmd::generate; +use cmd::{configure, generate}; // Creates a new CLI app with appropriate matches // and returns the initialized matches. @@ -38,7 +38,7 @@ fn init_app<'a>() -> ArgMatches<'a> { .subcommand( SubCommand::with_name("configure") .about("Edits config.json for the specified exercise") - .arg(Arg::with_name("exercise_name").help("The name of the configured exercise")), + .arg(Arg::with_name("exercise_name").required(true).help("The name of the configured exercise")), ) .get_matches() } @@ -48,11 +48,17 @@ fn init_app<'a>() -> ArgMatches<'a> { fn process_matches(matches: &ArgMatches) { match matches.subcommand() { ("generate", Some(generate_matches)) => generate::process_matches(&generate_matches), + ("update", Some(_update_matches)) => println!("Update!"), - ("configure", Some(_configure_matches)) => println!("Configure!"), + + ("configure", Some(configure_matches)) => { + configure::configure_exercise(configure_matches.value_of("exercise_name").unwrap()) + } + ("", None) => { println!("No subcommand was used.\nUse init_exercise --help to learn about the possible subcommands.") } + _ => unreachable!(), } } From 4ff5bf3704fe72a784ef75a12685efb4add910f3 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 4 Oct 2018 11:26:25 +0300 Subject: [PATCH 34/80] exercise: Fixed typo: 'run_configlate_command' -> 'run_configlet_command' --- exercise/src/cmd/configure.rs | 2 +- exercise/src/cmd/generate.rs | 2 +- exercise/src/utils.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index c3d54017e..9fb1011f3 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -184,5 +184,5 @@ pub fn configure_exercise(exercise_name: &str) { println!("Formatting the config.json file via 'bin/configlet fmt'"); - utils::run_configlate_command("fmt", &["."]); + utils::run_configlet_command("fmt", &["."]); } diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index f1fdc414f..e177fe086 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -357,7 +357,7 @@ fn generate_readme(exercise_name: &str, track_root: &str) { .expect("Failed to clone problem-specifications repo"); } - utils::run_configlate_command( + utils::run_configlet_command( "generate", &[ ".", diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index 8f66dfa47..dbe1bd1c4 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -15,7 +15,7 @@ pub fn get_track_root() -> String { track_root.trim().to_string() } -pub fn run_configlate_command(command: &str, args: &[&str]) { +pub fn run_configlet_command(command: &str, args: &[&str]) { let track_root = get_track_root(); let bin_path = Path::new(&track_root).join("bin"); From a9b7b40241dfca57dc1ef34b878e47462f57cec4 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 19:04:29 +0300 Subject: [PATCH 35/80] exercise: configure subcommand now updates existing exercise config --- exercise/src/cmd/configure.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index 9fb1011f3..4011d526e 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -90,7 +90,7 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { }) } -fn update_config_content(exercise_name: &str, config_content: &mut Value, user_config: Value) { +fn insert_user_config(exercise_name: &str, config_content: &mut Value, user_config: Value) { let config_exercises = config_content["exercises"].as_array_mut().unwrap(); let insert_index = { @@ -145,7 +145,20 @@ fn update_config_content(exercise_name: &str, config_content: &mut Value, user_c config_exercises.insert(insert_index, user_config); } -// TODO: Add a check for the existing exercise configuration +fn update_existing_config(exercise_name: &str, config_content: &mut Value, user_config: Value) { + let exercises = config_content["exercises"].as_array_mut().unwrap(); + + let (existing_exercise_index, _) = exercises + .iter() + .enumerate() + .find(|(_, exercise)| exercise["slug"] == exercise_name) + .unwrap(); + + exercises.remove(existing_exercise_index); + + exercises.insert(existing_exercise_index, user_config); +} + pub fn configure_exercise(exercise_name: &str) { println!( "Configuring config.json for the {} exercise.", @@ -161,7 +174,7 @@ pub fn configure_exercise(exercise_name: &str) { let mut config_content: Value = serde_json::from_str(&config_content_string).unwrap(); - let user_config = loop { + let user_config: Value = loop { let user_config = get_user_config(exercise_name, &config_content); let user_input = get_user_input(&format!( @@ -175,7 +188,16 @@ pub fn configure_exercise(exercise_name: &str) { } }; - update_config_content(exercise_name, &mut config_content, user_config); + if config_content["exercises"] + .as_array() + .unwrap() + .iter() + .any(|exercise| exercise["slug"] == exercise_name) + { + update_existing_config(exercise_name, &mut config_content, user_config); + } else { + insert_user_config(exercise_name, &mut config_content, user_config); + } fs::write( &config_path, From 5959a3f691e8801341ce9b53d749c020b383c0b8 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 19:36:07 +0300 Subject: [PATCH 36/80] exercise: configure subcommand now allows to move the existing exercise config depending on the chosen difficulty --- exercise/src/cmd/configure.rs | 89 ++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index 4011d526e..f2dcbe9ca 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -90,73 +90,88 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { }) } -fn insert_user_config(exercise_name: &str, config_content: &mut Value, user_config: Value) { - let config_exercises = config_content["exercises"].as_array_mut().unwrap(); - - let insert_index = { - let exercises_with_similar_difficulty = config_exercises - .iter() - .enumerate() - .filter(|(_, exercise)| exercise["difficulty"] == user_config["difficulty"]) - .map(|(index, exercise)| (index, exercise["slug"].as_str().unwrap())) - .collect::>(); - - let mut start_index = 0; +fn choose_exercise_insert_index( + exercise_name: &str, + exercises: &Vec, + difficulty: &Value, +) -> usize { + let exercises_with_similar_difficulty = exercises + .iter() + .enumerate() + .filter(|(_, exercise)| exercise["difficulty"] == *difficulty) + .map(|(index, exercise)| (index, exercise["slug"].as_str().unwrap())) + .collect::>(); - let mut end_index = exercises_with_similar_difficulty.len() - 1; + let mut start_index = 0; - let insert_index = loop { - if start_index == end_index { - break start_index; - } + let mut end_index = exercises_with_similar_difficulty.len() - 1; - let middle_index = start_index + ((end_index - start_index) / 2); + let insert_index = loop { + if start_index == end_index { + break start_index; + } - let user_input = get_user_input(&format!( - "Is {} easier then {}? (y/N): ", - exercise_name, exercises_with_similar_difficulty[middle_index].1 - )); + let middle_index = start_index + ((end_index - start_index) / 2); - if user_input.to_lowercase().starts_with('y') { - end_index = middle_index; - } else { - start_index = middle_index + 1; - } - }; + let user_input = get_user_input(&format!( + "Is {} easier then {}? (y/N): ", + exercise_name, exercises_with_similar_difficulty[middle_index].1 + )); - exercises_with_similar_difficulty[insert_index].0 + if user_input.to_lowercase().starts_with('y') { + end_index = middle_index; + } else { + start_index = middle_index + 1; + } }; + 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) - } else if insert_index == config_exercises.len() - 1 { + } else if insert_index == exercises.len() - 1 { format!("{} is the hardest exercise on the track.", exercise_name) } else { format!( "{} is placed between {} and {} exercises in difficulty.", exercise_name, - config_exercises[insert_index - 1]["slug"].as_str().unwrap(), - config_exercises[insert_index]["slug"].as_str().unwrap(), + exercises[insert_index - 1]["slug"].as_str().unwrap(), + exercises[insert_index]["slug"].as_str().unwrap(), ) }; println!("You have configured that {}", prompt); - config_exercises.insert(insert_index, user_config); + insert_index +} + +fn insert_user_config(exercise_name: &str, config_content: &mut Value, user_config: Value) { + let exercises = config_content["exercises"].as_array_mut().unwrap(); + + let insert_index = + choose_exercise_insert_index(exercise_name, exercises, &user_config["difficulty"]); + + exercises.insert(insert_index, user_config); } fn update_existing_config(exercise_name: &str, config_content: &mut Value, user_config: Value) { let exercises = config_content["exercises"].as_array_mut().unwrap(); - let (existing_exercise_index, _) = exercises + let existing_exercise_index = exercises .iter() - .enumerate() - .find(|(_, exercise)| exercise["slug"] == exercise_name) + .position(|exercise| exercise["slug"] == exercise_name) .unwrap(); + let insert_index = + if exercises[existing_exercise_index]["difficulty"] == user_config["difficulty"] { + existing_exercise_index + } else { + choose_exercise_insert_index(exercise_name, &exercises, &user_config["difficulty"]) + }; + exercises.remove(existing_exercise_index); - exercises.insert(existing_exercise_index, user_config); + exercises.insert(insert_index, user_config); } pub fn configure_exercise(exercise_name: &str) { From 7d3dd70355d39cd2fbe27479b37b15c6817cb7fe Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 19:38:58 +0300 Subject: [PATCH 37/80] exercise: Replaced the 'None' with 'hello-world' in the unlocked_by field input message --- exercise/src/cmd/configure.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index f2dcbe9ca..172a2a861 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -27,7 +27,8 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { let core = false; let unlocked_by = loop { - let user_input = get_user_input("Exercise slug which unlocks this (blank for None): "); + let user_input = get_user_input("Exercise slug which unlocks this (blank for 'hello-world'): "); + if user_input.is_empty() { break "hello-world".to_string(); } else if !config_content["exercises"] From 062a7b894ed092ce8b84887d8bfc804332d7fa95 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 21:24:26 +0300 Subject: [PATCH 38/80] exercise: Moved the processing of ArgMatches to the main module for the generate subcommand --- exercise/src/cmd/generate.rs | 18 +----------------- exercise/src/main.rs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index e177fe086..867088782 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -1,6 +1,4 @@ /// This module contains source for the `generate` command. -use clap::ArgMatches; -use cmd::configure; use reqwest::{self, StatusCode}; use serde_json::Value as JsonValue; use std::{ @@ -370,7 +368,7 @@ fn generate_readme(exercise_name: &str, track_root: &str) { } // Generate a new exercise with specified name and flags -fn generate_exercise(exercise_name: &str, use_maplit: bool) { +pub fn generate_exercise(exercise_name: &str, use_maplit: bool) { let track_root = utils::get_track_root(); let exercise_path = Path::new(&track_root).join("exercises").join(exercise_name); @@ -453,17 +451,3 @@ fn generate_exercise(exercise_name: &str, use_maplit: bool) { generate_readme(&exercise_name, &track_root); } - -pub fn process_matches(matches: &ArgMatches) { - let exercise_name = matches.value_of("exercise_name").unwrap(); - - let run_configure = !matches.is_present("no_configure"); - - let use_maplit = matches.is_present("use_maplit"); - - generate_exercise(exercise_name, use_maplit); - - if run_configure { - configure::configure_exercise(exercise_name); - } -} diff --git a/exercise/src/main.rs b/exercise/src/main.rs index 9111e73df..4d14545d4 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -47,7 +47,19 @@ fn init_app<'a>() -> ArgMatches<'a> { // and call the appropriate function. fn process_matches(matches: &ArgMatches) { match matches.subcommand() { - ("generate", Some(generate_matches)) => generate::process_matches(&generate_matches), + ("generate", Some(generate_matches)) => { + let exercise_name = generate_matches.value_of("exercise_name").unwrap(); + + let run_configure = !generate_matches.is_present("no_configure"); + + let use_maplit = generate_matches.is_present("use_maplit"); + + generate::generate_exercise(exercise_name, use_maplit); + + if run_configure { + configure::configure_exercise(exercise_name); + } + }, ("update", Some(_update_matches)) => println!("Update!"), From 5f382b81df17681345d96dc062f29e9915855eb5 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 21:27:04 +0300 Subject: [PATCH 39/80] exercise: Replaced &Vec argument type with &[Value] --- exercise/src/cmd/configure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index 172a2a861..60abf6803 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -93,7 +93,7 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { fn choose_exercise_insert_index( exercise_name: &str, - exercises: &Vec, + exercises: &[Value], difficulty: &Value, ) -> usize { let exercises_with_similar_difficulty = exercises From 755f1bf11306ccf74e243b94c260eb33ba6b299c Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 21:40:08 +0300 Subject: [PATCH 40/80] exercise: Replaced the 'no-configure' flag with the 'configure' flag for the generate and update subcommands --- exercise/src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exercise/src/main.rs b/exercise/src/main.rs index 4d14545d4..b3478f8d4 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -22,8 +22,8 @@ fn init_app<'a>() -> ArgMatches<'a> { SubCommand::with_name("generate") .about("Generates new exercise") .arg(Arg::with_name("exercise_name").required(true).help("The name of the generated exercise")) - .arg(Arg::with_name("no_configure").long("no-configure").short("n").help( - "If set, the command will not edit config.json after generating the exercise", + .arg(Arg::with_name("configure").long("configure").short("c").help( + "If set, the command will edit the config.json file after generating the exercise", )) .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the generated test suite")), ) @@ -31,8 +31,8 @@ fn init_app<'a>() -> ArgMatches<'a> { SubCommand::with_name("update") .about("Updates the specified exercise") .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")) - .arg(Arg::with_name("no_configure").long("no-configure").short("n").help( - "If set, the command will not edit config.json after updating the exercise", + .arg(Arg::with_name("configure").long("configure").short("c").help( + "If set, the command will edit the config.json file after updating the exercise", )) ) .subcommand( @@ -50,7 +50,7 @@ fn process_matches(matches: &ArgMatches) { ("generate", Some(generate_matches)) => { let exercise_name = generate_matches.value_of("exercise_name").unwrap(); - let run_configure = !generate_matches.is_present("no_configure"); + let run_configure = generate_matches.is_present("configure"); let use_maplit = generate_matches.is_present("use_maplit"); From 9415a3f62876f6c6cfb706b76d9fdb6eedc053c9 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 21:58:00 +0300 Subject: [PATCH 41/80] exercise: Added update_exercise call to the main module --- exercise/src/cmd/configure.rs | 3 ++- exercise/src/cmd/update.rs | 1 + exercise/src/main.rs | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index 60abf6803..fe3790e19 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -27,7 +27,8 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { let core = false; let unlocked_by = loop { - let user_input = get_user_input("Exercise slug which unlocks this (blank for 'hello-world'): "); + let user_input = + get_user_input("Exercise slug which unlocks this (blank for 'hello-world'): "); if user_input.is_empty() { break "hello-world".to_string(); diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index e69de29bb..09d5701ee 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -0,0 +1 @@ +pub fn update_exercise(_exercise_name: &str) {} diff --git a/exercise/src/main.rs b/exercise/src/main.rs index b3478f8d4..c74979aa5 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -9,7 +9,7 @@ mod cmd; mod utils; use clap::{App, Arg, ArgMatches, SubCommand}; -use cmd::{configure, generate}; +use cmd::{configure, generate, update}; // Creates a new CLI app with appropriate matches // and returns the initialized matches. @@ -61,7 +61,17 @@ fn process_matches(matches: &ArgMatches) { } }, - ("update", Some(_update_matches)) => println!("Update!"), + ("update", Some(update_matches)) => { + let exercise_name = update_matches.value_of("exercise_name").unwrap(); + + let run_configure = update_matches.is_present("configure"); + + update::update_exercise(exercise_name); + + if run_configure { + configure::configure_exercise(exercise_name); + } + }, ("configure", Some(configure_matches)) => { configure::configure_exercise(configure_matches.value_of("exercise_name").unwrap()) From 910625fdd26780bd8b8d42a5808b647e4f0e27b7 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 12 Oct 2018 22:31:05 +0300 Subject: [PATCH 42/80] exercise: Added check if exercise exists for the update subcommand --- exercise/src/cmd/update.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index 09d5701ee..6622dbd93 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -1 +1,30 @@ -pub fn update_exercise(_exercise_name: &str) {} +use std::path::Path; +use utils; + +fn exercise_exists(exercise_name: &str) -> bool { + let track_root = utils::get_track_root(); + + let exercises_path = Path::new(&track_root).join("exercises"); + + for entry in exercises_path + .read_dir() + .expect("Failed to read 'exercises' dir") + { + if let Ok(entry) = entry { + if entry.file_type().unwrap().is_dir() && entry.file_name() == exercise_name { + return true; + } + } + } + + false +} + +pub fn update_exercise(exercise_name: &str) { + if !exercise_exists(exercise_name) { + panic!( + "Exercise with the name '{}' does not exists. Aborting", + exercise_name + ); + } +} From 5c5a61e16cd6aaefc032d5b9b74c56d9809d77e4 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Tue, 16 Oct 2018 10:16:48 +0300 Subject: [PATCH 43/80] exercise: Moved get_canonical_data function to the utils module --- exercise/src/cmd/generate.rs | 21 +-------------------- exercise/src/utils.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 867088782..39dfe3bde 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -1,5 +1,4 @@ /// This module contains source for the `generate` command. -use reqwest::{self, StatusCode}; use serde_json::Value as JsonValue; use std::{ collections::HashMap, @@ -33,24 +32,6 @@ static EXAMPLE_RS_CONTENT: &'static str = "//! Example implementation //! - Test your example by running `../../bin/test-exercise` "; -// Try to get the canonical data for the exercise of the given name -fn get_canonical_data(exercise_name: &str) -> Option { - let url = format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name); - - let mut response = - reqwest::get(&url).expect("Failed to make HTTP request for the canonical data."); - - if response.status() != StatusCode::Ok { - None - } else { - Some( - response - .json() - .expect("Failed to parse the JSON canonical-data response"), - ) - } -} - // Generate .meta directory and it's contents without using the canonical data fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { fs::create_dir(exercise_path.join(".meta")).expect("Failed to create the .meta directory"); @@ -431,7 +412,7 @@ pub fn generate_exercise(exercise_name: &str, use_maplit: bool) { fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT) .expect("Failed to create example.rs file"); - if let Some(canonical_data) = get_canonical_data(exercise_name) { + if let Some(canonical_data) = utils::get_canonical_data(exercise_name) { println!("Generating tests from canonical data"); generate_tests_from_canonical_data( diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index dbe1bd1c4..37badeef5 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -1,3 +1,5 @@ +use reqwest::{self, StatusCode}; +use serde_json::Value; use std::{ path::Path, process::{Command, Stdio}, @@ -57,3 +59,21 @@ pub fn run_configlet_command(command: &str, args: &[&str]) { .output() .expect("Failed to run configlet generate command"); } + +// Try to get the canonical data for the exercise of the given name +pub fn get_canonical_data(exercise_name: &str) -> Option { + let url = format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name); + + let mut response = + reqwest::get(&url).expect("Failed to make HTTP request for the canonical data."); + + if response.status() != StatusCode::Ok { + None + } else { + Some( + response + .json() + .expect("Failed to parse the JSON canonical-data response"), + ) + } +} From 719b68078c0723a58f3535638625e6485ea1721f Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 17 Oct 2018 13:52:45 +0300 Subject: [PATCH 44/80] exercise: Added get_tests_content function to the utils module --- exercise/src/cmd/generate.rs | 8 ++++++-- exercise/src/utils.rs | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 39dfe3bde..c69bace5b 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -234,8 +234,12 @@ fn generate_tests_from_canonical_data( .join("tests") .join(format!("{}.rs", exercise_name)); - let tests_content = - fs::read_to_string(&tests_path).expect("Failed to read existing tests content."); + let tests_content = utils::get_tests_content(exercise_name).unwrap_or_else(|_| { + panic!( + "Failed to get the content of the test suite for the '{}' exercise. Aborting.", + exercise_name + ) + }); let updated_tests_content = format!( "//! Tests for {} \n\ diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index 37badeef5..729d84b79 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -1,6 +1,7 @@ use reqwest::{self, StatusCode}; use serde_json::Value; use std::{ + fs, io, path::Path, process::{Command, Stdio}, }; @@ -77,3 +78,15 @@ pub fn get_canonical_data(exercise_name: &str) -> Option { ) } } + +pub fn get_tests_content(exercise_name: &str) -> io::Result { + let track_root = get_track_root(); + + let tests_path = Path::new(&track_root) + .join("exercises") + .join(exercise_name) + .join("tests") + .join(format!("{}.rs", exercise_name)); + + fs::read_to_string(tests_path) +} From efd92f1ff85a8a2048e46fb78073f40563102078 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 17 Oct 2018 14:08:45 +0300 Subject: [PATCH 45/80] exercise: Added formatting functions for the 'property' and 'description' fields of the exercise --- exercise/src/cmd/generate.rs | 18 ++++++------------ exercise/src/utils.rs | 13 +++++++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index c69bace5b..2e1e874ac 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -108,17 +108,17 @@ fn generate_property_body<'a>( /// While rustc _may_ be able to handle things properly given a working example,\n\ /// students will face confusing errors if the `I` and `O` types are not concrete.\n\ /// \n\ - fn process_{property_lower}_case(input: I, expected: O) {{\n\ + fn process_{property_formatted}_case(input: I, expected: O) {{\n\ // typical implementation:\n\ // assert_eq!(\n\ - // student_{property_lower}_func(input),\n\ + // student_{property_formatted}_func(input),\n\ // expected\n\ // )\n unimplemented!()\n\ }}\n\ \n\ ", property = property, - property_lower = property.to_lowercase().replace("-", "_") + property_formatted = utils::format_exercise_property(property), ); property_functions.insert(property, property_function_body); @@ -173,13 +173,7 @@ fn into_literal(item: &JsonValue, use_maplit: bool) -> String { } fn generate_test_function(case: &JsonValue, use_maplit: bool) -> String { - let description = case.get("description").unwrap(); - - let description_formatted = description - .as_str() - .unwrap() - .to_lowercase() - .replace(" ", "_"); + let description = case.get("description").unwrap().as_str().unwrap(); let property = case.get("property").unwrap().as_str().unwrap(); @@ -213,8 +207,8 @@ fn generate_test_function(case: &JsonValue, use_maplit: bool) -> String { \n\ ", description = description, - description_formatted = description_formatted, - property = property, + description_formatted = utils::format_exercise_description(description), + property = utils::format_exercise_property(property), comments = comments, input = input, expected = expected diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index 729d84b79..fd79bd2b9 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -90,3 +90,16 @@ pub fn get_tests_content(exercise_name: &str) -> io::Result { fs::read_to_string(tests_path) } + +pub fn format_exercise_description(description: &str) -> String { + description + .chars() + .filter(|c| c.is_alphabetic()) + .collect::() + .replace(" ", "_") + .to_lowercase() +} + +pub fn format_exercise_property(property: &str) -> String { + property.replace(" ", "_").to_lowercase() +} From 0ddbdfbb7ae8312b9a01134cdabc54254c5c806c Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Wed, 17 Oct 2018 18:31:22 +0300 Subject: [PATCH 46/80] exercise: Implemented apply_diffs function for the update subcommand --- exercise/src/cmd/update.rs | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index 6622dbd93..40667e323 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -1,3 +1,4 @@ +use serde_json::Value; use std::path::Path; use utils; @@ -20,6 +21,73 @@ fn exercise_exists(exercise_name: &str) -> bool { false } +fn generate_diff_test(_case: &Value, _diff_prefix: &str) -> String { + String::new() +} + +fn generate_diff_property(_case: &Value, _diff_prefix: &str) -> String { + String::new() +} + +fn find_diffs(case: &Value, tests_content: &str, diffs: &mut Vec) { + let description = case["description"].as_str().unwrap(); + + let diff_prefix = if !tests_content.contains(&format!( + "test_{}", + utils::format_exercise_description(description) + )) { + "NEW" + } else { + "UPDATED" + }; + + diffs.push(generate_diff_test(&case, diff_prefix)); + + let property = case["property"].as_str().unwrap(); + + if !tests_content.contains(&format!( + "process_{}_case", + utils::format_exercise_property(property) + )) { + diffs.push(generate_diff_property(&case, "NEW")); + } +} + +fn apply_diffs(exercise_name: &str) { + let canonical_data = utils::get_canonical_data(exercise_name).unwrap_or_else(|| { + panic!( + "Failed to get canonical data for the '{}' exercise. Aborting", + exercise_name + ) + }); + + let cases = canonical_data.get("cases").unwrap_or_else(|| { + panic!( + "Failed to get 'cases' field from the canonical data of the '{}' exercise", + exercise_name + ) + }); + + let tests_content = utils::get_tests_content(exercise_name).unwrap_or_else(|_| { + panic!( + "Failed to get test content for the '{}' exercise", + exercise_name + ) + }); + + let mut diffs: Vec = vec![]; + + for case in cases.as_array().unwrap().iter() { + if let Some(sub_cases) = case.get("cases") { + for sub_case in sub_cases.as_array().unwrap().iter() { + find_diffs(&sub_case, &tests_content, &mut diffs); + } + } else { + find_diffs(&case, &tests_content, &mut diffs); + } + } +} + pub fn update_exercise(exercise_name: &str) { if !exercise_exists(exercise_name) { panic!( @@ -27,4 +95,6 @@ pub fn update_exercise(exercise_name: &str) { exercise_name ); } + + apply_diffs(exercise_name); } From fba82adbee38b1b505f6cce8a44ae2bc3bc5a6ed Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 18 Oct 2018 17:40:31 +0300 Subject: [PATCH 47/80] exercise: Replaced apply_diffs function get_diffs function --- exercise/src/cmd/generate.rs | 151 ++++------------------------------- exercise/src/cmd/update.rs | 59 +++++++++----- exercise/src/utils.rs | 119 ++++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 156 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 2e1e874ac..c00dd37d6 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -85,136 +85,6 @@ fn update_cargo_toml(exercise_name: &str, exercise_path: &Path, canonical_data: .expect("Failed to update Cargo.toml file"); } -fn generate_property_body<'a>( - property_functions: &mut HashMap<&'a str, String>, - case: &'a JsonValue, -) { - if let Some(property) = case.get("property") { - let property = property.as_str().unwrap(); - - if property_functions.contains_key(property) { - return; - } - - let property_function_body = format!( - "\ - /// Process a single test case for the property `{property}`\n\ - ///\n\ - /// All cases for the `{property}` property are implemented\n\ - /// in terms of this function.\n\ - /// \n\ - /// Note that you'll need to both name the expected transform which\n\ - /// the student needs to write, and name the types of the inputs and outputs.\n\ - /// While rustc _may_ be able to handle things properly given a working example,\n\ - /// students will face confusing errors if the `I` and `O` types are not concrete.\n\ - /// \n\ - fn process_{property_formatted}_case(input: I, expected: O) {{\n\ - // typical implementation:\n\ - // assert_eq!(\n\ - // student_{property_formatted}_func(input),\n\ - // expected\n\ - // )\n unimplemented!()\n\ - }}\n\ - \n\ - ", - property = property, - property_formatted = utils::format_exercise_property(property), - ); - - property_functions.insert(property, property_function_body); - } -} - -// Depending on the type of the item variable, -// transform item into corresponding Rust literal -fn into_literal(item: &JsonValue, use_maplit: bool) -> String { - if item.is_string() { - format!("\"{}\"", item.as_str().unwrap()) - } else if item.is_array() { - format!( - "vec![{}]", - item.as_array() - .unwrap() - .iter() - .map(|item| into_literal(item, use_maplit)) - .collect::>() - .join(", ") - ) - } else if item.is_number() || item.is_boolean() || item.is_null() { - format!("{}", item) - } else if !use_maplit { - let key_values = item - .as_object() - .unwrap() - .iter() - .map(|(key, value)| { - format!( - "hm.insert(\"{}\", {});", - key, - into_literal(value, use_maplit) - ) - }).collect::(); - - format!( - "{{let mut hm = ::std::collections::HashMap::new(); {} hm}}", - key_values - ) - } else { - let key_values = item - .as_object() - .unwrap() - .iter() - .map(|(key, value)| format!("\"{}\"=>{}", key, into_literal(value, use_maplit))) - .collect::>() - .join(","); - - format!("hashmap!{{{}}}", key_values) - } -} - -fn generate_test_function(case: &JsonValue, use_maplit: bool) -> String { - let description = case.get("description").unwrap().as_str().unwrap(); - - let property = case.get("property").unwrap().as_str().unwrap(); - - let comments = if let Some(comments) = case.get("comments") { - if comments.is_array() { - comments - .as_array() - .unwrap() - .iter() - .map(|line| format!("/// {}", line)) - .collect::() - } else { - format!("/// {}\n", comments.as_str().unwrap()) - } - } else { - "".to_string() - }; - - let input = into_literal(case.get("input").unwrap(), use_maplit); - - let expected = into_literal(case.get("expected").unwrap(), use_maplit); - - format!( - "#[test]\n\ - #[ignore]\n\ - /// {description}\n\ - {comments} - fn test_{description_formatted}() {{\n\ - process_{property}_case({input}, {expected});\n\ - }}\n\ - \n\ - ", - description = description, - description_formatted = utils::format_exercise_description(description), - property = utils::format_exercise_property(property), - comments = comments, - input = input, - expected = expected - ) -} - // Generate test suite using the canonical data fn generate_tests_from_canonical_data( exercise_name: &str, @@ -258,14 +128,27 @@ fn generate_tests_from_canonical_data( for case in cases.as_array().unwrap().iter() { if let Some(sub_cases) = case.get("cases") { for sub_case in sub_cases.as_array().unwrap().iter() { - generate_property_body(&mut property_functions, &sub_case); + if let Some(property) = sub_case.get("property") { + let property = property.as_str().unwrap(); - test_functions.push(generate_test_function(&sub_case, use_maplit)); + if !property_functions.contains_key(property) { + property_functions + .insert(property, utils::generate_property_body(property)); + } + } + + test_functions.push(utils::generate_test_function(&sub_case, use_maplit)); } } else { - generate_property_body(&mut property_functions, &case); + if let Some(property) = case.get("property") { + let property = property.as_str().unwrap(); + + if !property_functions.contains_key(property) { + property_functions.insert(property, utils::generate_property_body(property)); + } + } - test_functions.push(generate_test_function(&case, use_maplit)); + test_functions.push(utils::generate_test_function(&case, use_maplit)); } } diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index 40667e323..23a90eb9c 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -1,5 +1,5 @@ use serde_json::Value; -use std::path::Path; +use std::{collections::HashSet, path::Path}; use utils; fn exercise_exists(exercise_name: &str) -> bool { @@ -21,39 +21,54 @@ fn exercise_exists(exercise_name: &str) -> bool { false } -fn generate_diff_test(_case: &Value, _diff_prefix: &str) -> String { - String::new() +fn generate_diff_test(case: &Value, diff_prefix: &str) -> String { + // FIXME: Add use_maplit arg + format!( + "//{}\n{}", + diff_prefix, + utils::generate_test_function(case, false) + ) } -fn generate_diff_property(_case: &Value, _diff_prefix: &str) -> String { - String::new() +fn generate_diff_property(property: &str, diff_prefix: &str) -> String { + format!( + "//{}\n{}", + diff_prefix, + utils::generate_property_body(property) + ) } -fn find_diffs(case: &Value, tests_content: &str, diffs: &mut Vec) { +fn generate_diffs(case: &Value, tests_content: &str, diffs: &mut HashSet) { let description = case["description"].as_str().unwrap(); - let diff_prefix = if !tests_content.contains(&format!( - "test_{}", - utils::format_exercise_description(description) - )) { + let description_formatted = utils::format_exercise_description(description); + + let diff_prefix = if !tests_content.contains(&format!("test_{}", description_formatted)) { "NEW" } else { "UPDATED" }; - diffs.push(generate_diff_test(&case, diff_prefix)); + if diffs.insert(generate_diff_test(&case, diff_prefix)) { + if diff_prefix == "NEW" { + println!("New test case detected: {}.", description_formatted); + } else { + println!("Updated test case: {}.", description_formatted); + } + } let property = case["property"].as_str().unwrap(); - if !tests_content.contains(&format!( - "process_{}_case", - utils::format_exercise_property(property) - )) { - diffs.push(generate_diff_property(&case, "NEW")); + let property_formatted = utils::format_exercise_property(property); + + if !tests_content.contains(&format!("process_{}_case", property_formatted)) + && diffs.insert(generate_diff_property(property, "NEW")) + { + println!("New property detected: {}.", property); } } -fn apply_diffs(exercise_name: &str) { +fn get_diffs(exercise_name: &str) -> HashSet { let canonical_data = utils::get_canonical_data(exercise_name).unwrap_or_else(|| { panic!( "Failed to get canonical data for the '{}' exercise. Aborting", @@ -75,17 +90,19 @@ fn apply_diffs(exercise_name: &str) { ) }); - let mut diffs: Vec = vec![]; + let mut diffs: HashSet = HashSet::new(); for case in cases.as_array().unwrap().iter() { if let Some(sub_cases) = case.get("cases") { for sub_case in sub_cases.as_array().unwrap().iter() { - find_diffs(&sub_case, &tests_content, &mut diffs); + generate_diffs(&sub_case, &tests_content, &mut diffs); } } else { - find_diffs(&case, &tests_content, &mut diffs); + generate_diffs(&case, &tests_content, &mut diffs); } } + + diffs } pub fn update_exercise(exercise_name: &str) { @@ -96,5 +113,5 @@ pub fn update_exercise(exercise_name: &str) { ); } - apply_diffs(exercise_name); + let _diffs = get_diffs(exercise_name); } diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index fd79bd2b9..dafb3c2b7 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -94,7 +94,7 @@ pub fn get_tests_content(exercise_name: &str) -> io::Result { pub fn format_exercise_description(description: &str) -> String { description .chars() - .filter(|c| c.is_alphabetic()) + .filter(|c| c.is_alphanumeric() || *c == ' ') .collect::() .replace(" ", "_") .to_lowercase() @@ -103,3 +103,120 @@ pub fn format_exercise_description(description: &str) -> String { pub fn format_exercise_property(property: &str) -> String { property.replace(" ", "_").to_lowercase() } + +pub fn generate_property_body(property: &str) -> String { + format!( + "\ + /// Process a single test case for the property `{property}`\n\ + ///\n\ + /// All cases for the `{property}` property are implemented\n\ + /// in terms of this function.\n\ + /// \n\ + /// Note that you'll need to both name the expected transform which\n\ + /// the student needs to write, and name the types of the inputs and outputs.\n\ + /// While rustc _may_ be able to handle things properly given a working example,\n\ + /// students will face confusing errors if the `I` and `O` types are not concrete.\n\ + /// \n\ + fn process_{property_formatted}_case(input: I, expected: O) {{\n\ + // typical implementation:\n\ + // assert_eq!(\n\ + // student_{property_formatted}_func(input),\n\ + // expected\n\ + // )\n unimplemented!()\n\ + }}\n\ + \n\ + ", + property = property, + property_formatted = format_exercise_property(property), + ) +} + +// Depending on the type of the item variable, +// transform item into corresponding Rust literal +fn into_literal(item: &Value, use_maplit: bool) -> String { + if item.is_string() { + format!("\"{}\"", item.as_str().unwrap()) + } else if item.is_array() { + format!( + "vec![{}]", + item.as_array() + .unwrap() + .iter() + .map(|item| into_literal(item, use_maplit)) + .collect::>() + .join(", ") + ) + } else if item.is_number() || item.is_boolean() || item.is_null() { + format!("{}", item) + } else if !use_maplit { + let key_values = item + .as_object() + .unwrap() + .iter() + .map(|(key, value)| { + format!( + "hm.insert(\"{}\", {});", + key, + into_literal(value, use_maplit) + ) + }).collect::(); + + format!( + "{{let mut hm = ::std::collections::HashMap::new(); {} hm}}", + key_values + ) + } else { + let key_values = item + .as_object() + .unwrap() + .iter() + .map(|(key, value)| format!("\"{}\"=>{}", key, into_literal(value, use_maplit))) + .collect::>() + .join(","); + + format!("hashmap!{{{}}}", key_values) + } +} + +pub fn generate_test_function(case: &Value, use_maplit: bool) -> String { + let description = case.get("description").unwrap().as_str().unwrap(); + + let property = case.get("property").unwrap().as_str().unwrap(); + + let comments = if let Some(comments) = case.get("comments") { + if comments.is_array() { + comments + .as_array() + .unwrap() + .iter() + .map(|line| format!("/// {}", line)) + .collect::() + } else { + format!("/// {}\n", comments.as_str().unwrap()) + } + } else { + "".to_string() + }; + + let input = into_literal(case.get("input").unwrap(), use_maplit); + + let expected = into_literal(case.get("expected").unwrap(), use_maplit); + + format!( + "#[test]\n\ + #[ignore]\n\ + /// {description}\n\ + {comments} + fn test_{description_formatted}() {{\n\ + process_{property}_case({input}, {expected});\n\ + }}\n\ + \n\ + ", + description = description, + description_formatted = format_exercise_description(description), + property = format_exercise_property(property), + comments = comments, + input = input, + expected = expected + ) +} From 16956b19f8c593ea44f816632ce682a4ae5ab7a7 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 18 Oct 2018 18:40:19 +0300 Subject: [PATCH 48/80] exercise: Implemented the update of the tests file --- exercise/src/cmd/generate.rs | 13 +----- exercise/src/cmd/update.rs | 85 ++++++++++++++++++++++++------------ exercise/src/utils.rs | 15 +++++++ 3 files changed, 74 insertions(+), 39 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index c00dd37d6..93b59949b 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -175,18 +175,7 @@ fn generate_tests_from_canonical_data( .write_all(test_functions.join("\n\n").as_bytes()) .unwrap_or_else(|_| panic!("Failed to add test functions to the test file")); - // FIXME: The algorithm is Unix-specific and will always fail on Windows. General solution required - if let Ok(which_output) = Command::new("which").arg("rustfmt").output() { - if !String::from_utf8_lossy(&which_output.stdout) - .trim() - .is_empty() - { - Command::new("rustfmt") - .arg(&tests_path) - .output() - .expect("Failed to run rustfmt command on the test suite file"); - } - } + utils::rustfmt(&tests_path); } // Run bin/configlet generate command to generate README for the exercise diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index 23a90eb9c..9fd66d4b4 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -1,7 +1,12 @@ use serde_json::Value; -use std::{collections::HashSet, path::Path}; +use std::{collections::HashSet, fs, path::Path}; use utils; +enum DiffType { + NEW, + UPDATED, +} + fn exercise_exists(exercise_name: &str) -> bool { let track_root = utils::get_track_root(); @@ -21,21 +26,20 @@ fn exercise_exists(exercise_name: &str) -> bool { false } -fn generate_diff_test(case: &Value, diff_prefix: &str) -> String { +fn generate_diff_test(case: &Value, diff_type: &DiffType) -> String { // FIXME: Add use_maplit arg format!( "//{}\n{}", - diff_prefix, + match diff_type { + DiffType::NEW => "NEW", + DiffType::UPDATED => "UPDATED", + }, utils::generate_test_function(case, false) ) } -fn generate_diff_property(property: &str, diff_prefix: &str) -> String { - format!( - "//{}\n{}", - diff_prefix, - utils::generate_property_body(property) - ) +fn generate_diff_property(property: &str) -> String { + format!("//{}\n{}", "NEW", utils::generate_property_body(property)) } fn generate_diffs(case: &Value, tests_content: &str, diffs: &mut HashSet) { @@ -43,17 +47,16 @@ fn generate_diffs(case: &Value, tests_content: &str, diffs: &mut HashSet let description_formatted = utils::format_exercise_description(description); - let diff_prefix = if !tests_content.contains(&format!("test_{}", description_formatted)) { - "NEW" + let diff_type = if !tests_content.contains(&format!("test_{}", description_formatted)) { + DiffType::NEW } else { - "UPDATED" + DiffType::UPDATED }; - if diffs.insert(generate_diff_test(&case, diff_prefix)) { - if diff_prefix == "NEW" { - println!("New test case detected: {}.", description_formatted); - } else { - println!("Updated test case: {}.", description_formatted); + if diffs.insert(generate_diff_test(&case, &diff_type)) { + match diff_type { + DiffType::NEW => println!("New test case detected: {}.", description_formatted), + DiffType::UPDATED => println!("Updated test case: {}.", description_formatted), } } @@ -62,13 +65,13 @@ fn generate_diffs(case: &Value, tests_content: &str, diffs: &mut HashSet let property_formatted = utils::format_exercise_property(property); if !tests_content.contains(&format!("process_{}_case", property_formatted)) - && diffs.insert(generate_diff_property(property, "NEW")) + && diffs.insert(generate_diff_property(property)) { println!("New property detected: {}.", property); } } -fn get_diffs(exercise_name: &str) -> HashSet { +fn get_diffs(exercise_name: &str, tests_content: &str) -> HashSet { let canonical_data = utils::get_canonical_data(exercise_name).unwrap_or_else(|| { panic!( "Failed to get canonical data for the '{}' exercise. Aborting", @@ -83,13 +86,6 @@ fn get_diffs(exercise_name: &str) -> HashSet { ) }); - let tests_content = utils::get_tests_content(exercise_name).unwrap_or_else(|_| { - panic!( - "Failed to get test content for the '{}' exercise", - exercise_name - ) - }); - let mut diffs: HashSet = HashSet::new(); for case in cases.as_array().unwrap().iter() { @@ -105,6 +101,32 @@ fn get_diffs(exercise_name: &str) -> HashSet { diffs } +fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str) { + let updated_tests_content = format!( + "{}\n{}", + tests_content, + diffs + .iter() + .map(|diff| format!("\n{}", diff)) + .collect::() + ); + + let tests_path = Path::new(&utils::get_track_root()) + .join("exercises") + .join(exercise_name) + .join("tests") + .join(format!("{}.rs", exercise_name)); + + fs::write(&tests_path, updated_tests_content.as_bytes()).unwrap_or_else(|_| { + panic!( + "Failed to update tests file for the '{}' exercise. Aborting.", + exercise_name, + ) + }); + + utils::rustfmt(&tests_path); +} + pub fn update_exercise(exercise_name: &str) { if !exercise_exists(exercise_name) { panic!( @@ -113,5 +135,14 @@ pub fn update_exercise(exercise_name: &str) { ); } - let _diffs = get_diffs(exercise_name); + let tests_content = utils::get_tests_content(exercise_name).unwrap_or_else(|_| { + panic!( + "Failed to get test content for the '{}' exercise", + exercise_name + ) + }); + + let diffs = get_diffs(exercise_name, &tests_content); + + apply_diffs(exercise_name, &diffs, &tests_content); } diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index dafb3c2b7..ba1bc459f 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -220,3 +220,18 @@ pub fn generate_test_function(case: &Value, use_maplit: bool) -> String { expected = expected ) } + +// FIXME: The algorithm is Unix-specific and will always fail on Windows. General solution required +pub fn rustfmt(file_path: &Path) { + if let Ok(which_output) = Command::new("which").arg("rustfmt").output() { + if !String::from_utf8_lossy(&which_output.stdout) + .trim() + .is_empty() + { + Command::new("rustfmt") + .arg(file_path) + .output() + .expect("Failed to run rustfmt command on the test suite file"); + } + } +} From ece5f5edd5e59950c3151983ecda90b19226ae34 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 18 Oct 2018 18:47:58 +0300 Subject: [PATCH 49/80] exercise: Added use_maplit flag for the update subcommand --- exercise/src/cmd/update.rs | 24 ++++++++++++++---------- exercise/src/main.rs | 5 ++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index 9fd66d4b4..d9deedc90 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -26,15 +26,14 @@ fn exercise_exists(exercise_name: &str) -> bool { false } -fn generate_diff_test(case: &Value, diff_type: &DiffType) -> String { - // FIXME: Add use_maplit arg +fn generate_diff_test(case: &Value, diff_type: &DiffType, use_maplit: bool) -> String { format!( "//{}\n{}", match diff_type { DiffType::NEW => "NEW", DiffType::UPDATED => "UPDATED", }, - utils::generate_test_function(case, false) + utils::generate_test_function(case, use_maplit) ) } @@ -42,7 +41,12 @@ fn generate_diff_property(property: &str) -> String { format!("//{}\n{}", "NEW", utils::generate_property_body(property)) } -fn generate_diffs(case: &Value, tests_content: &str, diffs: &mut HashSet) { +fn generate_diffs( + case: &Value, + tests_content: &str, + diffs: &mut HashSet, + use_maplit: bool, +) { let description = case["description"].as_str().unwrap(); let description_formatted = utils::format_exercise_description(description); @@ -53,7 +57,7 @@ fn generate_diffs(case: &Value, tests_content: &str, diffs: &mut HashSet DiffType::UPDATED }; - if diffs.insert(generate_diff_test(&case, &diff_type)) { + if diffs.insert(generate_diff_test(&case, &diff_type, use_maplit)) { match diff_type { DiffType::NEW => println!("New test case detected: {}.", description_formatted), DiffType::UPDATED => println!("Updated test case: {}.", description_formatted), @@ -71,7 +75,7 @@ fn generate_diffs(case: &Value, tests_content: &str, diffs: &mut HashSet } } -fn get_diffs(exercise_name: &str, tests_content: &str) -> HashSet { +fn get_diffs(exercise_name: &str, tests_content: &str, use_maplit: bool) -> HashSet { let canonical_data = utils::get_canonical_data(exercise_name).unwrap_or_else(|| { panic!( "Failed to get canonical data for the '{}' exercise. Aborting", @@ -91,10 +95,10 @@ fn get_diffs(exercise_name: &str, tests_content: &str) -> HashSet { for case in cases.as_array().unwrap().iter() { if let Some(sub_cases) = case.get("cases") { for sub_case in sub_cases.as_array().unwrap().iter() { - generate_diffs(&sub_case, &tests_content, &mut diffs); + generate_diffs(&sub_case, &tests_content, &mut diffs, use_maplit); } } else { - generate_diffs(&case, &tests_content, &mut diffs); + generate_diffs(&case, &tests_content, &mut diffs, use_maplit); } } @@ -127,7 +131,7 @@ fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str utils::rustfmt(&tests_path); } -pub fn update_exercise(exercise_name: &str) { +pub fn update_exercise(exercise_name: &str, use_maplit: bool) { if !exercise_exists(exercise_name) { panic!( "Exercise with the name '{}' does not exists. Aborting", @@ -142,7 +146,7 @@ pub fn update_exercise(exercise_name: &str) { ) }); - let diffs = get_diffs(exercise_name, &tests_content); + let diffs = get_diffs(exercise_name, &tests_content, use_maplit); apply_diffs(exercise_name, &diffs, &tests_content); } diff --git a/exercise/src/main.rs b/exercise/src/main.rs index c74979aa5..5e77c8e66 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -31,6 +31,7 @@ fn init_app<'a>() -> ArgMatches<'a> { SubCommand::with_name("update") .about("Updates the specified exercise") .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")) + .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the updated test suite")) .arg(Arg::with_name("configure").long("configure").short("c").help( "If set, the command will edit the config.json file after updating the exercise", )) @@ -66,7 +67,9 @@ fn process_matches(matches: &ArgMatches) { let run_configure = update_matches.is_present("configure"); - update::update_exercise(exercise_name); + let use_maplit = update_matches.is_present("use_maplit"); + + update::update_exercise(exercise_name, use_maplit); if run_configure { configure::configure_exercise(exercise_name); From 668ce12bb4b9662b985fc53bfae8b3d23ecc8289 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Thu, 18 Oct 2018 19:13:06 +0300 Subject: [PATCH 50/80] README: Replaced the 'init_exercise.py' script description with the 'exercise' crate description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee944cb96..d0f13c2e3 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Please see the documentation about [adding new exercises](https://github.com/exe Note that: -- The simplest way to generate a project template is to run `bin/init_exercise.py`. You'll need a Python installation >= 3.5 in order to run this script, but it will automate most of the following points for you. +- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --realease` command in the `exercise/` directory and then copy the `exercise` binary from the `exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. - Each exercise must stand on its own. Do not reference files outside the exercise directory. They will not be included when the user fetches the exercise. From 78c31da0695a7808578a4ba08f21659b26fdabd7 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 00:52:08 +0300 Subject: [PATCH 51/80] README: Fixed typo - realease -> release --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0f13c2e3..714e9fa0a 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Please see the documentation about [adding new exercises](https://github.com/exe Note that: -- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --realease` command in the `exercise/` directory and then copy the `exercise` binary from the `exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. +- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `exercise/` directory and then copy the `exercise` binary from the `exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. - Each exercise must stand on its own. Do not reference files outside the exercise directory. They will not be included when the user fetches the exercise. From 3d582f62d518dbc692b99178884383ab72c6b3cb Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 00:58:20 +0300 Subject: [PATCH 52/80] build_exercise_crate: Fixed the Windows release path check --- bin/build_exercise_crate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh index c579ba928..f19fb94c8 100755 --- a/bin/build_exercise_crate.sh +++ b/bin/build_exercise_crate.sh @@ -16,7 +16,7 @@ BIN_DIR_PATH="$TRACK_ROOT/bin" RELEASE_PATH="$EXERCISE_CRATE_PATH/target/release/exercise" - if [ -f "$RELEASE_PATH/exercise.exe" ]; then + if [ -f "$RELEASE_PATH.exe" ]; then RELEASE_PATH="$RELEASE_PATH.exe" fi From 052d81e9529fc927f265ad0c37458edddc54f89c Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 01:07:25 +0300 Subject: [PATCH 53/80] exercise: Removed the authors field from the Cargo.toml and the '--help' output --- exercise/Cargo.toml | 1 - exercise/src/main.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/exercise/Cargo.toml b/exercise/Cargo.toml index eb865dc50..007da7ce7 100644 --- a/exercise/Cargo.toml +++ b/exercise/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "exercise" version = "0.1.0" -authors = ["ZapAnton "] description = "An utility for creating or updating the exercises on the Exercism Rust track" [dependencies] diff --git a/exercise/src/main.rs b/exercise/src/main.rs index 5e77c8e66..82488ed05 100644 --- a/exercise/src/main.rs +++ b/exercise/src/main.rs @@ -16,7 +16,6 @@ use cmd::{configure, generate, update}; fn init_app<'a>() -> ArgMatches<'a> { App::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) - .author(env!("CARGO_PKG_AUTHORS")) .about(env!("CARGO_PKG_DESCRIPTION")) .subcommand( SubCommand::with_name("generate") From 88792dac8fffc6cd38fe696dadc4e35f60b48022 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 01:09:08 +0300 Subject: [PATCH 54/80] exercise: Added space to the user difficulty input prompt --- exercise/src/cmd/configure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index fe3790e19..c825f1aee 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -47,7 +47,7 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { }; let difficulty = loop { - let user_input = get_user_input("Difficulty for this exercise(1, 4, 7, 10): "); + let user_input = get_user_input("Difficulty for this exercise (1, 4, 7, 10): "); if let Ok(difficulty) = user_input.parse::() { if ![1, 4, 7, 10].contains(&difficulty) { From 7d0dad3a94ab800bf9595bd6f8c2c6881186a248 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 01:17:30 +0300 Subject: [PATCH 55/80] exercise: Added the newline at the end of the default generated '.meta/description.md' --- exercise/src/cmd/generate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 93b59949b..d5d321132 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -38,7 +38,7 @@ fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { fs::write( exercise_path.join(".meta").join("description.md"), - "Describe your exercise here.\n\nDon't forget that `README.md` is automatically generated; update this within `.meta/description.md`.", + "Describe your exercise here.\n\nDon't forget that `README.md` is automatically generated; update this within `.meta/description.md`.\n", ).expect("Failed to create .meta/description.md file"); fs::write( From 036fbdbe8d17f94af980835aaada64ec88989b21 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 11:20:58 +0300 Subject: [PATCH 56/80] exercise: Moved exercise_exists function to the utils module --- exercise/src/cmd/generate.rs | 18 ++++++++++-------- exercise/src/cmd/update.rs | 21 +-------------------- exercise/src/utils.rs | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index d5d321132..b3aed3870 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -179,13 +179,15 @@ fn generate_tests_from_canonical_data( } // Run bin/configlet generate command to generate README for the exercise -fn generate_readme(exercise_name: &str, track_root: &str) { +fn generate_readme(exercise_name: &str) { println!( "Generating README for {} via 'bin/configlet generate'", exercise_name ); - let problem_specifications_path = Path::new(track_root) + let track_root = utils::get_track_root(); + + let problem_specifications_path = Path::new(&track_root) .join("..") .join("problem-specifications"); @@ -220,17 +222,17 @@ fn generate_readme(exercise_name: &str, track_root: &str) { // Generate a new exercise with specified name and flags pub fn generate_exercise(exercise_name: &str, use_maplit: bool) { - let track_root = utils::get_track_root(); - - let exercise_path = Path::new(&track_root).join("exercises").join(exercise_name); - - if exercise_path.exists() { + if utils::exercise_exists(exercise_name) { panic!( "Exercise with the name {} already exists. Aborting", exercise_name ); } + let exercise_path = Path::new(&utils::get_track_root()) + .join("exercises") + .join(exercise_name); + println!( "Generating a new exercise at the following path: {}", exercise_path.to_str().unwrap() @@ -300,5 +302,5 @@ pub fn generate_exercise(exercise_name: &str, use_maplit: bool) { generate_default_meta(&exercise_name, &exercise_path); } - generate_readme(&exercise_name, &track_root); + generate_readme(&exercise_name); } diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index d9deedc90..c6103120d 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -7,25 +7,6 @@ enum DiffType { UPDATED, } -fn exercise_exists(exercise_name: &str) -> bool { - let track_root = utils::get_track_root(); - - let exercises_path = Path::new(&track_root).join("exercises"); - - for entry in exercises_path - .read_dir() - .expect("Failed to read 'exercises' dir") - { - if let Ok(entry) = entry { - if entry.file_type().unwrap().is_dir() && entry.file_name() == exercise_name { - return true; - } - } - } - - false -} - fn generate_diff_test(case: &Value, diff_type: &DiffType, use_maplit: bool) -> String { format!( "//{}\n{}", @@ -132,7 +113,7 @@ fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str } pub fn update_exercise(exercise_name: &str, use_maplit: bool) { - if !exercise_exists(exercise_name) { + if !utils::exercise_exists(exercise_name) { panic!( "Exercise with the name '{}' does not exists. Aborting", exercise_name diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index ba1bc459f..7248c114b 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -235,3 +235,22 @@ pub fn rustfmt(file_path: &Path) { } } } + +pub fn exercise_exists(exercise_name: &str) -> bool { + let track_root = get_track_root(); + + let exercises_path = Path::new(&track_root).join("exercises"); + + for entry in exercises_path + .read_dir() + .expect("Failed to read 'exercises' dir") + { + if let Ok(entry) = entry { + if entry.file_type().unwrap().is_dir() && entry.file_name() == exercise_name { + return true; + } + } + } + + false +} From 7de3f3454522ced7bbe3a032da24d840ccbbd363 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 11:22:54 +0300 Subject: [PATCH 57/80] exercise: Replaced the panic in the exercise exists checks with println and return --- exercise/src/cmd/generate.rs | 4 +++- exercise/src/cmd/update.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index b3aed3870..7e2bea1c6 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -223,10 +223,12 @@ fn generate_readme(exercise_name: &str) { // Generate a new exercise with specified name and flags pub fn generate_exercise(exercise_name: &str, use_maplit: bool) { if utils::exercise_exists(exercise_name) { - panic!( + println!( "Exercise with the name {} already exists. Aborting", exercise_name ); + + return; } let exercise_path = Path::new(&utils::get_track_root()) diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index c6103120d..88f2dd530 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -114,10 +114,12 @@ fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str pub fn update_exercise(exercise_name: &str, use_maplit: bool) { if !utils::exercise_exists(exercise_name) { - panic!( + println!( "Exercise with the name '{}' does not exists. Aborting", exercise_name ); + + return; } let tests_content = utils::get_tests_content(exercise_name).unwrap_or_else(|_| { From 3830e3c6f421a7944f985dba1fee78d44b7a2c5e Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 11:29:52 +0300 Subject: [PATCH 58/80] exercise: Made the [utility] link to point at the exercise crate url on the Github in the test suite template --- exercise/src/cmd/generate.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 7e2bea1c6..02cb12bcd 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -114,7 +114,12 @@ fn generate_tests_from_canonical_data( //! [canonical_data]: {} \n\ \n\ {} \n\ - ", exercise_name, env!("CARGO_PKG_NAME"), format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name), tests_content); + ", + exercise_name, + "https://github.com/exercism/rust/tree/master/exercise", + format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name), + tests_content + ); fs::write(&tests_path, updated_tests_content) .expect("Failed to update the content of the test suite"); From 4106b94c170da37d847def78ebadaa93721c78d1 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 11:41:18 +0300 Subject: [PATCH 59/80] exercise: Removed the blank line in the generated test case if no comments are present --- exercise/src/utils.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index 7248c114b..56c3c571a 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -185,14 +185,16 @@ pub fn generate_test_function(case: &Value, use_maplit: bool) -> String { let comments = if let Some(comments) = case.get("comments") { if comments.is_array() { - comments + let comments_string = comments .as_array() .unwrap() .iter() .map(|line| format!("/// {}", line)) - .collect::() + .collect::(); + + format!("\n{}", comments_string) } else { - format!("/// {}\n", comments.as_str().unwrap()) + format!("\n/// {}", comments.as_str().unwrap()) } } else { "".to_string() @@ -205,8 +207,7 @@ pub fn generate_test_function(case: &Value, use_maplit: bool) -> String { format!( "#[test]\n\ #[ignore]\n\ - /// {description}\n\ - {comments} + /// {description}{comments}\n\ fn test_{description_formatted}() {{\n\ process_{property}_case({input}, {expected});\n\ }}\n\ From 06c783bf4d6c6f50165409acd6df536684d4fa84 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 12:04:38 +0300 Subject: [PATCH 60/80] exercise: 'update' subcommand now updates the version of the exercise in the Cargo.toml file --- exercise/src/cmd/update.rs | 58 ++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index 88f2dd530..7e1684714 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -1,5 +1,6 @@ use serde_json::Value; use std::{collections::HashSet, fs, path::Path}; +use toml::Value as TomlValue; use utils; enum DiffType { @@ -56,14 +57,12 @@ fn generate_diffs( } } -fn get_diffs(exercise_name: &str, tests_content: &str, use_maplit: bool) -> HashSet { - let canonical_data = utils::get_canonical_data(exercise_name).unwrap_or_else(|| { - panic!( - "Failed to get canonical data for the '{}' exercise. Aborting", - exercise_name - ) - }); - +fn get_diffs( + exercise_name: &str, + canonical_data: &Value, + tests_content: &str, + use_maplit: bool, +) -> HashSet { let cases = canonical_data.get("cases").unwrap_or_else(|| { panic!( "Failed to get 'cases' field from the canonical data of the '{}' exercise", @@ -112,6 +111,38 @@ fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str utils::rustfmt(&tests_path); } +fn update_cargo_toml(exercise_name: &str, canonical_data: &Value) { + let cargo_toml_path = Path::new(&utils::get_track_root()) + .join("exercises") + .join(exercise_name) + .join("Cargo.toml"); + + let cargo_toml_content = fs::read_to_string(&cargo_toml_path).unwrap_or_else(|_| { + panic!( + "Failed to read the contents of the {} file", + cargo_toml_path.to_str().unwrap() + ) + }); + + let mut cargo_toml: TomlValue = cargo_toml_content.parse().unwrap(); + + { + let package_table = cargo_toml["package"].as_table_mut().unwrap(); + + package_table.insert( + "version".to_string(), + TomlValue::String(canonical_data["version"].as_str().unwrap().to_string()), + ); + } + + fs::write(&cargo_toml_path, cargo_toml.to_string()).unwrap_or_else(|_| { + panic!( + "Failed to update the contents of the {} file", + cargo_toml_path.to_str().unwrap() + ); + }); +} + pub fn update_exercise(exercise_name: &str, use_maplit: bool) { if !utils::exercise_exists(exercise_name) { println!( @@ -129,7 +160,16 @@ pub fn update_exercise(exercise_name: &str, use_maplit: bool) { ) }); - let diffs = get_diffs(exercise_name, &tests_content, use_maplit); + let canonical_data = utils::get_canonical_data(exercise_name).unwrap_or_else(|| { + panic!( + "Failed to get canonical data for the '{}' exercise. Aborting", + exercise_name + ) + }); + + let diffs = get_diffs(exercise_name, &canonical_data, &tests_content, use_maplit); apply_diffs(exercise_name, &diffs, &tests_content); + + update_cargo_toml(exercise_name, &canonical_data); } From 623f0e7da3b46dd330d68c99a82fd5879ca9b657 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 12:36:16 +0300 Subject: [PATCH 61/80] exercise: Moved the update_cargo_toml_version function to the utils module --- exercise/src/cmd/generate.rs | 28 +--------------------------- exercise/src/cmd/update.rs | 35 +---------------------------------- exercise/src/utils.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 61 deletions(-) diff --git a/exercise/src/cmd/generate.rs b/exercise/src/cmd/generate.rs index 02cb12bcd..37054a828 100644 --- a/exercise/src/cmd/generate.rs +++ b/exercise/src/cmd/generate.rs @@ -7,7 +7,6 @@ use std::{ path::Path, process::{Command, Stdio}, }; -use toml::Value as TomlValue; use utils; static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo @@ -60,31 +59,6 @@ fn generate_default_meta(exercise_name: &str, exercise_path: &Path) { tests_file.write_all(b"// Add your tests here").unwrap(); } -// Update Cargo.toml of the generated exercise according to the fetched canonical data -fn update_cargo_toml(exercise_name: &str, exercise_path: &Path, canonical_data: &JsonValue) { - let cargo_toml_content = - fs::read_to_string(exercise_path.join("Cargo.toml")).expect("Error reading Cargo.toml"); - - let mut cargo_toml: TomlValue = cargo_toml_content.parse().unwrap(); - - { - let package_table = (&mut cargo_toml["package"]).as_table_mut().unwrap(); - - package_table.insert( - "version".to_string(), - TomlValue::String(canonical_data["version"].as_str().unwrap().to_string()), - ); - - package_table.insert( - "name".to_string(), - TomlValue::String(exercise_name.replace("-", "_")), - ); - } - - fs::write(exercise_path.join("Cargo.toml"), cargo_toml.to_string()) - .expect("Failed to update Cargo.toml file"); -} - // Generate test suite using the canonical data fn generate_tests_from_canonical_data( exercise_name: &str, @@ -92,7 +66,7 @@ fn generate_tests_from_canonical_data( canonical_data: &JsonValue, use_maplit: bool, ) { - update_cargo_toml(exercise_name, exercise_path, canonical_data); + utils::update_cargo_toml_version(exercise_name, canonical_data); let tests_path = exercise_path .join("tests") diff --git a/exercise/src/cmd/update.rs b/exercise/src/cmd/update.rs index 7e1684714..fdf5ea061 100644 --- a/exercise/src/cmd/update.rs +++ b/exercise/src/cmd/update.rs @@ -1,6 +1,5 @@ use serde_json::Value; use std::{collections::HashSet, fs, path::Path}; -use toml::Value as TomlValue; use utils; enum DiffType { @@ -111,38 +110,6 @@ fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str utils::rustfmt(&tests_path); } -fn update_cargo_toml(exercise_name: &str, canonical_data: &Value) { - let cargo_toml_path = Path::new(&utils::get_track_root()) - .join("exercises") - .join(exercise_name) - .join("Cargo.toml"); - - let cargo_toml_content = fs::read_to_string(&cargo_toml_path).unwrap_or_else(|_| { - panic!( - "Failed to read the contents of the {} file", - cargo_toml_path.to_str().unwrap() - ) - }); - - let mut cargo_toml: TomlValue = cargo_toml_content.parse().unwrap(); - - { - let package_table = cargo_toml["package"].as_table_mut().unwrap(); - - package_table.insert( - "version".to_string(), - TomlValue::String(canonical_data["version"].as_str().unwrap().to_string()), - ); - } - - fs::write(&cargo_toml_path, cargo_toml.to_string()).unwrap_or_else(|_| { - panic!( - "Failed to update the contents of the {} file", - cargo_toml_path.to_str().unwrap() - ); - }); -} - pub fn update_exercise(exercise_name: &str, use_maplit: bool) { if !utils::exercise_exists(exercise_name) { println!( @@ -171,5 +138,5 @@ pub fn update_exercise(exercise_name: &str, use_maplit: bool) { apply_diffs(exercise_name, &diffs, &tests_content); - update_cargo_toml(exercise_name, &canonical_data); + utils::update_cargo_toml_version(exercise_name, &canonical_data); } diff --git a/exercise/src/utils.rs b/exercise/src/utils.rs index 56c3c571a..849913b01 100644 --- a/exercise/src/utils.rs +++ b/exercise/src/utils.rs @@ -5,6 +5,7 @@ use std::{ path::Path, process::{Command, Stdio}, }; +use toml::Value as TomlValue; pub fn get_track_root() -> String { let rev_parse_output = Command::new("git") @@ -255,3 +256,36 @@ pub fn exercise_exists(exercise_name: &str) -> bool { false } + +// Update the version of the specified exersice in the Cargo.toml file according to the passed canonical data +pub fn update_cargo_toml_version(exercise_name: &str, canonical_data: &Value) { + let cargo_toml_path = Path::new(&get_track_root()) + .join("exercises") + .join(exercise_name) + .join("Cargo.toml"); + + let cargo_toml_content = fs::read_to_string(&cargo_toml_path).unwrap_or_else(|_| { + panic!( + "Failed to read the contents of the {} file", + cargo_toml_path.to_str().unwrap() + ) + }); + + let mut cargo_toml: TomlValue = cargo_toml_content.parse().unwrap(); + + { + let package_table = cargo_toml["package"].as_table_mut().unwrap(); + + package_table.insert( + "version".to_string(), + TomlValue::String(canonical_data["version"].as_str().unwrap().to_string()), + ); + } + + fs::write(&cargo_toml_path, cargo_toml.to_string()).unwrap_or_else(|_| { + panic!( + "Failed to update the contents of the {} file", + cargo_toml_path.to_str().unwrap() + ); + }); +} From 6feba5d213392b73a02b380df632ce2cab75557d Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 15:05:23 +0300 Subject: [PATCH 62/80] exercise: Made 'configure' subcommand to retain the existing values as default values, if an exercise exists in config.json --- exercise/src/cmd/configure.rs | 82 +++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index c825f1aee..ba537d410 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -22,16 +22,36 @@ fn get_user_input(prompt: &str) -> String { } fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { - let uuid = Uuid::new_v4().to_hyphenated().to_string(); + let existing_config: Option<&Value> = config_content["exercises"] + .as_array() + .unwrap() + .iter() + .find(|exercise| exercise["slug"] == exercise_name); + + println!("{:#?}", existing_config); + + let uuid = if let Some(existing_config) = existing_config { + existing_config["uuid"].as_str().unwrap().to_string() + } else { + Uuid::new_v4().to_hyphenated().to_string() + }; let core = false; let unlocked_by = loop { - let user_input = - get_user_input("Exercise slug which unlocks this (blank for 'hello-world'): "); + let default_value = if let Some(existing_config) = existing_config { + existing_config["unlocked_by"].as_str().unwrap() + } else { + "hello-world" + }; + + let user_input = get_user_input(&format!( + "Exercise slug which unlocks this (blank for '{}'): ", + default_value + )); if user_input.is_empty() { - break "hello-world".to_string(); + break default_value.to_string(); } else if !config_content["exercises"] .as_array() .unwrap() @@ -47,9 +67,20 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { }; let difficulty = loop { - let user_input = get_user_input("Difficulty for this exercise (1, 4, 7, 10): "); + let default_value = if let Some(existing_config) = existing_config { + existing_config["difficulty"].as_u64().unwrap() + } else { + 1 + }; + + let user_input = get_user_input(&format!( + "Difficulty for this exercise [1, 4, 7, 10] (blank for {}): ", + default_value + )); - if let Ok(difficulty) = user_input.parse::() { + if user_input.is_empty() { + break default_value; + } else if let Ok(difficulty) = user_input.parse::() { if ![1, 4, 7, 10].contains(&difficulty) { println!("Difficulty should be 1, 4, 7 or 10, not '{}'.", difficulty); @@ -65,7 +96,31 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { }; let topics = loop { - let user_input = get_user_input("List of topics for this exercise, comma-separated: "); + let default_value = if let Some(existing_config) = existing_config { + let topics = &existing_config["topics"]; + + if topics.is_array() { + topics + .as_array() + .unwrap() + .iter() + .map(|topic| topic.as_str().unwrap().to_string()) + .collect::>() + } else { + vec![topics.as_str().unwrap().to_string()] + } + } else { + vec![exercise_name.to_string()] + }; + + let user_input = get_user_input(&format!( + "List of topics for this exercise, comma-separated (blank for {:?}): ", + default_value, + )); + + if user_input.is_empty() { + break default_value; + } let topics = user_input .split(',') @@ -191,6 +246,12 @@ pub fn configure_exercise(exercise_name: &str) { let mut config_content: Value = serde_json::from_str(&config_content_string).unwrap(); + let config_exists = config_content["exercises"] + .as_array() + .unwrap() + .iter() + .any(|exercise| exercise["slug"] == exercise_name); + let user_config: Value = loop { let user_config = get_user_config(exercise_name, &config_content); @@ -205,12 +266,7 @@ pub fn configure_exercise(exercise_name: &str) { } }; - if config_content["exercises"] - .as_array() - .unwrap() - .iter() - .any(|exercise| exercise["slug"] == exercise_name) - { + if config_exists { update_existing_config(exercise_name, &mut config_content, user_config); } else { insert_user_config(exercise_name, &mut config_content, user_config); From ce5e2c86886811ab34ce1f241a1b482efff13fb5 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 15:21:40 +0300 Subject: [PATCH 63/80] exercise: Made 'configure' subcommand accept difficulties equal to or greater than the exercise which unlocks the configured exercise --- exercise/src/cmd/configure.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index ba537d410..dbc1ce681 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -28,8 +28,6 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { .iter() .find(|exercise| exercise["slug"] == exercise_name); - println!("{:#?}", existing_config); - let uuid = if let Some(existing_config) = existing_config { existing_config["uuid"].as_str().unwrap().to_string() } else { @@ -67,22 +65,40 @@ fn get_user_config(exercise_name: &str, config_content: &Value) -> Value { }; let difficulty = loop { + let unlocked_by_difficulty = config_content["exercises"] + .as_array() + .unwrap() + .iter() + .find(|exercise| exercise["slug"] == unlocked_by) + .unwrap()["difficulty"] + .as_u64() + .unwrap(); + + let available_difficulties: Vec = [1, 4, 7, 10] + .iter() + .skip_while(|&&difficulty| difficulty < unlocked_by_difficulty) + .cloned() + .collect(); + let default_value = if let Some(existing_config) = existing_config { existing_config["difficulty"].as_u64().unwrap() } else { - 1 + *available_difficulties.first().unwrap() }; let user_input = get_user_input(&format!( - "Difficulty for this exercise [1, 4, 7, 10] (blank for {}): ", - default_value + "Difficulty for this exercise {:?} (blank for {}): ", + available_difficulties, default_value )); if user_input.is_empty() { break default_value; } else if let Ok(difficulty) = user_input.parse::() { - if ![1, 4, 7, 10].contains(&difficulty) { - println!("Difficulty should be 1, 4, 7 or 10, not '{}'.", difficulty); + if !available_difficulties.contains(&difficulty) { + println!( + "Difficulty should be {:?}, not '{}'.", + available_difficulties, difficulty + ); continue; } From b497fbee4916d185a97bb7e17ff843345f3849c6 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 17:00:08 +0300 Subject: [PATCH 64/80] exercise: Added an option to restart the positioning binary search --- exercise/src/cmd/configure.rs | 83 +++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/exercise/src/cmd/configure.rs b/exercise/src/cmd/configure.rs index dbc1ce681..dbbd0aad7 100644 --- a/exercise/src/cmd/configure.rs +++ b/exercise/src/cmd/configure.rs @@ -168,54 +168,61 @@ fn choose_exercise_insert_index( exercises: &[Value], difficulty: &Value, ) -> usize { - let exercises_with_similar_difficulty = exercises - .iter() - .enumerate() - .filter(|(_, exercise)| exercise["difficulty"] == *difficulty) - .map(|(index, exercise)| (index, exercise["slug"].as_str().unwrap())) - .collect::>(); + loop { + let exercises_with_similar_difficulty = exercises + .iter() + .enumerate() + .filter(|(_, exercise)| exercise["difficulty"] == *difficulty) + .map(|(index, exercise)| (index, exercise["slug"].as_str().unwrap())) + .collect::>(); - let mut start_index = 0; + let mut start_index = 0; - let mut end_index = exercises_with_similar_difficulty.len() - 1; + let mut end_index = exercises_with_similar_difficulty.len() - 1; - let insert_index = loop { - if start_index == end_index { - break start_index; - } + let insert_index = loop { + if start_index == end_index { + break start_index; + } - let middle_index = start_index + ((end_index - start_index) / 2); + let middle_index = start_index + ((end_index - start_index) / 2); - let user_input = get_user_input(&format!( - "Is {} easier then {}? (y/N): ", - exercise_name, exercises_with_similar_difficulty[middle_index].1 - )); + let user_input = get_user_input(&format!( + "Is {} easier then {}? (y/N): ", + exercise_name, exercises_with_similar_difficulty[middle_index].1 + )); - if user_input.to_lowercase().starts_with('y') { - end_index = middle_index; - } else { - start_index = middle_index + 1; - } - }; + if user_input.to_lowercase().starts_with('y') { + end_index = middle_index; + } else { + start_index = middle_index + 1; + } + }; - let insert_index = exercises_with_similar_difficulty[insert_index].0; + 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) - } else if insert_index == exercises.len() - 1 { - format!("{} is the hardest exercise on the track.", exercise_name) - } else { - format!( - "{} is placed between {} and {} exercises in difficulty.", - exercise_name, - exercises[insert_index - 1]["slug"].as_str().unwrap(), - exercises[insert_index]["slug"].as_str().unwrap(), - ) - }; + 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) + } else { + format!( + "{} is placed between {} and {} exercises in difficulty.", + exercise_name, + exercises[insert_index - 1]["slug"].as_str().unwrap(), + exercises[insert_index]["slug"].as_str().unwrap(), + ) + }; - println!("You have configured that {}", prompt); + let user_input = get_user_input(&format!( + "You have configured that {}.\nIs this correct? (y/N): ", + prompt + )); - insert_index + if user_input.to_lowercase().starts_with('y') { + break insert_index; + } + } } fn insert_user_config(exercise_name: &str, config_content: &mut Value, user_config: Value) { From 88f87b0307bc6641f70d4f1b86d590a51d2a39d6 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 17:08:50 +0300 Subject: [PATCH 65/80] exercise: Moved the crate into the 'util' directory --- README.md | 2 +- bin/build_exercise_crate.sh | 2 +- {exercise => util/exercise}/Cargo.lock | 0 {exercise => util/exercise}/Cargo.toml | 0 {exercise => util/exercise}/src/cmd/configure.rs | 0 {exercise => util/exercise}/src/cmd/generate.rs | 2 +- {exercise => util/exercise}/src/cmd/mod.rs | 0 {exercise => util/exercise}/src/cmd/update.rs | 0 {exercise => util/exercise}/src/main.rs | 0 {exercise => util/exercise}/src/utils.rs | 0 10 files changed, 3 insertions(+), 3 deletions(-) rename {exercise => util/exercise}/Cargo.lock (100%) rename {exercise => util/exercise}/Cargo.toml (100%) rename {exercise => util/exercise}/src/cmd/configure.rs (100%) rename {exercise => util/exercise}/src/cmd/generate.rs (99%) rename {exercise => util/exercise}/src/cmd/mod.rs (100%) rename {exercise => util/exercise}/src/cmd/update.rs (100%) rename {exercise => util/exercise}/src/main.rs (100%) rename {exercise => util/exercise}/src/utils.rs (100%) diff --git a/README.md b/README.md index 714e9fa0a..27417dbc1 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Please see the documentation about [adding new exercises](https://github.com/exe Note that: -- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `exercise/` directory and then copy the `exercise` binary from the `exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. +- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/util/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `util/exercise/` directory and then copy the `exercise` binary from the `util/exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. - Each exercise must stand on its own. Do not reference files outside the exercise directory. They will not be included when the user fetches the exercise. diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh index f19fb94c8..f77f4bd8a 100755 --- a/bin/build_exercise_crate.sh +++ b/bin/build_exercise_crate.sh @@ -3,7 +3,7 @@ TRACK_ROOT="$(git rev-parse --show-toplevel)" -EXERCISE_CRATE_PATH="$TRACK_ROOT/exercise" +EXERCISE_CRATE_PATH="$TRACK_ROOT/util/exercise" BIN_DIR_PATH="$TRACK_ROOT/bin" diff --git a/exercise/Cargo.lock b/util/exercise/Cargo.lock similarity index 100% rename from exercise/Cargo.lock rename to util/exercise/Cargo.lock diff --git a/exercise/Cargo.toml b/util/exercise/Cargo.toml similarity index 100% rename from exercise/Cargo.toml rename to util/exercise/Cargo.toml diff --git a/exercise/src/cmd/configure.rs b/util/exercise/src/cmd/configure.rs similarity index 100% rename from exercise/src/cmd/configure.rs rename to util/exercise/src/cmd/configure.rs diff --git a/exercise/src/cmd/generate.rs b/util/exercise/src/cmd/generate.rs similarity index 99% rename from exercise/src/cmd/generate.rs rename to util/exercise/src/cmd/generate.rs index 37054a828..592cebb96 100644 --- a/exercise/src/cmd/generate.rs +++ b/util/exercise/src/cmd/generate.rs @@ -90,7 +90,7 @@ fn generate_tests_from_canonical_data( {} \n\ ", exercise_name, - "https://github.com/exercism/rust/tree/master/exercise", + "https://github.com/exercism/rust/tree/master/util/exercise", format!("https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{}/canonical-data.json", exercise_name), tests_content ); diff --git a/exercise/src/cmd/mod.rs b/util/exercise/src/cmd/mod.rs similarity index 100% rename from exercise/src/cmd/mod.rs rename to util/exercise/src/cmd/mod.rs diff --git a/exercise/src/cmd/update.rs b/util/exercise/src/cmd/update.rs similarity index 100% rename from exercise/src/cmd/update.rs rename to util/exercise/src/cmd/update.rs diff --git a/exercise/src/main.rs b/util/exercise/src/main.rs similarity index 100% rename from exercise/src/main.rs rename to util/exercise/src/main.rs diff --git a/exercise/src/utils.rs b/util/exercise/src/utils.rs similarity index 100% rename from exercise/src/utils.rs rename to util/exercise/src/utils.rs From c9e30d26d596b89740222b140826be7c9b9f5cd5 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 18:39:12 +0300 Subject: [PATCH 66/80] travis: Added a script to check if the 'exercise' crate compiles if it was modified --- .travis.yml | 1 + _test/check-exercise-crate.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100755 _test/check-exercise-crate.sh diff --git a/.travis.yml b/.travis.yml index 0a0e6a008..fb99e10ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ script: - "./bin/configlet lint ." - "./_test/verify-exercise-difficulties.sh" - "./_test/check-exercises-for-authors.sh" +- "sh ./_test/check-exercise-crate.sh" sudo: false rust: - stable diff --git a/_test/check-exercise-crate.sh b/_test/check-exercise-crate.sh new file mode 100755 index 000000000..45528efe4 --- /dev/null +++ b/_test/check-exercise-crate.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env sh + +# A script to ensure that the util/exercise crate builds after it was modified. + +EXERCISE_CRATE_PATH="util/exercise" + +CURRENT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) + +if [ "$CURRENT_BRANCH_NAME" != "master" ]; then + # Check the changes on the current branch against master branch + git diff --name-only master | grep "$EXERCISE_CRATE_PATH" +else + # Check the commits on the master branch made during the week + # This is because Travis cron is set to test the master branch weekly. + git diff --name-only "@{7 days ago}" | grep "$EXERCISE_CRATE_PATH" +fi + +if [ $? != 0 ]; then + echo "exercise crate was not modified. The script is aborted." + + exit 0 +fi + +TRACK_ROOT="$(git rev-parse --show-toplevel)" + +if !(cd "$TRACK_ROOT/$EXERCISE_CRATE_PATH" && cargo build --release); then + echo "\nAn error has occurred while building the exercise crate.\nPlease make it compile." + + exit 1 +else + echo "\nexercise crate has been successfully built." + + exit 0 +fi From 7113651dd681d33ed2b8e971fe80b6b68f7a5b3b Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 21:27:40 +0300 Subject: [PATCH 67/80] check-exercise-crate: Replaced the 'cargo build --release' command with the 'cargo check' command --- _test/check-exercise-crate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_test/check-exercise-crate.sh b/_test/check-exercise-crate.sh index 45528efe4..27935079f 100755 --- a/_test/check-exercise-crate.sh +++ b/_test/check-exercise-crate.sh @@ -23,7 +23,7 @@ fi TRACK_ROOT="$(git rev-parse --show-toplevel)" -if !(cd "$TRACK_ROOT/$EXERCISE_CRATE_PATH" && cargo build --release); then +if !(cd "$TRACK_ROOT/$EXERCISE_CRATE_PATH" && cargo check); then echo "\nAn error has occurred while building the exercise crate.\nPlease make it compile." exit 1 From f23c5e85d7256f4387668a3c96d4e2227cc26c9e Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 22:25:35 +0300 Subject: [PATCH 68/80] exercise: Replaced the locating of the 'rustfmt' utility via 'which' command with PATH variable parsing --- util/exercise/src/utils.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/util/exercise/src/utils.rs b/util/exercise/src/utils.rs index 849913b01..66a905b59 100644 --- a/util/exercise/src/utils.rs +++ b/util/exercise/src/utils.rs @@ -1,7 +1,7 @@ use reqwest::{self, StatusCode}; use serde_json::Value; use std::{ - fs, io, + env, fs, io, path::Path, process::{Command, Stdio}, }; @@ -223,18 +223,29 @@ pub fn generate_test_function(case: &Value, use_maplit: bool) -> String { ) } -// FIXME: The algorithm is Unix-specific and will always fail on Windows. General solution required pub fn rustfmt(file_path: &Path) { if let Ok(which_output) = Command::new("which").arg("rustfmt").output() { if !String::from_utf8_lossy(&which_output.stdout) .trim() .is_empty() - { - Command::new("rustfmt") - .arg(file_path) - .output() - .expect("Failed to run rustfmt command on the test suite file"); + {} + } + + let rustfmt_is_available = { + if let Some(path_var) = env::var_os("PATH") { + env::split_paths(&path_var) + .into_iter() + .any(|path| path.join("rustfmt").exists()) + } else { + false } + }; + + if rustfmt_is_available { + Command::new("rustfmt") + .arg(file_path) + .output() + .expect("Failed to run rustfmt command on the test suite file"); } } From 18131cb9eefe9f09f74e48365e3c6b446c6ba26b Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 23:04:28 +0300 Subject: [PATCH 69/80] exercise: Simplified the exercise_exists function --- util/exercise/src/utils.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/util/exercise/src/utils.rs b/util/exercise/src/utils.rs index 66a905b59..e8891f07f 100644 --- a/util/exercise/src/utils.rs +++ b/util/exercise/src/utils.rs @@ -250,22 +250,10 @@ pub fn rustfmt(file_path: &Path) { } pub fn exercise_exists(exercise_name: &str) -> bool { - let track_root = get_track_root(); - - let exercises_path = Path::new(&track_root).join("exercises"); - - for entry in exercises_path - .read_dir() - .expect("Failed to read 'exercises' dir") - { - if let Ok(entry) = entry { - if entry.file_type().unwrap().is_dir() && entry.file_name() == exercise_name { - return true; - } - } - } - - false + Path::new(&get_track_root()) + .join("exercises") + .join(exercise_name) + .exists() } // Update the version of the specified exersice in the Cargo.toml file according to the passed canonical data From 01f3de259785b5e9d849cca4395c9c9fef85bd63 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 23:07:34 +0300 Subject: [PATCH 70/80] exercise: Fixed typo in the 'exercise does not exist' message --- util/exercise/src/cmd/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/exercise/src/cmd/update.rs b/util/exercise/src/cmd/update.rs index fdf5ea061..e2e8c84b9 100644 --- a/util/exercise/src/cmd/update.rs +++ b/util/exercise/src/cmd/update.rs @@ -113,7 +113,7 @@ fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str 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", + "Exercise with the name '{}' does not exist. Aborting", exercise_name ); From 048d36920b7ad726b27b4046628df4c47ec4539c Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 23:12:13 +0300 Subject: [PATCH 71/80] exercise: Specified the exercise difficulty prompt --- util/exercise/src/cmd/configure.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/util/exercise/src/cmd/configure.rs b/util/exercise/src/cmd/configure.rs index dbbd0aad7..a16a17121 100644 --- a/util/exercise/src/cmd/configure.rs +++ b/util/exercise/src/cmd/configure.rs @@ -202,9 +202,15 @@ fn choose_exercise_insert_index( 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) + format!( + "{} is the easiest exercise of difficulty {}.", + exercise_name, *difficulty + ) } else if insert_index == exercises.len() - 1 { - format!("{} is the hardest exercise on the track.", exercise_name) + format!( + "{} is the hardest exercise of difficulty {}.", + exercise_name, *difficulty + ) } else { format!( "{} is placed between {} and {} exercises in difficulty.", From fab22298e8ca316ae84faaccabefb822912f39aa Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 23:24:49 +0300 Subject: [PATCH 72/80] exercise: Specified that the new exercise is generated via 'exercise' utility --- util/exercise/src/cmd/generate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/exercise/src/cmd/generate.rs b/util/exercise/src/cmd/generate.rs index 592cebb96..f5a6c49aa 100644 --- a/util/exercise/src/cmd/generate.rs +++ b/util/exercise/src/cmd/generate.rs @@ -9,7 +9,7 @@ use std::{ }; use utils; -static GITIGNORE_CONTENT: &'static str = "# Generated by Cargo +static GITIGNORE_CONTENT: &'static str = "# Generated by exercism rust track exercise tool # will have compiled files and executables /target/ **/*.rs.bk From 9850f9b000793784323ade51de94c057f5eae7d0 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 23:25:55 +0300 Subject: [PATCH 73/80] exercise: Fixed comment typo --- util/exercise/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/exercise/src/utils.rs b/util/exercise/src/utils.rs index e8891f07f..948cc3f50 100644 --- a/util/exercise/src/utils.rs +++ b/util/exercise/src/utils.rs @@ -256,7 +256,7 @@ pub fn exercise_exists(exercise_name: &str) -> bool { .exists() } -// 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 pub fn update_cargo_toml_version(exercise_name: &str, canonical_data: &Value) { let cargo_toml_path = Path::new(&get_track_root()) .join("exercises") From 967bc9869b7209ad01d2a1284dbe66f4f98dcc0a Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 19 Oct 2018 23:32:17 +0300 Subject: [PATCH 74/80] exercise: Inlined the urls in the generated tests suite template --- util/exercise/src/cmd/generate.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/util/exercise/src/cmd/generate.rs b/util/exercise/src/cmd/generate.rs index f5a6c49aa..4c0a319c1 100644 --- a/util/exercise/src/cmd/generate.rs +++ b/util/exercise/src/cmd/generate.rs @@ -80,19 +80,17 @@ fn generate_tests_from_canonical_data( }); let updated_tests_content = format!( - "//! Tests for {} \n\ + "//! Tests for {exercise_name} \n\ //! \n\ - //! Generated by [utility][utility] using [canonical data][canonical_data] \n\ + //! Generated by [utility][utility] using [canonical data][canonical_data]\n\ //! \n\ - //! [utility]: {} \n\ - //! [canonical_data]: {} \n\ + //! [utility]: https://github.com/exercism/rust/tree/master/util/exercise\n\ + //! [canonical_data]: https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/{exercise_name}/canonical-data.json\n\ \n\ {} \n\ ", - 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), - tests_content + tests_content, + exercise_name=exercise_name, ); fs::write(&tests_path, updated_tests_content) From cf3d151af187be89239460a853223b08ad7b8232 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sat, 20 Oct 2018 00:04:33 +0300 Subject: [PATCH 75/80] exercise: Replaced the get_track_root function with lazy_static TRACK_ROOT value --- util/exercise/Cargo.lock | 1 + util/exercise/Cargo.toml | 1 + util/exercise/src/cmd/configure.rs | 4 +--- util/exercise/src/cmd/generate.rs | 8 +++---- util/exercise/src/cmd/update.rs | 2 +- util/exercise/src/main.rs | 2 ++ util/exercise/src/utils.rs | 37 +++++++++++++++--------------- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/util/exercise/Cargo.lock b/util/exercise/Cargo.lock index a5ee70577..38b937a50 100644 --- a/util/exercise/Cargo.lock +++ b/util/exercise/Cargo.lock @@ -169,6 +169,7 @@ name = "exercise" version = "0.1.0" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/util/exercise/Cargo.toml b/util/exercise/Cargo.toml index 007da7ce7..c92ae3fdf 100644 --- a/util/exercise/Cargo.toml +++ b/util/exercise/Cargo.toml @@ -8,4 +8,5 @@ clap = "2.32.0" reqwest = "0.8.8" serde_json = "1.0.26" toml = "0.4" +lazy_static = "1.1.0" uuid = { version = "0.7", features = ["v4"] } diff --git a/util/exercise/src/cmd/configure.rs b/util/exercise/src/cmd/configure.rs index a16a17121..a4478db1b 100644 --- a/util/exercise/src/cmd/configure.rs +++ b/util/exercise/src/cmd/configure.rs @@ -266,9 +266,7 @@ pub fn configure_exercise(exercise_name: &str) { exercise_name ); - let track_root = utils::get_track_root(); - - let config_path = Path::new(&track_root).join("config.json"); + let config_path = Path::new(&*utils::TRACK_ROOT).join("config.json"); let config_content_string = fs::read_to_string(&config_path) .expect("Failed to read the contents of the config.json file"); diff --git a/util/exercise/src/cmd/generate.rs b/util/exercise/src/cmd/generate.rs index 4c0a319c1..f8dddf59f 100644 --- a/util/exercise/src/cmd/generate.rs +++ b/util/exercise/src/cmd/generate.rs @@ -162,9 +162,7 @@ fn generate_readme(exercise_name: &str) { exercise_name ); - let track_root = utils::get_track_root(); - - let problem_specifications_path = Path::new(&track_root) + let problem_specifications_path = Path::new(&*utils::TRACK_ROOT) .join("..") .join("problem-specifications"); @@ -176,7 +174,7 @@ fn generate_readme(exercise_name: &str) { ); Command::new("git") - .current_dir(track_root) + .current_dir(&*utils::TRACK_ROOT) .stdout(Stdio::inherit()) .arg("clone") .arg(problem_specifications_url) @@ -208,7 +206,7 @@ pub fn generate_exercise(exercise_name: &str, use_maplit: bool) { return; } - let exercise_path = Path::new(&utils::get_track_root()) + let exercise_path = Path::new(&*utils::TRACK_ROOT) .join("exercises") .join(exercise_name); diff --git a/util/exercise/src/cmd/update.rs b/util/exercise/src/cmd/update.rs index e2e8c84b9..93579604c 100644 --- a/util/exercise/src/cmd/update.rs +++ b/util/exercise/src/cmd/update.rs @@ -94,7 +94,7 @@ fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str .collect::() ); - let tests_path = Path::new(&utils::get_track_root()) + let tests_path = Path::new(&*utils::TRACK_ROOT) .join("exercises") .join(exercise_name) .join("tests") diff --git a/util/exercise/src/main.rs b/util/exercise/src/main.rs index 82488ed05..98d982567 100644 --- a/util/exercise/src/main.rs +++ b/util/exercise/src/main.rs @@ -2,6 +2,8 @@ extern crate clap; extern crate reqwest; #[macro_use] extern crate serde_json; +#[macro_use] +extern crate lazy_static; extern crate toml; extern crate uuid; diff --git a/util/exercise/src/utils.rs b/util/exercise/src/utils.rs index 948cc3f50..f6b9c7270 100644 --- a/util/exercise/src/utils.rs +++ b/util/exercise/src/utils.rs @@ -7,22 +7,25 @@ use std::{ }; use toml::Value as TomlValue; -pub fn get_track_root() -> String { - let rev_parse_output = Command::new("git") - .arg("rev-parse") - .arg("--show-toplevel") - .output() - .expect("Failed to get the path to the track repo."); - - let track_root = String::from_utf8(rev_parse_output.stdout).unwrap(); +lazy_static! { + pub static ref TRACK_ROOT: String = { + let rev_parse_output = Command::new("git") + .arg("rev-parse") + .arg("--show-toplevel") + .output() + .expect("Failed to get the path to the track repo."); - track_root.trim().to_string() + String::from_utf8(rev_parse_output.stdout) + .unwrap() + .trim() + .to_string() + }; } pub fn run_configlet_command(command: &str, args: &[&str]) { - let track_root = get_track_root(); + let track_root = &*TRACK_ROOT; - let bin_path = Path::new(&track_root).join("bin"); + let bin_path = Path::new(track_root).join("bin"); let configlet_name_unix = "configlet"; @@ -38,7 +41,7 @@ pub fn run_configlet_command(command: &str, args: &[&str]) { // FIXME: Uses bash script that would not work on Windows. // RIIR is preferred. Command::new("bash") - .current_dir(&track_root) + .current_dir(track_root) .stdout(Stdio::inherit()) .arg(bin_path.join("fetch-configlet")) .output() @@ -54,7 +57,7 @@ pub fn run_configlet_command(command: &str, args: &[&str]) { }; Command::new(&bin_path.join(configlet_name)) - .current_dir(&track_root) + .current_dir(track_root) .stdout(Stdio::inherit()) .arg(command) .args(args) @@ -81,9 +84,7 @@ pub fn get_canonical_data(exercise_name: &str) -> Option { } pub fn get_tests_content(exercise_name: &str) -> io::Result { - let track_root = get_track_root(); - - let tests_path = Path::new(&track_root) + let tests_path = Path::new(&*TRACK_ROOT) .join("exercises") .join(exercise_name) .join("tests") @@ -250,7 +251,7 @@ pub fn rustfmt(file_path: &Path) { } pub fn exercise_exists(exercise_name: &str) -> bool { - Path::new(&get_track_root()) + Path::new(&*TRACK_ROOT) .join("exercises") .join(exercise_name) .exists() @@ -258,7 +259,7 @@ pub fn exercise_exists(exercise_name: &str) -> bool { // Update the version of the specified exercise in the Cargo.toml file according to the passed canonical data pub fn update_cargo_toml_version(exercise_name: &str, canonical_data: &Value) { - let cargo_toml_path = Path::new(&get_track_root()) + let cargo_toml_path = Path::new(&*TRACK_ROOT) .join("exercises") .join(exercise_name) .join("Cargo.toml"); From dab2ebfcce869cd6d4f6f9e9a5af73cd89205037 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sat, 20 Oct 2018 00:58:38 +0300 Subject: [PATCH 76/80] README: Updated the 'exercise' crate section to mention the openssl dependency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27417dbc1..adaed643f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Please see the documentation about [adding new exercises](https://github.com/exe Note that: -- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/util/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `util/exercise/` directory and then copy the `exercise` binary from the `util/exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. +- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/util/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `util/exercise/` directory and then copy the `exercise` binary from the `util/exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. Please note that this utility depends on the `reqwest` crate and therefore you may need to install it's [required libraries](https://github.com/seanmonstar/reqwest#requirements) (namely `openssl`) in your system. - Each exercise must stand on its own. Do not reference files outside the exercise directory. They will not be included when the user fetches the exercise. From 6f73c975ee771c8c09023aa3ef6675df876b4588 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sat, 20 Oct 2018 00:59:52 +0300 Subject: [PATCH 77/80] exercise: Updated the crate's version to '1.0.0' --- util/exercise/Cargo.lock | 2 +- util/exercise/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/util/exercise/Cargo.lock b/util/exercise/Cargo.lock index 38b937a50..7a6253aae 100644 --- a/util/exercise/Cargo.lock +++ b/util/exercise/Cargo.lock @@ -166,7 +166,7 @@ dependencies = [ [[package]] name = "exercise" -version = "0.1.0" +version = "1.0.0" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/util/exercise/Cargo.toml b/util/exercise/Cargo.toml index c92ae3fdf..51bb8264e 100644 --- a/util/exercise/Cargo.toml +++ b/util/exercise/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "exercise" -version = "0.1.0" +version = "1.0.0" description = "An utility for creating or updating the exercises on the Exercism Rust track" [dependencies] From d8d464b7980603dbdcddd42af3cefaea1c7d9749 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sat, 20 Oct 2018 01:00:59 +0300 Subject: [PATCH 78/80] init_exercise.py: Removed the script from the track --- bin/init_exercise.py | 591 ------------------------------------------- 1 file changed, 591 deletions(-) delete mode 100644 bin/init_exercise.py diff --git a/bin/init_exercise.py b/bin/init_exercise.py deleted file mode 100644 index 1e8c28c57..000000000 --- a/bin/init_exercise.py +++ /dev/null @@ -1,591 +0,0 @@ -#!/usr/bin/env python -""" -Script to initialize an exercise for Exercism's Rust track - -Why a Python script in the Rust track repo? Distribution. -A rust program would either need to be precompiled for various -platforms, and available for download (in which case it wouldn't -conveniently work in the repository), or would need to be included -as a sub-crate and compiled locally. A python script can simply -be a single file and depend on the user's system Python, if desired. - -This module requires Python3.5 or newer -""" -from __future__ import print_function - -try: - import collections - import json - import os - import shlex - import subprocess - import string - import sys - import urllib.request - - from contextlib import contextmanager - from uuid import uuid4 -except ImportError: - print("This script requires Python 3.5 or higher", file=sys.stderr) - # exiting like this isn't great for library use, but at least it's a quick fail - sys.exit(1) - -# check version info -if sys.version_info[0] != 3 or sys.version_info[1] < 5: - print("This script requires Python 3.5 or higher", file=sys.stderr) - # exiting like this isn't great for library use, but at least it's a quick fail - sys.exit(1) - - -def output_of(cmd, check_returncode=True): - "Return the stdout of the given command" - sp = subprocess.run(shlex.split(cmd), - stdout=subprocess.PIPE, - universal_newlines=True) - if check_returncode: - sp.check_returncode() - return sp.stdout.strip() - - -os.chdir(os.path.dirname(__file__)) -REPO_ROOT = output_of('git rev-parse --show-toplevel') -EXERCISES = os.path.join(REPO_ROOT, 'exercises') -ITEM_NAME_CHARS = {c for c in string.ascii_lowercase + string.digits + '_'} -VERSION = "1.0.0" - - -def to_item_name(description): - "Produce a valid rust item name from arbitrary inputs" - item = description.lower().replace(' ', '_') - item = [c for c in item if c in ITEM_NAME_CHARS] - while len(item) > 0 and item[0] in string.digits: - item = item[1:] - if len(item) == 0: - raise ValueError("Could not produce an item name from " + description) - return ''.join(item) - - -def to_crate_name(name): - return name.replace('-', '_') - - -def url_for(name, file): - return ( - "https://raw.githubusercontent.com/exercism/problem-specifications" - f"/master/exercises/{name}/{file}" - ) - - -def get_problem_specification(name): - """ - Try to get problem specifications for the exercise of the given name. - - If the problem specifications repo doesn't exist or the exercise does not - exist within the specifications repo, returns None. - Otherwise, returns a dict, of which the values might be None or str. - """ - try: - with urllib.request.urlopen(url_for(name, 'canonical-data.json')) as response: - return json.loads(response.read()) - except (urllib.request.URLError, json.JSONDecodeError): - pass - - -@contextmanager -def inside(path): - cwd = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(cwd) - - -def make_exercise(name, use_maplit): - "Make a new exercise with the specified name" - with inside(EXERCISES): - if os.path.exists(name): - print(f"{name} already exists; aborting", file=sys.stderr) - sys.exit(1) - subprocess.run(['cargo', 'new', name]) - exercise_dir = os.path.join(EXERCISES, name) - # blank out the default lib.rs - with inside(exercise_dir): - with open('.gitignore', 'w') as gitignore: - print("# Generated by Cargo", file=gitignore) - print("# will have compiled files and executables", file=gitignore) - print("/target/", file=gitignore) - print("**/*.rs.bk", file=gitignore) - print("", file=gitignore) - print("# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries", file=gitignore) - print("# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock", file=gitignore) - print("Cargo.lock", file=gitignore) - with open(os.path.join('src', 'lib.rs'), 'w') as lib_rs: - lib_rs.truncate() - if use_maplit: - with open('Cargo.toml', 'a') as cargo_toml: - print("maplit = \"1.0.0\"", file=cargo_toml) - os.mkdir('tests') - with inside('tests'): - with open(f'{name}.rs', 'w') as tests_rs: - if use_maplit: - print("#[macro_use] extern crate maplit;", file=tests_rs) - print(f"extern crate {to_crate_name(name)};", file=tests_rs) - print(f"use {to_crate_name(name)}::*;", file=tests_rs) - print(file=tests_rs) - with open('example.rs', 'w') as example_rs: - print(f"//! Example implementation for {name}", file=example_rs) - print('//!', file=example_rs) - print("//! - Implement the solution to your exercise here.", file=example_rs) - print("//! - Put the stubs for any tested functions in `src/lib.rs`,", file=example_rs) - print("//! whose variable names are `_` and", file=example_rs) - print("//! whose contents are `unimplemented!()`.", file=example_rs) - print("//! - If your example implementation has dependencies, copy", file=example_rs) - print("//! `Cargo.toml` into `Cargo-example.toml` and then make", file=example_rs) - print("//! any modifications necessary to the latter so your example will run.", file=example_rs) - print("//! - Test your example by running `../../bin/test-exercise`", file=example_rs) - - cd = get_problem_specification(name) - if cd is None: - print(f"No problem specification for {name} found") - make_new_exercise(name, exercise_dir) - generate_readme(name, get_problem_specification=False) - else: - make_exercise_with_specifications(name, exercise_dir, cd, use_maplit) - generate_readme(name, get_problem_specification=True) - - -def make_new_exercise(name, exercise_dir): - print("Creating new exercise from scratch...") - with inside(exercise_dir): - os.mkdir('.meta') - with inside('.meta'): - with open('description.md', 'w') as description: - print("Describe your exercise here", file=description) - print(file=description) - print("Don't forget that `README.md` is automatically generated; update this within `.meta\description.md`.", file=description) - with open('metadata.yml', 'w') as metadata: - print("---", file=metadata) - print(f"blurb: \"{name}\"", file=metadata) - print("source: \"\"", file=metadata) - print("source_url: \"\"", file=metadata) - with inside('tests'): - with open(f'{name}.rs', 'a') as tests_rs: - print("// Add your tests here", file=tests_rs) - - -def make_exercise_with_specifications(name, exercise_dir, canonical_data, use_maplit): - print("Creating exercise from specification...") - # Update Cargo.toml - with open('Cargo.toml', 'r') as cargo_toml: - cargo_data = cargo_toml.read() - with open('Cargo.toml', 'w') as cargo_toml: - for line in cargo_data.splitlines(): - if 'version' in canonical_data and line.lower().startswith('version'): - print(f"version = \"{canonical_data['version']}\"", file=cargo_toml) - elif line.lower().startswith('name'): - print(f"name = \"{to_crate_name(name)}\"", file=cargo_toml) - else: - print(line.strip(), file=cargo_toml) - - tests_filename = os.path.join(exercise_dir, 'tests', f'{name}.rs') - # prepend doc comment about the nature of this file - with open(tests_filename, 'r') as tests_rs: - existing = tests_rs.read() - with open(tests_filename, 'w') as tests_rs: - print(f'//! Tests for {name}', file=tests_rs) - print('//!', file=tests_rs) - print('//! Generated by [script][script] using [canonical data][canonical-data]', - file=tests_rs) - print("//!", file=tests_rs) - print("//! [script]: https://github.com/exercism/rust/blob/master/bin/init_exercise.py", - file=tests_rs) - print("//! [canonical-data]: {}".format(url_for(name, 'canonical_data.json')), - file=tests_rs) - if 'comments' in canonical_data: - c = canonical_data['comments'] - print('//!', file=tests_rs) - if isinstance(c, list) or isinstance(c, tuple): - for l in c: - print(f'//! {l}', file=tests_rs) - else: - print(f'//! {c}', file=tests_rs) - - print(file=tests_rs) - print(file=tests_rs) - tests_rs.write(existing) - - # now add test data - with open(tests_filename, 'a') as tests_rs: - first_case = True - - # {property : {(input key names), ...}} - PIK_MAP = {} - - def generate_pik_map(cases): - nonlocal PIK_MAP - for case in cases: - if 'property' in case: - ikeys = get_input_keys(case) - if ikeys is not None: - pkeys = PIK_MAP.get(case['property'], set()) - pkeys.add(ikeys) - PIK_MAP[case['property']] = pkeys - if 'cases' in case: - generate_pik_map(case['cases']) - - def collect_properties(cases): - properties = set() - for case in cases: - if 'expected' in case and 'property' in case: - properties.add(case['property']) - if 'cases' in case: - properties |= collect_properties(case['cases']) - return properties - - def property_processor(property): - print(f"/// Process a single test case for the property `{property}`", file=tests_rs) - print("///", file=tests_rs) - print(f"/// All cases for the `{property}` property are implemented", - file=tests_rs) - print("/// in terms of this function.", file=tests_rs) - print('///', file=tests_rs) - print("/// Note that you'll need to both name the expected transform which", - file=tests_rs) - print("/// the student needs to write, and name the types of the inputs and outputs.", - file=tests_rs) - print("/// While rustc _may_ be able to handle things properly given a working example,", - file=tests_rs) - print("/// students will face confusing errors if the `I` and `O` types are not concrete.", - file=tests_rs) - if property in PIK_MAP: - print('///', file=tests_rs) - if len(PIK_MAP[property]) == 1: - print(f"/// Expected input format: {next(iter(PIK_MAP[property]))}", - file=tests_rs) - else: - print("/// CAUTION: Multiple input formats were detected in this test's cases:", - file=tests_rs) - for ifmt in PIK_MAP[property]: - print(f"/// {ifmt}") - print( - f"fn process_{property.lower()}_case(input: I, expected: O) {{", file=tests_rs) - print(" // typical implementation:", file=tests_rs) - print(" // assert_eq!(", file=tests_rs) - print(f" // student_{property}_func(input),", file=tests_rs) - print(" // expected", file=tests_rs) - print(" // )", file=tests_rs) - print(" unimplemented!()", file=tests_rs) - print("}", file=tests_rs) - print(file=tests_rs) - - def literal(item): - if isinstance(item, str): - return f'"{item}"' - elif isinstance(item, tuple): - return "({})".format( - ', '.join((literal(i) for i in item)) - ) - elif isinstance(item, list): - return "vec![{}]".format( - ', '.join((literal(i) for i in item)) - ) - elif isinstance(item, dict): - if use_maplit: - return "hashmap!{{{}}}".format( - ','.join(( - "{}=>{}".format(literal(k), literal(v)) - for k, v in item.items() - )) - ) - else: - return "{{let mut hm=::std::collections::HashMap::new();{}hm}}".format( - ''.join(( - "hm.insert({}, {});".format(literal(k), literal(v)) - for k, v in item.items() - )) - ) - else: - return str(item) - - def write_case(case): - nonlocal first_case - - print("#[test]", file=tests_rs) - if first_case: - first_case = False - else: - print("#[ignore]", file=tests_rs) - print(f"/// {case['description']}", file=tests_rs) - if 'comments' in case: - print('///', file=tests_rs) - if isinstance(case['comments'], list): - for line in case['comments']: - print(f"/// {line}", file=tests_rs) - else: - print(f"/// {case['comments']}", file=tests_rs) - print("fn test_{}() {{".format(to_item_name(case['description'])), file=tests_rs) - print(" process_{}_case({}, {});".format( - case['property'].lower(), - literal(case['input']), - literal(case['expected'])), - file=tests_rs) - print("}", file=tests_rs) - print(file=tests_rs) - - def get_input_keys(item): - if 'description' in item and 'expected' in item: - return tuple(sorted(set(item.keys()) - - {'comments', - 'description', - 'expected', - 'property'} - )) - # else None - - def write_cases(cases): - for item in cases: - if 'description' in item and 'expected' not in item: - if isinstance(item['description'], list): - for line in item['description']: - print(f"// {line}", file=tests_rs) - else: - print(f"// {item['description']}", file=tests_rs) - if 'comments' in item and 'expected' not in item: - if isinstance(item['comments'], list): - for line in item['comments']: - print(f"// {line}", file=tests_rs) - else: - print(f"// {item['comments']}", file=tests_rs) - if 'expected' not in item and 'comments' in item or 'description' in item: - print(file=tests_rs) - if 'property' not in item: - item['property'] = '' - if all(key in item for key in ('description', 'input', 'expected')): - write_case(item) - elif 'description' in item and 'expected' in item: - item['input'] = tuple((item[k] for k in get_input_keys(item))) - write_case(item) - if 'cases' in item: - write_cases(item['cases']) - - generate_pik_map(canonical_data['cases']) - - for ppty in collect_properties(canonical_data['cases']): - property_processor(ppty) - - write_cases(canonical_data['cases']) - - -def prompt(prompt, validator): - """ - Prompt the user for a value - - Validator is a function which accepts the user's input and either - returns a (possibly transformed) value, or raises an exception. - On an exception, the user is asked again. - """ - while True: - try: - return validator(input(prompt).strip()) - except Exception as e: - print(f"Problem: {e}") - - -def update_config(name): - "Update the configuration based on user input" - with inside(REPO_ROOT): - with open('config.json') as config_json: - config = json.load(config_json, object_pairs_hook=collections.OrderedDict) - - while True: - conf_values = collections.OrderedDict() - conf_values['uuid'] = str(uuid4()) - conf_values['slug'] = name - conf_values['core'] = False - - def unlock_validator(v): - if len(v) == 0: - return None - if not any(v == ex['slug'] for ex in config['exercises']): - raise ValueError(f"{v} is not an existing exercise slug") - return v - conf_values['unlocked_by'] = prompt( - "Exercise slug which unlocks this (blank for None): ", unlock_validator) - - def difficulty_validator(v): - i = int(v) - if i <= 0 or i > 10: - raise ValueError("difficulty must be > 0 and <= 10") - return i - conf_values['difficulty'] = prompt( - "Difficulty for this exercise([1...10]): ", difficulty_validator) - - def topics_validator(v): - topics = [t.strip() for t in v.split(',') if len(t.strip()) > 0] - if len(topics) == 0: - raise ValueError("must enter at least one topic") - return topics - conf_values['topics'] = prompt( - "List of topics for this exercise, comma-separated: ", topics_validator) - - print("You have configured this exercise as follows:") - print(json.dumps(conf_values, sort_keys=True, indent=4)) - - yn = input('Is this correct? (y/N): ').strip().lower() - if len(yn) > 0 and yn[0] == 'y': - break - - if not any(conf_values['difficulty'] == ex['difficulty'] for ex in config['exercises']): - config['exercises'].append(conf_values) - config['exercises'].sort(key=lambda ex: ex['difficulty']) - else: - # find the index bounds before which we might insert this - first_idx = None - last_idx = None - for idx, exercise in enumerate(config['exercises']): - if 'difficulty' in exercise and exercise['difficulty'] == conf_values['difficulty'] and first_idx is None: - first_idx = idx - if 'difficulty' in exercise and exercise['difficulty'] != conf_values['difficulty'] and first_idx is not None: - last_idx = idx - if last_idx is None: - last_idx = len(config['exercises']) - - def binary_search(start_idx, end_idx): - if start_idx == end_idx: - return start_idx - mid_idx = start_idx + ((end_idx - start_idx) // 2) - - def easy_hard_validator(v): - v = v.lower()[0] - if v not in {'e', 'h'}: - raise ValueError("must enter 'easier' or 'harder' or a substring") - return v - relative_difficulty = prompt( - f"Is {name} easier or harder than {config['exercises'][mid_idx]['slug']}: ", - easy_hard_validator - ) - - if relative_difficulty == 'e': - return binary_search(start_idx, mid_idx) - else: - return binary_search(mid_idx + 1, end_idx) - - while True: - insert_idx = binary_search(first_idx, last_idx) - if insert_idx == 0: - ptext = f"{name} is the easiest exercise in the track." - elif insert_idx == len(config['exercises']): - ptext = f"{name} is the hardest exercise in the track." - else: - ptext = "{} fits between {} and {} in difficulty.".format( - name, - config['exercises'][insert_idx - 1]['slug'], - config['exercises'][insert_idx]['slug'], - ) - print(f"You have indicated that {ptext}") - yn = input('Is this correct? (y/N): ').strip().lower() - if len(yn) > 0 and yn[0] == 'y': - break - - config['exercises'].insert(insert_idx, conf_values) - - with inside(REPO_ROOT): - with open('config.json', 'w') as config_json: - json.dump( - config, - config_json, - sort_keys=False, - indent=2, - ) - # end the config file with a newline - print(file=config_json) - - -@contextmanager -def git_master(git_path): - "A context inside of which you are on the clean master branch" - with inside(git_path): - dirty = len(output_of('git status --porcelain')) > 0 - if dirty: - subprocess.run(['git', 'stash']) - branch = output_of('git rev-parse --abbrev-ref HEAD') - if branch != 'master': - subprocess.run(['git', 'checkout', 'master']) - subprocess.run(['git', 'pull']) - - try: - yield - finally: - if branch != 'master': - subprocess.run(['git', 'checkout', branch]) - if dirty: - subprocess.run(['git', 'stash', 'pop']) - - -def generate_readme(exercise_name, get_problem_specification): - configlet = None - with inside(os.path.join(REPO_ROOT, 'bin')): - if not os.path.exists('configlet') and not os.path.exists('configlet.exe'): - with inside(REPO_ROOT): - subprocess.run(os.path.join('bin', 'fetch-configlet')) - for configlet_name in ('configlet', 'configlet.exe'): - if os.path.exists(configlet_name): - configlet = configlet_name - break - if configlet is None: - print("Could not locate configlet; aborting", file=sys.stderr) - sys.exit(1) - if get_problem_specification: - with inside(os.path.join(REPO_ROOT, '..')): - if os.path.exists('problem-specifications'): - with git_master('problem-specifications'): - with inside(REPO_ROOT): - subprocess.run([ - os.path.join('bin', configlet), - 'generate', '.', - '--only', exercise_name, - '--spec-path', - os.path.join('..', 'problem-specifications') - ]) - else: - subprocess.run( - ['git', 'clone', 'https://github.com/exercism/problem-specifications.git'] - ) - with inside(REPO_ROOT): - subprocess.run([ - os.path.join('bin', configlet), - 'generate', '.', - '--only', exercise_name, - '--spec-path', - os.path.join('..', 'problem-specifications') - ]) - else: - with inside(REPO_ROOT): - subprocess.run([ - os.path.join('bin', configlet), - 'generate', '.', - '--only', exercise_name, - ]) - - -if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser(description='Create a Rust Track exercise for Exercism') - parser.add_argument('name', help='name of the exercise to create') - parser.add_argument('--dont-create-exercise', action='store_true', - help='Don\'t create the exercise. Useful when just updating config.json') - parser.add_argument('--dont-update-config', action='store_true', - help='Don\'t update config.json. Useful when you don\'t yet ' - 'have a sense of exercise difficulty.') - parser.add_argument('--version', action='version', version=VERSION) - parser.add_argument('--use-maplit', action='store_true', - help='Use the maplit crate to improve readability of tests with lots of map literals') - - args = parser.parse_args() - - if not args.dont_create_exercise: - make_exercise(args.name, args.use_maplit) - - if not args.dont_update_config: - update_config(args.name) From e0778ee99fead4de6fe598d952f3f18a2e72e9c8 Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sat, 20 Oct 2018 13:12:21 +0200 Subject: [PATCH 79/80] Revert "init_exercise.py: Removed the script from the track" This reverts commit d8d464b7980603dbdcddd42af3cefaea1c7d9749. That commit caused a merge conflict. --- bin/init_exercise.py | 591 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 591 insertions(+) create mode 100644 bin/init_exercise.py diff --git a/bin/init_exercise.py b/bin/init_exercise.py new file mode 100644 index 000000000..1e8c28c57 --- /dev/null +++ b/bin/init_exercise.py @@ -0,0 +1,591 @@ +#!/usr/bin/env python +""" +Script to initialize an exercise for Exercism's Rust track + +Why a Python script in the Rust track repo? Distribution. +A rust program would either need to be precompiled for various +platforms, and available for download (in which case it wouldn't +conveniently work in the repository), or would need to be included +as a sub-crate and compiled locally. A python script can simply +be a single file and depend on the user's system Python, if desired. + +This module requires Python3.5 or newer +""" +from __future__ import print_function + +try: + import collections + import json + import os + import shlex + import subprocess + import string + import sys + import urllib.request + + from contextlib import contextmanager + from uuid import uuid4 +except ImportError: + print("This script requires Python 3.5 or higher", file=sys.stderr) + # exiting like this isn't great for library use, but at least it's a quick fail + sys.exit(1) + +# check version info +if sys.version_info[0] != 3 or sys.version_info[1] < 5: + print("This script requires Python 3.5 or higher", file=sys.stderr) + # exiting like this isn't great for library use, but at least it's a quick fail + sys.exit(1) + + +def output_of(cmd, check_returncode=True): + "Return the stdout of the given command" + sp = subprocess.run(shlex.split(cmd), + stdout=subprocess.PIPE, + universal_newlines=True) + if check_returncode: + sp.check_returncode() + return sp.stdout.strip() + + +os.chdir(os.path.dirname(__file__)) +REPO_ROOT = output_of('git rev-parse --show-toplevel') +EXERCISES = os.path.join(REPO_ROOT, 'exercises') +ITEM_NAME_CHARS = {c for c in string.ascii_lowercase + string.digits + '_'} +VERSION = "1.0.0" + + +def to_item_name(description): + "Produce a valid rust item name from arbitrary inputs" + item = description.lower().replace(' ', '_') + item = [c for c in item if c in ITEM_NAME_CHARS] + while len(item) > 0 and item[0] in string.digits: + item = item[1:] + if len(item) == 0: + raise ValueError("Could not produce an item name from " + description) + return ''.join(item) + + +def to_crate_name(name): + return name.replace('-', '_') + + +def url_for(name, file): + return ( + "https://raw.githubusercontent.com/exercism/problem-specifications" + f"/master/exercises/{name}/{file}" + ) + + +def get_problem_specification(name): + """ + Try to get problem specifications for the exercise of the given name. + + If the problem specifications repo doesn't exist or the exercise does not + exist within the specifications repo, returns None. + Otherwise, returns a dict, of which the values might be None or str. + """ + try: + with urllib.request.urlopen(url_for(name, 'canonical-data.json')) as response: + return json.loads(response.read()) + except (urllib.request.URLError, json.JSONDecodeError): + pass + + +@contextmanager +def inside(path): + cwd = os.getcwd() + os.chdir(path) + try: + yield + finally: + os.chdir(cwd) + + +def make_exercise(name, use_maplit): + "Make a new exercise with the specified name" + with inside(EXERCISES): + if os.path.exists(name): + print(f"{name} already exists; aborting", file=sys.stderr) + sys.exit(1) + subprocess.run(['cargo', 'new', name]) + exercise_dir = os.path.join(EXERCISES, name) + # blank out the default lib.rs + with inside(exercise_dir): + with open('.gitignore', 'w') as gitignore: + print("# Generated by Cargo", file=gitignore) + print("# will have compiled files and executables", file=gitignore) + print("/target/", file=gitignore) + print("**/*.rs.bk", file=gitignore) + print("", file=gitignore) + print("# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries", file=gitignore) + print("# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock", file=gitignore) + print("Cargo.lock", file=gitignore) + with open(os.path.join('src', 'lib.rs'), 'w') as lib_rs: + lib_rs.truncate() + if use_maplit: + with open('Cargo.toml', 'a') as cargo_toml: + print("maplit = \"1.0.0\"", file=cargo_toml) + os.mkdir('tests') + with inside('tests'): + with open(f'{name}.rs', 'w') as tests_rs: + if use_maplit: + print("#[macro_use] extern crate maplit;", file=tests_rs) + print(f"extern crate {to_crate_name(name)};", file=tests_rs) + print(f"use {to_crate_name(name)}::*;", file=tests_rs) + print(file=tests_rs) + with open('example.rs', 'w') as example_rs: + print(f"//! Example implementation for {name}", file=example_rs) + print('//!', file=example_rs) + print("//! - Implement the solution to your exercise here.", file=example_rs) + print("//! - Put the stubs for any tested functions in `src/lib.rs`,", file=example_rs) + print("//! whose variable names are `_` and", file=example_rs) + print("//! whose contents are `unimplemented!()`.", file=example_rs) + print("//! - If your example implementation has dependencies, copy", file=example_rs) + print("//! `Cargo.toml` into `Cargo-example.toml` and then make", file=example_rs) + print("//! any modifications necessary to the latter so your example will run.", file=example_rs) + print("//! - Test your example by running `../../bin/test-exercise`", file=example_rs) + + cd = get_problem_specification(name) + if cd is None: + print(f"No problem specification for {name} found") + make_new_exercise(name, exercise_dir) + generate_readme(name, get_problem_specification=False) + else: + make_exercise_with_specifications(name, exercise_dir, cd, use_maplit) + generate_readme(name, get_problem_specification=True) + + +def make_new_exercise(name, exercise_dir): + print("Creating new exercise from scratch...") + with inside(exercise_dir): + os.mkdir('.meta') + with inside('.meta'): + with open('description.md', 'w') as description: + print("Describe your exercise here", file=description) + print(file=description) + print("Don't forget that `README.md` is automatically generated; update this within `.meta\description.md`.", file=description) + with open('metadata.yml', 'w') as metadata: + print("---", file=metadata) + print(f"blurb: \"{name}\"", file=metadata) + print("source: \"\"", file=metadata) + print("source_url: \"\"", file=metadata) + with inside('tests'): + with open(f'{name}.rs', 'a') as tests_rs: + print("// Add your tests here", file=tests_rs) + + +def make_exercise_with_specifications(name, exercise_dir, canonical_data, use_maplit): + print("Creating exercise from specification...") + # Update Cargo.toml + with open('Cargo.toml', 'r') as cargo_toml: + cargo_data = cargo_toml.read() + with open('Cargo.toml', 'w') as cargo_toml: + for line in cargo_data.splitlines(): + if 'version' in canonical_data and line.lower().startswith('version'): + print(f"version = \"{canonical_data['version']}\"", file=cargo_toml) + elif line.lower().startswith('name'): + print(f"name = \"{to_crate_name(name)}\"", file=cargo_toml) + else: + print(line.strip(), file=cargo_toml) + + tests_filename = os.path.join(exercise_dir, 'tests', f'{name}.rs') + # prepend doc comment about the nature of this file + with open(tests_filename, 'r') as tests_rs: + existing = tests_rs.read() + with open(tests_filename, 'w') as tests_rs: + print(f'//! Tests for {name}', file=tests_rs) + print('//!', file=tests_rs) + print('//! Generated by [script][script] using [canonical data][canonical-data]', + file=tests_rs) + print("//!", file=tests_rs) + print("//! [script]: https://github.com/exercism/rust/blob/master/bin/init_exercise.py", + file=tests_rs) + print("//! [canonical-data]: {}".format(url_for(name, 'canonical_data.json')), + file=tests_rs) + if 'comments' in canonical_data: + c = canonical_data['comments'] + print('//!', file=tests_rs) + if isinstance(c, list) or isinstance(c, tuple): + for l in c: + print(f'//! {l}', file=tests_rs) + else: + print(f'//! {c}', file=tests_rs) + + print(file=tests_rs) + print(file=tests_rs) + tests_rs.write(existing) + + # now add test data + with open(tests_filename, 'a') as tests_rs: + first_case = True + + # {property : {(input key names), ...}} + PIK_MAP = {} + + def generate_pik_map(cases): + nonlocal PIK_MAP + for case in cases: + if 'property' in case: + ikeys = get_input_keys(case) + if ikeys is not None: + pkeys = PIK_MAP.get(case['property'], set()) + pkeys.add(ikeys) + PIK_MAP[case['property']] = pkeys + if 'cases' in case: + generate_pik_map(case['cases']) + + def collect_properties(cases): + properties = set() + for case in cases: + if 'expected' in case and 'property' in case: + properties.add(case['property']) + if 'cases' in case: + properties |= collect_properties(case['cases']) + return properties + + def property_processor(property): + print(f"/// Process a single test case for the property `{property}`", file=tests_rs) + print("///", file=tests_rs) + print(f"/// All cases for the `{property}` property are implemented", + file=tests_rs) + print("/// in terms of this function.", file=tests_rs) + print('///', file=tests_rs) + print("/// Note that you'll need to both name the expected transform which", + file=tests_rs) + print("/// the student needs to write, and name the types of the inputs and outputs.", + file=tests_rs) + print("/// While rustc _may_ be able to handle things properly given a working example,", + file=tests_rs) + print("/// students will face confusing errors if the `I` and `O` types are not concrete.", + file=tests_rs) + if property in PIK_MAP: + print('///', file=tests_rs) + if len(PIK_MAP[property]) == 1: + print(f"/// Expected input format: {next(iter(PIK_MAP[property]))}", + file=tests_rs) + else: + print("/// CAUTION: Multiple input formats were detected in this test's cases:", + file=tests_rs) + for ifmt in PIK_MAP[property]: + print(f"/// {ifmt}") + print( + f"fn process_{property.lower()}_case(input: I, expected: O) {{", file=tests_rs) + print(" // typical implementation:", file=tests_rs) + print(" // assert_eq!(", file=tests_rs) + print(f" // student_{property}_func(input),", file=tests_rs) + print(" // expected", file=tests_rs) + print(" // )", file=tests_rs) + print(" unimplemented!()", file=tests_rs) + print("}", file=tests_rs) + print(file=tests_rs) + + def literal(item): + if isinstance(item, str): + return f'"{item}"' + elif isinstance(item, tuple): + return "({})".format( + ', '.join((literal(i) for i in item)) + ) + elif isinstance(item, list): + return "vec![{}]".format( + ', '.join((literal(i) for i in item)) + ) + elif isinstance(item, dict): + if use_maplit: + return "hashmap!{{{}}}".format( + ','.join(( + "{}=>{}".format(literal(k), literal(v)) + for k, v in item.items() + )) + ) + else: + return "{{let mut hm=::std::collections::HashMap::new();{}hm}}".format( + ''.join(( + "hm.insert({}, {});".format(literal(k), literal(v)) + for k, v in item.items() + )) + ) + else: + return str(item) + + def write_case(case): + nonlocal first_case + + print("#[test]", file=tests_rs) + if first_case: + first_case = False + else: + print("#[ignore]", file=tests_rs) + print(f"/// {case['description']}", file=tests_rs) + if 'comments' in case: + print('///', file=tests_rs) + if isinstance(case['comments'], list): + for line in case['comments']: + print(f"/// {line}", file=tests_rs) + else: + print(f"/// {case['comments']}", file=tests_rs) + print("fn test_{}() {{".format(to_item_name(case['description'])), file=tests_rs) + print(" process_{}_case({}, {});".format( + case['property'].lower(), + literal(case['input']), + literal(case['expected'])), + file=tests_rs) + print("}", file=tests_rs) + print(file=tests_rs) + + def get_input_keys(item): + if 'description' in item and 'expected' in item: + return tuple(sorted(set(item.keys()) - + {'comments', + 'description', + 'expected', + 'property'} + )) + # else None + + def write_cases(cases): + for item in cases: + if 'description' in item and 'expected' not in item: + if isinstance(item['description'], list): + for line in item['description']: + print(f"// {line}", file=tests_rs) + else: + print(f"// {item['description']}", file=tests_rs) + if 'comments' in item and 'expected' not in item: + if isinstance(item['comments'], list): + for line in item['comments']: + print(f"// {line}", file=tests_rs) + else: + print(f"// {item['comments']}", file=tests_rs) + if 'expected' not in item and 'comments' in item or 'description' in item: + print(file=tests_rs) + if 'property' not in item: + item['property'] = '' + if all(key in item for key in ('description', 'input', 'expected')): + write_case(item) + elif 'description' in item and 'expected' in item: + item['input'] = tuple((item[k] for k in get_input_keys(item))) + write_case(item) + if 'cases' in item: + write_cases(item['cases']) + + generate_pik_map(canonical_data['cases']) + + for ppty in collect_properties(canonical_data['cases']): + property_processor(ppty) + + write_cases(canonical_data['cases']) + + +def prompt(prompt, validator): + """ + Prompt the user for a value + + Validator is a function which accepts the user's input and either + returns a (possibly transformed) value, or raises an exception. + On an exception, the user is asked again. + """ + while True: + try: + return validator(input(prompt).strip()) + except Exception as e: + print(f"Problem: {e}") + + +def update_config(name): + "Update the configuration based on user input" + with inside(REPO_ROOT): + with open('config.json') as config_json: + config = json.load(config_json, object_pairs_hook=collections.OrderedDict) + + while True: + conf_values = collections.OrderedDict() + conf_values['uuid'] = str(uuid4()) + conf_values['slug'] = name + conf_values['core'] = False + + def unlock_validator(v): + if len(v) == 0: + return None + if not any(v == ex['slug'] for ex in config['exercises']): + raise ValueError(f"{v} is not an existing exercise slug") + return v + conf_values['unlocked_by'] = prompt( + "Exercise slug which unlocks this (blank for None): ", unlock_validator) + + def difficulty_validator(v): + i = int(v) + if i <= 0 or i > 10: + raise ValueError("difficulty must be > 0 and <= 10") + return i + conf_values['difficulty'] = prompt( + "Difficulty for this exercise([1...10]): ", difficulty_validator) + + def topics_validator(v): + topics = [t.strip() for t in v.split(',') if len(t.strip()) > 0] + if len(topics) == 0: + raise ValueError("must enter at least one topic") + return topics + conf_values['topics'] = prompt( + "List of topics for this exercise, comma-separated: ", topics_validator) + + print("You have configured this exercise as follows:") + print(json.dumps(conf_values, sort_keys=True, indent=4)) + + yn = input('Is this correct? (y/N): ').strip().lower() + if len(yn) > 0 and yn[0] == 'y': + break + + if not any(conf_values['difficulty'] == ex['difficulty'] for ex in config['exercises']): + config['exercises'].append(conf_values) + config['exercises'].sort(key=lambda ex: ex['difficulty']) + else: + # find the index bounds before which we might insert this + first_idx = None + last_idx = None + for idx, exercise in enumerate(config['exercises']): + if 'difficulty' in exercise and exercise['difficulty'] == conf_values['difficulty'] and first_idx is None: + first_idx = idx + if 'difficulty' in exercise and exercise['difficulty'] != conf_values['difficulty'] and first_idx is not None: + last_idx = idx + if last_idx is None: + last_idx = len(config['exercises']) + + def binary_search(start_idx, end_idx): + if start_idx == end_idx: + return start_idx + mid_idx = start_idx + ((end_idx - start_idx) // 2) + + def easy_hard_validator(v): + v = v.lower()[0] + if v not in {'e', 'h'}: + raise ValueError("must enter 'easier' or 'harder' or a substring") + return v + relative_difficulty = prompt( + f"Is {name} easier or harder than {config['exercises'][mid_idx]['slug']}: ", + easy_hard_validator + ) + + if relative_difficulty == 'e': + return binary_search(start_idx, mid_idx) + else: + return binary_search(mid_idx + 1, end_idx) + + while True: + insert_idx = binary_search(first_idx, last_idx) + if insert_idx == 0: + ptext = f"{name} is the easiest exercise in the track." + elif insert_idx == len(config['exercises']): + ptext = f"{name} is the hardest exercise in the track." + else: + ptext = "{} fits between {} and {} in difficulty.".format( + name, + config['exercises'][insert_idx - 1]['slug'], + config['exercises'][insert_idx]['slug'], + ) + print(f"You have indicated that {ptext}") + yn = input('Is this correct? (y/N): ').strip().lower() + if len(yn) > 0 and yn[0] == 'y': + break + + config['exercises'].insert(insert_idx, conf_values) + + with inside(REPO_ROOT): + with open('config.json', 'w') as config_json: + json.dump( + config, + config_json, + sort_keys=False, + indent=2, + ) + # end the config file with a newline + print(file=config_json) + + +@contextmanager +def git_master(git_path): + "A context inside of which you are on the clean master branch" + with inside(git_path): + dirty = len(output_of('git status --porcelain')) > 0 + if dirty: + subprocess.run(['git', 'stash']) + branch = output_of('git rev-parse --abbrev-ref HEAD') + if branch != 'master': + subprocess.run(['git', 'checkout', 'master']) + subprocess.run(['git', 'pull']) + + try: + yield + finally: + if branch != 'master': + subprocess.run(['git', 'checkout', branch]) + if dirty: + subprocess.run(['git', 'stash', 'pop']) + + +def generate_readme(exercise_name, get_problem_specification): + configlet = None + with inside(os.path.join(REPO_ROOT, 'bin')): + if not os.path.exists('configlet') and not os.path.exists('configlet.exe'): + with inside(REPO_ROOT): + subprocess.run(os.path.join('bin', 'fetch-configlet')) + for configlet_name in ('configlet', 'configlet.exe'): + if os.path.exists(configlet_name): + configlet = configlet_name + break + if configlet is None: + print("Could not locate configlet; aborting", file=sys.stderr) + sys.exit(1) + if get_problem_specification: + with inside(os.path.join(REPO_ROOT, '..')): + if os.path.exists('problem-specifications'): + with git_master('problem-specifications'): + with inside(REPO_ROOT): + subprocess.run([ + os.path.join('bin', configlet), + 'generate', '.', + '--only', exercise_name, + '--spec-path', + os.path.join('..', 'problem-specifications') + ]) + else: + subprocess.run( + ['git', 'clone', 'https://github.com/exercism/problem-specifications.git'] + ) + with inside(REPO_ROOT): + subprocess.run([ + os.path.join('bin', configlet), + 'generate', '.', + '--only', exercise_name, + '--spec-path', + os.path.join('..', 'problem-specifications') + ]) + else: + with inside(REPO_ROOT): + subprocess.run([ + os.path.join('bin', configlet), + 'generate', '.', + '--only', exercise_name, + ]) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Create a Rust Track exercise for Exercism') + parser.add_argument('name', help='name of the exercise to create') + parser.add_argument('--dont-create-exercise', action='store_true', + help='Don\'t create the exercise. Useful when just updating config.json') + parser.add_argument('--dont-update-config', action='store_true', + help='Don\'t update config.json. Useful when you don\'t yet ' + 'have a sense of exercise difficulty.') + parser.add_argument('--version', action='version', version=VERSION) + parser.add_argument('--use-maplit', action='store_true', + help='Use the maplit crate to improve readability of tests with lots of map literals') + + args = parser.parse_args() + + if not args.dont_create_exercise: + make_exercise(args.name, args.use_maplit) + + if not args.dont_update_config: + update_config(args.name) From 34ff773a96acb9e83fcd5569f4c444190b9f5224 Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sat, 20 Oct 2018 13:17:48 +0200 Subject: [PATCH 80/80] fix readme possessive typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index adaed643f..6424f2b87 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Please see the documentation about [adding new exercises](https://github.com/exe Note that: -- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/util/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `util/exercise/` directory and then copy the `exercise` binary from the `util/exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. Please note that this utility depends on the `reqwest` crate and therefore you may need to install it's [required libraries](https://github.com/seanmonstar/reqwest#requirements) (namely `openssl`) in your system. +- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/master/util/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/master/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `util/exercise/` directory and then copy the `exercise` binary from the `util/exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. Please note that this utility depends on the `reqwest` crate and therefore you may need to install its [required libraries](https://github.com/seanmonstar/reqwest#requirements) (namely `openssl`) in your system. - Each exercise must stand on its own. Do not reference files outside the exercise directory. They will not be included when the user fetches the exercise.