diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..cc26876 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,2572 @@ +{ + "files": [ + "AUTHORS.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "carols10cents", + "name": "Carol (Nichols || Goulding)", + "avatar_url": "https://avatars2.githubusercontent.com/u/193874?v=4", + "profile": "http://carol-nichols.com", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "QuietMisdreavus", + "name": "QuietMisdreavus", + "avatar_url": "https://avatars2.githubusercontent.com/u/5217170?v=4", + "profile": "https://twitter.com/QuietMisdreavus", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "robertlugg", + "name": "Robert M Lugg", + "avatar_url": "https://avatars0.githubusercontent.com/u/6054540?v=4", + "profile": "https://github.com/robertlugg", + "contributions": [ + "content" + ] + }, + { + "login": "hynek", + "name": "Hynek Schlawack", + "avatar_url": "https://avatars3.githubusercontent.com/u/41240?v=4", + "profile": "https://hynek.me/about/", + "contributions": [ + "code" + ] + }, + { + "login": "spacekookie", + "name": "Katharina Fey", + "avatar_url": "https://avatars0.githubusercontent.com/u/7669898?v=4", + "profile": "https://spacekookie.de", + "contributions": [ + "code" + ] + }, + { + "login": "lukabavdaz", + "name": "lukabavdaz", + "avatar_url": "https://avatars0.githubusercontent.com/u/9624558?v=4", + "profile": "https://github.com/lukabavdaz", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "evestera", + "name": "Erik Vesteraas", + "avatar_url": "https://avatars2.githubusercontent.com/u/4187449?v=4", + "profile": "http://vestera.as", + "contributions": [ + "code" + ] + }, + { + "login": "Delet0r", + "name": "delet0r", + "avatar_url": "https://avatars1.githubusercontent.com/u/23195618?v=4", + "profile": "https://github.com/Delet0r", + "contributions": [ + "code" + ] + }, + { + "login": "shaunbennett", + "name": "Shaun Bennett", + "avatar_url": "https://avatars1.githubusercontent.com/u/10522375?v=4", + "profile": "http://phinary.ca", + "contributions": [ + "code" + ] + }, + { + "login": "abagshaw", + "name": "Andrew Bagshaw", + "avatar_url": "https://avatars2.githubusercontent.com/u/8594541?v=4", + "profile": "https://github.com/abagshaw", + "contributions": [ + "code" + ] + }, + { + "login": "kisom", + "name": "Kyle Isom", + "avatar_url": "https://avatars2.githubusercontent.com/u/175578?v=4", + "profile": "https://ai6ua.net/", + "contributions": [ + "code" + ] + }, + { + "login": "ColinPitrat", + "name": "Colin Pitrat", + "avatar_url": "https://avatars3.githubusercontent.com/u/1541863?v=4", + "profile": "https://github.com/ColinPitrat", + "contributions": [ + "code" + ] + }, + { + "login": "zacanger", + "name": "Zac Anger", + "avatar_url": "https://avatars3.githubusercontent.com/u/12520493?v=4", + "profile": "https://zacanger.com", + "contributions": [ + "code" + ] + }, + { + "login": "mgeier", + "name": "Matthias Geier", + "avatar_url": "https://avatars1.githubusercontent.com/u/705404?v=4", + "profile": "https://github.com/mgeier", + "contributions": [ + "code" + ] + }, + { + "login": "cjpearce", + "name": "Chris Pearce", + "avatar_url": "https://avatars1.githubusercontent.com/u/3453268?v=4", + "profile": "https://github.com/cjpearce", + "contributions": [ + "code" + ] + }, + { + "login": "yvan-sraka", + "name": "Yvan Sraka", + "avatar_url": "https://avatars2.githubusercontent.com/u/705213?v=4", + "profile": "https://yvan-sraka.github.io", + "contributions": [ + "code" + ] + }, + { + "login": "dendi239", + "name": "Denys Smirnov", + "avatar_url": "https://avatars3.githubusercontent.com/u/16478650?v=4", + "profile": "https://github.com/dendi239", + "contributions": [ + "code" + ] + }, + { + "login": "eddyp", + "name": "eddyp", + "avatar_url": "https://avatars2.githubusercontent.com/u/123772?v=4", + "profile": "https://github.com/eddyp", + "contributions": [ + "code" + ] + }, + { + "login": "briankung", + "name": "Brian Kung", + "avatar_url": "https://avatars1.githubusercontent.com/u/2836167?v=4", + "profile": "http://about.me/BrianKung", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "miller-time", + "name": "Russell", + "avatar_url": "https://avatars3.githubusercontent.com/u/281039?v=4", + "profile": "https://rcousineau.gitlab.io", + "contributions": [ + "code" + ] + }, + { + "login": "danwilhelm", + "name": "Dan Wilhelm", + "avatar_url": "https://avatars3.githubusercontent.com/u/6137185?v=4", + "profile": "http://danwilhelm.com", + "contributions": [ + "doc" + ] + }, + { + "login": "Jesse-Cameron", + "name": "Jesse", + "avatar_url": "https://avatars3.githubusercontent.com/u/3723654?v=4", + "profile": "https://github.com/Jesse-Cameron", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "MrFroop", + "name": "Fredrik Jambrén", + "avatar_url": "https://avatars3.githubusercontent.com/u/196700?v=4", + "profile": "https://github.com/MrFroop", + "contributions": [ + "code" + ] + }, + { + "login": "petemcfarlane", + "name": "Pete McFarlane", + "avatar_url": "https://avatars3.githubusercontent.com/u/3472717?v=4", + "profile": "https://github.com/petemcfarlane", + "contributions": [ + "content" + ] + }, + { + "login": "nkanderson", + "name": "nkanderson", + "avatar_url": "https://avatars0.githubusercontent.com/u/4128825?v=4", + "profile": "https://github.com/nkanderson", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "ajaxm", + "name": "Ajax M", + "avatar_url": "https://avatars0.githubusercontent.com/u/13360138?v=4", + "profile": "https://github.com/ajaxm", + "contributions": [ + "doc" + ] + }, + { + "login": "Dylnuge", + "name": "Dylan Nugent", + "avatar_url": "https://avatars2.githubusercontent.com/u/118624?v=4", + "profile": "https://dylnuge.com", + "contributions": [ + "content" + ] + }, + { + "login": "vyaslav", + "name": "vyaslav", + "avatar_url": "https://avatars0.githubusercontent.com/u/1385427?v=4", + "profile": "https://github.com/vyaslav", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "gdoenlen", + "name": "George", + "avatar_url": "https://avatars1.githubusercontent.com/u/17297466?v=4", + "profile": "https://join.sfxd.org", + "contributions": [ + "code" + ] + }, + { + "login": "nyxtom", + "name": "Thomas Holloway", + "avatar_url": "https://avatars2.githubusercontent.com/u/222763?v=4", + "profile": "https://github.com/nyxtom", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "workingjubilee", + "name": "Jubilee", + "avatar_url": "https://avatars1.githubusercontent.com/u/46493976?v=4", + "profile": "https://github.com/workingjubilee", + "contributions": [ + "code" + ] + }, + { + "login": "WofWca", + "name": "WofWca", + "avatar_url": "https://avatars1.githubusercontent.com/u/39462442?v=4", + "profile": "https://github.com/WofWca", + "contributions": [ + "code" + ] + }, + { + "login": "jrvidal", + "name": "Roberto Vidal", + "avatar_url": "https://avatars0.githubusercontent.com/u/1636604?v=4", + "profile": "https://github.com/jrvidal", + "contributions": [ + "code", + "doc", + "ideas", + "maintenance" + ] + }, + { + "login": "jensim", + "name": "Jens", + "avatar_url": "https://avatars0.githubusercontent.com/u/3663856?v=4", + "profile": "https://github.com/jensim", + "contributions": [ + "doc" + ] + }, + { + "login": "rahatarmanahmed", + "name": "Rahat Ahmed", + "avatar_url": "https://avatars3.githubusercontent.com/u/3174006?v=4", + "profile": "http://rahatah.me/d", + "contributions": [ + "doc" + ] + }, + { + "login": "AbdouSeck", + "name": "Abdou Seck", + "avatar_url": "https://avatars2.githubusercontent.com/u/6490055?v=4", + "profile": "https://github.com/AbdouSeck", + "contributions": [ + "code", + "content", + "review" + ] + }, + { + "login": "codehearts", + "name": "Katie", + "avatar_url": "https://avatars0.githubusercontent.com/u/2885412?v=4", + "profile": "https://codehearts.com", + "contributions": [ + "code" + ] + }, + { + "login": "Socratides", + "name": "Socrates", + "avatar_url": "https://avatars3.githubusercontent.com/u/27732983?v=4", + "profile": "https://github.com/Socratides", + "contributions": [ + "doc" + ] + }, + { + "login": "gnodarse", + "name": "gnodarse", + "avatar_url": "https://avatars3.githubusercontent.com/u/46761795?v=4", + "profile": "https://github.com/gnodarse", + "contributions": [ + "content" + ] + }, + { + "login": "harrisonmetz", + "name": "Harrison Metzger", + "avatar_url": "https://avatars1.githubusercontent.com/u/7883408?v=4", + "profile": "https://github.com/harrisonmetz", + "contributions": [ + "code" + ] + }, + { + "login": "TorbenJ", + "name": "Torben Jonas", + "avatar_url": "https://avatars2.githubusercontent.com/u/9077102?v=4", + "profile": "https://github.com/TorbenJ", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "pbx", + "name": "Paul Bissex", + "avatar_url": "https://avatars0.githubusercontent.com/u/641?v=4", + "profile": "http://paulbissex.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "sjmann", + "name": "Steven Mann", + "avatar_url": "https://avatars0.githubusercontent.com/u/6589896?v=4", + "profile": "https://github.com/sjmann", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "Tarnadas", + "name": "Mario Reder", + "avatar_url": "https://avatars2.githubusercontent.com/u/5855071?v=4", + "profile": "https://smmdb.net/", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "sl4m", + "name": "skim", + "avatar_url": "https://avatars0.githubusercontent.com/u/47347?v=4", + "profile": "https://keybase.io/skim", + "contributions": [ + "code" + ] + }, + { + "login": "sanjaykdragon", + "name": "Sanjay K", + "avatar_url": "https://avatars1.githubusercontent.com/u/10261698?v=4", + "profile": "https://github.com/sanjaykdragon", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "crodjer", + "name": "Rohan Jain", + "avatar_url": "https://avatars1.githubusercontent.com/u/343499?v=4", + "profile": "http://www.rohanjain.in", + "contributions": [ + "code" + ] + }, + { + "login": "saidaspen", + "name": "Said Aspen", + "avatar_url": "https://avatars1.githubusercontent.com/u/7727687?v=4", + "profile": "https://www.saidaspen.se", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "uce", + "name": "Ufuk Celebi", + "avatar_url": "https://avatars3.githubusercontent.com/u/1756620?v=4", + "profile": "https://github.com/uce", + "contributions": [ + "code" + ] + }, + { + "login": "lebedevsergey", + "name": "lebedevsergey", + "avatar_url": "https://avatars2.githubusercontent.com/u/7325764?v=4", + "profile": "https://github.com/lebedevsergey", + "contributions": [ + "doc" + ] + }, + { + "login": "avrong", + "name": "Aleksei Trifonov", + "avatar_url": "https://avatars2.githubusercontent.com/u/6342851?v=4", + "profile": "https://github.com/avrong", + "contributions": [ + "content" + ] + }, + { + "login": "Darrenmeehan", + "name": "Darren Meehan", + "avatar_url": "https://avatars2.githubusercontent.com/u/411136?v=4", + "profile": "https://drn.ie", + "contributions": [ + "content" + ] + }, + { + "login": "jihchi", + "name": "Jihchi Lee", + "avatar_url": "https://avatars1.githubusercontent.com/u/87983?v=4", + "profile": "https://github.com/jihchi", + "contributions": [ + "content" + ] + }, + { + "login": "bertonha", + "name": "Christofer Bertonha", + "avatar_url": "https://avatars3.githubusercontent.com/u/1225902?v=4", + "profile": "https://github.com/bertonha", + "contributions": [ + "content" + ] + }, + { + "login": "apatniv", + "name": "Vivek Bharath Akupatni", + "avatar_url": "https://avatars2.githubusercontent.com/u/22565917?v=4", + "profile": "https://github.com/apatniv", + "contributions": [ + "code", + "test" + ] + }, + { + "login": "DiD92", + "name": "Dídac Sementé Fernández", + "avatar_url": "https://avatars3.githubusercontent.com/u/6002416?v=4", + "profile": "https://github.com/DiD92", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "wrobstory", + "name": "Rob Story", + "avatar_url": "https://avatars3.githubusercontent.com/u/2601457?v=4", + "profile": "https://github.com/wrobstory", + "contributions": [ + "code" + ] + }, + { + "login": "siobhanjacobson", + "name": "Siobhan Jacobson", + "avatar_url": "https://avatars2.githubusercontent.com/u/28983835?v=4", + "profile": "https://github.com/siobhanjacobson", + "contributions": [ + "code" + ] + }, + { + "login": "EvanCarroll", + "name": "Evan Carroll", + "avatar_url": "https://avatars2.githubusercontent.com/u/19922?v=4", + "profile": "https://www.linkedin.com/in/evancarroll/", + "contributions": [ + "content" + ] + }, + { + "login": "jmahmood", + "name": "Jawaad Mahmood", + "avatar_url": "https://avatars3.githubusercontent.com/u/95606?v=4", + "profile": "http://www.jawaadmahmood.com", + "contributions": [ + "content" + ] + }, + { + "login": "GaurangTandon", + "name": "Gaurang Tandon", + "avatar_url": "https://avatars1.githubusercontent.com/u/6308683?v=4", + "profile": "https://github.com/GaurangTandon", + "contributions": [ + "content" + ] + }, + { + "login": "dev-cyprium", + "name": "Stefan Kupresak", + "avatar_url": "https://avatars1.githubusercontent.com/u/6002628?v=4", + "profile": "https://github.com/dev-cyprium", + "contributions": [ + "content" + ] + }, + { + "login": "greg-el", + "name": "Greg Leonard", + "avatar_url": "https://avatars3.githubusercontent.com/u/45019882?v=4", + "profile": "https://github.com/greg-el", + "contributions": [ + "content" + ] + }, + { + "login": "ryanpcmcquen", + "name": "Ryan McQuen", + "avatar_url": "https://avatars3.githubusercontent.com/u/772937?v=4", + "profile": "https://ryanpcmcquen.org", + "contributions": [ + "code" + ] + }, + { + "login": "AnnikaCodes", + "name": "Annika", + "avatar_url": "https://avatars3.githubusercontent.com/u/56906084?v=4", + "profile": "https://github.com/AnnikaCodes", + "contributions": [ + "review" + ] + }, + { + "login": "darnuria", + "name": "Axel Viala", + "avatar_url": "https://avatars1.githubusercontent.com/u/2827553?v=4", + "profile": "https://darnuria.eu", + "contributions": [ + "code" + ] + }, + { + "login": "sazid", + "name": "Mohammed Sazid Al Rashid", + "avatar_url": "https://avatars1.githubusercontent.com/u/2370167?v=4", + "profile": "https://sazid.github.io", + "contributions": [ + "content", + "code" + ] + }, + { + "login": "seeplusplus", + "name": "Caleb Webber", + "avatar_url": "https://avatars1.githubusercontent.com/u/17479099?v=4", + "profile": "https://codingthemsoftly.com", + "contributions": [ + "maintenance" + ] + }, + { + "login": "pcn", + "name": "Peter N", + "avatar_url": "https://avatars2.githubusercontent.com/u/1056756?v=4", + "profile": "https://github.com/pcn", + "contributions": [ + "maintenance" + ] + }, + { + "login": "seancad", + "name": "seancad", + "avatar_url": "https://avatars1.githubusercontent.com/u/47405611?v=4", + "profile": "https://github.com/seancad", + "contributions": [ + "maintenance" + ] + }, + { + "login": "wsh", + "name": "Will Hayworth", + "avatar_url": "https://avatars3.githubusercontent.com/u/181174?v=4", + "profile": "http://willhayworth.com", + "contributions": [ + "content" + ] + }, + { + "login": "chrizel", + "name": "Christian Zeller", + "avatar_url": "https://avatars3.githubusercontent.com/u/20802?v=4", + "profile": "https://github.com/chrizel", + "contributions": [ + "content" + ] + }, + { + "login": "jfchevrette", + "name": "Jean-Francois Chevrette", + "avatar_url": "https://avatars.githubusercontent.com/u/3001?v=4", + "profile": "https://github.com/jfchevrette", + "contributions": [ + "content", + "code" + ] + }, + { + "login": "jbaber", + "name": "John Baber-Lucero", + "avatar_url": "https://avatars.githubusercontent.com/u/1908117?v=4", + "profile": "https://github.com/jbaber", + "contributions": [ + "content" + ] + }, + { + "login": "tal-zvon", + "name": "Tal", + "avatar_url": "https://avatars.githubusercontent.com/u/3195851?v=4", + "profile": "https://github.com/tal-zvon", + "contributions": [ + "content" + ] + }, + { + "login": "apogeeoak", + "name": "apogeeoak", + "avatar_url": "https://avatars.githubusercontent.com/u/59737221?v=4", + "profile": "https://github.com/apogeeoak", + "contributions": [ + "content", + "code" + ] + }, + { + "login": "Crell", + "name": "Larry Garfield", + "avatar_url": "https://avatars.githubusercontent.com/u/254863?v=4", + "profile": "http://www.garfieldtech.com/", + "contributions": [ + "content" + ] + }, + { + "login": "circumspect", + "name": "circumspect", + "avatar_url": "https://avatars.githubusercontent.com/u/40770208?v=4", + "profile": "https://github.com/circumspect", + "contributions": [ + "content" + ] + }, + { + "login": "cjwyett", + "name": "Cyrus Wyett", + "avatar_url": "https://avatars.githubusercontent.com/u/34195737?v=4", + "profile": "https://github.com/cjwyett", + "contributions": [ + "content" + ] + }, + { + "login": "cadolphs", + "name": "cadolphs", + "avatar_url": "https://avatars.githubusercontent.com/u/13894820?v=4", + "profile": "https://github.com/cadolphs", + "contributions": [ + "code" + ] + }, + { + "login": "hpwxf", + "name": "Pascal H.", + "avatar_url": "https://avatars.githubusercontent.com/u/26146722?v=4", + "profile": "https://www.haveneer.com", + "contributions": [ + "content" + ] + }, + { + "login": "chapeupreto", + "name": "Rod Elias", + "avatar_url": "https://avatars.githubusercontent.com/u/834048?v=4", + "profile": "https://twitter.com/chapeupreto", + "contributions": [ + "content" + ] + }, + { + "login": "blerchy", + "name": "Matt Lebl", + "avatar_url": "https://avatars.githubusercontent.com/u/2555355?v=4", + "profile": "https://github.com/blerchy", + "contributions": [ + "code" + ] + }, + { + "login": "flakolefluk", + "name": "Ignacio Le Fluk", + "avatar_url": "https://avatars.githubusercontent.com/u/11986564?v=4", + "profile": "http://flakolefluk.dev", + "contributions": [ + "content" + ] + }, + { + "login": "tlyu", + "name": "Taylor Yu", + "avatar_url": "https://avatars.githubusercontent.com/u/431873?v=4", + "profile": "https://github.com/tlyu", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "Zerotask", + "name": "Patrick Hintermayer", + "avatar_url": "https://avatars.githubusercontent.com/u/20150243?v=4", + "profile": "https://zerotask.github.io", + "contributions": [ + "code" + ] + }, + { + "login": "arthas168", + "name": "Pete Pavlovski", + "avatar_url": "https://avatars.githubusercontent.com/u/32264020?v=4", + "profile": "https://petkopavlovski.com/", + "contributions": [ + "content" + ] + }, + { + "login": "k12ish", + "name": "k12ish", + "avatar_url": "https://avatars.githubusercontent.com/u/45272873?v=4", + "profile": "https://github.com/k12ish", + "contributions": [ + "content" + ] + }, + { + "login": "hongshaoyang", + "name": "Shao Yang Hong", + "avatar_url": "https://avatars.githubusercontent.com/u/19281800?v=4", + "profile": "https://github.com/hongshaoyang", + "contributions": [ + "content" + ] + }, + { + "login": "bmacer", + "name": "Brandon Macer", + "avatar_url": "https://avatars.githubusercontent.com/u/13931806?v=4", + "profile": "https://github.com/bmacer", + "contributions": [ + "content" + ] + }, + { + "login": "stoiandan", + "name": "Stoian Dan", + "avatar_url": "https://avatars.githubusercontent.com/u/10388612?v=4", + "profile": "https://github.com/stoiandan", + "contributions": [ + "content" + ] + }, + { + "login": "PiDelport", + "name": "Pi Delport", + "avatar_url": "https://avatars.githubusercontent.com/u/630271?v=4", + "profile": "https://about.me/pjdelport", + "contributions": [ + "content" + ] + }, + { + "login": "sateeshkumarb", + "name": "Sateesh ", + "avatar_url": "https://avatars.githubusercontent.com/u/429263?v=4", + "profile": "https://github.com/sateeshkumarb", + "contributions": [ + "code", + "content" + ] + }, + { + "login": "kayuapi", + "name": "ZC", + "avatar_url": "https://avatars.githubusercontent.com/u/10304328?v=4", + "profile": "https://github.com/kayuapi", + "contributions": [ + "content" + ] + }, + { + "login": "hyperparabolic", + "name": "hyperparabolic", + "avatar_url": "https://avatars.githubusercontent.com/u/12348474?v=4", + "profile": "https://github.com/hyperparabolic", + "contributions": [ + "code" + ] + }, + { + "login": "kolbma", + "name": "arlecchino", + "avatar_url": "https://avatars.githubusercontent.com/u/5228369?v=4", + "profile": "https://www.net4visions.at", + "contributions": [ + "doc" + ] + }, + { + "login": "jazzplato", + "name": "Richthofen", + "avatar_url": "https://avatars.githubusercontent.com/u/7576730?v=4", + "profile": "https://richthofen.io/", + "contributions": [ + "code" + ] + }, + { + "login": "cseltol", + "name": "Ivan Nerazumov", + "avatar_url": "https://avatars.githubusercontent.com/u/64264529?v=4", + "profile": "https://github.com/cseltol", + "contributions": [ + "doc" + ] + }, + { + "login": "lauralindzey", + "name": "lauralindzey", + "avatar_url": "https://avatars.githubusercontent.com/u/65185744?v=4", + "profile": "https://github.com/lauralindzey", + "contributions": [ + "doc" + ] + }, + { + "login": "sinharaksh1t", + "name": "Rakshit Sinha", + "avatar_url": "https://avatars.githubusercontent.com/u/28585848?v=4", + "profile": "https://github.com/sinharaksh1t", + "contributions": [ + "content" + ] + }, + { + "login": "dbednar230", + "name": "Damian", + "avatar_url": "https://avatars.githubusercontent.com/u/54457902?v=4", + "profile": "https://github.com/dbednar230", + "contributions": [ + "content" + ] + }, + { + "login": "benarmstead", + "name": "Ben Armstead", + "avatar_url": "https://avatars.githubusercontent.com/u/70973680?v=4", + "profile": "https://benarmstead.co.uk", + "contributions": [ + "code" + ] + }, + { + "login": "anuk909", + "name": "anuk909", + "avatar_url": "https://avatars.githubusercontent.com/u/34924662?v=4", + "profile": "https://github.com/anuk909", + "contributions": [ + "content", + "code" + ] + }, + { + "login": "granddaifuku", + "name": "granddaifuku", + "avatar_url": "https://avatars.githubusercontent.com/u/49578068?v=4", + "profile": "https://granddaifuku.com/", + "contributions": [ + "content" + ] + }, + { + "login": "Weilet", + "name": "Weilet", + "avatar_url": "https://avatars.githubusercontent.com/u/32561597?v=4", + "profile": "https://weilet.me", + "contributions": [ + "content" + ] + }, + { + "login": "Millione", + "name": "LIU JIE", + "avatar_url": "https://avatars.githubusercontent.com/u/38575932?v=4", + "profile": "https://github.com/Millione", + "contributions": [ + "content" + ] + }, + { + "login": "abusch", + "name": "Antoine Büsch", + "avatar_url": "https://avatars.githubusercontent.com/u/506344?v=4", + "profile": "https://github.com/abusch", + "contributions": [ + "code" + ] + }, + { + "login": "frogtd", + "name": "frogtd", + "avatar_url": "https://avatars.githubusercontent.com/u/31412003?v=4", + "profile": "https://frogtd.com/", + "contributions": [ + "content" + ] + }, + { + "login": "EmisonLu", + "name": "Zhenghao Lu", + "avatar_url": "https://avatars.githubusercontent.com/u/54395432?v=4", + "profile": "https://github.com/EmisonLu", + "contributions": [ + "content" + ] + }, + { + "login": "fredr", + "name": "Fredrik Enestad", + "avatar_url": "https://avatars.githubusercontent.com/u/762956?v=4", + "profile": "https://soundtrackyourbrand.com", + "contributions": [ + "content" + ] + }, + { + "login": "xuesongbj", + "name": "xuesong", + "avatar_url": "https://avatars.githubusercontent.com/u/18476085?v=4", + "profile": "http://xuesong.pydevops.com", + "contributions": [ + "content" + ] + }, + { + "login": "MpdWalsh", + "name": "Michael Walsh", + "avatar_url": "https://avatars.githubusercontent.com/u/48160144?v=4", + "profile": "https://github.com/MpdWalsh", + "contributions": [ + "code" + ] + }, + { + "login": "alirezaghey", + "name": "alirezaghey", + "avatar_url": "https://avatars.githubusercontent.com/u/26653424?v=4", + "profile": "https://github.com/alirezaghey", + "contributions": [ + "content" + ] + }, + { + "login": "frvannes16", + "name": "Franklin van Nes", + "avatar_url": "https://avatars.githubusercontent.com/u/3188475?v=4", + "profile": "https://github.com/frvannes16", + "contributions": [ + "code" + ] + }, + { + "login": "nekonako", + "name": "nekonako", + "avatar_url": "https://avatars.githubusercontent.com/u/46141275?v=4", + "profile": "https://nekonako.github.io", + "contributions": [ + "code" + ] + }, + { + "login": "tan-zx", + "name": "ZX", + "avatar_url": "https://avatars.githubusercontent.com/u/67887489?v=4", + "profile": "https://github.com/tan-zx", + "contributions": [ + "content" + ] + }, + { + "login": "sundevilyang", + "name": "Yang Wen", + "avatar_url": "https://avatars.githubusercontent.com/u/1499214?v=4", + "profile": "https://github.com/sundevilyang", + "contributions": [ + "content" + ] + }, + { + "login": "highb", + "name": "Brandon High", + "avatar_url": "https://avatars.githubusercontent.com/u/759848?v=4", + "profile": "https://brandon-high.com", + "contributions": [ + "doc" + ] + }, + { + "login": "x-hgg-x", + "name": "x-hgg-x", + "avatar_url": "https://avatars.githubusercontent.com/u/39058530?v=4", + "profile": "https://github.com/x-hgg-x", + "contributions": [ + "code" + ] + }, + { + "login": "KisaragiEffective", + "name": "Kisaragi", + "avatar_url": "https://avatars.githubusercontent.com/u/48310258?v=4", + "profile": "http://kisaragieffective.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "Kallu-A", + "name": "Lucas Aries", + "avatar_url": "https://avatars.githubusercontent.com/u/73198738?v=4", + "profile": "https://github.com/Kallu-A", + "contributions": [ + "content" + ] + }, + { + "login": "ragreenburg", + "name": "ragreenburg", + "avatar_url": "https://avatars.githubusercontent.com/u/24358100?v=4", + "profile": "https://github.com/ragreenburg", + "contributions": [ + "content" + ] + }, + { + "login": "stevenfukase", + "name": "stevenfukase", + "avatar_url": "https://avatars.githubusercontent.com/u/66785624?v=4", + "profile": "https://github.com/stevenfukase", + "contributions": [ + "content" + ] + }, + { + "login": "J-S-Kim", + "name": "J-S-Kim", + "avatar_url": "https://avatars.githubusercontent.com/u/17569303?v=4", + "profile": "https://github.com/J-S-Kim", + "contributions": [ + "content" + ] + }, + { + "login": "Fointard", + "name": "Fointard", + "avatar_url": "https://avatars.githubusercontent.com/u/9333398?v=4", + "profile": "https://github.com/Fointard", + "contributions": [ + "content" + ] + }, + { + "login": "rytheo", + "name": "Ryan Lowe", + "avatar_url": "https://avatars.githubusercontent.com/u/22184325?v=4", + "profile": "https://github.com/rytheo", + "contributions": [ + "code" + ] + }, + { + "login": "cuishuang", + "name": "cui fliter", + "avatar_url": "https://avatars.githubusercontent.com/u/15921519?v=4", + "profile": "http://www.dashen.tech", + "contributions": [ + "content" + ] + }, + { + "login": "luskwater", + "name": "Ron Lusk", + "avatar_url": "https://avatars.githubusercontent.com/u/42529?v=4", + "profile": "https://github.com/luskwater", + "contributions": [ + "content" + ] + }, + { + "login": "liby", + "name": "Bryan Lee", + "avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4", + "profile": "http://liby.github.io/liby/", + "contributions": [ + "content" + ] + }, + { + "login": "nandajavarma", + "name": "Nandaja Varma", + "avatar_url": "https://avatars.githubusercontent.com/u/2624550?v=4", + "profile": "http://nandaja.space", + "contributions": [ + "doc" + ] + }, + { + "login": "merelymyself", + "name": "pwygab", + "avatar_url": "https://avatars.githubusercontent.com/u/88221256?v=4", + "profile": "https://github.com/merelymyself", + "contributions": [ + "code" + ] + }, + { + "login": "lucasgrvarela", + "name": "Lucas Grigolon Varela", + "avatar_url": "https://avatars.githubusercontent.com/u/37870368?v=4", + "profile": "http://linkedin.com/in/lucasgrvarela", + "contributions": [ + "content" + ] + }, + { + "login": "bufo24", + "name": "Bufo", + "avatar_url": "https://avatars.githubusercontent.com/u/32884105?v=4", + "profile": "https://github.com/bufo24", + "contributions": [ + "content" + ] + }, + { + "login": "jackos", + "name": "Jack Clayton", + "avatar_url": "https://avatars.githubusercontent.com/u/77730378?v=4", + "profile": "http://rustnote.com", + "contributions": [ + "code" + ] + }, + { + "login": "klkl0808", + "name": "Konstantin", + "avatar_url": "https://avatars.githubusercontent.com/u/24694249?v=4", + "profile": "https://github.com/klkl0808", + "contributions": [ + "content" + ] + }, + { + "login": "0pling", + "name": "0pling", + "avatar_url": "https://avatars.githubusercontent.com/u/104090344?v=4", + "profile": "https://github.com/0pling", + "contributions": [ + "content" + ] + }, + { + "login": "KatanaFluorescent", + "name": "KatanaFluorescent", + "avatar_url": "https://avatars.githubusercontent.com/u/60199077?v=4", + "profile": "https://github.com/KatanaFluorescent", + "contributions": [ + "code" + ] + }, + { + "login": "Drew-Morris", + "name": "Drew Morris", + "avatar_url": "https://avatars.githubusercontent.com/u/95818166?v=4", + "profile": "https://github.com/Drew-Morris", + "contributions": [ + "code" + ] + }, + { + "login": "camperdue42", + "name": "camperdue42", + "avatar_url": "https://avatars.githubusercontent.com/u/43047763?v=4", + "profile": "https://github.com/camperdue42", + "contributions": [ + "content" + ] + }, + { + "login": "YsuOS", + "name": "YsuOS", + "avatar_url": "https://avatars.githubusercontent.com/u/30138661?v=4", + "profile": "https://github.com/YsuOS", + "contributions": [ + "content" + ] + }, + { + "login": "icecream17", + "name": "Steven Nguyen", + "avatar_url": "https://avatars.githubusercontent.com/u/58114641?v=4", + "profile": "https://lichess.org/@/StevenEmily", + "contributions": [ + "content" + ] + }, + { + "login": "nacairns1", + "name": "nacairns1", + "avatar_url": "https://avatars.githubusercontent.com/u/94420090?v=4", + "profile": "https://noahcairns.dev", + "contributions": [ + "content" + ] + }, + { + "login": "pgjbz", + "name": "Paulo Gabriel Justino Bezerra", + "avatar_url": "https://avatars.githubusercontent.com/u/22059237?v=4", + "profile": "https://github.com/pgjbz", + "contributions": [ + "content" + ] + }, + { + "login": "jaystile", + "name": "Jason", + "avatar_url": "https://avatars.githubusercontent.com/u/46078028?v=4", + "profile": "https://github.com/jaystile", + "contributions": [ + "content" + ] + }, + { + "login": "exdx", + "name": "exdx", + "avatar_url": "https://avatars.githubusercontent.com/u/31546601?v=4", + "profile": "https://exdx.github.io", + "contributions": [ + "content" + ] + }, + { + "login": "Jzow", + "name": "James Zow", + "avatar_url": "https://avatars.githubusercontent.com/u/68860495?v=4", + "profile": "https://github.com/Jzow", + "contributions": [ + "content" + ] + }, + { + "login": "jayber", + "name": "James Bromley", + "avatar_url": "https://avatars.githubusercontent.com/u/2474334?v=4", + "profile": "https://jamesabromley.wordpress.com/", + "contributions": [ + "content" + ] + }, + { + "login": "swhiteCQC", + "name": "swhiteCQC", + "avatar_url": "https://avatars.githubusercontent.com/u/77438466?v=4", + "profile": "https://github.com/swhiteCQC", + "contributions": [ + "content" + ] + }, + { + "login": "neilpate", + "name": "Neil Pate", + "avatar_url": "https://avatars.githubusercontent.com/u/7802334?v=4", + "profile": "https://github.com/neilpate", + "contributions": [ + "content" + ] + }, + { + "login": "wojexe", + "name": "wojexe", + "avatar_url": "https://avatars.githubusercontent.com/u/21208490?v=4", + "profile": "https://wojexe.com", + "contributions": [ + "content" + ] + }, + { + "login": "Tostapunk", + "name": "Mattia Schiavon", + "avatar_url": "https://avatars.githubusercontent.com/u/25140297?v=4", + "profile": "https://github.com/Tostapunk", + "contributions": [ + "content" + ] + }, + { + "login": "PrettyWood", + "name": "Eric Jolibois", + "avatar_url": "https://avatars.githubusercontent.com/u/18406791?v=4", + "profile": "http://toucantoco.com", + "contributions": [ + "content" + ] + }, + { + "login": "EdwinChang24", + "name": "Edwin Chang", + "avatar_url": "https://avatars.githubusercontent.com/u/88263098?v=4", + "profile": "http://edwinchang.vercel.app", + "contributions": [ + "content" + ] + }, + { + "login": "saikatdas0790", + "name": "Saikat Das", + "avatar_url": "https://avatars.githubusercontent.com/u/7412443?v=4", + "profile": "https://saikat.dev/", + "contributions": [ + "content" + ] + }, + { + "login": "thatlittleboy", + "name": "Jeremy Goh", + "avatar_url": "https://avatars.githubusercontent.com/u/30731072?v=4", + "profile": "https://github.com/thatlittleboy", + "contributions": [ + "content" + ] + }, + { + "login": "Lioness100", + "name": "Lioness100", + "avatar_url": "https://avatars.githubusercontent.com/u/65814829?v=4", + "profile": "https://github.com/Lioness100", + "contributions": [ + "content" + ] + }, + { + "login": "tvkn", + "name": "Tristan Nicholls", + "avatar_url": "https://avatars.githubusercontent.com/u/79277926?v=4", + "profile": "https://github.com/tvkn", + "contributions": [ + "content" + ] + }, + { + "login": "clairew", + "name": "Claire", + "avatar_url": "https://avatars.githubusercontent.com/u/9344258?v=4", + "profile": "http://clairewang.net", + "contributions": [ + "content" + ] + }, + { + "login": "Mouwrice", + "name": "Maurice Van Wassenhove", + "avatar_url": "https://avatars.githubusercontent.com/u/56763273?v=4", + "profile": "https://github.com/Mouwrice", + "contributions": [ + "content" + ] + }, + { + "login": "johnmendel", + "name": "John Mendelewski", + "avatar_url": "https://avatars.githubusercontent.com/u/77524?v=4", + "profile": "http://jmthree.com", + "contributions": [ + "code" + ] + }, + { + "login": "brianfakhoury", + "name": "Brian Fakhoury", + "avatar_url": "https://avatars.githubusercontent.com/u/20828724?v=4", + "profile": "http://fakhoury.xyz", + "contributions": [ + "content" + ] + }, + { + "login": "markusboehme", + "name": "Markus Boehme", + "avatar_url": "https://avatars.githubusercontent.com/u/5074759?v=4", + "profile": "https://github.com/markusboehme", + "contributions": [ + "code" + ] + }, + { + "login": "nico-vromans", + "name": "Nico Vromans", + "avatar_url": "https://avatars.githubusercontent.com/u/48183857?v=4", + "profile": "https://github.com/nico-vromans", + "contributions": [ + "content" + ] + }, + { + "login": "vostok92", + "name": "vostok92", + "avatar_url": "https://avatars.githubusercontent.com/u/540339?v=4", + "profile": "https://github.com/vostok92", + "contributions": [ + "content" + ] + }, + { + "login": "magnusrodseth", + "name": "Magnus Rødseth", + "avatar_url": "https://avatars.githubusercontent.com/u/59113973?v=4", + "profile": "http://magnusrodseth.vercel.app", + "contributions": [ + "content" + ] + }, + { + "login": "rubiesonthesky", + "name": "rubiesonthesky", + "avatar_url": "https://avatars.githubusercontent.com/u/2591240?v=4", + "profile": "https://github.com/rubiesonthesky", + "contributions": [ + "content" + ] + }, + { + "login": "GabrielBianconi", + "name": "Gabriel Bianconi", + "avatar_url": "https://avatars.githubusercontent.com/u/1275491?v=4", + "profile": "http://www.gabrielbianconi.com/", + "contributions": [ + "content" + ] + }, + { + "login": "Kodylow", + "name": "Kody Low", + "avatar_url": "https://avatars.githubusercontent.com/u/74332828?v=4", + "profile": "https://github.com/Kodylow", + "contributions": [ + "content" + ] + }, + { + "login": "rzrymiak", + "name": "rzrymiak", + "avatar_url": "https://avatars.githubusercontent.com/u/106121613?v=4", + "profile": "https://github.com/rzrymiak", + "contributions": [ + "content" + ] + }, + { + "login": "miguelraz", + "name": "Miguel Raz Guzmán Macedo", + "avatar_url": "https://avatars.githubusercontent.com/u/13056181?v=4", + "profile": "https://github.com/miguelraz", + "contributions": [ + "content" + ] + }, + { + "login": "memark", + "name": "Magnus Markling", + "avatar_url": "https://avatars.githubusercontent.com/u/318504?v=4", + "profile": "https://github.com/memark", + "contributions": [ + "content" + ] + }, + { + "login": "gasparitiago", + "name": "Tiago De Gaspari", + "avatar_url": "https://avatars.githubusercontent.com/u/3237254?v=4", + "profile": "https://github.com/gasparitiago", + "contributions": [ + "content" + ] + }, + { + "login": "skaunov", + "name": "skaunov", + "avatar_url": "https://avatars.githubusercontent.com/u/65976143?v=4", + "profile": "https://github.com/skaunov", + "contributions": [ + "content" + ] + }, + { + "login": "cj81499", + "name": "Cal Jacobson", + "avatar_url": "https://avatars.githubusercontent.com/u/9152032?v=4", + "profile": "http://caljacobson.dev", + "contributions": [ + "content" + ] + }, + { + "login": "duchonic", + "name": "Duchoud Nicolas", + "avatar_url": "https://avatars.githubusercontent.com/u/34117620?v=4", + "profile": "https://github.com/duchonic", + "contributions": [ + "content" + ] + }, + { + "login": "gfaugere", + "name": "Gaëtan Faugère", + "avatar_url": "https://avatars.githubusercontent.com/u/11901979?v=4", + "profile": "https://github.com/gfaugere", + "contributions": [ + "tool" + ] + }, + { + "login": "bhbuehler", + "name": "bhbuehler", + "avatar_url": "https://avatars.githubusercontent.com/u/25541343?v=4", + "profile": "https://github.com/bhbuehler", + "contributions": [ + "content" + ] + }, + { + "login": "nyurik", + "name": "Yuri Astrakhan", + "avatar_url": "https://avatars.githubusercontent.com/u/1641515?v=4", + "profile": "https://github.com/nyurik", + "contributions": [ + "code" + ] + }, + { + "login": "azzamsa", + "name": "azzamsa", + "avatar_url": "https://avatars.githubusercontent.com/u/17734314?v=4", + "profile": "http://azzamsa.com", + "contributions": [ + "code" + ] + }, + { + "login": "mvanschellebeeck", + "name": "mvanschellebeeck", + "avatar_url": "https://avatars.githubusercontent.com/u/17671052?v=4", + "profile": "https://github.com/mvanschellebeeck", + "contributions": [ + "content" + ] + }, + { + "login": "aaarkid", + "name": "Arkid", + "avatar_url": "https://avatars.githubusercontent.com/u/39987510?v=4", + "profile": "https://github.com/aaarkid", + "contributions": [ + "content" + ] + }, + { + "login": "tfpk", + "name": "Tom Kunc", + "avatar_url": "https://avatars.githubusercontent.com/u/10906982?v=4", + "profile": "http://tfpk.dev", + "contributions": [ + "content" + ] + }, + { + "login": "mfurak", + "name": "Marek Furák", + "avatar_url": "https://avatars.githubusercontent.com/u/38523093?v=4", + "profile": "https://github.com/mfurak", + "contributions": [ + "content" + ] + }, + { + "login": "winterqt", + "name": "Winter", + "avatar_url": "https://avatars.githubusercontent.com/u/78392041?v=4", + "profile": "https://winter.cafe", + "contributions": [ + "code" + ] + }, + { + "login": "MoritzBoehme", + "name": "Moritz Böhme", + "avatar_url": "https://avatars.githubusercontent.com/u/42215704?v=4", + "profile": "https://moritzboeh.me", + "contributions": [ + "code" + ] + }, + { + "login": "craymel", + "name": "craymel", + "avatar_url": "https://avatars.githubusercontent.com/u/71062756?v=4", + "profile": "https://github.com/craymel", + "contributions": [ + "content" + ] + }, + { + "login": "tkburis", + "name": "TK Buristrakul", + "avatar_url": "https://avatars.githubusercontent.com/u/20501289?v=4", + "profile": "https://github.com/tkburis", + "contributions": [ + "content" + ] + }, + { + "login": "HerschelW", + "name": "Kent Worthington", + "avatar_url": "https://avatars.githubusercontent.com/u/17935816?v=4", + "profile": "https://github.com/HerschelW", + "contributions": [ + "content" + ] + }, + { + "login": "seporterfield", + "name": "seporterfield", + "avatar_url": "https://avatars.githubusercontent.com/u/107010978?v=4", + "profile": "https://github.com/seporterfield", + "contributions": [ + "content" + ] + }, + { + "login": "dbarrosop", + "name": "David Barroso", + "avatar_url": "https://avatars.githubusercontent.com/u/6246622?v=4", + "profile": "https://www.linkedin.com/in/dbarrosop", + "contributions": [ + "infra" + ] + }, + { + "login": "tklauser", + "name": "Tobias Klauser", + "avatar_url": "https://avatars.githubusercontent.com/u/539708?v=4", + "profile": "https://distanz.ch", + "contributions": [ + "code" + ] + }, + { + "login": "0xMySt1c", + "name": "0xMySt1c", + "avatar_url": "https://avatars.githubusercontent.com/u/101825630?v=4", + "profile": "https://github.com/0xMySt1c", + "contributions": [ + "tool" + ] + }, + { + "login": "AxolotlTears", + "name": "Ten", + "avatar_url": "https://avatars.githubusercontent.com/u/87157047?v=4", + "profile": "https://github.com/AxolotlTears", + "contributions": [ + "code" + ] + }, + { + "login": "h4x5p4c3", + "name": "jones martin", + "avatar_url": "https://avatars.githubusercontent.com/u/66133688?v=4", + "profile": "http://h4x5p4c3.xyz", + "contributions": [ + "content" + ] + }, + { + "login": "cloppingemu", + "name": "cloppingemu", + "avatar_url": "https://avatars.githubusercontent.com/u/12227963?v=4", + "profile": "https://github.com/cloppingemu", + "contributions": [ + "code" + ] + }, + { + "login": "kevwan", + "name": "Kevin Wan", + "avatar_url": "https://avatars.githubusercontent.com/u/1918356?v=4", + "profile": "http://github.com/zeromicro/go-zero", + "contributions": [ + "content" + ] + }, + { + "login": "wjwrh", + "name": "Ruby", + "avatar_url": "https://avatars.githubusercontent.com/u/43495006?v=4", + "profile": "http://kurowasaruby.cn", + "contributions": [ + "code" + ] + }, + { + "login": "alexandergill", + "name": "Alexander Gill", + "avatar_url": "https://avatars.githubusercontent.com/u/7033716?v=4", + "profile": "https://github.com/alexandergill", + "contributions": [ + "code" + ] + }, + { + "login": "kawaiiPlat", + "name": "Jarrod Sanders", + "avatar_url": "https://avatars.githubusercontent.com/u/50600614?v=4", + "profile": "https://www.linkedin.com/in/jarrod-sanders/", + "contributions": [ + "content" + ] + }, + { + "login": "platformer", + "name": "Andrew Sen", + "avatar_url": "https://avatars.githubusercontent.com/u/40146328?v=4", + "profile": "https://github.com/platformer", + "contributions": [ + "content" + ] + }, + { + "login": "grzegorz-zur", + "name": "Grzegorz Żur", + "avatar_url": "https://avatars.githubusercontent.com/u/5297583?v=4", + "profile": "https://grzegorz-zur.com/", + "contributions": [ + "content" + ] + }, + { + "login": "black-puppydog", + "name": "Daan Wynen", + "avatar_url": "https://avatars.githubusercontent.com/u/189241?v=4", + "profile": "https://github.com/black-puppydog", + "contributions": [ + "content" + ] + }, + { + "login": "Anush008", + "name": "Anush", + "avatar_url": "https://avatars.githubusercontent.com/u/46051506?v=4", + "profile": "https://github.com/Anush008", + "contributions": [ + "doc" + ] + }, + { + "login": "shgew", + "name": "Gleb Shevchenko", + "avatar_url": "https://avatars.githubusercontent.com/u/5584672?v=4", + "profile": "https://github.com/shgew", + "contributions": [ + "content" + ] + }, + { + "login": "mdmundo", + "name": "Edmundo Paulino", + "avatar_url": "https://avatars.githubusercontent.com/u/60408300?v=4", + "profile": "https://github.com/mdmundo", + "contributions": [ + "infra" + ] + }, + { + "login": "eroullit", + "name": "Emmanuel Roullit", + "avatar_url": "https://avatars.githubusercontent.com/u/301795?v=4", + "profile": "https://github.com/eroullit", + "contributions": [ + "infra" + ] + }, + { + "login": "nidhalmessaoudi", + "name": "Nidhal Messaoudi", + "avatar_url": "https://avatars.githubusercontent.com/u/63377412?v=4", + "profile": "https://nidhalmessaoudi.herokuapp.com", + "contributions": [ + "code" + ] + }, + { + "login": "MahdiBM", + "name": "Mahdi Bahrami", + "avatar_url": "https://avatars.githubusercontent.com/u/54685446?v=4", + "profile": "https://github.com/MahdiBM", + "contributions": [ + "tool" + ] + }, + { + "login": "Nagidal", + "name": "Nagidal", + "avatar_url": "https://avatars.githubusercontent.com/u/7075397?v=4", + "profile": "https://github.com/Nagidal", + "contributions": [ + "content" + ] + }, + { + "login": "adamhb123", + "name": "Adam Brewer", + "avatar_url": "https://avatars.githubusercontent.com/u/25161597?v=4", + "profile": "https://adabrew.com", + "contributions": [ + "content" + ] + }, + { + "login": "eugkhp", + "name": "Eugene", + "avatar_url": "https://avatars.githubusercontent.com/u/25910599?v=4", + "profile": "https://github.com/eugkhp", + "contributions": [ + "tool" + ] + }, + { + "login": "navicore", + "name": "Ed Sweeney", + "avatar_url": "https://avatars.githubusercontent.com/u/110999?v=4", + "profile": "https://social.linux.pizza/@navicore", + "contributions": [ + "content" + ] + }, + { + "login": "javihernant", + "name": "javihernant", + "avatar_url": "https://avatars.githubusercontent.com/u/73640929?v=4", + "profile": "https://github.com/javihernant", + "contributions": [ + "content" + ] + }, + { + "login": "VegardMatthey", + "name": "Vegard", + "avatar_url": "https://avatars.githubusercontent.com/u/59250656?v=4", + "profile": "https://github.com/VegardMatthey", + "contributions": [ + "content" + ] + }, + { + "login": "ryanwhitehouse", + "name": "Ryan Whitehouse", + "avatar_url": "https://avatars.githubusercontent.com/u/13400784?v=4", + "profile": "https://github.com/ryanwhitehouse", + "contributions": [ + "content" + ] + }, + { + "login": "guoard", + "name": "Ali Afsharzadeh", + "avatar_url": "https://avatars.githubusercontent.com/u/65511355?v=4", + "profile": "https://github.com/guoard", + "contributions": [ + "content" + ] + }, + { + "login": "keogami", + "name": "Keogami", + "avatar_url": "https://avatars.githubusercontent.com/u/41939011?v=4", + "profile": "http://keogami.ml", + "contributions": [ + "doc" + ] + }, + { + "login": "ahresse", + "name": "Alexandre Esse", + "avatar_url": "https://avatars.githubusercontent.com/u/28402488?v=4", + "profile": "https://github.com/ahresse", + "contributions": [ + "content" + ] + }, + { + "login": "sagarvora", + "name": "Sagar Vora", + "avatar_url": "https://avatars.githubusercontent.com/u/16315650?v=4", + "profile": "https://resilient.tech", + "contributions": [ + "content" + ] + }, + { + "login": "poneciak57", + "name": "Kacper Poneta", + "avatar_url": "https://avatars.githubusercontent.com/u/94321164?v=4", + "profile": "https://github.com/poneciak57", + "contributions": [ + "content" + ] + }, + { + "login": "ktheory", + "name": "Aaron Suggs", + "avatar_url": "https://avatars.githubusercontent.com/u/975?v=4", + "profile": "https://ktheory.com/", + "contributions": [ + "content" + ] + }, + { + "login": "alexwh", + "name": "Alex", + "avatar_url": "https://avatars.githubusercontent.com/u/1723612?v=4", + "profile": "https://github.com/alexwh", + "contributions": [ + "content" + ] + }, + { + "login": "stornquist", + "name": "Sebastian Törnquist", + "avatar_url": "https://avatars.githubusercontent.com/u/42915664?v=4", + "profile": "https://github.com/stornquist", + "contributions": [ + "content" + ] + }, + { + "login": "smlavine", + "name": "Sebastian LaVine", + "avatar_url": "https://avatars.githubusercontent.com/u/33563640?v=4", + "profile": "http://smlavine.com", + "contributions": [ + "code" + ] + }, + { + "login": "akgerber", + "name": "Alan Gerber", + "avatar_url": "https://avatars.githubusercontent.com/u/201313?v=4", + "profile": "http://www.alangerber.us", + "contributions": [ + "content" + ] + }, + { + "login": "esotuvaka", + "name": "Eric", + "avatar_url": "https://avatars.githubusercontent.com/u/104941850?v=4", + "profile": "http://esotuvaka.github.io", + "contributions": [ + "content" + ] + }, + { + "login": "az0977776", + "name": "Aaron Wang", + "avatar_url": "https://avatars.githubusercontent.com/u/9172038?v=4", + "profile": "https://github.com/az0977776", + "contributions": [ + "content" + ] + }, + { + "login": "nmay231", + "name": "Noah", + "avatar_url": "https://avatars.githubusercontent.com/u/35386821?v=4", + "profile": "https://github.com/nmay231", + "contributions": [ + "content" + ] + }, + { + "login": "rb5014", + "name": "rb5014", + "avatar_url": "https://avatars.githubusercontent.com/u/105397317?v=4", + "profile": "https://github.com/rb5014", + "contributions": [ + "content" + ] + }, + { + "login": "deedy5", + "name": "deedy5", + "avatar_url": "https://avatars.githubusercontent.com/u/65482418?v=4", + "profile": "https://github.com/deedy5", + "contributions": [ + "content" + ] + }, + { + "login": "lionel-rowe", + "name": "lionel-rowe", + "avatar_url": "https://avatars.githubusercontent.com/u/26078826?v=4", + "profile": "https://github.com/lionel-rowe", + "contributions": [ + "content" + ] + }, + { + "login": "Ben2917", + "name": "Ben", + "avatar_url": "https://avatars.githubusercontent.com/u/10279994?v=4", + "profile": "https://github.com/Ben2917", + "contributions": [ + "content" + ] + }, + { + "login": "b1ue64", + "name": "b1ue64", + "avatar_url": "https://avatars.githubusercontent.com/u/77976308?v=4", + "profile": "https://github.com/b1ue64", + "contributions": [ + "content" + ] + }, + { + "login": "lazywalker", + "name": "lazywalker", + "avatar_url": "https://avatars.githubusercontent.com/u/53956?v=4", + "profile": "https://github.com/lazywalker", + "contributions": [ + "content" + ] + }, + { + "login": "proofconstruction", + "name": "proofconstruction", + "avatar_url": "https://avatars.githubusercontent.com/u/74747193?v=4", + "profile": "https://github.com/proofconstruction", + "contributions": [ + "infra" + ] + }, + { + "login": "IVIURRAY", + "name": "IVIURRAY", + "avatar_url": "https://avatars.githubusercontent.com/u/16007179?v=4", + "profile": "https://www.youtube.com/channel/UCQCjA6qUutAtWqkCA4Z36CQ", + "contributions": [ + "content" + ] + }, + { + "login": "b-apperlo", + "name": "Bert Apperlo", + "avatar_url": "https://avatars.githubusercontent.com/u/91734527?v=4", + "profile": "https://github.com/b-apperlo", + "contributions": [ + "content" + ] + }, + { + "login": "FWDekker", + "name": "Florine W. Dekker", + "avatar_url": "https://avatars.githubusercontent.com/u/13442533?v=4", + "profile": "https://fwdekker.com/", + "contributions": [ + "content" + ] + }, + { + "login": "luhem7", + "name": "Mehul Gangavelli", + "avatar_url": "https://avatars.githubusercontent.com/u/4008215?v=4", + "profile": "https://github.com/luhem7", + "contributions": [ + "content" + ] + }, + { + "login": "Frosthage", + "name": "Mikael Frosthage", + "avatar_url": "https://avatars.githubusercontent.com/u/14823314?v=4", + "profile": "https://github.com/Frosthage", + "contributions": [ + "content" + ] + }, + { + "login": "robertefry", + "name": "Robert Fry", + "avatar_url": "https://avatars.githubusercontent.com/u/43712054?v=4", + "profile": "https://robertfry.xyz", + "contributions": [ + "content" + ] + }, + { + "login": "tajo48", + "name": "tajo48", + "avatar_url": "https://avatars.githubusercontent.com/u/55502906?v=4", + "profile": "https://github.com/tajo48", + "contributions": [ + "content" + ] + }, + { + "login": "novanish", + "name": "Anish", + "avatar_url": "https://avatars.githubusercontent.com/u/98446102?v=4", + "profile": "https://anishchhetri.com.np", + "contributions": [ + "content" + ] + }, + { + "login": "vnprc", + "name": "vnprc", + "avatar_url": "https://avatars.githubusercontent.com/u/9425366?v=4", + "profile": "https://github.com/vnprc", + "contributions": [ + "content" + ] + }, + { + "login": "jrcarl624", + "name": "Joshua Carlson", + "avatar_url": "https://avatars.githubusercontent.com/u/61999256?v=4", + "profile": "http://androecia.net", + "contributions": [ + "content" + ] + }, + { + "login": "johnDeSilencio", + "name": "Nicholas R. Smith", + "avatar_url": "https://avatars.githubusercontent.com/u/20136554?v=4", + "profile": "https://johndesilencio.me", + "contributions": [ + "code" + ] + }, + { + "login": "alexfertel", + "name": "Alexander González", + "avatar_url": "https://avatars.githubusercontent.com/u/22298999?v=4", + "profile": "https://alexfertel.me", + "contributions": [ + "content" + ] + }, + { + "login": "softarn", + "name": "Marcus Höjvall", + "avatar_url": "https://avatars.githubusercontent.com/u/517619?v=4", + "profile": "https://github.com/softarn", + "contributions": [ + "content" + ] + }, + { + "login": "barlevalon", + "name": "Alon Hearter", + "avatar_url": "https://avatars.githubusercontent.com/u/3397911?v=4", + "profile": "https://github.com/barlevalon", + "contributions": [ + "content" + ] + }, + { + "login": "shirts", + "name": "shirts", + "avatar_url": "https://avatars.githubusercontent.com/u/4952151?v=4", + "profile": "https://github.com/shirts", + "contributions": [ + "content" + ] + }, + { + "login": "eLVas", + "name": "Ivan Vasiunyk", + "avatar_url": "https://avatars.githubusercontent.com/u/6797156?v=4", + "profile": "https://github.com/eLVas", + "contributions": [ + "content" + ] + }, + { + "login": "mo8it", + "name": "Mo", + "avatar_url": "https://avatars.githubusercontent.com/u/76752051?v=4", + "profile": "https://mo8it.com", + "contributions": [ + "code" + ] + }, + { + "login": "x10an14", + "name": "x10an14", + "avatar_url": "https://avatars.githubusercontent.com/u/710608?v=4", + "profile": "https://github.com/x10an14", + "contributions": [ + "infra" + ] + }, + { + "login": "gabay", + "name": "Roi Gabay", + "avatar_url": "https://avatars.githubusercontent.com/u/5773610?v=4", + "profile": "https://github.com/gabay", + "contributions": [ + "content" + ] + }, + { + "login": "mkovaxx", + "name": "Máté Kovács", + "avatar_url": "https://avatars.githubusercontent.com/u/481354?v=4", + "profile": "https://github.com/mkovaxx", + "contributions": [ + "content" + ] + }, + { + "login": "szabgab", + "name": "Gábor Szabó", + "avatar_url": "https://avatars.githubusercontent.com/u/48833?v=4", + "profile": "https://szabgab.com/", + "contributions": [ + "content" + ] + }, + { + "login": "yamila-moreno", + "name": "Yamila Moreno", + "avatar_url": "https://avatars.githubusercontent.com/u/3340793?v=4", + "profile": "https://moduslaborandi.net", + "contributions": [ + "content" + ] + }, + { + "login": "willhack", + "name": "Will Hack", + "avatar_url": "https://avatars.githubusercontent.com/u/18036720?v=4", + "profile": "https://github.com/willhack", + "contributions": [ + "content" + ] + }, + { + "login": "bean5", + "name": "Michael", + "avatar_url": "https://avatars.githubusercontent.com/u/2052646?v=4", + "profile": "http://cancompute.tech", + "contributions": [ + "content" + ] + }, + { + "login": "pksadiq", + "name": "Mohammed Sadiq", + "avatar_url": "https://avatars.githubusercontent.com/u/1289514?v=4", + "profile": "https://www.sadiqpk.org", + "contributions": [ + "content" + ] + }, + { + "login": "Jak-Ch-ll", + "name": "Jakob", + "avatar_url": "https://avatars.githubusercontent.com/u/56225668?v=4", + "profile": "https://github.com/Jak-Ch-ll", + "contributions": [ + "content" + ] + }, + { + "login": "ob", + "name": "Oscar Bonilla", + "avatar_url": "https://avatars.githubusercontent.com/u/4950?v=4", + "profile": "http://oscarbonilla.com", + "contributions": [ + "content" + ] + }, + { + "login": "husjon", + "name": "Jon Erling Hustadnes", + "avatar_url": "https://avatars.githubusercontent.com/u/554229?v=4", + "profile": "https://github.com/husjon", + "contributions": [ + "content" + ] + }, + { + "login": "CobaltCause", + "name": "Charles Hall", + "avatar_url": "https://avatars.githubusercontent.com/u/7003738?v=4", + "profile": "https://github.com/CobaltCause", + "contributions": [ + "infra" + ] + }, + { + "login": "krmpotic", + "name": "Luka Krmpotić", + "avatar_url": "https://avatars.githubusercontent.com/u/10350645?v=4", + "profile": "https://github.com/krmpotic", + "contributions": [ + "content" + ] + }, + { + "login": "jurglic", + "name": "Jurglic", + "avatar_url": "https://avatars.githubusercontent.com/u/112600?v=4", + "profile": "https://github.com/jurglic", + "contributions": [ + "content" + ] + }, + { + "login": "OfirLauber", + "name": "Ofir Lauber", + "avatar_url": "https://avatars.githubusercontent.com/u/5631030?v=4", + "profile": "https://github.com/OfirLauber", + "contributions": [ + "content" + ] + }, + { + "login": "offbyone", + "name": "Chris Rose", + "avatar_url": "https://avatars.githubusercontent.com/u/181693?v=4", + "profile": "https://github.com/offbyone", + "contributions": [ + "infra" + ] + }, + { + "login": "dieterplex", + "name": "d1t2", + "avatar_url": "https://avatars.githubusercontent.com/u/507502?v=4", + "profile": "https://github.com/dieterplex", + "contributions": [ + "infra" + ] + }, + { + "login": "docwilco", + "name": "docwilco", + "avatar_url": "https://avatars.githubusercontent.com/u/66911096?v=4", + "profile": "https://github.com/docwilco", + "contributions": [ + "code" + ] + }, + { + "login": "matthewjnield", + "name": "Matt Nield", + "avatar_url": "https://avatars.githubusercontent.com/u/64328730?v=4", + "profile": "https://www.linkedin.com/in/matthew-nield1/", + "contributions": [ + "content" + ] + }, + { + "login": "TheBearodactyl", + "name": "The Bearodactyl", + "avatar_url": "https://avatars.githubusercontent.com/u/114454115?v=4", + "profile": "https://github.com/TheBearodactyl", + "contributions": [ + "code" + ] + }, + { + "login": "markgreene74", + "name": "markgreene74", + "avatar_url": "https://avatars.githubusercontent.com/u/18945890?v=4", + "profile": "https://github.com/markgreene74", + "contributions": [ + "code" + ] + }, + { + "login": "VeeDeltaVee", + "name": "Versha Dhankar", + "avatar_url": "https://avatars.githubusercontent.com/u/45564258?v=4", + "profile": "https://github.com/VeeDeltaVee", + "contributions": [ + "doc" + ] + }, + { + "login": "0atman", + "name": "Tristram Oaten", + "avatar_url": "https://avatars.githubusercontent.com/u/114097?v=4", + "profile": "http://0atman.com", + "contributions": [ + "content" + ] + }, + { + "login": "danieltinazzi", + "name": "Daniel Tinazzi", + "avatar_url": "https://avatars.githubusercontent.com/u/11833533?v=4", + "profile": "https://github.com/danieltinazzi", + "contributions": [ + "content" + ] + }, + { + "login": "raymon-roos", + "name": "Raymon Roos", + "avatar_url": "https://avatars.githubusercontent.com/u/38888470?v=4", + "profile": "https://github.com/raymon-roos", + "contributions": [ + "content" + ] + }, + { + "login": "matthri", + "name": "Matthias", + "avatar_url": "https://avatars.githubusercontent.com/u/67913999?v=4", + "profile": "https://github.com/matthri", + "contributions": [ + "code" + ] + }, + { + "login": "neuschaefer", + "name": "J. Neuschäfer", + "avatar_url": "https://avatars.githubusercontent.com/u/1021512?v=4", + "profile": "https://github.com/neuschaefer", + "contributions": [ + "code" + ] + }, + { + "login": "bastianpedersen", + "name": "Bastian Pedersen", + "avatar_url": "https://avatars.githubusercontent.com/u/58905488?v=4", + "profile": "https://scooterhacking.org", + "contributions": [ + "content" + ] + }, + { + "login": "gerases", + "name": "gerases", + "avatar_url": "https://avatars.githubusercontent.com/u/8953623?v=4", + "profile": "https://github.com/gerases", + "contributions": [ + "content" + ] + } + ], + "contributorsPerLine": 8, + "projectName": "rustlings", + "projectOwner": "rust-lang", + "repoType": "github", + "repoHost": "https://github.com", + "skipCi": true, + "commitConvention": "angular", + "commitType": "docs" +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e1b2cec --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2-linux", + "waitFor": "onCreateCommand", + "onCreateCommand": ".devcontainer/setup.sh", + "updateContentCommand": "cargo build", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rustlings watch" + }, + "customizations": { + "vscode": { + "extensions": [ + "rust-lang.rust-analyzer" + ] + } + } +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 0000000..0e090a8 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash +curl https://sh.rustup.rs -sSf | sh -s -- -y + +# Update current shell environment variables after install to find rustup +. "$HOME/.cargo/env" +rustup install stable +bash install.sh diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..aab09aa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.rs] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..bd4fa07 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,37 @@ +# name: Rustlings Tests + +# on: +# push: +# branches: [ main ] +# pull_request: +# branches: [ main ] + +# env: +# CARGO_TERM_COLOR: always + +# jobs: +# fmt: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v3 +# - uses: dtolnay/rust-toolchain@stable +# with: +# components: rustfmt +# - uses: DavidAnson/markdownlint-cli2-action@v9 +# with: +# globs: "exercises/**/*.md" +# - name: Run cargo fmt +# run: | +# cargo fmt --all -- --check +# test: +# runs-on: ${{ matrix.os }} +# strategy: +# matrix: +# os: [ubuntu-latest, windows-latest, macOS-latest] +# steps: +# - uses: actions/checkout@v3 +# - uses: dtolnay/rust-toolchain@stable +# - uses: swatinem/rust-cache@v2 +# - name: Run cargo test +# run: | +# cargo test diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml new file mode 100644 index 0000000..f7b992f --- /dev/null +++ b/.github/workflows/web.yml @@ -0,0 +1,88 @@ +# # Workflow to build your docs with oranda (and mdbook) +# # and deploy them to Github Pages +# name: Web + +# # We're going to push to the gh-pages branch, so we need that permission +# permissions: +# contents: write + +# # What situations do we want to build docs in? +# # All of these work independently and can be removed / commented out +# # if you don't want oranda/mdbook running in that situation +# on: +# # Check that a PR didn't break docs! +# # +# # Note that the "Deploy to Github Pages" step won't run in this mode, +# # so this won't have any side-effects. But it will tell you if a PR +# # completely broke oranda/mdbook. Sadly we don't provide previews (yet)! +# pull_request: + +# # Whenever something gets pushed to main, update the docs! +# # This is great for getting docs changes live without cutting a full release. +# # +# # Note that if you're using cargo-dist, this will "race" the Release workflow +# # that actually builds the Github Release that oranda tries to read (and +# # this will almost certainly complete first). As a result you will publish +# # docs for the latest commit but the oranda landing page won't know about +# # the latest release. The workflow_run trigger below will properly wait for +# # cargo-dist, and so this half-published state will only last for ~10 minutes. +# # +# # If you only want docs to update with releases, disable this, or change it to +# # a "release" branch. You can, of course, also manually trigger a workflow run +# # when you want the docs to update. +# push: +# branches: +# - main + +# # Whenever a workflow called "Release" completes, update the docs! +# # +# # If you're using cargo-dist, this is recommended, as it will ensure that +# # oranda always sees the latest release right when it's available. Note +# # however that Github's UI is wonky when you use workflow_run, and won't +# # show this workflow as part of any commit. You have to go to the "actions" +# # tab for your repo to see this one running (the gh-pages deploy will also +# # only show up there). +# workflow_run: +# workflows: [ "Release" ] +# types: +# - completed + +# # Alright, let's do it! +# jobs: +# web: +# name: Build and deploy site and docs +# runs-on: ubuntu-latest +# steps: +# # Setup +# - uses: actions/checkout@v3 +# with: +# fetch-depth: 0 +# - uses: dtolnay/rust-toolchain@stable +# - uses: swatinem/rust-cache@v2 + +# # If you use any mdbook plugins, here's the place to install them! + +# # Install and run oranda (and mdbook) +# # This will write all output to ./public/ (including copying mdbook's output to there) +# - name: Install and run oranda +# run: | +# curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/oranda/releases/download/v0.3.1/oranda-installer.sh | sh +# oranda build + +# # Deploy to our gh-pages branch (creating it if it doesn't exist) +# # the "public" dir that oranda made above will become the root dir +# # of this branch. +# # +# # Note that once the gh-pages branch exists, you must +# # go into repo's settings > pages and set "deploy from branch: gh-pages" +# # the other defaults work fine. +# - name: Deploy to Github Pages +# uses: JamesIves/github-pages-deploy-action@v4.4.1 +# # ONLY if we're on main (so no PRs or feature branches allowed!) +# if: ${{ github.ref == 'refs/heads/main' }} +# with: +# branch: gh-pages +# # Gotta tell the action where to find oranda's output +# folder: public +# token: ${{ secrets.GITHUB_TOKEN }} +# single-commit: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f319d39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.swp +target/ +**/*.rs.bk +.DS_Store +*.pdb +exercises/clippy/Cargo.toml +exercises/clippy/Cargo.lock +rust-project.json +.idea +.vscode/* +!.vscode/extensions.json +*.iml +*.o +public/ +.direnv/ + +# Local Netlify folder +.netlify diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..0691933 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,7 @@ +tasks: + - init: /workspace/rustlings/install.sh + command: /workspace/.cargo/bin/rustlings watch + +vscode: + extensions: + - rust-lang.rust-analyzer@0.3.1348 diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..d5f7e39 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,2 @@ +# MD013/line-length Line length, Expected: 80 +MD013: false diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b85de74 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "rust-lang.rust-analyzer" + ] +} diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..5466c9c --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,374 @@ +## Authors + +This file lists the people that have contributed to this project. + +Excluded from this list are @carols10cents and @diannasoreil, the principal +authors. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Carol (Nichols || Goulding)
Carol (Nichols || Goulding)

💻 🖋
QuietMisdreavus
QuietMisdreavus

💻 🖋
Robert M Lugg
Robert M Lugg

🖋
Hynek Schlawack
Hynek Schlawack

💻
Katharina Fey
Katharina Fey

💻
lukabavdaz
lukabavdaz

💻 🖋
Erik Vesteraas
Erik Vesteraas

💻
delet0r
delet0r

💻
Shaun Bennett
Shaun Bennett

💻
Andrew Bagshaw
Andrew Bagshaw

💻
Kyle Isom
Kyle Isom

💻
Colin Pitrat
Colin Pitrat

💻
Zac Anger
Zac Anger

💻
Matthias Geier
Matthias Geier

💻
Chris Pearce
Chris Pearce

💻
Yvan Sraka
Yvan Sraka

💻
Denys Smirnov
Denys Smirnov

💻
eddyp
eddyp

💻
Brian Kung
Brian Kung

💻 🖋
Russell
Russell

💻
Dan Wilhelm
Dan Wilhelm

📖
Jesse
Jesse

💻 🖋
Fredrik Jambrén
Fredrik Jambrén

💻
Pete McFarlane
Pete McFarlane

🖋
nkanderson
nkanderson

💻 🖋
Ajax M
Ajax M

📖
Dylan Nugent
Dylan Nugent

🖋
vyaslav
vyaslav

💻 🖋
George
George

💻
Thomas Holloway
Thomas Holloway

💻 🖋
Jubilee
Jubilee

💻
WofWca
WofWca

💻
Roberto Vidal
Roberto Vidal

💻 📖 🤔 🚧
Jens
Jens

📖
Rahat Ahmed
Rahat Ahmed

📖
Abdou Seck
Abdou Seck

💻 🖋 👀
Katie
Katie

💻
Socrates
Socrates

📖
gnodarse
gnodarse

🖋
Harrison Metzger
Harrison Metzger

💻
Torben Jonas
Torben Jonas

💻 🖋
Paul Bissex
Paul Bissex

📖
Steven Mann
Steven Mann

💻 🖋
Mario Reder
Mario Reder

💻 🖋
skim
skim

💻
Sanjay K
Sanjay K

💻 🖋
Rohan Jain
Rohan Jain

💻
Said Aspen
Said Aspen

💻 🖋
Ufuk Celebi
Ufuk Celebi

💻
lebedevsergey
lebedevsergey

📖
Aleksei Trifonov
Aleksei Trifonov

🖋
Darren Meehan
Darren Meehan

🖋
Jihchi Lee
Jihchi Lee

🖋
Christofer Bertonha
Christofer Bertonha

🖋
Vivek Bharath Akupatni
Vivek Bharath Akupatni

💻 ⚠️
Dídac Sementé Fernández
Dídac Sementé Fernández

💻 🖋
Rob Story
Rob Story

💻
Siobhan Jacobson
Siobhan Jacobson

💻
Evan Carroll
Evan Carroll

🖋
Jawaad Mahmood
Jawaad Mahmood

🖋
Gaurang Tandon
Gaurang Tandon

🖋
Stefan Kupresak
Stefan Kupresak

🖋
Greg Leonard
Greg Leonard

🖋
Ryan McQuen
Ryan McQuen

💻
Annika
Annika

👀
Axel Viala
Axel Viala

💻
Mohammed Sazid Al Rashid
Mohammed Sazid Al Rashid

🖋 💻
Caleb Webber
Caleb Webber

🚧
Peter N
Peter N

🚧
seancad
seancad

🚧
Will Hayworth
Will Hayworth

🖋
Christian Zeller
Christian Zeller

🖋
Jean-Francois Chevrette
Jean-Francois Chevrette

🖋 💻
John Baber-Lucero
John Baber-Lucero

🖋
Tal
Tal

🖋
apogeeoak
apogeeoak

🖋 💻
Larry Garfield
Larry Garfield

🖋
circumspect
circumspect

🖋
Cyrus Wyett
Cyrus Wyett

🖋
cadolphs
cadolphs

💻
Pascal H.
Pascal H.

🖋
Rod Elias
Rod Elias

🖋
Matt Lebl
Matt Lebl

💻
Ignacio Le Fluk
Ignacio Le Fluk

🖋
Taylor Yu
Taylor Yu

💻 🖋
Patrick Hintermayer
Patrick Hintermayer

💻
Pete Pavlovski
Pete Pavlovski

🖋
k12ish
k12ish

🖋
Shao Yang Hong
Shao Yang Hong

🖋
Brandon Macer
Brandon Macer

🖋
Stoian Dan
Stoian Dan

🖋
Pi Delport
Pi Delport

🖋
Sateesh
Sateesh

💻 🖋
ZC
ZC

🖋
hyperparabolic
hyperparabolic

💻
arlecchino
arlecchino

📖
Richthofen
Richthofen

💻
Ivan Nerazumov
Ivan Nerazumov

📖
lauralindzey
lauralindzey

📖
Rakshit Sinha
Rakshit Sinha

🖋
Damian
Damian

🖋
Ben Armstead
Ben Armstead

💻
anuk909
anuk909

🖋 💻
granddaifuku
granddaifuku

🖋
Weilet
Weilet

🖋
LIU JIE
LIU JIE

🖋
Antoine Büsch
Antoine Büsch

💻
frogtd
frogtd

🖋
Zhenghao Lu
Zhenghao Lu

🖋
Fredrik Enestad
Fredrik Enestad

🖋
xuesong
xuesong

🖋
Michael Walsh
Michael Walsh

💻
alirezaghey
alirezaghey

🖋
Franklin van Nes
Franklin van Nes

💻
nekonako
nekonako

💻
ZX
ZX

🖋
Yang Wen
Yang Wen

🖋
Brandon High
Brandon High

📖
x-hgg-x
x-hgg-x

💻
Kisaragi
Kisaragi

📖
Lucas Aries
Lucas Aries

🖋
ragreenburg
ragreenburg

🖋
stevenfukase
stevenfukase

🖋
J-S-Kim
J-S-Kim

🖋
Fointard
Fointard

🖋
Ryan Lowe
Ryan Lowe

💻
cui fliter
cui fliter

🖋
Ron Lusk
Ron Lusk

🖋
Bryan Lee
Bryan Lee

🖋
Nandaja Varma
Nandaja Varma

📖
pwygab
pwygab

💻
Lucas Grigolon Varela
Lucas Grigolon Varela

🖋
Bufo
Bufo

🖋
Jack Clayton
Jack Clayton

💻
Konstantin
Konstantin

🖋
0pling
0pling

🖋
KatanaFluorescent
KatanaFluorescent

💻
Drew Morris
Drew Morris

💻
camperdue42
camperdue42

🖋
YsuOS
YsuOS

🖋
Steven Nguyen
Steven Nguyen

🖋
nacairns1
nacairns1

🖋
Paulo Gabriel Justino Bezerra
Paulo Gabriel Justino Bezerra

🖋
Jason
Jason

🖋
exdx
exdx

🖋
James Zow
James Zow

🖋
James Bromley
James Bromley

🖋
swhiteCQC
swhiteCQC

🖋
Neil Pate
Neil Pate

🖋
wojexe
wojexe

🖋
Mattia Schiavon
Mattia Schiavon

🖋
Eric Jolibois
Eric Jolibois

🖋
Edwin Chang
Edwin Chang

🖋
Saikat Das
Saikat Das

🖋
Jeremy Goh
Jeremy Goh

🖋
Lioness100
Lioness100

🖋
Tristan Nicholls
Tristan Nicholls

🖋
Claire
Claire

🖋
Maurice Van Wassenhove
Maurice Van Wassenhove

🖋
John Mendelewski
John Mendelewski

💻
Brian Fakhoury
Brian Fakhoury

🖋
Markus Boehme
Markus Boehme

💻
Nico Vromans
Nico Vromans

🖋
vostok92
vostok92

🖋
Magnus Rødseth
Magnus Rødseth

🖋
rubiesonthesky
rubiesonthesky

🖋
Gabriel Bianconi
Gabriel Bianconi

🖋
Kody Low
Kody Low

🖋
rzrymiak
rzrymiak

🖋
Miguel Raz Guzmán Macedo
Miguel Raz Guzmán Macedo

🖋
Magnus Markling
Magnus Markling

🖋
Tiago De Gaspari
Tiago De Gaspari

🖋
skaunov
skaunov

🖋
Cal Jacobson
Cal Jacobson

🖋
Duchoud Nicolas
Duchoud Nicolas

🖋
Gaëtan Faugère
Gaëtan Faugère

🔧
bhbuehler
bhbuehler

🖋
Yuri Astrakhan
Yuri Astrakhan

💻
azzamsa
azzamsa

💻
mvanschellebeeck
mvanschellebeeck

🖋
Arkid
Arkid

🖋
Tom Kunc
Tom Kunc

🖋
Marek Furák
Marek Furák

🖋
Winter
Winter

💻
Moritz Böhme
Moritz Böhme

💻
craymel
craymel

🖋
TK Buristrakul
TK Buristrakul

🖋
Kent Worthington
Kent Worthington

🖋
seporterfield
seporterfield

🖋
David Barroso
David Barroso

🚇
Tobias Klauser
Tobias Klauser

💻
0xMySt1c
0xMySt1c

🔧
Ten
Ten

💻
jones martin
jones martin

🖋
cloppingemu
cloppingemu

💻
Kevin Wan
Kevin Wan

🖋
Ruby
Ruby

💻
Alexander Gill
Alexander Gill

💻
Jarrod Sanders
Jarrod Sanders

🖋
Andrew Sen
Andrew Sen

🖋
Grzegorz Żur
Grzegorz Żur

🖋
Daan Wynen
Daan Wynen

🖋
Anush
Anush

📖
Gleb Shevchenko
Gleb Shevchenko

🖋
Edmundo Paulino
Edmundo Paulino

🚇
Emmanuel Roullit
Emmanuel Roullit

🚇
Nidhal Messaoudi
Nidhal Messaoudi

💻
Mahdi Bahrami
Mahdi Bahrami

🔧
Nagidal
Nagidal

🖋
Adam Brewer
Adam Brewer

🖋
Eugene
Eugene

🔧
Ed Sweeney
Ed Sweeney

🖋
javihernant
javihernant

🖋
Vegard
Vegard

🖋
Ryan Whitehouse
Ryan Whitehouse

🖋
Ali Afsharzadeh
Ali Afsharzadeh

🖋
Keogami
Keogami

📖
Alexandre Esse
Alexandre Esse

🖋
Sagar Vora
Sagar Vora

🖋
Kacper Poneta
Kacper Poneta

🖋
Aaron Suggs
Aaron Suggs

🖋
Alex
Alex

🖋
Sebastian Törnquist
Sebastian Törnquist

🖋
Sebastian LaVine
Sebastian LaVine

💻
Alan Gerber
Alan Gerber

🖋
Eric
Eric

🖋
Aaron Wang
Aaron Wang

🖋
Noah
Noah

🖋
rb5014
rb5014

🖋
deedy5
deedy5

🖋
lionel-rowe
lionel-rowe

🖋
Ben
Ben

🖋
b1ue64
b1ue64

🖋
lazywalker
lazywalker

🖋
proofconstruction
proofconstruction

🚇
IVIURRAY
IVIURRAY

🖋
Bert Apperlo
Bert Apperlo

🖋
Florine W. Dekker
Florine W. Dekker

🖋
Mehul Gangavelli
Mehul Gangavelli

🖋
Mikael Frosthage
Mikael Frosthage

🖋
Robert Fry
Robert Fry

🖋
tajo48
tajo48

🖋
Anish
Anish

🖋
vnprc
vnprc

🖋
Joshua Carlson
Joshua Carlson

🖋
Nicholas R. Smith
Nicholas R. Smith

💻
Alexander González
Alexander González

🖋
Marcus Höjvall
Marcus Höjvall

🖋
Alon Hearter
Alon Hearter

🖋
shirts
shirts

🖋
Ivan Vasiunyk
Ivan Vasiunyk

🖋
Mo
Mo

💻
x10an14
x10an14

🚇
Roi Gabay
Roi Gabay

🖋
Máté Kovács
Máté Kovács

🖋
Gábor Szabó
Gábor Szabó

🖋
Yamila Moreno
Yamila Moreno

🖋
Will Hack
Will Hack

🖋
Michael
Michael

🖋
Mohammed Sadiq
Mohammed Sadiq

🖋
Jakob
Jakob

🖋
Oscar Bonilla
Oscar Bonilla

🖋
Jon Erling Hustadnes
Jon Erling Hustadnes

🖋
Charles Hall
Charles Hall

🚇
Luka Krmpotić
Luka Krmpotić

🖋
Jurglic
Jurglic

🖋
Ofir Lauber
Ofir Lauber

🖋
Chris Rose
Chris Rose

🚇
d1t2
d1t2

🚇
docwilco
docwilco

💻
Matt Nield
Matt Nield

🖋
The Bearodactyl
The Bearodactyl

💻
markgreene74
markgreene74

💻
Versha Dhankar
Versha Dhankar

📖
Tristram Oaten
Tristram Oaten

🖋
Daniel Tinazzi
Daniel Tinazzi

🖋
Raymon Roos
Raymon Roos

🖋
Matthias
Matthias

💻
J. Neuschäfer
J. Neuschäfer

💻
Bastian Pedersen
Bastian Pedersen

🖋
gerases
gerases

🖋
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a7226a4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,871 @@ + +## 5.6.1 (2023-09-18) + +#### Changed + +- Converted all exercises with assertions to test mode. + +#### Fixed + +- `cow1`: Reverted regression introduced by calling `to_mut` where it + shouldn't have been called, and clarified comment. +- `primitive_types3`: Require at least an array of 100 elements. +- Removed hint comments when no hint exists for the exercise. +- `as_ref_mut`: Fixed a typo in a test function name. +- `enums3`: Fixed formatting with `rustfmt`. + + +## 5.6.0 (2023-09-04) + +#### Added + +- New exercise: `if3`, teaching the user about `if let` statements. +- `hashmaps2`: Added an extra test function to check if the amount of fruits is higher than zero. +- `enums3`: Added a test for `Message`. +- `if1`: Added a test case to check equal values. +- `if3`: Added a note specifying that there are no test changes needed. + +#### Changed + +- Swapped the order of threads and smart pointer exercises. +- Rewrote the CLI to use `clap` - it's matured much since we switched to `argh` :) +- `structs3`: Switched from i32 to u32. +- `move_semantics`: Switched 1-4 to tests, and rewrote them to be way simpler, while still teaching about the same + concepts. + +#### Fixed + +- `iterators5`: + - Removed an outdated part of the hint. + - Renamed variables to use snake_case. +- `vecs2`: Updated the hint to reference the renamed loop variable. +- `enums3`: Changed message string in test so that it gets properly tested. +- `strings2`: Corrected line number in hint, then removed it (this both happened as part of this release cycle). +- `primitive_types4`: Updated hint to the correct ending index. +- `quiz1`: Removed duplicated sentence from exercise comments. +- `errors4`: Improved comment. +- `from_into`: Fixed test values. +- `cow1`: Added `.to_mut()` to distinguish from the previous test case. +- `threads2`: Updated hint text to reference the correct book heading. + +#### Housekeeping + +- Cleaned up the explanation paragraphs at the start of each exercise. +- Lots of Nix housekeeping that I don't feel qualified to write about! +- Improved CI workflows, we're now testing on multiple platforms at once. + + +## 5.5.1 (2023-05-17) + +#### Fixed + +- Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix. + + +## 5.5.0 (2023-05-17) + +#### Added + +- `strings2`: Added a reference to the book chapter for reference conversion +- `lifetimes`: Added a link to the lifetimekata project +- Added a new `tests4` exercises, which teaches about testing for panics +- Added a `!` prefix command to watch mode that runs an external command +- Added a `--success-hints` option to watch mode that shows hints on exercise success + +#### Changed + +- `vecs2`: Renamed iterator variable bindings for clarify +- `lifetimes`: Changed order of book references +- `hashmaps2`: Clarified instructions in the todo block +- Moved lifetime exercises before test exercises (via the recommended book ordering) +- `options2`: Improved tests for layering options +- `modules2`: Added more information to the hint + +#### Fixed + +- `errors2`: Corrected a comment wording +- `iterators2`: Fixed a spelling mistake in the hint text +- `variables`: Wrapped the mut keyword with backticks for readability +- `move_semantics2`: Removed references to line numbers +- `cow1`: Clarified the `owned_no_mutation` comments +- `options3`: Changed exercise to panic when no match is found +- `rustlings lsp` now generates absolute paths, which should fix VSCode `rust-analyzer` usage on Windows + +#### Housekeeping + +- Added a markdown linter to run on GitHub actions +- Split quick installation section into two code blocks + + +## 5.4.1 (2023-03-10) + +#### Changed + +- `vecs`: Added links to `iter_mut` and `map` to README.md +- `cow1`: Changed main to tests +- `iterators1`: Formatted according to rustfmt + +#### Fixed + +- `errors5`: Unified undisclosed type notation +- `arc1`: Improved readability by avoiding implicit dereference +- `macros4`: Prevented auto-fix by adding `#[rustfmt::skip]` +- `cli`: Actually show correct progress percentages + + + +## 5.4.0 (2023-02-12) + +#### Changed + +- Reordered exercises + - Unwrapped `standard_library_types` into `iterators` and `smart_pointers` + - Moved smart pointer exercises behind threads + - Ordered `rc1` before `arc1` +- **intro1**: Added a note on `rustlings lsp` +- **threads1**: Panic if threads are not joined +- **cli**: + - Made progress bar update proportional to amount of files verified + - Decreased `watch` delay from 2 to 1 second + +#### Fixed + +- Capitalized "Rust" in exercise hints +- **enums3**: Removed superfluous tuple brackets +- **quiz2, clippy1, iterators1**: Fixed a typo +- **rc1**: Fixed a prompt error +- **cli**: + - Fixed a typo in a method name + - Specified the edition in `rustc` commands + +#### Housekeeping + +- Bumped min Rust version to 1.58 in installation script + + + +## 5.3.0 (2022-12-23) + +#### Added + +- **cli**: Added a percentage display in watch mode +- Added a `flake.nix` for Nix users + +#### Changed + +- **structs3**: Added an additional test +- **macros**: Added a link to MacroKata in the README + +#### Fixed + +- **strings3**: Added a link to `std` in the hint +- **threads1**: Corrected a hint link +- **iterators1**: Clarified hint steps +- **errors5**: Fix a typo in the hint +- **options1**: Clarified on the usage of the 24-hour system +- **threads2, threads3**: Explicitly use `Arc::clone` +- **structs3**: Clarifed the hint +- **quiz2, as_ref_mut, options1, traits1, traits2**: Clarified hints +- **traits1, traits2, cli**: Tidied up unmatching backticks +- **enums2**: Removed unneccessary indirection of self +- **enums3**: Added an extra tuple comment + +#### Housekeeping + +- Added a VSCode extension recommendation +- Applied some Clippy and rustfmt formatting +- Added a note on Windows PowerShell and other shell compatibility + + + +## 5.2.1 (2022-09-06) + +#### Fixed + +- **quiz1**: Reworded the comment to actually reflect what's going on in the tests. + Also added another assert just to make sure. +- **rc1**: Fixed a typo in the hint. +- **lifetimes**: Add quotes to the `println!` output, for readability. + +#### Housekeeping + +- Fixed a typo in README.md + + + +## 5.2.0 (2022-08-27) + +#### Added + +- Added a `reset` command + +#### Changed + +- **options2**: Convert the exercise to use tests + +#### Fixed + +- **threads3**: Fixed a typo +- **quiz1**: Adjusted the explanations to be consistent with + the tests + + + +## 5.1.1 (2022-08-17) + +#### Bug Fixes + +- Fixed an incorrect assertion in options1 + + + +## 5.1.0 (2022-08-16) + +#### Features + +- Added a new `rc1` exercise. +- Added a new `cow1` exercise. + +#### Bug Fixes + +- **variables5**: Corrected reference to previous exercise +- **functions4**: Fixed line number reference +- **strings3**: Clarified comment wording +- **traits4, traits5**: Fixed line number reference +- **traits5**: + - Fixed typo in "parameter" + - Made exercise prefer a traits-based solution +- **lifetimes2**: Improved hint +- **threads3**: Fixed typo in hint +- **box1**: Replaced `unimplemented!` with `todo!` +- **errors5**: Provided an explanation for usage of `Box` +- **quiz2**: Fixed a typo +- **macros**: Updated the macros book link +- **options1**: + - Removed unused code + - Added more granular tests +- Fixed some comment syntax shenanigans in info.toml + +#### Housekeeping + +- Fixed a typo in .editorconfig +- Fixed a typo in integration_tests.rs +- Clarified manual installation instructions using `cargo install --path .` +- Added a link to our Zulip in the readme file + + + +## 5.0.0 (2022-07-16) + +#### Features + +- Hint comments in exercises now also include a reference to the + `hint` watch mode subcommand. +- **intro1**: Added more hints to point the user to the source file. +- **variables**: Switched variables3 and variables4. +- Moved `vec` and `primitive_types` exercises before `move_semantics`. +- Renamed `vec` to `vecs` to be more in line with the naming in general. +- Split up the `collections` exercises in their own folders. +- **vec2**: Added a second part of the function that provides an alternative, + immutable way of modifying vec values. +- **enums3**: Added a hint. +- Moved `strings` before `modules`. +- Added a `strings3` exercise to teach modifying strings. +- Added a `hashmaps3` exercise for some advanced usage of hashmaps. +- Moved the original `quiz2` to be `strings4`, since it only tested strings + anyways. +- Reworked `quiz2` into a new exercise that tests more chapters. +- Renamed `option` to `options`. +- **options1**: Rewrote parts of the exercise to remove the weird array + iteration stuff. +- Moved `generics3` to be `quiz3`. +- Moved box/arc exercises behind `iterators`. +- **iterators4**: Added a test for factorials of zero. +- Split `threads1` between two exercises, the first one focusing more on + `JoinHandle`s. +- Added a `threads3` exercises that uses `std::sync::mpsc`. +- Added a `clippy3` exercises with some more interesting checks. +- **as_ref_mut**: Added a section that actually tests `AsMut`. +- Added 3 new lifetimes exercises. +- Added 3 new traits exercises. + +#### Bug Fixes + +- **variables2**: Made output messages more verbose. +- **variables5**: Added a nudging hint about shadowing. +- **variables6**: Fixed link to book. +- **functions**: Clarified the README wording. Generally cleaned up + some hints and added some extra comments. +- **if2**: Renamed function name to `foo_if_fizz`. +- **move_semantics**: Clarified some hints. +- **quiz1**: Renamed the function name to be more verbose. +- **structs1**: Use an integer type instead of strings. Renamed "unit structs" + to "unit-like structs", as is used in the book. +- **structs3**: Added the `panic!` statement in from the beginning. +- **errors1**: Use `is_empty()` instead of `len() > 0` +- **errors3**: Improved the hint. +- **errors5**: Improved exercise instructions and the hint. +- **errors6**: Provided the skeleton of one of the functions that's supposed + to be implemented. +- **iterators3**: Inserted `todo!` into `divide()` to keep a compiler error + from happening. +- **from_str**: Added a hint comment about string error message conversion with + `Box`. +- **try_from_into**: Fixed the function name in comment. + +#### Removed + +- Removed the legacy LSP feature that was using `mod.rs` files. +- Removed `quiz4`. +- Removed `advanced_errs`. These were the last exercises in the recommended + order, and I've always felt like they didn't quite fit in with the mostly + simple, book-following style we've had in Rustlings. + +#### Housekeeping + +- Added missing exercises to the book index. +- Updated spacing in Cargo.toml. +- Added a GitHub actions config so that tests run on every PR/commit. + + + +## 4.8.0 (2022-07-01) + +#### Features + +- Added a progress indicator for `rustlings watch`. +- The installation script now checks for Rustup being installed. +- Added a `rustlings lsp` command to enable `rust-analyzer`. + +#### Bug Fixes + +- **move_semantics5**: Replaced "in vogue" with "in scope" in hint. +- **if2**: Fixed a typo in the hint. +- **variables1**: Fixed an incorrect line reference in the hint. +- Fixed an out of bounds check in the installation Bash script. + +#### Housekeeping + +- Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting. +- Removed the deprecated Rust GitPod extension. + + + +## 4.7.1 (2022-04-20) + +#### Features + +- The amount of dependency crates that need to be compiled went down from ~65 to + ~45 by bumping dependency versions. +- The minimum Rust version in the install scripts has been bumped to 1.56.0 (this isn't in + the release itself, since install scripts don't really get versioned) + +#### Bug Fixes + +- **arc1**: A small part has been rewritten using a more functional code style (#968). +- **using_as**: A small part has been refactored to use `sum` instead of `fold`, resulting + in better readability. + +#### Housekeeping + +- The changelog will now be manually written instead of being automatically generated by the + Git log. + + + +## 4.7.0 (2022-04-14) + +#### Features + +- Add move_semantics6.rs exercise (#908) ([3f0e1303](https://github.com/rust-lang/rustlings/commit/3f0e1303e0b3bf3fecc0baced3c8b8a37f83c184)) +- **intro:** Add intro section. ([21c9f441](https://github.com/rust-lang/rustlings/commit/21c9f44168394e08338fd470b5f49b1fd235986f)) +- Include exercises folder in the project structure behind a feature, enabling rust-analyzer to work (#917) ([179a75a6](https://github.com/rust-lang/rustlings/commit/179a75a68d03ac9518dec2297fb17f91a4fc506b)) + +#### Bug Fixes + +- Fix a few spelling mistakes ([1c0fe3cb](https://github.com/rust-lang/rustlings/commit/1c0fe3cbcca85f90b3985985b8e265ee872a2ab2)) +- **cli:** + - Move long text strings into constants. ([f78c4802](https://github.com/rust-lang/rustlings/commit/f78c48020830d7900dd8d81f355606581670446d)) + - Replace `filter_map()` with `find_map()` ([9b27e8d](https://github.com/rust-lang/rustlings/commit/9b27e8d993ca20232fe38a412750c3f845a83b65)) +- **clippy1:** + - Set clippy::float_cmp lint to deny (#907) ([71a06044](https://github.com/rust-lang/rustlings/commit/71a06044e6a96ff756dc31d7b0ed665ae4badb57)) + - Updated code to test correctness clippy lint with approx_constant lint rule ([f2650de3](https://github.com/rust-lang/rustlings/commit/f2650de369810867d2763e935ac0963c32ec420e)) +- **errors1:** + - Add a comment to make the purpose more clear (#486) ([cbcde345](https://github.com/rust-lang/rustlings/commit/cbcde345409c3e550112e449242848eaa3391bb6)) + - Don't modify tests (#958) ([60bb7cc](https://github.com/rust-lang/rustlings/commit/60bb7cc3931d21d3986ad52b2b302e632a93831c)) +- **errors6:** Remove existing answer code ([43d0623](https://github.com/rust-lang/rustlings/commit/43d0623086edbc46fe896ba59c7afa22c3da9f7a)) +- **functions5:** Remove wrong new line and small English improvements (#885) ([8ef4869b](https://github.com/rust-lang/rustlings/commit/8ef4869b264094e5a9b50452b4534823a9df19c3)) +- **install:** protect path with whitespaces using quotes and stop at the first error ([d114847f](https://github.com/rust-lang/rustlings/commit/d114847f256c5f571c0b4c87e04b04bce3435509)) +- **intro1:** Add compiler error explanation. ([9b8de655](https://github.com/rust-lang/rustlings/commit/9b8de65525a5576b78cf0c8e4098cdd34296338f)) +- **iterators1:** reorder TODO steps ([0bd7a063](https://github.com/rust-lang/rustlings/commit/0bd7a0631a17a9d69af5746795a30efc9cf64e6e)) +- **move_semantics2:** Add comment ([89650f80](https://github.com/rust-lang/rustlings/commit/89650f808af23a32c9a2c6d46592b77547a6a464)) +- **move_semantics5:** correct typo (#857) ([46c28d5c](https://github.com/rust-lang/rustlings/commit/46c28d5cef3d8446b5a356b19d8dbc725f91a3a0)) +- **quiz1:** update to say quiz covers "If" ([1622e8c1](https://github.com/rust-lang/rustlings/commit/1622e8c198d89739765c915203efff0091bdeb78)) +- **structs3:** + - Add a hint for panic (#608) ([4f7ff5d9](https://github.com/rust-lang/rustlings/commit/4f7ff5d9c7b2d8b045194c1a9469d37e30257c4a)) + - remove redundant 'return' (#852) ([bf33829d](https://github.com/rust-lang/rustlings/commit/bf33829da240375d086f96267fc2e02fa6b07001)) + - Assigned value to `cents_per_gram` in test ([d1ee2da](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532)) +- **structs3.rs:** assigned value to cents_per_gram in test ([d1ee2daf](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532)) +- **traits1:** rename test functions to snake case (#854) ([1663a16e](https://github.com/rust-lang/rustlings/commit/1663a16eade6ca646b6ed061735f7982434d530d)) + +#### Documentation improvements + +- Add hints on how to get GCC installed (#741) ([bc56861](https://github.com/rust-lang/rustlings/commit/bc5686174463ad6f4f6b824b0e9b97c3039d4886)) +- Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929)) + + + +## 4.6.0 (2021-09-25) + +#### Features + +- add advanced_errs2 ([abd6b70c](https://github.com/rust-lang/rustlings/commit/abd6b70c72dc6426752ff41f09160b839e5c449e)) +- add advanced_errs1 ([882d535b](https://github.com/rust-lang/rustlings/commit/882d535ba8628d5e0b37e8664b3e2f26260b2671)) +- Add a farewell message when quitting `watch` ([1caef0b4](https://github.com/rust-lang/rustlings/commit/1caef0b43494c8b8cdd6c9260147e70d510f1aca)) +- add more watch commands ([a7dc080b](https://github.com/rust-lang/rustlings/commit/a7dc080b95e49146fbaafe6922a6de2f8cb1582a), closes [#842](https://github.com/rust-lang/rustlings/issues/842)) +- **modules:** update exercises, add modules3 (#822) ([dfd2fab4](https://github.com/rust-lang/rustlings/commit/dfd2fab4f33d1bf59e2e5ee03123c0c9a67a9481)) +- **quiz1:** add default function name in comment (#838) ([0a11bad7](https://github.com/rust-lang/rustlings/commit/0a11bad71402b5403143d642f439f57931278c07)) + +#### Bug Fixes + +- Correct small typo in exercises/conversions/from_str.rs ([86cc8529](https://github.com/rust-lang/rustlings/commit/86cc85295ae36948963ae52882e285d7e3e29323)) +- **cli:** typo in exercise.rs (#848) ([06d5c097](https://github.com/rust-lang/rustlings/commit/06d5c0973a3dffa3c6c6f70acb775d4c6630323c)) +- **from_str, try_from_into:** custom error types ([2dc93cad](https://github.com/rust-lang/rustlings/commit/2dc93caddad43821743e4903d89b355df58d7a49)) +- **modules2:** fix typo (#835) ([1c3beb0a](https://github.com/rust-lang/rustlings/commit/1c3beb0a59178c950dc05fe8ee2346b017429ae0)) +- **move_semantics5:** + - change &mut \*y to &mut x (#814) ([d75759e8](https://github.com/rust-lang/rustlings/commit/d75759e829fdcd64ef071cf4b6eae2a011a7718b)) + - Clarify instructions ([df25684c](https://github.com/rust-lang/rustlings/commit/df25684cb79f8413915e00b5efef29369849cef1)) +- **quiz1:** Fix inconsistent wording (#826) ([03131a3d](https://github.com/rust-lang/rustlings/commit/03131a3d35d9842598150f9da817f7cc26e2669a)) + + + +## 4.5.0 (2021-07-07) + +#### Features + +- Add move_semantics5 exercise. (#746) ([399ab328](https://github.com/rust-lang/rustlings/commit/399ab328d8d407265c09563aa4ef4534b2503ff2)) +- **cli:** Add "next" to run the next unsolved exercise. (#785) ([d20e413a](https://github.com/rust-lang/rustlings/commit/d20e413a68772cd493561f2651cf244e822b7ca5)) + +#### Bug Fixes + +- rename result1 to errors4 ([50ab289d](https://github.com/rust-lang/rustlings/commit/50ab289da6b9eb19a7486c341b00048c516b88c0)) +- move_semantics5 hints ([1b858285](https://github.com/rust-lang/rustlings/commit/1b85828548f46f58b622b5e0c00f8c989f928807)) +- remove trailing whitespaces from iterators1 ([4d4fa774](https://github.com/rust-lang/rustlings/commit/4d4fa77459392acd3581c6068aa8be9a02de12fc)) +- add hints to generics1 and generics2 exercises ([31457940](https://github.com/rust-lang/rustlings/commit/31457940846b3844d78d4a4d2b074bc8d6aaf1eb)) +- remove trailing whitespace ([d9b69bd1](https://github.com/rust-lang/rustlings/commit/d9b69bd1a0a7a99f2c0d80933ad2eea44c8c71b2)) +- **installation:** first PowerShell command ([aa9a943d](https://github.com/rust-lang/rustlings/commit/aa9a943ddf3ae260782e73c26bcc9db60e5894b6)) +- **iterators5:** derive Clone, Copy ([91fc9e31](https://github.com/rust-lang/rustlings/commit/91fc9e3118f4af603c9911698cc2a234725cb032)) +- **quiz1:** Updated question description (#794) ([d8766496](https://github.com/rust-lang/rustlings/commit/d876649616cc8a8dd5f539f8bc1a5434b960b1e9)) +- **try_from_into, from_str:** hints for dyn Error ([11d2cf0d](https://github.com/rust-lang/rustlings/commit/11d2cf0d604dee3f5023c17802d69438e69fa50e)) +- **variables5:** confine the answer further ([48ffcbd2](https://github.com/rust-lang/rustlings/commit/48ffcbd2c4cc4d936c2c7480019190f179813cc5)) + + + +## 4.4.0 (2021-04-24) + +#### Bug Fixes + +- Fix spelling error in main.rs ([91ee27f2](https://github.com/rust-lang/rustlings/commit/91ee27f22bd3797a9db57e5fd430801c170c5db8)) +- typo in default out text ([644c49f1](https://github.com/rust-lang/rustlings/commit/644c49f1e04cbb24e95872b3a52b07d692ae3bc8)) +- **collections:** Naming exercises for vectors and hashmap ([bef39b12](https://github.com/rust-lang/rustlings/commit/bef39b125961310b34b34871e480a82e82af4678)) +- **from_str:** + - Correct typos ([5f7c89f8](https://github.com/rust-lang/rustlings/commit/5f7c89f85db1f33da01911eaa479c3a2d4721678)) + - test for error instead of unwrap/should_panic ([15e71535](https://github.com/rust-lang/rustlings/commit/15e71535f37cfaed36e22eb778728d186e2104ab)) + - use trait objects for from_str ([c3e7b831](https://github.com/rust-lang/rustlings/commit/c3e7b831786c9172ed8bd5d150f3c432f242fba9)) +- **functions3:** improve function argument type (#687) ([a6509cc4](https://github.com/rust-lang/rustlings/commit/a6509cc4d545d8825f01ddf7ee37823b372154dd)) +- **hashmap2:** Update incorrect assertion (#660) ([72aaa15e](https://github.com/rust-lang/rustlings/commit/72aaa15e6ab4b72b3422f1c6356396e20a2a2bb8)) +- **info:** Fix typo (#635) ([cddc1e86](https://github.com/rust-lang/rustlings/commit/cddc1e86e7ec744ee644cc774a4887b1a0ded3e8)) +- **iterators2:** Moved errors out of tests. ([baf4ba17](https://github.com/rust-lang/rustlings/commit/baf4ba175ba6eb92989e3dd54ecbec4bedc9a863), closes [#359](https://github.com/rust-lang/rustlings/issues/359)) +- **iterators3:** Enabled iterators3.rs to run without commented out tests. ([c6712dfc](https://github.com/rust-lang/rustlings/commit/c6712dfccd1a093e590ad22bbc4f49edc417dac0)) +- **main:** Let find_exercise work with borrows ([347f30bd](https://github.com/rust-lang/rustlings/commit/347f30bd867343c5ace1097e085a1f7e356553f7)) +- **move_semantics4:** + - Remove redundant "instead" (#640) ([cc266d7d](https://github.com/rust-lang/rustlings/commit/cc266d7d80b91e79df3f61984f231b7f1587218e)) + - Small readbility improvement (#617) ([10965920](https://github.com/rust-lang/rustlings/commit/10965920fbdf8a1efc85bed869e55a1787006404)) +- **option2:** Rename uninformative variables (#675) ([b4de6594](https://github.com/rust-lang/rustlings/commit/b4de6594380636817d13c2677ec6f472a964cf43)) +- **quiz3:** Force an answer to Q2 (#672) ([0d894e6f](https://github.com/rust-lang/rustlings/commit/0d894e6ff739943901e1ae8c904582e5c2f843bd)) +- **structs:** Add 5.3 to structs/README (#652) ([6bd791f2](https://github.com/rust-lang/rustlings/commit/6bd791f2f44aa7f0ad926df767f6b1fa8f12a9a9)) +- **structs2:** correct grammar in hint (#663) ([ebdb66c7](https://github.com/rust-lang/rustlings/commit/ebdb66c7bfb6d687a14cc511a559a222e6fc5de4)) +- **structs3:** + - reword heading comment (#664) ([9f3e8c2d](https://github.com/rust-lang/rustlings/commit/9f3e8c2dde645e5264c2d2200e68842b5f47bfa3)) + - add check to prevent naive implementation of is_international ([05a753fe](https://github.com/rust-lang/rustlings/commit/05a753fe6333d36dbee5f68c21dec04eacdc75df)) +- **threads1:** line number correction ([7857b0a6](https://github.com/rust-lang/rustlings/commit/7857b0a689b0847f48d8c14cbd1865e3b812d5ca)) +- **try_from_into:** use trait objects ([2e93a588](https://github.com/rust-lang/rustlings/commit/2e93a588e0abe8badb7eafafb9e7d073c2be5df8)) + +#### Features + +- Replace clap with argh ([7928122f](https://github.com/rust-lang/rustlings/commit/7928122fcef9ca7834d988b1ec8ca0687478beeb)) +- Replace emojis when NO_EMOJI env variable present ([8d62a996](https://github.com/rust-lang/rustlings/commit/8d62a9963708dbecd9312e8bcc4b47049c72d155)) +- Added iterators5.rs exercise. ([b29ea17e](https://github.com/rust-lang/rustlings/commit/b29ea17ea94d1862114af2cf5ced0e09c197dc35)) +- **arc1:** Add more details to description and hint (#710) ([81be4044](https://github.com/rust-lang/rustlings/commit/81be40448777fa338ebced3b0bfc1b32d6370313)) +- **cli:** Improve the list command with options, and then some ([8bbe4ff1](https://github.com/rust-lang/rustlings/commit/8bbe4ff1385c5c169c90cd3ff9253f9a91daaf8e)) +- **list:** + - updated progress percentage ([1c6f7e4b](https://github.com/rust-lang/rustlings/commit/1c6f7e4b7b9b3bd36f4da2bb2b69c549cc8bd913)) + - added progress info ([c0e3daac](https://github.com/rust-lang/rustlings/commit/c0e3daacaf6850811df5bc57fa43e0f249d5cfa4)) + + + +## 4.3.0 (2020-12-29) + +#### Features + +- Rewrite default out text ([44d39112](https://github.com/rust-lang/rustlings/commit/44d39112ff122b29c9793fe52e605df1612c6490)) +- match exercise order to book chapters (#541) ([033bf119](https://github.com/rust-lang/rustlings/commit/033bf1198fc8bfce1b570e49da7cde010aa552e3)) +- Crab? (#586) ([fa9f522b](https://github.com/rust-lang/rustlings/commit/fa9f522b7f043d7ef73a39f003a9272dfe72c4f4)) +- add "rustlings list" command ([838f9f30](https://github.com/rust-lang/rustlings/commit/838f9f30083d0b23fd67503dcf0fbeca498e6647)) +- **try_from_into:** remove duplicate annotation ([04f1d079](https://github.com/rust-lang/rustlings/commit/04f1d079aa42a2f49af694bc92c67d731d31a53f)) + +#### Bug Fixes + +- update structs README ([bcf14cf6](https://github.com/rust-lang/rustlings/commit/bcf14cf677adb3a38a3ac3ca53f3c69f61153025)) +- added missing exercises to info.toml ([90cfb6ff](https://github.com/rust-lang/rustlings/commit/90cfb6ff28377531bfc34acb70547bdb13374f6b)) +- gives a bit more context to magic number ([30644c9a](https://github.com/rust-lang/rustlings/commit/30644c9a062b825c0ea89435dc59f0cad86b110e)) +- **functions2:** Change signature to trigger precise error message: (#605) ([0ef95947](https://github.com/rust-lang/rustlings/commit/0ef95947cc30482e63a7045be6cc2fb6f6dcb4cc)) +- **structs1:** Adjust wording (#573) ([9334783d](https://github.com/rust-lang/rustlings/commit/9334783da31d821cc59174fbe8320df95828926c)) +- **try_from_into:** + - type error ([4f4cfcf3](https://github.com/rust-lang/rustlings/commit/4f4cfcf3c36c8718c7c170c9c3a6935e6ef0618c)) + - Update description (#584) ([96347df9](https://github.com/rust-lang/rustlings/commit/96347df9df294f01153b29d9ad4ba361f665c755)) +- **vec1:** Have test compare every element in a and v ([9b6c6293](https://github.com/rust-lang/rustlings/commit/9b6c629397b24b944f484f5b2bbd8144266b5695)) + + + +## 4.2.0 (2020-11-07) + +#### Features + +- Add HashMap exercises ([633c00cf](https://github.com/rust-lang/rustlings/commit/633c00cf8071e1e82959a3010452a32f34f29fc9)) +- Add Vec exercises ([0c12fa31](https://github.com/rust-lang/rustlings/commit/0c12fa31c57c03c6287458a0a8aca7afd057baf6)) +- **primitive_types6:** Add a test (#548) ([2b1fb2b7](https://github.com/rust-lang/rustlings/commit/2b1fb2b739bf9ad8d6b7b12af25fee173011bfc4)) +- **try_from_into:** Add tests (#571) ([95ccd926](https://github.com/rust-lang/rustlings/commit/95ccd92616ae79ba287cce221101e0bbe4f68cdc)) + +#### Bug Fixes + +- log error output when inotify limit is exceeded ([d61b4e5a](https://github.com/rust-lang/rustlings/commit/d61b4e5a13b44d72d004082f523fa1b6b24c1aca)) +- more unique temp_file ([5643ef05](https://github.com/rust-lang/rustlings/commit/5643ef05bc81e4a840e9456f4406a769abbe1392)) +- **installation:** Update the MinRustVersion ([21bfb2d4](https://github.com/rust-lang/rustlings/commit/21bfb2d4777429c87d8d3b5fbf0ce66006dcd034)) +- **iterators2:** Update description (#578) ([197d3a3d](https://github.com/rust-lang/rustlings/commit/197d3a3d8961b2465579218a6749b2b2cefa8ddd)) +- **primitive_types6:** + - remove 'unused doc comment' warning ([472d8592](https://github.com/rust-lang/rustlings/commit/472d8592d65c8275332a20dfc269e7ac0d41bc88)) + - missing comma in test ([4fb230da](https://github.com/rust-lang/rustlings/commit/4fb230daf1251444fcf29e085cee222a91f8a37e)) +- **quiz3:** Second test is for odd numbers, not even. (#553) ([18e0bfef](https://github.com/rust-lang/rustlings/commit/18e0bfef1de53071e353ba1ec5837002ff7290e6)) + + + +## 4.1.0 (2020-10-05) + +#### Bug Fixes + +- Update rustlings version in Cargo.lock ([1cc40bc9](https://github.com/rust-lang/rustlings/commit/1cc40bc9ce95c23d56f6d91fa1c4deb646231fef)) +- **arc1:** index mod should equal thread count ([b4062ef6](https://github.com/rust-lang/rustlings/commit/b4062ef6993e80dac107c4093ea85166ad3ee0fa)) +- **enums3:** Update Message::ChangeColor to take a tuple. (#457) ([4b6540c7](https://github.com/rust-lang/rustlings/commit/4b6540c71adabad647de8a09e57295e7c7c7d794)) +- **exercises:** adding question mark to quiz2 ([101072ab](https://github.com/rust-lang/rustlings/commit/101072ab9f8c80b40b8b88cb06cbe38aca2481c5)) +- **generics3:** clarify grade change ([47f7672c](https://github.com/rust-lang/rustlings/commit/47f7672c0307732056e7426e81d351f0dd7e22e5)) +- **structs3:** Small adjustment of variable name ([114b54cb](https://github.com/rust-lang/rustlings/commit/114b54cbdb977234b39e5f180d937c14c78bb8b2)) +- **using_as:** Add test so that proper type is returned. (#512) ([3286c5ec](https://github.com/rust-lang/rustlings/commit/3286c5ec19ea5fb7ded81d047da5f8594108a490)) + +#### Features + +- Added iterators1.rs exercise ([9642f5a3](https://github.com/rust-lang/rustlings/commit/9642f5a3f686270a4f8f6ba969919ddbbc4f7fdd)) +- Add ability to run rustlings on repl.it (#471) ([8f7b5bd0](https://github.com/rust-lang/rustlings/commit/8f7b5bd00eb83542b959830ef55192d2d76db90a)) +- Add gitpod support (#473) ([4821a8be](https://github.com/rust-lang/rustlings/commit/4821a8be94af4f669042a06ab917934cfacd032f)) +- Remind the user of the hint option (#425) ([816b1f5e](https://github.com/rust-lang/rustlings/commit/816b1f5e85d6cc6e72673813a85d0ada2a8f84af)) +- Remind the user of the hint option (#425) ([9f61db5d](https://github.com/rust-lang/rustlings/commit/9f61db5dbe38538cf06571fcdd5f806e7901e83a)) +- **cli:** Added 'cls' command to 'watch' mode (#474) ([4f2468e1](https://github.com/rust-lang/rustlings/commit/4f2468e14f574a93a2e9b688367b5752ed96ae7b)) +- **try_from_into:** Add insufficient length test (#469) ([523d18b8](https://github.com/rust-lang/rustlings/commit/523d18b873a319f7c09262f44bd40e2fab1830e5)) + + + +## 4.0.0 (2020-07-08) + +#### Breaking Changes + +- Add a --nocapture option to display test harnesses' outputs ([8ad5f9bf](https://github.com/rust-lang/rustlings/commit/8ad5f9bf531a4848b1104b7b389a20171624c82f)) +- Rename test to quiz, fixes #244 ([010a0456](https://github.com/rust-lang/rustlings/commit/010a04569282149cea7f7a76fc4d7f4c9f0f08dd)) + +#### Features + +- Add traits README ([173bb141](https://github.com/rust-lang/rustlings/commit/173bb14140c5530cbdb59e53ace3991a99d804af)) +- Add box1.rs exercise ([7479a473](https://github.com/rust-lang/rustlings/commit/7479a4737bdcac347322ad0883ca528c8675e720)) +- Rewrite try_from_into (#393) ([763aa6e3](https://github.com/rust-lang/rustlings/commit/763aa6e378a586caae2d8d63755a85eeba227933)) +- Add if2 exercise ([1da84b5f](https://github.com/rust-lang/rustlings/commit/1da84b5f7c489f65bd683c244f13c7d1ee812df0)) +- Added exercise structs3.rs ([b66e2e09](https://github.com/rust-lang/rustlings/commit/b66e2e09622243e086a0f1258dd27e1a2d61c891)) +- Add exercise variables6 covering const (#352) ([5999acd2](https://github.com/rust-lang/rustlings/commit/5999acd24a4f203292be36e0fd18d385887ec481)) + +#### Bug Fixes + +- Change then to than ([ddd98ad7](https://github.com/rust-lang/rustlings/commit/ddd98ad75d3668fbb10eff74374148aa5ed2344d)) +- rename quiz1 to tests1 in info (#420) ([0dd1c6ca](https://github.com/rust-lang/rustlings/commit/0dd1c6ca6b389789e0972aa955fe17aa15c95f29)) +- fix quiz naming inconsistency (#421) ([5563adbb](https://github.com/rust-lang/rustlings/commit/5563adbb890587fc48fbbc9c4028642687f1e85b)) +- confine the user further in variable exercises ([06ef4cc6](https://github.com/rust-lang/rustlings/commit/06ef4cc654e75d22a526812919ee49b8956280bf)) +- update iterator and macro text for typos and clarity ([95900828](https://github.com/rust-lang/rustlings/commit/959008284834bece0196a01e17ac69a7e3590116)) +- update generics2 closes #362 ([964c974a](https://github.com/rust-lang/rustlings/commit/964c974a0274199d755073b917c2bc5da0c9b4f1)) +- confusing comment in conversions/try_from_into.rs ([c9e4f2cf](https://github.com/rust-lang/rustlings/commit/c9e4f2cfb4c48d0b7451263cfb43b9426438122d)) +- **arc1:** Passively introduce attributes (#429) ([113cdae2](https://github.com/rust-lang/rustlings/commit/113cdae2d4e4c55905e8056ad326ede7fd7de356)) +- **box1:** fix comment typo (#426) ([bb2ca251](https://github.com/rust-lang/rustlings/commit/bb2ca251106b27a7272d9a30872904dd1376654c)) +- **errorsn:** Try harder to confine the user. (#388) ([2b20c8a0](https://github.com/rust-lang/rustlings/commit/2b20c8a0f5774d07c58d110d75879f33fc6273b5)) +- **from_into.rs:** typo ([a901499e](https://github.com/rust-lang/rustlings/commit/a901499ededd3ce1995164700514fe4e9a0373ea)) +- **generics2:** Guide students to the answer (#430) ([e6bd8021](https://github.com/rust-lang/rustlings/commit/e6bd8021d9a7dd06feebc30c9d5f953901d7b419)) +- **installation:** + - Provide a backup git reference when tag can't be curl ([9e4fb100](https://github.com/rust-lang/rustlings/commit/9e4fb1009f1c9e3433915c03e22c2af422e5c5fe)) + - Check if python is available while checking for git,rustc and cargo ([9cfb617d](https://github.com/rust-lang/rustlings/commit/9cfb617d5b0451b4b51644a1298965390cda9884)) +- **option1:** + - Don't add only zeros to the numbers array ([cce6a442](https://github.com/rust-lang/rustlings/commit/cce6a4427718724a9096800754cd3abeca6a1580)) + - Add cast to usize, as it is confusing in the context of an exercise about Option ([f6cffc7e](https://github.com/rust-lang/rustlings/commit/f6cffc7e487b42f15a6f958e49704c93a8d4465b)) +- **option2:** Add TODO to comments (#400) ([10967bce](https://github.com/rust-lang/rustlings/commit/10967bce57682812dc0891a9f9757da1a9d87404)) +- **options1:** Add hint about Array Initialization (#389) ([9f75554f](https://github.com/rust-lang/rustlings/commit/9f75554f2a30295996f03f0160b98c0458305502)) +- **test2:** name of type String and &str (#394) ([d6c0a688](https://github.com/rust-lang/rustlings/commit/d6c0a688e6a96f93ad60d540d4b326f342fc0d45)) +- **variables6:** minor typo (#419) ([524e17df](https://github.com/rust-lang/rustlings/commit/524e17df10db95f7b90a0f75cc8997182a8a4094)) + + + +## 3.0.0 (2020-04-11) + +#### Breaking Changes + +- make "compile" exercises print output (#278) ([3b6d5c](https://github.com/fmoko/rustlings/commit/3b6d5c3aaa27a242a832799eb66e96897d26fde3)) + +#### Bug Fixes + +- **primitive_types:** revert primitive_types4 (#296) ([b3a3351e](https://github.com/rust-lang/rustlings/commit/b3a3351e8e6a0bdee07077d7b0382953821649ae)) +- **run:** compile clippy exercise files (#295) ([3ab084a4](https://github.com/rust-lang/rustlings/commit/3ab084a421c0f140ae83bf1fc3f47b39342e7373)) +- **conversions:** + - add additional test to meet exercise rules (#284) ([bc22ec3](https://github.com/fmoko/rustlings/commit/bc22ec382f843347333ef1301fc1bad773657f38)) + - remove duplicate not done comment (#292) ([dab90f](https://github.com/fmoko/rustlings/commit/dab90f7b91a6000fe874e3d664f244048e5fa342)) +- don't hardcode documentation version for traits (#288) ([30e6af](https://github.com/fmoko/rustlings/commit/30e6af60690c326fb5d3a9b7335f35c69c09137d)) + +#### Features + +- add Option2 exercise (#290) ([86b5c08b](https://github.com/rust-lang/rustlings/commit/86b5c08b9bea1576127a7c5f599f5752072c087d)) +- add exercise for option (#282) ([135e5d47](https://github.com/rust-lang/rustlings/commit/135e5d47a7c395aece6f6022117fb20c82f2d3d4)) +- add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229)) +- **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71)) + + + +### 2.2.1 (2020-02-27) + +#### Bug Fixes + +- Re-add cloning the repo to install scripts ([3d9b03c5](https://github.com/rust-lang/rustlings/commit/3d9b03c52b8dc51b140757f6fd25ad87b5782ef5)) + +#### Features + +- Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921)) + + + +## 2.2.0 (2020-02-25) + +#### Bug Fixes + +- Update deps to version compatable with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401)) +- **docs:** + - Added a necessary step to Windows installation process (#242) ([3906efcd](https://github.com/rust-lang/rustlings/commit/3906efcd52a004047b460ed548037093de3f523f)) + - Fixed mangled sentence from book; edited for clarity (#266) ([ade52ff](https://github.com/rust-lang/rustlings/commit/ade52ffb739987287ddd5705944c8777705faed9)) + - Updated iterators readme to account for iterators4 exercise (#273) ([bec8e3a](https://github.com/rust-lang/rustlings/commit/bec8e3a644cbd88db1c73ea5f1d8a364f4a34016)) +- **installation:** make fatal errors more obvious (#272) ([17d0951e](https://github.com/rust-lang/rustlings/commit/17d0951e66fda8e11b204d5c4c41a0d5e22e78f7)) +- **iterators2:** + - Remove reference to missing iterators2.rs (#245) ([419f7797](https://github.com/rust-lang/rustlings/commit/419f7797f294e4ce6a2b883199731b5bde77d262)) +- **as_ref_mut:** Enable a test and improve per clippy's suggestion (#256) ([dfdf809](https://github.com/rust-lang/rustlings/commit/dfdf8093ebbd4145864995627b812780de52f902)) +- **tests1:** + - Change test command ([fe10e06c](https://github.com/rust-lang/rustlings/commit/fe10e06c3733ddb4a21e90d09bf79bfe618e97ce) + - Correct test command in tests1.rs comment (#263) ([39fa7ae](https://github.com/rust-lang/rustlings/commit/39fa7ae8b70ad468da49b06f11b2383135a63bcf)) + +#### Features + +- Add variables5.rs exercise (#264) ([0c73609e](https://github.com/rust-lang/rustlings/commit/0c73609e6f2311295e95d6f96f8c747cfc4cba03)) +- Show a completion message when watching (#253) ([d25ee55a](https://github.com/rust-lang/rustlings/commit/d25ee55a3205882d35782e370af855051b39c58c)) +- Add type conversion and parsing exercises (#249) ([0c85dc11](https://github.com/rust-lang/rustlings/commit/0c85dc1193978b5165491b99cc4922caf8d14a65)) +- Created consistent money unit (#258) ([fd57f8f](https://github.com/rust-lang/rustlings/commit/fd57f8f2c1da2af8ddbebbccec214e6f40f4dbab)) +- Enable test for exercise test4 (#276) ([8b971ff](https://github.com/rust-lang/rustlings/commit/8b971ffab6079a706ac925f5917f987932b55c07)) +- Added traits exercises (#274 but specifically #216, which originally added + this :heart:) ([b559cdd](https://github.com/rust-lang/rustlings/commit/b559cdd73f32c0d0cfc1feda39f82b3e3583df17)) + + + +## 2.1.0 (2019-11-27) + +#### Bug Fixes + +- add line numbers in several exercises and hints ([b565c4d3](https://github.com/rust-lang/rustlings/commit/b565c4d3e74e8e110bef201a082fa1302722a7c3)) +- **arc1:** Fix some words in the comment ([c42c3b21](https://github.com/rust-lang/rustlings/commit/c42c3b2101df9164c8cd7bb344def921e5ba3e61)) +- **enums:** Add link to chapter on pattern syntax (#242) ([615ce327](https://github.com/rust-lang/rustlings/commit/615ce3279800c56d89f19d218ccb7ef576624feb)) +- **primitive_types4:** + - update outdated hint ([4c5189df](https://github.com/rust-lang/rustlings/commit/4c5189df2bdd9a231f6b2611919ba5aa14da0d3f)) + - update outdated comment ([ded2c034](https://github.com/rust-lang/rustlings/commit/ded2c034ba93fa1e3c2c2ea16b83abc1a57265e8)) +- **strings2:** update line number in hint ([a09f684f](https://github.com/rust-lang/rustlings/commit/a09f684f05c58d239a6fc59ec5f81c2533e8b820)) +- **variables1:** Correct wrong word in comment ([fda5a470](https://github.com/rust-lang/rustlings/commit/fda5a47069e0954f16a04e8e50945e03becb71a5)) + +#### Features + +- **watch:** show hint while watching ([8143d57b](https://github.com/rust-lang/rustlings/commit/8143d57b4e88c51341dd4a18a14c536042cc009c)) + + + +## 2.0.0 (2019-11-12) + +#### Bug Fixes + +- **default:** Clarify the installation procedure ([c371b853](https://github.com/rust-lang/rustlings/commit/c371b853afa08947ddeebec0edd074b171eeaae0)) +- **info:** Fix trailing newlines for hints ([795b6e34](https://github.com/rust-lang/rustlings/commit/795b6e348094a898e9227a14f6232f7bb94c8d31)) +- **run:** make `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) + +#### Breaking Changes + +- Refactor hint system ([9bdb0a12](https://github.com/rust-lang/rustlings/commit/9bdb0a12e45a8e9f9f6a4bd4a9c172c5376c7f60)) +- improve `watch` execution mode ([2cdd6129](https://github.com/rust-lang/rustlings/commit/2cdd61294f0d9a53775ee24ad76435bec8a21e60)) +- Index exercises by name ([627cdc07](https://github.com/rust-lang/rustlings/commit/627cdc07d07dfe6a740e885e0ddf6900e7ec336b)) +- **run:** makes `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) + +#### Features + +- **cli:** check for rustc before doing anything ([36a033b8](https://github.com/rust-lang/rustlings/commit/36a033b87a6549c1e5639c908bf7381c84f4f425)) +- **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f)) + + + +### 1.5.1 (2019-11-11) + +#### Bug Fixes + +- **errors3:** Update hint ([dcfb427b](https://github.com/rust-lang/rustlings/commit/dcfb427b09585f0193f0a294443fdf99f11c64cb), closes [#185](https://github.com/rust-lang/rustlings/issues/185)) +- **if1:** Remove `return` reference ([ad03d180](https://github.com/rust-lang/rustlings/commit/ad03d180c9311c0093e56a3531eec1a9a70cdb45)) +- **strings:** Move Strings before Structs ([6dcecb38](https://github.com/rust-lang/rustlings/commit/6dcecb38a4435593beb87c8e12d6314143631482), closes [#204](https://github.com/rust-lang/rustlings/issues/204)) +- **structs1:** Remove misleading comment ([f72e5a8f](https://github.com/rust-lang/rustlings/commit/f72e5a8f05568dde04eaeac10b9a69872f21cb37)) +- **threads:** Move Threads behind SLT ([fbe91a67](https://github.com/rust-lang/rustlings/commit/fbe91a67a482bfe64cbcdd58d06ba830a0f39da3), closes [#205](https://github.com/rust-lang/rustlings/issues/205)) +- **watch:** clear screen before each `verify()` ([3aff590](https://github.com/rust-lang/rustlings/commit/3aff59085586c24196a547c2693adbdcf4432648)) + + + +## 1.5.0 (2019-11-09) + +#### Bug Fixes + +- **test1:** Rewrite logic ([79a56942](https://github.com/rust-lang/rustlings/commit/79a569422c8309cfc9e4aed25bf4ab3b3859996b)) +- **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) +- **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6)) +- **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac)) +- **option1:** + - Fix arguments passed to assert! macro (#222) ([4c2cf6da](https://github.com/rust-lang/rustlings/commit/4c2cf6da755efe02725e05ecc3a303304c10a6da)) + - Fix arguments passed to assert! macro ([ead4f7af](https://github.com/rust-lang/rustlings/commit/ead4f7af9e10e53418efdde5c359159347282afd)) + - Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) +- **primitive_types4:** Fail on a slice covering the wrong area ([5b1e673c](https://github.com/rust-lang/rustlings/commit/5b1e673cec1658afc4ebbbc800213847804facf5)) +- **readme:** http to https ([70946b85](https://github.com/rust-lang/rustlings/commit/70946b85e536e80e70ed9505cb650ca0a3a1fbb5)) +- **test1:** + - Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) + - renamed function name to snake case closes #180 ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b)) + +#### Features + +- Add enums exercises ([dc150321](https://github.com/rust-lang/rustlings/commit/dc15032112fc485226a573a18139e5ce928b1755)) +- Added exercise for struct update syntax ([1c4c8764](https://github.com/rust-lang/rustlings/commit/1c4c8764ed118740cd4cee73272ddc6cceb9d959)) +- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) + + + +### 1.4.1 (2019-08-13) + +#### Bug Fixes + +- **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac)) +- **option1:** Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) +- **test1:** Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) + + + +## 1.4.0 (2019-07-13) + +#### Bug Fixes + +- **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) +- **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6)) +- **readme:** http to https ([70946b85](https://github.com/rust-lang/rustlings/commit/70946b85e536e80e70ed9505cb650ca0a3a1fbb5)) +- **test1:** renamed function name to snake case ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b)) +- **cli:** Check if changed exercise file exists before calling verify ([ba85ca3](https://github.com/rust-lang/rustlings/commit/ba85ca32c4cfc61de46851ab89f9c58a28f33c88)) +- **structs1:** Fix the irrefutable let pattern warning ([cc6a141](https://github.com/rust-lang/rustlings/commit/cc6a14104d7c034eadc98297eaaa972d09c50b1f)) + +#### Features + +- **changelog:** Use clog for changelogs ([34e31232](https://github.com/rust-lang/rustlings/commit/34e31232dfddde284a341c9609b33cd27d9d5724)) +- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) + + + +### 1.3.0 (2019-06-05) + +#### Features + +- Adds a simple exercise for structures (#163, @briankung) + +#### Bug Fixes + +- Add Result type signature as it is difficult for new comers to understand Generics and Error all at once. (#157, @veggiemonk) +- Rustfmt and whitespace fixes (#161, @eddyp) +- errorsn.rs: Separate also the hints from each other to avoid accidental viewing (#162, @eddyp) +- fixed outdated links (#165, @gushroom) +- Fix broken link (#164, @HanKruiger) +- Remove highlighting and syntect (#167, @komaeda) + + + +### 1.2.2 (2019-05-07) + +#### Bug Fixes + +- Reverted `--nocapture` flag since it was causing tests to pass unconditionally + + + +### 1.2.1 (2019-04-22) + +#### Bug Fixes + +- Fix the `--nocapture` feature (@komaeda) +- Provide a nicer error message for when you're in the wrong directory + + + +### 1.2.0 (2019-04-22) + +#### Features + +- Add errors to exercises that compile without user changes (@yvan-sraka) +- Use --nocapture when testing, enabling `println!` when running (@komaeda) + + + +### 1.1.1 (2019-04-14) + +#### Bug fixes + +- Fix permissions on exercise files (@zacanger, #133) +- Make installation checks more thorough (@komaeda, 1b3469f236bc6979c27f6e1a04e4138a88e55de3) +- Fix order of true/false in tests for executables (@mgeier, #137) +- Stop run from panicking when compile fails (@cjpearce, #141) +- Fix intermittent test failure caused by race condition (@cjpearce, #140) +- Fix links by deleting book version (@diodfr, #142) +- Canonicalize paths to fix path matching (@cjpearce, #143) + + + +### 1.1.0 (2019-03-20) + +- errors2.rs: update link to Rust book (#124) +- Start verification at most recently modified file (#120) +- Watch for file creation events in watch mode (#117) +- Add standard library types to exercises suite (#119) +- Give a warning when Rustlings isn't run from the right directory (#123) +- Verify that rust version is recent enough to install Rustlings (#131) + + + +### 1.0.1 (2019-03-06) + +- Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`) +- Makes `rustlings watch` react to create file events (@shaunbennett, #117) +- Reworks the exercise management to use an external TOML file instead of just listing them in the code + + + +### 1.0.0 (2019-03-06) + +Initial release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cc8ac92 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,130 @@ +## Contributing to Rustlings + +First off, thanks for taking the time to contribute!! ❤️ + +### Quick Reference + +I want to... + +_add an exercise! ➡️ [read this](#addex) and then [open a Pull Request](#prs)_ + +_update an outdated exercise! ➡️ [open a Pull Request](#prs)_ + +_report a bug! ➡️ [open an Issue](#issues)_ + +_fix a bug! ➡️ [open a Pull Request](#prs)_ + +_implement a new feature! ➡️ [open an Issue to discuss it first, then a Pull Request](#issues)_ + + +### Working on the source code + +`rustlings` is basically a glorified `rustc` wrapper. Therefore the source code +isn't really that complicated since the bulk of the work is done by `rustc`. +`src/main.rs` contains a simple `argh` CLI that connects to most of the other source files. + + +### Adding an exercise + +The first step is to add the exercise! Name the file `exercises/yourTopic/yourTopicN.rs`, make sure to +put in some helpful links, and link to sections of the book in `exercises/yourTopic/README.md`. + +Next make sure it runs with `rustlings`. The exercise metadata is stored in `info.toml`, under the `exercises` array. The order of the `exercises` array determines the order the exercises are run by `rustlings verify` and `rustlings watch`. + +Add the metadata for your exercise in the correct order in the `exercises` array. If you are unsure of the correct ordering, add it at the bottom and ask in your pull request. The exercise metadata should contain the following: +```diff + ... ++ [[exercises]] ++ name = "yourTopicN" ++ path = "exercises/yourTopic/yourTopicN.rs" ++ mode = "compile" ++ hint = """ ++ Some kind of useful hint for your exercise.""" + ... +``` + +The `mode` attribute decides whether Rustlings will only compile your exercise, or compile and test it. If you have tests to verify in your exercise, choose `test`, otherwise `compile`. If you're working on a Clippy exercise, use `mode = "clippy"`. + +That's all! Feel free to put up a pull request. + + +### Issues + +You can open an issue [here](https://github.com/rust-lang/rustlings/issues/new). +If you're reporting a bug, please include the output of the following commands: + +- `rustc --version` +- `rustlings --version` +- `ls -la` +- Your OS name and version + + +### Pull Requests + +Opening a pull request is as easy as forking the repository and committing your +changes. There's a couple of things to watch out for: + +#### Write correct commit messages + +We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) +specification. +This means that you have to format your commit messages in a specific way. Say +you're working on adding a new exercise called `foobar1.rs`. You could write +the following commit message: + +``` +feat: add foobar1.rs exercise +``` + +If you're just fixing a bug, please use the `fix` type: + +``` +fix(verify): make sure verify doesn't self-destruct +``` + +The scope within the brackets is optional, but should be any of these: + +- `installation` (for the installation script) +- `cli` (for general CLI changes) +- `verify` (for the verification source file) +- `watch` (for the watch functionality source) +- `run` (for the run functionality source) +- `EXERCISENAME` (if you're changing a specific exercise, or set of exercises, + substitute them here) + +When the commit also happens to close an existing issue, link it in the message +body: + +``` +fix: update foobar + +closes #101029908 +``` + +If you're doing simple changes, like updating a book link, use `chore`: + +``` +chore: update exercise1.rs book link +``` + +If you're updating documentation, use `docs`: + +``` +docs: add more information to Readme +``` + +If, and only if, you're absolutely sure you want to make a breaking change +(please discuss this beforehand!), add an exclamation mark to the type and +explain the breaking change in the message body: + +``` +fix!: completely change verification + +BREAKING CHANGE: This has to be done because lorem ipsum dolor +``` + +#### Pull Request Workflow + +Once you open a Pull Request, it may be reviewed or labeled (or both) until +the maintainers accept your change. Please be patient, it may take some time +for this to happen! diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..93489eb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,875 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "assert_cmd" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bstr" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.42.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.48.0", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "home" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" +dependencies = [ + "winapi", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.1", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "notify-debouncer-mini" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43" +dependencies = [ + "crossbeam-channel", + "log", + "notify", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "portable-atomic" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" + +[[package]] +name = "predicates" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro2" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rustlings" +version = "5.6.1" +dependencies = [ + "assert_cmd", + "clap", + "console", + "glob", + "home", + "indicatif", + "notify-debouncer-mini", + "predicates", + "regex", + "serde", + "serde_json", + "toml", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..24bee7c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "rustlings" +description = "Small exercises to get you used to reading and writing Rust code!" +version = "5.6.1" +authors = [ + "Liv ", + "Carol (Nichols || Goulding) ", +] +edition = "2021" + +[dependencies] +indicatif = "0.17.6" +console = "0.15" +notify-debouncer-mini = "0.4.1" +toml = "0.7.6" +regex = "1.5" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.81" +home = "0.5.3" +glob = "0.3.0" +clap = { version = "4.4.0", features = ["derive"] } + +[[bin]] +name = "rustlings" +path = "src/main.rs" + +[dev-dependencies] +assert_cmd = "2.0.12" +predicates = "3.0.3" +glob = "0.3.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..30f5c61 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Carol (Nichols || Goulding) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md index 0906f38..8fac7a2 100644 --- a/README.md +++ b/README.md @@ -1 +1,180 @@ -# Rustling \ No newline at end of file +
+ +# rustlings 🦀❤️ + +
+ +Greetings and welcome to `rustlings`. This project contains small exercises to get you used to reading and writing Rust code. This includes reading and responding to compiler messages! + +_...looking for the old, web-based version of Rustlings? Try [here](https://github.com/rust-lang/rustlings/tree/rustlings-1)_ + +Alternatively, for a first-time Rust learner, there are several other resources: + +- [The Book](https://doc.rust-lang.org/book/index.html) - The most comprehensive resource for learning Rust, but a bit theoretical sometimes. You will be using this along with Rustlings! +- [Rust By Example](https://doc.rust-lang.org/rust-by-example/index.html) - Learn Rust by solving little exercises! It's almost like `rustlings`, but online + +## Getting Started + +_Note: If you're on MacOS, make sure you've installed Xcode and its developer tools by typing `xcode-select --install`._ +_Note: If you're on Linux, make sure you've installed gcc. Deb: `sudo apt install gcc`. Yum: `sudo yum -y install gcc`._ + +You will need to have Rust installed. You can get it by visiting . This'll also install Cargo, Rust's package/project manager. + +## MacOS/Linux + +Just run: + +```bash +curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash +``` + +Or if you want it to be installed to a different path: + +```bash +curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash -s mypath/ +``` + +This will install Rustlings and give you access to the `rustlings` command. Run it to get started! + +### Nix + +Basically: Clone the repository at the latest tag, finally run `nix develop` or `nix-shell`. + +```bash +# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.6.1) +git clone -b 5.6.1 --depth 1 https://github.com/rust-lang/rustlings +cd rustlings +# if nix version > 2.3 +nix develop +# if nix version <= 2.3 +nix-shell +``` + +## Windows + +In PowerShell (Run as Administrator), set `ExecutionPolicy` to `RemoteSigned`: + +```ps1 +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +``` + +Then, you can run: + +```ps1 +Start-BitsTransfer -Source https://raw.githubusercontent.com/rust-lang/rustlings/main/install.ps1 -Destination $env:TMP/install_rustlings.ps1; Unblock-File $env:TMP/install_rustlings.ps1; Invoke-Expression $env:TMP/install_rustlings.ps1 +``` + +To install Rustlings. Same as on MacOS/Linux, you will have access to the `rustlings` command after it. Keep in mind that this works best in PowerShell, and any other terminals may give you errors. + +If you get a permission denied message, you might have to exclude the directory where you cloned Rustlings in your antivirus. + +## Browser + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/rust-lang/rustlings) + +[![Open Rustlings On Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/?repo=rust-lang%2Frustlings&ref=main) + +## Manually + +Basically: Clone the repository at the latest tag, run `cargo install --path .`. + +```bash +# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.6.1) +git clone -b 5.6.1 --depth 1 https://github.com/rust-lang/rustlings +cd rustlings +cargo install --force --path . +``` + +If there are installation errors, ensure that your toolchain is up to date. For the latest, run: + +```bash +rustup update +``` + +Then, same as above, run `rustlings` to get started. + +## Doing exercises + +The exercises are sorted by topic and can be found in the subdirectory `rustlings/exercises/`. For every topic there is an additional README file with some resources to get you started on the topic. We really recommend that you have a look at them before you start. + +The task is simple. Most exercises contain an error that keeps them from compiling, and it's up to you to fix it! Some exercises are also run as tests, but rustlings handles them all the same. To run the exercises in the recommended order, execute: + +```bash +rustlings watch +``` + +This will try to verify the completion of every exercise in a predetermined order (what we think is best for newcomers). It will also rerun automatically every time you change a file in the `exercises/` directory. If you want to only run it once, you can use: + +```bash +rustlings verify +``` + +This will do the same as watch, but it'll quit after running. + +In case you want to go by your own order, or want to only verify a single exercise, you can run: + +```bash +rustlings run myExercise1 +``` + +Or simply use the following command to run the next unsolved exercise in the course: + +```bash +rustlings run next +``` + +In case you get stuck, you can run the following command to get a hint for your +exercise: + +```bash +rustlings hint myExercise1 +``` + +You can also get the hint for the next unsolved exercise with the following command: + +```bash +rustlings hint next +``` + +To check your progress, you can run the following command: + +```bash +rustlings list +``` + +## Testing yourself + +After every couple of sections, there will be a quiz that'll test your knowledge on a bunch of sections at once. These quizzes are found in `exercises/quizN.rs`. + +## Enabling `rust-analyzer` + +Run the command `rustlings lsp` which will generate a `rust-project.json` at the root of the project, this allows [rust-analyzer](https://rust-analyzer.github.io/) to parse each exercise. + +## Continuing On + +Once you've completed Rustlings, put your new knowledge to good use! Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to. + +## Uninstalling Rustlings + +If you want to remove Rustlings from your system, there are two steps. First, you'll need to remove the exercises folder that the install script created +for you: + +```bash +rm -rf rustlings # or your custom folder name, if you chose and or renamed it +``` + +Second, run `cargo uninstall` to remove the `rustlings` binary: + +```bash +cargo uninstall rustlings +``` + +Now you should be done! + +## Contributing + +See [CONTRIBUTING.md](https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md). + +## Contributors ✨ + +Thanks goes to the wonderful people listed in [AUTHORS.md](https://github.com/rust-lang/rustlings/blob/main/AUTHORS.md) 🎉 diff --git a/exercises/00_intro/README.md b/exercises/00_intro/README.md new file mode 100644 index 0000000..d32e4a8 --- /dev/null +++ b/exercises/00_intro/README.md @@ -0,0 +1,8 @@ +# Intro + +Rust uses the `print!` and `println!` macros to print text to the console. + +## Further information + +- [Hello World](https://doc.rust-lang.org/rust-by-example/hello.html) +- [Formatted print](https://doc.rust-lang.org/rust-by-example/hello/print.html) diff --git a/exercises/00_intro/intro1.rs b/exercises/00_intro/intro1.rs new file mode 100644 index 0000000..5dd18b4 --- /dev/null +++ b/exercises/00_intro/intro1.rs @@ -0,0 +1,41 @@ +// intro1.rs +// +// About this `I AM NOT DONE` thing: +// We sometimes encourage you to keep trying things on a given exercise, even +// after you already figured it out. If you got everything working and feel +// ready for the next exercise, remove the `I AM NOT DONE` comment below. +// +// If you're running this using `rustlings watch`: The exercise file will be +// reloaded when you change one of the lines below! Try adding a `println!` +// line, or try changing what it outputs in your terminal. Try removing a +// semicolon and see what happens! +// +// Execute `rustlings hint intro1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + println!("Hello and"); + println!(r#" welcome to... "#); + println!(r#" _ _ _ "#); + println!(r#" _ __ _ _ ___| |_| (_)_ __ __ _ ___ "#); + println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#); + println!(r#" | | | |_| \__ \ |_| | | | | | (_| \__ \ "#); + println!(r#" |_| \__,_|___/\__|_|_|_| |_|\__, |___/ "#); + println!(r#" |___/ "#); + println!(); + println!("This exercise compiles successfully. The remaining exercises contain a compiler"); + println!("or logic error. The central concept behind Rustlings is to fix these errors and"); + println!("solve the exercises. Good luck!"); + println!(); + println!("The source for this exercise is in `exercises/00_intro/intro1.rs`. Have a look!"); + println!( + "Going forward, the source of the exercises will always be in the success/failure output." + ); + println!(); + println!( + "If you want to use rust-analyzer, Rust's LSP implementation, make sure your editor is set" + ); + println!("up, and then run `rustlings lsp` before continuing.") +} diff --git a/exercises/00_intro/intro2.rs b/exercises/00_intro/intro2.rs new file mode 100644 index 0000000..a28ad3d --- /dev/null +++ b/exercises/00_intro/intro2.rs @@ -0,0 +1,12 @@ +// intro2.rs +// +// Make the code print a greeting to the world. +// +// Execute `rustlings hint intro2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + printline!("Hello there!") +} diff --git a/exercises/01_variables/README.md b/exercises/01_variables/README.md new file mode 100644 index 0000000..7964ff2 --- /dev/null +++ b/exercises/01_variables/README.md @@ -0,0 +1,9 @@ +# Variables + +In Rust, variables are immutable by default. +When a variable is immutable, once a value is bound to a name, you can’t change that value. +You can make them mutable by adding `mut` in front of the variable name. + +## Further information + +- [Variables and Mutability](https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html) diff --git a/exercises/01_variables/variables1.rs b/exercises/01_variables/variables1.rs new file mode 100644 index 0000000..b3e089a --- /dev/null +++ b/exercises/01_variables/variables1.rs @@ -0,0 +1,13 @@ +// variables1.rs +// +// Make me compile! +// +// Execute `rustlings hint variables1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + x = 5; + println!("x has the value {}", x); +} diff --git a/exercises/01_variables/variables2.rs b/exercises/01_variables/variables2.rs new file mode 100644 index 0000000..e1c23ed --- /dev/null +++ b/exercises/01_variables/variables2.rs @@ -0,0 +1,15 @@ +// variables2.rs +// +// Execute `rustlings hint variables2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let x; + if x == 10 { + println!("x is ten!"); + } else { + println!("x is not ten!"); + } +} diff --git a/exercises/01_variables/variables3.rs b/exercises/01_variables/variables3.rs new file mode 100644 index 0000000..86bed41 --- /dev/null +++ b/exercises/01_variables/variables3.rs @@ -0,0 +1,11 @@ +// variables3.rs +// +// Execute `rustlings hint variables3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let x: i32; + println!("Number {}", x); +} diff --git a/exercises/01_variables/variables4.rs b/exercises/01_variables/variables4.rs new file mode 100644 index 0000000..5394f39 --- /dev/null +++ b/exercises/01_variables/variables4.rs @@ -0,0 +1,13 @@ +// variables4.rs +// +// Execute `rustlings hint variables4` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let x = 3; + println!("Number {}", x); + x = 5; // don't change this line + println!("Number {}", x); +} diff --git a/exercises/01_variables/variables5.rs b/exercises/01_variables/variables5.rs new file mode 100644 index 0000000..a29b38b --- /dev/null +++ b/exercises/01_variables/variables5.rs @@ -0,0 +1,13 @@ +// variables5.rs +// +// Execute `rustlings hint variables5` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let number = "T-H-R-E-E"; // don't change this line + println!("Spell a Number : {}", number); + number = 3; // don't rename this variable + println!("Number plus two is : {}", number + 2); +} diff --git a/exercises/01_variables/variables6.rs b/exercises/01_variables/variables6.rs new file mode 100644 index 0000000..853183b --- /dev/null +++ b/exercises/01_variables/variables6.rs @@ -0,0 +1,11 @@ +// variables6.rs +// +// Execute `rustlings hint variables6` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +const NUMBER = 3; +fn main() { + println!("Number {}", NUMBER); +} diff --git a/exercises/02_functions/README.md b/exercises/02_functions/README.md new file mode 100644 index 0000000..6662d0d --- /dev/null +++ b/exercises/02_functions/README.md @@ -0,0 +1,8 @@ +# Functions + +Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even +in more complex code. + +## Further information + +- [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html) diff --git a/exercises/02_functions/functions1.rs b/exercises/02_functions/functions1.rs new file mode 100644 index 0000000..40ed9a0 --- /dev/null +++ b/exercises/02_functions/functions1.rs @@ -0,0 +1,10 @@ +// functions1.rs +// +// Execute `rustlings hint functions1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + call_me(); +} diff --git a/exercises/02_functions/functions2.rs b/exercises/02_functions/functions2.rs new file mode 100644 index 0000000..5154f34 --- /dev/null +++ b/exercises/02_functions/functions2.rs @@ -0,0 +1,16 @@ +// functions2.rs +// +// Execute `rustlings hint functions2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + call_me(3); +} + +fn call_me(num:) { + for i in 0..num { + println!("Ring! Call number {}", i + 1); + } +} diff --git a/exercises/02_functions/functions3.rs b/exercises/02_functions/functions3.rs new file mode 100644 index 0000000..74f44d6 --- /dev/null +++ b/exercises/02_functions/functions3.rs @@ -0,0 +1,16 @@ +// functions3.rs +// +// Execute `rustlings hint functions3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + call_me(); +} + +fn call_me(num: u32) { + for i in 0..num { + println!("Ring! Call number {}", i + 1); + } +} diff --git a/exercises/02_functions/functions4.rs b/exercises/02_functions/functions4.rs new file mode 100644 index 0000000..77c4b2a --- /dev/null +++ b/exercises/02_functions/functions4.rs @@ -0,0 +1,28 @@ +// functions4.rs +// +// This store is having a sale where if the price is an even number, you get 10 +// Rustbucks off, but if it's an odd number, it's 3 Rustbucks off. (Don't worry +// about the function bodies themselves, we're only interested in the signatures +// for now. If anything, this is a good way to peek ahead to future exercises!) +// +// Execute `rustlings hint functions4` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let original_price = 51; + println!("Your sale price is {}", sale_price(original_price)); +} + +fn sale_price(price: i32) -> { + if is_even(price) { + price - 10 + } else { + price - 3 + } +} + +fn is_even(num: i32) -> bool { + num % 2 == 0 +} diff --git a/exercises/02_functions/functions5.rs b/exercises/02_functions/functions5.rs new file mode 100644 index 0000000..f1b63f4 --- /dev/null +++ b/exercises/02_functions/functions5.rs @@ -0,0 +1,15 @@ +// functions5.rs +// +// Execute `rustlings hint functions5` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let answer = square(3); + println!("The square of 3 is {}", answer); +} + +fn square(num: i32) -> i32 { + num * num; +} diff --git a/exercises/03_if/README.md b/exercises/03_if/README.md new file mode 100644 index 0000000..b52c392 --- /dev/null +++ b/exercises/03_if/README.md @@ -0,0 +1,7 @@ +# If + +`if`, the most basic (but still surprisingly versatile!) type of control flow, is what you'll learn here. + +## Further information + +- [Control Flow - if expressions](https://doc.rust-lang.org/book/ch03-05-control-flow.html#if-expressions) diff --git a/exercises/03_if/if1.rs b/exercises/03_if/if1.rs new file mode 100644 index 0000000..4734d78 --- /dev/null +++ b/exercises/03_if/if1.rs @@ -0,0 +1,33 @@ +// if1.rs +// +// Execute `rustlings hint if1` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +pub fn bigger(a: i32, b: i32) -> i32 { + // Complete this function to return the bigger number! + // Do not use: + // - another function call + // - additional variables +} + +// Don't mind this for now :) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ten_is_bigger_than_eight() { + assert_eq!(10, bigger(10, 8)); + } + + #[test] + fn fortytwo_is_bigger_than_thirtytwo() { + assert_eq!(42, bigger(32, 42)); + } + + #[test] + fn equal_numbers() { + assert_eq!(42, bigger(42, 42)); + } +} diff --git a/exercises/03_if/if2.rs b/exercises/03_if/if2.rs new file mode 100644 index 0000000..f512f13 --- /dev/null +++ b/exercises/03_if/if2.rs @@ -0,0 +1,37 @@ +// if2.rs +// +// Step 1: Make me compile! +// Step 2: Get the bar_for_fuzz and default_to_baz tests passing! +// +// Execute `rustlings hint if2` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +pub fn foo_if_fizz(fizzish: &str) -> &str { + if fizzish == "fizz" { + "foo" + } else { + 1 + } +} + +// No test changes needed! +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn foo_for_fizz() { + assert_eq!(foo_if_fizz("fizz"), "foo") + } + + #[test] + fn bar_for_fuzz() { + assert_eq!(foo_if_fizz("fuzz"), "bar") + } + + #[test] + fn default_to_baz() { + assert_eq!(foo_if_fizz("literally anything"), "baz") + } +} diff --git a/exercises/03_if/if3.rs b/exercises/03_if/if3.rs new file mode 100644 index 0000000..1696274 --- /dev/null +++ b/exercises/03_if/if3.rs @@ -0,0 +1,56 @@ +// if3.rs +// +// Execute `rustlings hint if3` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +pub fn animal_habitat(animal: &str) -> &'static str { + let identifier = if animal == "crab" { + 1 + } else if animal == "gopher" { + 2.0 + } else if animal == "snake" { + 3 + } else { + "Unknown" + }; + + // DO NOT CHANGE THIS STATEMENT BELOW + let habitat = if identifier == 1 { + "Beach" + } else if identifier == 2 { + "Burrow" + } else if identifier == 3 { + "Desert" + } else { + "Unknown" + }; + + habitat +} + +// No test changes needed. +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gopher_lives_in_burrow() { + assert_eq!(animal_habitat("gopher"), "Burrow") + } + + #[test] + fn snake_lives_in_desert() { + assert_eq!(animal_habitat("snake"), "Desert") + } + + #[test] + fn crab_lives_on_beach() { + assert_eq!(animal_habitat("crab"), "Beach") + } + + #[test] + fn unknown_animal() { + assert_eq!(animal_habitat("dinosaur"), "Unknown") + } +} diff --git a/exercises/04_primitive_types/README.md b/exercises/04_primitive_types/README.md new file mode 100644 index 0000000..cea69b0 --- /dev/null +++ b/exercises/04_primitive_types/README.md @@ -0,0 +1,9 @@ +# Primitive Types + +Rust has a couple of basic types that are directly implemented into the +compiler. In this section, we'll go through the most important ones. + +## Further information + +- [Data Types](https://doc.rust-lang.org/stable/book/ch03-02-data-types.html) +- [The Slice Type](https://doc.rust-lang.org/stable/book/ch04-03-slices.html) diff --git a/exercises/04_primitive_types/primitive_types1.rs b/exercises/04_primitive_types/primitive_types1.rs new file mode 100644 index 0000000..3663340 --- /dev/null +++ b/exercises/04_primitive_types/primitive_types1.rs @@ -0,0 +1,20 @@ +// primitive_types1.rs +// +// Fill in the rest of the line that has code missing! No hints, there's no +// tricks, just get used to typing these :) + +// I AM NOT DONE + +fn main() { + // Booleans (`bool`) + + let is_morning = true; + if is_morning { + println!("Good morning!"); + } + + let // Finish the rest of this line like the example! Or make it be false! + if is_evening { + println!("Good evening!"); + } +} diff --git a/exercises/04_primitive_types/primitive_types2.rs b/exercises/04_primitive_types/primitive_types2.rs new file mode 100644 index 0000000..f1616ed --- /dev/null +++ b/exercises/04_primitive_types/primitive_types2.rs @@ -0,0 +1,32 @@ +// primitive_types2.rs +// +// Fill in the rest of the line that has code missing! No hints, there's no +// tricks, just get used to typing these :) + +// I AM NOT DONE + +fn main() { + // Characters (`char`) + + // Note the _single_ quotes, these are different from the double quotes + // you've been seeing around. + let my_first_initial = 'C'; + if my_first_initial.is_alphabetic() { + println!("Alphabetical!"); + } else if my_first_initial.is_numeric() { + println!("Numerical!"); + } else { + println!("Neither alphabetic nor numeric!"); + } + + let // Finish this line like the example! What's your favorite character? + // Try a letter, try a number, try a special character, try a character + // from a different language than your own, try an emoji! + if your_character.is_alphabetic() { + println!("Alphabetical!"); + } else if your_character.is_numeric() { + println!("Numerical!"); + } else { + println!("Neither alphabetic nor numeric!"); + } +} diff --git a/exercises/04_primitive_types/primitive_types3.rs b/exercises/04_primitive_types/primitive_types3.rs new file mode 100644 index 0000000..8b0de44 --- /dev/null +++ b/exercises/04_primitive_types/primitive_types3.rs @@ -0,0 +1,19 @@ +// primitive_types3.rs +// +// Create an array with at least 100 elements in it where the ??? is. +// +// Execute `rustlings hint primitive_types3` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +fn main() { + let a = ??? + + if a.len() >= 100 { + println!("Wow, that's a big array!"); + } else { + println!("Meh, I eat arrays like that for breakfast."); + panic!("Array not big enough, more elements needed") + } +} diff --git a/exercises/04_primitive_types/primitive_types4.rs b/exercises/04_primitive_types/primitive_types4.rs new file mode 100644 index 0000000..d44d877 --- /dev/null +++ b/exercises/04_primitive_types/primitive_types4.rs @@ -0,0 +1,17 @@ +// primitive_types4.rs +// +// Get a slice out of Array a where the ??? is so that the test passes. +// +// Execute `rustlings hint primitive_types4` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +#[test] +fn slice_out_of_array() { + let a = [1, 2, 3, 4, 5]; + + let nice_slice = ??? + + assert_eq!([2, 3, 4], nice_slice) +} diff --git a/exercises/04_primitive_types/primitive_types5.rs b/exercises/04_primitive_types/primitive_types5.rs new file mode 100644 index 0000000..f646986 --- /dev/null +++ b/exercises/04_primitive_types/primitive_types5.rs @@ -0,0 +1,15 @@ +// primitive_types5.rs +// +// Destructure the `cat` tuple so that the println will work. +// +// Execute `rustlings hint primitive_types5` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +fn main() { + let cat = ("Furry McFurson", 3.5); + let /* your pattern here */ = cat; + + println!("{} is {} years old.", name, age); +} diff --git a/exercises/04_primitive_types/primitive_types6.rs b/exercises/04_primitive_types/primitive_types6.rs new file mode 100644 index 0000000..07cc46c --- /dev/null +++ b/exercises/04_primitive_types/primitive_types6.rs @@ -0,0 +1,19 @@ +// primitive_types6.rs +// +// Use a tuple index to access the second element of `numbers`. You can put the +// expression for the second element where ??? is so that the test passes. +// +// Execute `rustlings hint primitive_types6` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +#[test] +fn indexing_tuple() { + let numbers = (1, 2, 3); + // Replace below ??? with the tuple indexing syntax. + let second = ???; + + assert_eq!(2, second, + "This is not the 2nd number in the tuple!") +} diff --git a/exercises/05_vecs/README.md b/exercises/05_vecs/README.md new file mode 100644 index 0000000..8ff9b85 --- /dev/null +++ b/exercises/05_vecs/README.md @@ -0,0 +1,17 @@ +# Vectors + +Vectors are one of the most-used Rust data structures. In other programming +languages, they'd simply be called Arrays, but since Rust operates on a +bit of a lower level, an array in Rust is stored on the stack (meaning it +can't grow or shrink, and the size needs to be known at compile time), +and a Vector is stored in the heap (where these restrictions do not apply). + +Vectors are a bit of a later chapter in the book, but we think that they're +useful enough to talk about them a bit earlier. We shall be talking about +the other useful data structure, hash maps, later. + +## Further information + +- [Storing Lists of Values with Vectors](https://doc.rust-lang.org/stable/book/ch08-01-vectors.html) +- [`iter_mut`](https://doc.rust-lang.org/std/primitive.slice.html#method.iter_mut) +- [`map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map) diff --git a/exercises/05_vecs/vecs1.rs b/exercises/05_vecs/vecs1.rs new file mode 100644 index 0000000..65b7a7f --- /dev/null +++ b/exercises/05_vecs/vecs1.rs @@ -0,0 +1,28 @@ +// vecs1.rs +// +// Your task is to create a `Vec` which holds the exact same elements as in the +// array `a`. +// +// Make me compile and pass the test! +// +// Execute `rustlings hint vecs1` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +fn array_and_vec() -> ([i32; 4], Vec) { + let a = [10, 20, 30, 40]; // a plain array + let v = // TODO: declare your vector here with the macro for vectors + + (a, v) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_array_and_vec_similarity() { + let (a, v) = array_and_vec(); + assert_eq!(a, v[..]); + } +} diff --git a/exercises/05_vecs/vecs2.rs b/exercises/05_vecs/vecs2.rs new file mode 100644 index 0000000..e92c970 --- /dev/null +++ b/exercises/05_vecs/vecs2.rs @@ -0,0 +1,50 @@ +// vecs2.rs +// +// A Vec of even numbers is given. Your task is to complete the loop so that +// each number in the Vec is multiplied by 2. +// +// Make me pass the test! +// +// Execute `rustlings hint vecs2` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +fn vec_loop(mut v: Vec) -> Vec { + for element in v.iter_mut() { + // TODO: Fill this up so that each element in the Vec `v` is + // multiplied by 2. + ??? + } + + // At this point, `v` should be equal to [4, 8, 12, 16, 20]. + v +} + +fn vec_map(v: &Vec) -> Vec { + v.iter().map(|element| { + // TODO: Do the same thing as above - but instead of mutating the + // Vec, you can just return the new number! + ??? + }).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vec_loop() { + let v: Vec = (1..).filter(|x| x % 2 == 0).take(5).collect(); + let ans = vec_loop(v.clone()); + + assert_eq!(ans, v.iter().map(|x| x * 2).collect::>()); + } + + #[test] + fn test_vec_map() { + let v: Vec = (1..).filter(|x| x % 2 == 0).take(5).collect(); + let ans = vec_map(&v); + + assert_eq!(ans, v.iter().map(|x| x * 2).collect::>()); + } +} diff --git a/exercises/06_move_semantics/README.md b/exercises/06_move_semantics/README.md new file mode 100644 index 0000000..54ddd8e --- /dev/null +++ b/exercises/06_move_semantics/README.md @@ -0,0 +1,10 @@ +# Move Semantics + +These exercises are adapted from [pnkfelix](https://github.com/pnkfelix)'s [Rust Tutorial](https://pnkfelix.github.io/rust-examples-icfp2014/) -- Thank you Felix!!! + +## Further information + +For this section, the book links are especially important. + +- [Ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) +- [Reference and borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) diff --git a/exercises/06_move_semantics/move_semantics1.rs b/exercises/06_move_semantics/move_semantics1.rs new file mode 100644 index 0000000..e063937 --- /dev/null +++ b/exercises/06_move_semantics/move_semantics1.rs @@ -0,0 +1,23 @@ +// move_semantics1.rs +// +// Execute `rustlings hint move_semantics1` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +#[test] +fn main() { + let vec0 = vec![22, 44, 66]; + + let vec1 = fill_vec(vec0); + + assert_eq!(vec1, vec![22, 44, 66, 88]); +} + +fn fill_vec(vec: Vec) -> Vec { + let vec = vec; + + vec.push(88); + + vec +} diff --git a/exercises/06_move_semantics/move_semantics2.rs b/exercises/06_move_semantics/move_semantics2.rs new file mode 100644 index 0000000..baf6bcc --- /dev/null +++ b/exercises/06_move_semantics/move_semantics2.rs @@ -0,0 +1,26 @@ +// move_semantics2.rs +// +// Make the test pass by finding a way to keep both Vecs separate! +// +// Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +#[test] +fn main() { + let vec0 = vec![22, 44, 66]; + + let mut vec1 = fill_vec(vec0); + + assert_eq!(vec0, vec![22, 44, 66]); + assert_eq!(vec1, vec![22, 44, 66, 88]); +} + +fn fill_vec(vec: Vec) -> Vec { + let mut vec = vec; + + vec.push(88); + + vec +} diff --git a/exercises/06_move_semantics/move_semantics3.rs b/exercises/06_move_semantics/move_semantics3.rs new file mode 100644 index 0000000..7af9e69 --- /dev/null +++ b/exercises/06_move_semantics/move_semantics3.rs @@ -0,0 +1,24 @@ +// move_semantics3.rs +// +// Make me compile without adding new lines -- just changing existing lines! (no +// lines with multiple semicolons necessary!) +// +// Execute `rustlings hint move_semantics3` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +#[test] +fn main() { + let vec0 = vec![22, 44, 66]; + + let mut vec1 = fill_vec(vec0); + + assert_eq!(vec1, vec![22, 44, 66, 88]); +} + +fn fill_vec(vec: Vec) -> Vec { + vec.push(88); + + vec +} diff --git a/exercises/06_move_semantics/move_semantics4.rs b/exercises/06_move_semantics/move_semantics4.rs new file mode 100644 index 0000000..80b49db --- /dev/null +++ b/exercises/06_move_semantics/move_semantics4.rs @@ -0,0 +1,29 @@ +// move_semantics4.rs +// +// Refactor this code so that instead of passing `vec0` into the `fill_vec` +// function, the Vector gets created in the function itself and passed back to +// the main function. +// +// Execute `rustlings hint move_semantics4` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +#[test] +fn main() { + let vec0 = vec![22, 44, 66]; + + let mut vec1 = fill_vec(vec0); + + assert_eq!(vec1, vec![22, 44, 66, 88]); +} + +// `fill_vec()` no longer takes `vec: Vec` as argument - don't change this! +fn fill_vec() -> Vec { + // Instead, let's create and fill the Vec in here - how do you do that? + let mut vec = vec; + + vec.push(88); + + vec +} diff --git a/exercises/06_move_semantics/move_semantics5.rs b/exercises/06_move_semantics/move_semantics5.rs new file mode 100644 index 0000000..267bdcc --- /dev/null +++ b/exercises/06_move_semantics/move_semantics5.rs @@ -0,0 +1,19 @@ +// move_semantics5.rs +// +// Make me compile only by reordering the lines in `main()`, but without adding, +// changing or removing any of them. +// +// Execute `rustlings hint move_semantics5` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +#[test] +fn main() { + let mut x = 100; + let y = &mut x; + let z = &mut x; + *y += 100; + *z += 1000; + assert_eq!(x, 1200); +} diff --git a/exercises/06_move_semantics/move_semantics6.rs b/exercises/06_move_semantics/move_semantics6.rs new file mode 100644 index 0000000..cace4ca --- /dev/null +++ b/exercises/06_move_semantics/move_semantics6.rs @@ -0,0 +1,28 @@ +// move_semantics6.rs +// +// You can't change anything except adding or removing references. +// +// Execute `rustlings hint move_semantics6` or use the `hint` watch subcommand +// for a hint. + +// I AM NOT DONE + +fn main() { + let data = "Rust is great!".to_string(); + + get_char(data); + + string_uppercase(&data); +} + +// Should not take ownership +fn get_char(data: String) -> char { + data.chars().last().unwrap() +} + +// Should take ownership +fn string_uppercase(mut data: &String) { + data = &data.to_uppercase(); + + println!("{}", data); +} diff --git a/exercises/07_structs/README.md b/exercises/07_structs/README.md new file mode 100644 index 0000000..3fc1fdc --- /dev/null +++ b/exercises/07_structs/README.md @@ -0,0 +1,8 @@ +# Structs + +Rust has three struct types: a classic C struct, a tuple struct, and a unit struct. + +## Further information + +- [Structures](https://doc.rust-lang.org/book/ch05-01-defining-structs.html) +- [Method Syntax](https://doc.rust-lang.org/book/ch05-03-method-syntax.html) diff --git a/exercises/07_structs/structs1.rs b/exercises/07_structs/structs1.rs new file mode 100644 index 0000000..5fa5821 --- /dev/null +++ b/exercises/07_structs/structs1.rs @@ -0,0 +1,51 @@ +// structs1.rs +// +// Address all the TODOs to make the tests pass! +// +// Execute `rustlings hint structs1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +struct ColorClassicStruct { + // TODO: Something goes here +} + +struct ColorTupleStruct(/* TODO: Something goes here */); + +#[derive(Debug)] +struct UnitLikeStruct; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn classic_c_structs() { + // TODO: Instantiate a classic c struct! + // let green = + + assert_eq!(green.red, 0); + assert_eq!(green.green, 255); + assert_eq!(green.blue, 0); + } + + #[test] + fn tuple_structs() { + // TODO: Instantiate a tuple struct! + // let green = + + assert_eq!(green.0, 0); + assert_eq!(green.1, 255); + assert_eq!(green.2, 0); + } + + #[test] + fn unit_structs() { + // TODO: Instantiate a unit-like struct! + // let unit_like_struct = + let message = format!("{:?}s are fun!", unit_like_struct); + + assert_eq!(message, "UnitLikeStructs are fun!"); + } +} diff --git a/exercises/07_structs/structs2.rs b/exercises/07_structs/structs2.rs new file mode 100644 index 0000000..328567f --- /dev/null +++ b/exercises/07_structs/structs2.rs @@ -0,0 +1,50 @@ +// structs2.rs +// +// Address all the TODOs to make the tests pass! +// +// Execute `rustlings hint structs2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[derive(Debug)] +struct Order { + name: String, + year: u32, + made_by_phone: bool, + made_by_mobile: bool, + made_by_email: bool, + item_number: u32, + count: u32, +} + +fn create_order_template() -> Order { + Order { + name: String::from("Bob"), + year: 2019, + made_by_phone: false, + made_by_mobile: false, + made_by_email: true, + item_number: 123, + count: 0, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn your_order() { + let order_template = create_order_template(); + // TODO: Create your own order using the update syntax and template above! + // let your_order = + assert_eq!(your_order.name, "Hacker in Rust"); + assert_eq!(your_order.year, order_template.year); + assert_eq!(your_order.made_by_phone, order_template.made_by_phone); + assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile); + assert_eq!(your_order.made_by_email, order_template.made_by_email); + assert_eq!(your_order.item_number, order_template.item_number); + assert_eq!(your_order.count, 1); + } +} diff --git a/exercises/07_structs/structs3.rs b/exercises/07_structs/structs3.rs new file mode 100644 index 0000000..7cda5af --- /dev/null +++ b/exercises/07_structs/structs3.rs @@ -0,0 +1,88 @@ +// structs3.rs +// +// Structs contain data, but can also have logic. In this exercise we have +// defined the Package struct and we want to test some logic attached to it. +// Make the code compile and the tests pass! +// +// Execute `rustlings hint structs3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[derive(Debug)] +struct Package { + sender_country: String, + recipient_country: String, + weight_in_grams: u32, +} + +impl Package { + fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Package { + if weight_in_grams < 10 { + // This is not how you should handle errors in Rust, + // but we will learn about error handling later. + panic!("Can not ship a package with weight below 10 grams.") + } else { + Package { + sender_country, + recipient_country, + weight_in_grams, + } + } + } + + fn is_international(&self) -> ??? { + // Something goes here... + } + + fn get_fees(&self, cents_per_gram: u32) -> ??? { + // Something goes here... + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn fail_creating_weightless_package() { + let sender_country = String::from("Spain"); + let recipient_country = String::from("Austria"); + + Package::new(sender_country, recipient_country, 5); + } + + #[test] + fn create_international_package() { + let sender_country = String::from("Spain"); + let recipient_country = String::from("Russia"); + + let package = Package::new(sender_country, recipient_country, 1200); + + assert!(package.is_international()); + } + + #[test] + fn create_local_package() { + let sender_country = String::from("Canada"); + let recipient_country = sender_country.clone(); + + let package = Package::new(sender_country, recipient_country, 1200); + + assert!(!package.is_international()); + } + + #[test] + fn calculate_transport_fees() { + let sender_country = String::from("Spain"); + let recipient_country = String::from("Spain"); + + let cents_per_gram = 3; + + let package = Package::new(sender_country, recipient_country, 1500); + + assert_eq!(package.get_fees(cents_per_gram), 4500); + assert_eq!(package.get_fees(cents_per_gram * 2), 9000); + } +} diff --git a/exercises/08_enums/README.md b/exercises/08_enums/README.md new file mode 100644 index 0000000..30d4d91 --- /dev/null +++ b/exercises/08_enums/README.md @@ -0,0 +1,10 @@ +# Enums + +Rust allows you to define types called "enums" which enumerate possible values. +Enums are a feature in many languages, but their capabilities differ in each language. Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell. +Useful in combination with enums is Rust's "pattern matching" facility, which makes it easy to run different code for different values of an enumeration. + +## Further information + +- [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html) +- [Pattern syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html) diff --git a/exercises/08_enums/enums1.rs b/exercises/08_enums/enums1.rs new file mode 100644 index 0000000..25525b2 --- /dev/null +++ b/exercises/08_enums/enums1.rs @@ -0,0 +1,17 @@ +// enums1.rs +// +// No hints this time! ;) + +// I AM NOT DONE + +#[derive(Debug)] +enum Message { + // TODO: define a few types of messages as used below +} + +fn main() { + println!("{:?}", Message::Quit); + println!("{:?}", Message::Echo); + println!("{:?}", Message::Move); + println!("{:?}", Message::ChangeColor); +} diff --git a/exercises/08_enums/enums2.rs b/exercises/08_enums/enums2.rs new file mode 100644 index 0000000..df93fe0 --- /dev/null +++ b/exercises/08_enums/enums2.rs @@ -0,0 +1,30 @@ +// enums2.rs +// +// Execute `rustlings hint enums2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[derive(Debug)] +enum Message { + // TODO: define the different variants used below +} + +impl Message { + fn call(&self) { + println!("{:?}", self); + } +} + +fn main() { + let messages = [ + Message::Move { x: 10, y: 30 }, + Message::Echo(String::from("hello world")), + Message::ChangeColor(200, 255, 255), + Message::Quit, + ]; + + for message in &messages { + message.call(); + } +} diff --git a/exercises/08_enums/enums3.rs b/exercises/08_enums/enums3.rs new file mode 100644 index 0000000..92d18c4 --- /dev/null +++ b/exercises/08_enums/enums3.rs @@ -0,0 +1,73 @@ +// enums3.rs +// +// Address all the TODOs to make the tests pass! +// +// Execute `rustlings hint enums3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +enum Message { + // TODO: implement the message variant types based on their usage below +} + +struct Point { + x: u8, + y: u8, +} + +struct State { + color: (u8, u8, u8), + position: Point, + quit: bool, + message: String, +} + +impl State { + fn change_color(&mut self, color: (u8, u8, u8)) { + self.color = color; + } + + fn quit(&mut self) { + self.quit = true; + } + + fn echo(&mut self, s: String) { + self.message = s + } + + fn move_position(&mut self, p: Point) { + self.position = p; + } + + fn process(&mut self, message: Message) { + // TODO: create a match expression to process the different message variants + // Remember: When passing a tuple as a function argument, you'll need extra parentheses: + // fn function((t, u, p, l, e)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_match_message_call() { + let mut state = State { + quit: false, + position: Point { x: 0, y: 0 }, + color: (0, 0, 0), + message: "hello world".to_string(), + }; + state.process(Message::ChangeColor(255, 0, 255)); + state.process(Message::Echo(String::from("Hello world!"))); + state.process(Message::Move(Point { x: 10, y: 15 })); + state.process(Message::Quit); + + assert_eq!(state.color, (255, 0, 255)); + assert_eq!(state.position.x, 10); + assert_eq!(state.position.y, 15); + assert_eq!(state.quit, true); + assert_eq!(state.message, "Hello world!"); + } +} diff --git a/exercises/09_strings/README.md b/exercises/09_strings/README.md new file mode 100644 index 0000000..fa2104c --- /dev/null +++ b/exercises/09_strings/README.md @@ -0,0 +1,9 @@ +# Strings + +Rust has two string types, a string slice (`&str`) and an owned string (`String`). +We're not going to dictate when you should use which one, but we'll show you how +to identify and create them, as well as use them. + +## Further information + +- [Strings](https://doc.rust-lang.org/book/ch08-02-strings.html) diff --git a/exercises/09_strings/strings1.rs b/exercises/09_strings/strings1.rs new file mode 100644 index 0000000..f50e1fa --- /dev/null +++ b/exercises/09_strings/strings1.rs @@ -0,0 +1,17 @@ +// strings1.rs +// +// Make me compile without changing the function signature! +// +// Execute `rustlings hint strings1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let answer = current_favorite_color(); + println!("My current favorite color is {}", answer); +} + +fn current_favorite_color() -> String { + "blue" +} diff --git a/exercises/09_strings/strings2.rs b/exercises/09_strings/strings2.rs new file mode 100644 index 0000000..4d95d16 --- /dev/null +++ b/exercises/09_strings/strings2.rs @@ -0,0 +1,21 @@ +// strings2.rs +// +// Make me compile without changing the function signature! +// +// Execute `rustlings hint strings2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let word = String::from("green"); // Try not changing this line :) + if is_a_color_word(word) { + println!("That is a color word I know!"); + } else { + println!("That is not a color word I know."); + } +} + +fn is_a_color_word(attempt: &str) -> bool { + attempt == "green" || attempt == "blue" || attempt == "red" +} diff --git a/exercises/09_strings/strings3.rs b/exercises/09_strings/strings3.rs new file mode 100644 index 0000000..b29f932 --- /dev/null +++ b/exercises/09_strings/strings3.rs @@ -0,0 +1,45 @@ +// strings3.rs +// +// Execute `rustlings hint strings3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn trim_me(input: &str) -> String { + // TODO: Remove whitespace from both ends of a string! + ??? +} + +fn compose_me(input: &str) -> String { + // TODO: Add " world!" to the string! There's multiple ways to do this! + ??? +} + +fn replace_me(input: &str) -> String { + // TODO: Replace "cars" in the string with "balloons"! + ??? +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn trim_a_string() { + assert_eq!(trim_me("Hello! "), "Hello!"); + assert_eq!(trim_me(" What's up!"), "What's up!"); + assert_eq!(trim_me(" Hola! "), "Hola!"); + } + + #[test] + fn compose_a_string() { + assert_eq!(compose_me("Hello"), "Hello world!"); + assert_eq!(compose_me("Goodbye"), "Goodbye world!"); + } + + #[test] + fn replace_a_string() { + assert_eq!(replace_me("I think cars are cool"), "I think balloons are cool"); + assert_eq!(replace_me("I love to look at cars"), "I love to look at balloons"); + } +} diff --git a/exercises/09_strings/strings4.rs b/exercises/09_strings/strings4.rs new file mode 100644 index 0000000..e8c54ac --- /dev/null +++ b/exercises/09_strings/strings4.rs @@ -0,0 +1,30 @@ +// strings4.rs +// +// Ok, here are a bunch of values-- some are `String`s, some are `&str`s. Your +// task is to call one of these two functions on each value depending on what +// you think each value is. That is, add either `string_slice` or `string` +// before the parentheses on each line. If you're right, it will compile! +// +// No hints this time! + +// I AM NOT DONE + +fn string_slice(arg: &str) { + println!("{}", arg); +} +fn string(arg: String) { + println!("{}", arg); +} + +fn main() { + ???("blue"); + ???("red".to_string()); + ???(String::from("hi")); + ???("rust is fun!".to_owned()); + ???("nice weather".into()); + ???(format!("Interpolation {}", "Station")); + ???(&String::from("abc")[0..1]); + ???(" hello there ".trim()); + ???("Happy Monday!".to_string().replace("Mon", "Tues")); + ???("mY sHiFt KeY iS sTiCkY".to_lowercase()); +} diff --git a/exercises/10_modules/README.md b/exercises/10_modules/README.md new file mode 100644 index 0000000..3dc8a48 --- /dev/null +++ b/exercises/10_modules/README.md @@ -0,0 +1,7 @@ +# Modules + +In this section we'll give you an introduction to Rust's module system. + +## Further information + +- [The Module System](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html) diff --git a/exercises/10_modules/modules1.rs b/exercises/10_modules/modules1.rs new file mode 100644 index 0000000..9eb5a48 --- /dev/null +++ b/exercises/10_modules/modules1.rs @@ -0,0 +1,22 @@ +// modules1.rs +// +// Execute `rustlings hint modules1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +mod sausage_factory { + // Don't let anybody outside of this module see this! + fn get_secret_recipe() -> String { + String::from("Ginger") + } + + fn make_sausage() { + get_secret_recipe(); + println!("sausage!"); + } +} + +fn main() { + sausage_factory::make_sausage(); +} diff --git a/exercises/10_modules/modules2.rs b/exercises/10_modules/modules2.rs new file mode 100644 index 0000000..0415454 --- /dev/null +++ b/exercises/10_modules/modules2.rs @@ -0,0 +1,34 @@ +// modules2.rs +// +// You can bring module paths into scopes and provide new names for them with +// the 'use' and 'as' keywords. Fix these 'use' statements to make the code +// compile. +// +// Execute `rustlings hint modules2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +mod delicious_snacks { + // TODO: Fix these use statements + use self::fruits::PEAR as ??? + use self::veggies::CUCUMBER as ??? + + mod fruits { + pub const PEAR: &'static str = "Pear"; + pub const APPLE: &'static str = "Apple"; + } + + mod veggies { + pub const CUCUMBER: &'static str = "Cucumber"; + pub const CARROT: &'static str = "Carrot"; + } +} + +fn main() { + println!( + "favorite snacks: {} and {}", + delicious_snacks::fruit, + delicious_snacks::veggie + ); +} diff --git a/exercises/10_modules/modules3.rs b/exercises/10_modules/modules3.rs new file mode 100644 index 0000000..f2bb050 --- /dev/null +++ b/exercises/10_modules/modules3.rs @@ -0,0 +1,21 @@ +// modules3.rs +// +// You can use the 'use' keyword to bring module paths from modules from +// anywhere and especially from the Rust standard library into your scope. Bring +// SystemTime and UNIX_EPOCH from the std::time module. Bonus style points if +// you can do it with one line! +// +// Execute `rustlings hint modules3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +// TODO: Complete this use statement +use ??? + +fn main() { + match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), + Err(_) => panic!("SystemTime before UNIX EPOCH!"), + } +} diff --git a/exercises/11_hashmaps/README.md b/exercises/11_hashmaps/README.md new file mode 100644 index 0000000..80ec144 --- /dev/null +++ b/exercises/11_hashmaps/README.md @@ -0,0 +1,12 @@ +# Hashmaps + +A *hash map* allows you to associate a value with a particular key. +You may also know this by the names [*unordered map* in C++](https://en.cppreference.com/w/cpp/container/unordered_map), +[*dictionary* in Python](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) or an *associative array* in other languages. + +This is the other data structure that we've been talking about before, when +talking about Vecs. + +## Further information + +- [Storing Keys with Associated Values in Hash Maps](https://doc.rust-lang.org/book/ch08-03-hash-maps.html) diff --git a/exercises/11_hashmaps/hashmaps1.rs b/exercises/11_hashmaps/hashmaps1.rs new file mode 100644 index 0000000..80829ea --- /dev/null +++ b/exercises/11_hashmaps/hashmaps1.rs @@ -0,0 +1,44 @@ +// hashmaps1.rs +// +// A basket of fruits in the form of a hash map needs to be defined. The key +// represents the name of the fruit and the value represents how many of that +// particular fruit is in the basket. You have to put at least three different +// types of fruits (e.g apple, banana, mango) in the basket and the total count +// of all the fruits should be at least five. +// +// Make me compile and pass the tests! +// +// Execute `rustlings hint hashmaps1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::collections::HashMap; + +fn fruit_basket() -> HashMap { + let mut basket = // TODO: declare your hash map here. + + // Two bananas are already given for you :) + basket.insert(String::from("banana"), 2); + + // TODO: Put more fruits in your basket here. + + basket +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn at_least_three_types_of_fruits() { + let basket = fruit_basket(); + assert!(basket.len() >= 3); + } + + #[test] + fn at_least_five_fruits() { + let basket = fruit_basket(); + assert!(basket.values().sum::() >= 5); + } +} diff --git a/exercises/11_hashmaps/hashmaps2.rs b/exercises/11_hashmaps/hashmaps2.rs new file mode 100644 index 0000000..a592569 --- /dev/null +++ b/exercises/11_hashmaps/hashmaps2.rs @@ -0,0 +1,93 @@ +// hashmaps2.rs +// +// We're collecting different fruits to bake a delicious fruit cake. For this, +// we have a basket, which we'll represent in the form of a hash map. The key +// represents the name of each fruit we collect and the value represents how +// many of that particular fruit we have collected. Three types of fruits - +// Apple (4), Mango (2) and Lychee (5) are already in the basket hash map. You +// must add fruit to the basket so that there is at least one of each kind and +// more than 11 in total - we have a lot of mouths to feed. You are not allowed +// to insert any more of these fruits! +// +// Make me pass the tests! +// +// Execute `rustlings hint hashmaps2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::collections::HashMap; + +#[derive(Hash, PartialEq, Eq)] +enum Fruit { + Apple, + Banana, + Mango, + Lychee, + Pineapple, +} + +fn fruit_basket(basket: &mut HashMap) { + let fruit_kinds = vec![ + Fruit::Apple, + Fruit::Banana, + Fruit::Mango, + Fruit::Lychee, + Fruit::Pineapple, + ]; + + for fruit in fruit_kinds { + // TODO: Insert new fruits if they are not already present in the + // basket. Note that you are not allowed to put any type of fruit that's + // already present! + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Don't modify this function! + fn get_fruit_basket() -> HashMap { + let mut basket = HashMap::::new(); + basket.insert(Fruit::Apple, 4); + basket.insert(Fruit::Mango, 2); + basket.insert(Fruit::Lychee, 5); + + basket + } + + #[test] + fn test_given_fruits_are_not_modified() { + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4); + assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2); + assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5); + } + + #[test] + fn at_least_five_types_of_fruits() { + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + let count_fruit_kinds = basket.len(); + assert!(count_fruit_kinds >= 5); + } + + #[test] + fn greater_than_eleven_fruits() { + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + let count = basket.values().sum::(); + assert!(count > 11); + } + + #[test] + fn all_fruit_types_in_basket() { + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + for amount in basket.values() { + assert_ne!(amount, &0); + } + } +} diff --git a/exercises/11_hashmaps/hashmaps3.rs b/exercises/11_hashmaps/hashmaps3.rs new file mode 100644 index 0000000..36544ee --- /dev/null +++ b/exercises/11_hashmaps/hashmaps3.rs @@ -0,0 +1,86 @@ +// hashmaps3.rs +// +// A list of scores (one per line) of a soccer match is given. Each line is of +// the form : ",,," +// Example: England,France,4,2 (England scored 4 goals, France 2). +// +// You have to build a scores table containing the name of the team, goals the +// team scored, and goals the team conceded. One approach to build the scores +// table is to use a Hashmap. The solution is partially written to use a +// Hashmap, complete it to pass the test. +// +// Make me pass the tests! +// +// Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::collections::HashMap; + +// A structure to store the goal details of a team. +struct Team { + goals_scored: u8, + goals_conceded: u8, +} + +fn build_scores_table(results: String) -> HashMap { + // The name of the team is the key and its associated struct is the value. + let mut scores: HashMap = HashMap::new(); + + for r in results.lines() { + let v: Vec<&str> = r.split(',').collect(); + let team_1_name = v[0].to_string(); + let team_1_score: u8 = v[2].parse().unwrap(); + let team_2_name = v[1].to_string(); + let team_2_score: u8 = v[3].parse().unwrap(); + // TODO: Populate the scores table with details extracted from the + // current line. Keep in mind that goals scored by team_1 + // will be the number of goals conceded by team_2, and similarly + // goals scored by team_2 will be the number of goals conceded by + // team_1. + } + scores +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_results() -> String { + let results = "".to_string() + + "England,France,4,2\n" + + "France,Italy,3,1\n" + + "Poland,Spain,2,0\n" + + "Germany,England,2,1\n"; + results + } + + #[test] + fn build_scores() { + let scores = build_scores_table(get_results()); + + let mut keys: Vec<&String> = scores.keys().collect(); + keys.sort(); + assert_eq!( + keys, + vec!["England", "France", "Germany", "Italy", "Poland", "Spain"] + ); + } + + #[test] + fn validate_team_score_1() { + let scores = build_scores_table(get_results()); + let team = scores.get("England").unwrap(); + assert_eq!(team.goals_scored, 5); + assert_eq!(team.goals_conceded, 4); + } + + #[test] + fn validate_team_score_2() { + let scores = build_scores_table(get_results()); + let team = scores.get("Spain").unwrap(); + assert_eq!(team.goals_scored, 0); + assert_eq!(team.goals_conceded, 2); + } +} diff --git a/exercises/12_options/README.md b/exercises/12_options/README.md new file mode 100644 index 0000000..bdd3374 --- /dev/null +++ b/exercises/12_options/README.md @@ -0,0 +1,21 @@ +# Options + +Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not. +Option types are very common in Rust code, as they have a number of uses: + +- Initial values +- Return values for functions that are not defined over their entire input range (partial functions) +- Return value for otherwise reporting simple errors, where None is returned on error +- Optional struct fields +- Struct fields that can be loaned or "taken" +- Optional function arguments +- Nullable pointers +- Swapping things out of difficult situations + +## Further Information + +- [Option Enum Format](https://doc.rust-lang.org/stable/book/ch10-01-syntax.html#in-enum-definitions) +- [Option Module Documentation](https://doc.rust-lang.org/std/option/) +- [Option Enum Documentation](https://doc.rust-lang.org/std/option/enum.Option.html) +- [if let](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html) +- [while let](https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html) diff --git a/exercises/12_options/options1.rs b/exercises/12_options/options1.rs new file mode 100644 index 0000000..e131b48 --- /dev/null +++ b/exercises/12_options/options1.rs @@ -0,0 +1,39 @@ +// options1.rs +// +// Execute `rustlings hint options1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +// This function returns how much icecream there is left in the fridge. +// If it's before 10PM, there's 5 pieces left. At 10PM, someone eats them +// all, so there'll be no more left :( +fn maybe_icecream(time_of_day: u16) -> Option { + // We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a + // value of 0 The Option output should gracefully handle cases where + // time_of_day > 23. + // TODO: Complete the function body - remember to return an Option! + ??? +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_icecream() { + assert_eq!(maybe_icecream(9), Some(5)); + assert_eq!(maybe_icecream(10), Some(5)); + assert_eq!(maybe_icecream(23), Some(0)); + assert_eq!(maybe_icecream(22), Some(0)); + assert_eq!(maybe_icecream(25), None); + } + + #[test] + fn raw_value() { + // TODO: Fix this test. How do you get at the value contained in the + // Option? + let icecreams = maybe_icecream(12); + assert_eq!(icecreams, 5); + } +} diff --git a/exercises/12_options/options2.rs b/exercises/12_options/options2.rs new file mode 100644 index 0000000..4d998e7 --- /dev/null +++ b/exercises/12_options/options2.rs @@ -0,0 +1,42 @@ +// options2.rs +// +// Execute `rustlings hint options2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[cfg(test)] +mod tests { + #[test] + fn simple_option() { + let target = "rustlings"; + let optional_target = Some(target); + + // TODO: Make this an if let statement whose value is "Some" type + word = optional_target { + assert_eq!(word, target); + } + } + + #[test] + fn layered_option() { + let range = 10; + let mut optional_integers: Vec> = vec![None]; + + for i in 1..(range + 1) { + optional_integers.push(Some(i)); + } + + let mut cursor = range; + + // TODO: make this a while let statement - remember that vector.pop also + // adds another layer of Option. You can stack `Option`s into + // while let and if let. + integer = optional_integers.pop() { + assert_eq!(integer, cursor); + cursor -= 1; + } + + assert_eq!(cursor, 0); + } +} diff --git a/exercises/12_options/options3.rs b/exercises/12_options/options3.rs new file mode 100644 index 0000000..23c15ea --- /dev/null +++ b/exercises/12_options/options3.rs @@ -0,0 +1,21 @@ +// options3.rs +// +// Execute `rustlings hint options3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +struct Point { + x: i32, + y: i32, +} + +fn main() { + let y: Option = Some(Point { x: 100, y: 200 }); + + match y { + Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y), + _ => panic!("no match!"), + } + y; // Fix without deleting this line. +} diff --git a/exercises/13_error_handling/README.md b/exercises/13_error_handling/README.md new file mode 100644 index 0000000..3b21f2b --- /dev/null +++ b/exercises/13_error_handling/README.md @@ -0,0 +1,12 @@ +# Error handling + +Most errors aren’t serious enough to require the program to stop entirely. +Sometimes, when a function fails, it’s for a reason that you can easily interpret and respond to. +For example, if you try to open a file and that operation fails because the file doesn’t exist, you might want to create the file instead of terminating the process. + +## Further information + +- [Error Handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html) +- [Generics](https://doc.rust-lang.org/book/ch10-01-syntax.html) +- [Result](https://doc.rust-lang.org/rust-by-example/error/result.html) +- [Boxing errors](https://doc.rust-lang.org/rust-by-example/error/multiple_error_types/boxing_errors.html) diff --git a/exercises/13_error_handling/errors1.rs b/exercises/13_error_handling/errors1.rs new file mode 100644 index 0000000..0ba59a5 --- /dev/null +++ b/exercises/13_error_handling/errors1.rs @@ -0,0 +1,43 @@ +// errors1.rs +// +// This function refuses to generate text to be printed on a nametag if you pass +// it an empty string. It'd be nicer if it explained what the problem was, +// instead of just sometimes returning `None`. Thankfully, Rust has a similar +// construct to `Option` that can be used to express error conditions. Let's use +// it! +// +// Execute `rustlings hint errors1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +pub fn generate_nametag_text(name: String) -> Option { + if name.is_empty() { + // Empty names aren't allowed. + None + } else { + Some(format!("Hi! My name is {}", name)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generates_nametag_text_for_a_nonempty_name() { + assert_eq!( + generate_nametag_text("Beyoncé".into()), + Ok("Hi! My name is Beyoncé".into()) + ); + } + + #[test] + fn explains_why_generating_nametag_text_fails() { + assert_eq!( + generate_nametag_text("".into()), + // Don't change this line + Err("`name` was empty; it must be nonempty.".into()) + ); + } +} diff --git a/exercises/13_error_handling/errors2.rs b/exercises/13_error_handling/errors2.rs new file mode 100644 index 0000000..631fe67 --- /dev/null +++ b/exercises/13_error_handling/errors2.rs @@ -0,0 +1,50 @@ +// errors2.rs +// +// Say we're writing a game where you can buy items with tokens. All items cost +// 5 tokens, and whenever you purchase items there is a processing fee of 1 +// token. A player of the game will type in how many items they want to buy, and +// the `total_cost` function will calculate the total cost of the items. Since +// the player typed in the quantity, though, we get it as a string-- and they +// might have typed anything, not just numbers! +// +// Right now, this function isn't handling the error case at all (and isn't +// handling the success case properly either). What we want to do is: if we call +// the `total_cost` function on a string that is not a number, that function +// will return a `ParseIntError`, and in that case, we want to immediately +// return that error from our function and not try to multiply and add. +// +// There are at least two ways to implement this that are both correct-- but one +// is a lot shorter! +// +// Execute `rustlings hint errors2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::num::ParseIntError; + +pub fn total_cost(item_quantity: &str) -> Result { + let processing_fee = 1; + let cost_per_item = 5; + let qty = item_quantity.parse::(); + + Ok(qty * cost_per_item + processing_fee) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn item_quantity_is_a_valid_number() { + assert_eq!(total_cost("34"), Ok(171)); + } + + #[test] + fn item_quantity_is_an_invalid_number() { + assert_eq!( + total_cost("beep boop").unwrap_err().to_string(), + "invalid digit found in string" + ); + } +} diff --git a/exercises/13_error_handling/errors3.rs b/exercises/13_error_handling/errors3.rs new file mode 100644 index 0000000..d42d3b1 --- /dev/null +++ b/exercises/13_error_handling/errors3.rs @@ -0,0 +1,34 @@ +// errors3.rs +// +// This is a program that is trying to use a completed version of the +// `total_cost` function from the previous exercise. It's not working though! +// Why not? What should we do to fix it? +// +// Execute `rustlings hint errors3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::num::ParseIntError; + +fn main() { + let mut tokens = 100; + let pretend_user_input = "8"; + + let cost = total_cost(pretend_user_input)?; + + if cost > tokens { + println!("You can't afford that many!"); + } else { + tokens -= cost; + println!("You now have {} tokens.", tokens); + } +} + +pub fn total_cost(item_quantity: &str) -> Result { + let processing_fee = 1; + let cost_per_item = 5; + let qty = item_quantity.parse::()?; + + Ok(qty * cost_per_item + processing_fee) +} diff --git a/exercises/13_error_handling/errors4.rs b/exercises/13_error_handling/errors4.rs new file mode 100644 index 0000000..d6d6fcb --- /dev/null +++ b/exercises/13_error_handling/errors4.rs @@ -0,0 +1,32 @@ +// errors4.rs +// +// Execute `rustlings hint errors4` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + // Hmm... Why is this always returning an Ok value? + Ok(PositiveNonzeroInteger(value as u64)) + } +} + +#[test] +fn test_creation() { + assert!(PositiveNonzeroInteger::new(10).is_ok()); + assert_eq!( + Err(CreationError::Negative), + PositiveNonzeroInteger::new(-10) + ); + assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0)); +} diff --git a/exercises/13_error_handling/errors5.rs b/exercises/13_error_handling/errors5.rs new file mode 100644 index 0000000..92461a7 --- /dev/null +++ b/exercises/13_error_handling/errors5.rs @@ -0,0 +1,71 @@ +// errors5.rs +// +// This program uses an altered version of the code from errors4. +// +// This exercise uses some concepts that we won't get to until later in the +// course, like `Box` and the `From` trait. It's not important to understand +// them in detail right now, but you can read ahead if you like. For now, think +// of the `Box` type as an "I want anything that does ???" type, which, +// given Rust's usual standards for runtime safety, should strike you as +// somewhat lenient! +// +// In short, this particular use case for boxes is for when you want to own a +// value and you care only that it is a type which implements a particular +// trait. To do so, The Box is declared as of type Box where Trait is +// the trait the compiler looks for on any value used in that context. For this +// exercise, that context is the potential errors which can be returned in a +// Result. +// +// What can we use to describe both errors? In other words, is there a trait +// which both errors implement? +// +// Execute `rustlings hint errors5` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::error; +use std::fmt; +use std::num::ParseIntError; + +// TODO: update the return type of `main()` to make this compile. +fn main() -> Result<(), Box> { + let pretend_user_input = "42"; + let x: i64 = pretend_user_input.parse()?; + println!("output={:?}", PositiveNonzeroInteger::new(x)?); + Ok(()) +} + +// Don't change anything below this line. + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + match value { + x if x < 0 => Err(CreationError::Negative), + x if x == 0 => Err(CreationError::Zero), + x => Ok(PositiveNonzeroInteger(x as u64)), + } + } +} + +// This is required so that `CreationError` can implement `error::Error`. +impl fmt::Display for CreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let description = match *self { + CreationError::Negative => "number is negative", + CreationError::Zero => "number is zero", + }; + f.write_str(description) + } +} + +impl error::Error for CreationError {} diff --git a/exercises/13_error_handling/errors6.rs b/exercises/13_error_handling/errors6.rs new file mode 100644 index 0000000..aaf0948 --- /dev/null +++ b/exercises/13_error_handling/errors6.rs @@ -0,0 +1,94 @@ +// errors6.rs +// +// Using catch-all error types like `Box` isn't recommended +// for library code, where callers might want to make decisions based on the +// error content, instead of printing it out or propagating it further. Here, we +// define a custom error type to make it possible for callers to decide what to +// do next when our function returns an error. +// +// Execute `rustlings hint errors6` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::num::ParseIntError; + +// This is a custom error type that we will be using in `parse_pos_nonzero()`. +#[derive(PartialEq, Debug)] +enum ParsePosNonzeroError { + Creation(CreationError), + ParseInt(ParseIntError), +} + +impl ParsePosNonzeroError { + fn from_creation(err: CreationError) -> ParsePosNonzeroError { + ParsePosNonzeroError::Creation(err) + } + // TODO: add another error conversion function here. + // fn from_parseint... +} + +fn parse_pos_nonzero(s: &str) -> Result { + // TODO: change this to return an appropriate error instead of panicking + // when `parse()` returns an error. + let x: i64 = s.parse().unwrap(); + PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation) +} + +// Don't change anything below this line. + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + match value { + x if x < 0 => Err(CreationError::Negative), + x if x == 0 => Err(CreationError::Zero), + x => Ok(PositiveNonzeroInteger(x as u64)), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_error() { + // We can't construct a ParseIntError, so we have to pattern match. + assert!(matches!( + parse_pos_nonzero("not a number"), + Err(ParsePosNonzeroError::ParseInt(_)) + )); + } + + #[test] + fn test_negative() { + assert_eq!( + parse_pos_nonzero("-555"), + Err(ParsePosNonzeroError::Creation(CreationError::Negative)) + ); + } + + #[test] + fn test_zero() { + assert_eq!( + parse_pos_nonzero("0"), + Err(ParsePosNonzeroError::Creation(CreationError::Zero)) + ); + } + + #[test] + fn test_positive() { + let x = PositiveNonzeroInteger::new(42); + assert!(x.is_ok()); + assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap())); + } +} diff --git a/exercises/14_generics/README.md b/exercises/14_generics/README.md new file mode 100644 index 0000000..de46d50 --- /dev/null +++ b/exercises/14_generics/README.md @@ -0,0 +1,11 @@ +# Generics + +Generics is the topic of generalizing types and functionalities to broader cases. +This is extremely useful for reducing code duplication in many ways, but can call for rather involving syntax. +Namely, being generic requires taking great care to specify over which types a generic type is actually considered valid. +The simplest and most common use of generics is for type parameters. + +## Further information + +- [Generic Data Types](https://doc.rust-lang.org/stable/book/ch10-01-syntax.html) +- [Bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html) diff --git a/exercises/14_generics/generics1.rs b/exercises/14_generics/generics1.rs new file mode 100644 index 0000000..35c1d2f --- /dev/null +++ b/exercises/14_generics/generics1.rs @@ -0,0 +1,14 @@ +// generics1.rs +// +// This shopping list program isn't compiling! Use your knowledge of generics to +// fix it. +// +// Execute `rustlings hint generics1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let mut shopping_list: Vec = Vec::new(); + shopping_list.push("milk"); +} diff --git a/exercises/14_generics/generics2.rs b/exercises/14_generics/generics2.rs new file mode 100644 index 0000000..074cd93 --- /dev/null +++ b/exercises/14_generics/generics2.rs @@ -0,0 +1,34 @@ +// generics2.rs +// +// This powerful wrapper provides the ability to store a positive integer value. +// Rewrite it using generics so that it supports wrapping ANY type. +// +// Execute `rustlings hint generics2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +struct Wrapper { + value: u32, +} + +impl Wrapper { + pub fn new(value: u32) -> Self { + Wrapper { value } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn store_u32_in_wrapper() { + assert_eq!(Wrapper::new(42).value, 42); + } + + #[test] + fn store_str_in_wrapper() { + assert_eq!(Wrapper::new("Foo").value, "Foo"); + } +} diff --git a/exercises/15_traits/README.md b/exercises/15_traits/README.md new file mode 100644 index 0000000..ac87c64 --- /dev/null +++ b/exercises/15_traits/README.md @@ -0,0 +1,19 @@ +# Traits + +A trait is a collection of methods. + +Data types can implement traits. To do so, the methods making up the trait are defined for the data type. For example, the `String` data type implements the `From<&str>` trait. This allows a user to write `String::from("hello")`. + +In this way, traits are somewhat similar to Java interfaces and C++ abstract classes. + +Some additional common Rust traits include: + +- `Clone` (the `clone` method) +- `Display` (which allows formatted display via `{}`) +- `Debug` (which allows formatted display via `{:?}`) + +Because traits indicate shared behavior between data types, they are useful when writing generics. + +## Further information + +- [Traits](https://doc.rust-lang.org/book/ch10-02-traits.html) diff --git a/exercises/15_traits/traits1.rs b/exercises/15_traits/traits1.rs new file mode 100644 index 0000000..37dfcbf --- /dev/null +++ b/exercises/15_traits/traits1.rs @@ -0,0 +1,42 @@ +// traits1.rs +// +// Time to implement some traits! Your task is to implement the trait +// `AppendBar` for the type `String`. The trait AppendBar has only one function, +// which appends "Bar" to any object implementing this trait. +// +// Execute `rustlings hint traits1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +trait AppendBar { + fn append_bar(self) -> Self; +} + +impl AppendBar for String { + // TODO: Implement `AppendBar` for type `String`. +} + +fn main() { + let s = String::from("Foo"); + let s = s.append_bar(); + println!("s: {}", s); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_foo_bar() { + assert_eq!(String::from("Foo").append_bar(), String::from("FooBar")); + } + + #[test] + fn is_bar_bar() { + assert_eq!( + String::from("").append_bar().append_bar(), + String::from("BarBar") + ); + } +} diff --git a/exercises/15_traits/traits2.rs b/exercises/15_traits/traits2.rs new file mode 100644 index 0000000..3e35f8e --- /dev/null +++ b/exercises/15_traits/traits2.rs @@ -0,0 +1,29 @@ +// traits2.rs +// +// Your task is to implement the trait `AppendBar` for a vector of strings. To +// implement this trait, consider for a moment what it means to 'append "Bar"' +// to a vector of strings. +// +// No boiler plate code this time, you can do this! +// +// Execute `rustlings hint traits2` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +trait AppendBar { + fn append_bar(self) -> Self; +} + +// TODO: Implement trait `AppendBar` for a vector of strings. + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_vec_pop_eq_bar() { + let mut foo = vec![String::from("Foo")].append_bar(); + assert_eq!(foo.pop().unwrap(), String::from("Bar")); + assert_eq!(foo.pop().unwrap(), String::from("Foo")); + } +} diff --git a/exercises/15_traits/traits3.rs b/exercises/15_traits/traits3.rs new file mode 100644 index 0000000..4e2b06b --- /dev/null +++ b/exercises/15_traits/traits3.rs @@ -0,0 +1,42 @@ +// traits3.rs +// +// Your task is to implement the Licensed trait for both structures and have +// them return the same information without writing the same function twice. +// +// Consider what you can add to the Licensed trait. +// +// Execute `rustlings hint traits3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +pub trait Licensed { + fn licensing_info(&self) -> String; +} + +struct SomeSoftware { + version_number: i32, +} + +struct OtherSoftware { + version_number: String, +} + +impl Licensed for SomeSoftware {} // Don't edit this line +impl Licensed for OtherSoftware {} // Don't edit this line + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_licensing_info_the_same() { + let licensing_info = String::from("Some information"); + let some_software = SomeSoftware { version_number: 1 }; + let other_software = OtherSoftware { + version_number: "v2.0.0".to_string(), + }; + assert_eq!(some_software.licensing_info(), licensing_info); + assert_eq!(other_software.licensing_info(), licensing_info); + } +} diff --git a/exercises/15_traits/traits4.rs b/exercises/15_traits/traits4.rs new file mode 100644 index 0000000..4bda3e5 --- /dev/null +++ b/exercises/15_traits/traits4.rs @@ -0,0 +1,49 @@ +// traits4.rs +// +// Your task is to replace the '??' sections so the code compiles. +// +// Don't change any line other than the marked one. +// +// Execute `rustlings hint traits4` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +pub trait Licensed { + fn licensing_info(&self) -> String { + "some information".to_string() + } +} + +struct SomeSoftware {} + +struct OtherSoftware {} + +impl Licensed for SomeSoftware {} +impl Licensed for OtherSoftware {} + +// YOU MAY ONLY CHANGE THE NEXT LINE +fn compare_license_types(software: ??, software_two: ??) -> bool { + software.licensing_info() == software_two.licensing_info() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compare_license_information() { + let some_software = SomeSoftware {}; + let other_software = OtherSoftware {}; + + assert!(compare_license_types(some_software, other_software)); + } + + #[test] + fn compare_license_information_backwards() { + let some_software = SomeSoftware {}; + let other_software = OtherSoftware {}; + + assert!(compare_license_types(other_software, some_software)); + } +} diff --git a/exercises/15_traits/traits5.rs b/exercises/15_traits/traits5.rs new file mode 100644 index 0000000..df18380 --- /dev/null +++ b/exercises/15_traits/traits5.rs @@ -0,0 +1,40 @@ +// traits5.rs +// +// Your task is to replace the '??' sections so the code compiles. +// +// Don't change any line other than the marked one. +// +// Execute `rustlings hint traits5` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +pub trait SomeTrait { + fn some_function(&self) -> bool { + true + } +} + +pub trait OtherTrait { + fn other_function(&self) -> bool { + true + } +} + +struct SomeStruct {} +struct OtherStruct {} + +impl SomeTrait for SomeStruct {} +impl OtherTrait for SomeStruct {} +impl SomeTrait for OtherStruct {} +impl OtherTrait for OtherStruct {} + +// YOU MAY ONLY CHANGE THE NEXT LINE +fn some_func(item: ??) -> bool { + item.some_function() && item.other_function() +} + +fn main() { + some_func(SomeStruct {}); + some_func(OtherStruct {}); +} diff --git a/exercises/16_lifetimes/README.md b/exercises/16_lifetimes/README.md new file mode 100644 index 0000000..91373f7 --- /dev/null +++ b/exercises/16_lifetimes/README.md @@ -0,0 +1,22 @@ +# Lifetimes + +Lifetimes tell the compiler how to check whether references live long +enough to be valid in any given situation. For example lifetimes say +"make sure parameter 'a' lives as long as parameter 'b' so that the return +value is valid". + +They are only necessary on borrows, i.e. references, +since copied parameters or moves are owned in their scope and cannot +be referenced outside. Lifetimes mean that calling code of e.g. functions +can be checked to make sure their arguments are valid. Lifetimes are +restrictive of their callers. + +If you'd like to learn more about lifetime annotations, the +[lifetimekata](https://tfpk.github.io/lifetimekata/) project +has a similar style of exercises to Rustlings, but is all about +learning to write lifetime annotations. + +## Further information + +- [Lifetimes (in Rust By Example)](https://doc.rust-lang.org/stable/rust-by-example/scope/lifetime.html) +- [Validating References with Lifetimes](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) diff --git a/exercises/16_lifetimes/lifetimes1.rs b/exercises/16_lifetimes/lifetimes1.rs new file mode 100644 index 0000000..87bde49 --- /dev/null +++ b/exercises/16_lifetimes/lifetimes1.rs @@ -0,0 +1,27 @@ +// lifetimes1.rs +// +// The Rust compiler needs to know how to check whether supplied references are +// valid, so that it can let the programmer know if a reference is at risk of +// going out of scope before it is used. Remember, references are borrows and do +// not own their own data. What if their owner goes out of scope? +// +// Execute `rustlings hint lifetimes1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn longest(x: &str, y: &str) -> &str { + if x.len() > y.len() { + x + } else { + y + } +} + +fn main() { + let string1 = String::from("abcd"); + let string2 = "xyz"; + + let result = longest(string1.as_str(), string2); + println!("The longest string is '{}'", result); +} diff --git a/exercises/16_lifetimes/lifetimes2.rs b/exercises/16_lifetimes/lifetimes2.rs new file mode 100644 index 0000000..4f3d8c1 --- /dev/null +++ b/exercises/16_lifetimes/lifetimes2.rs @@ -0,0 +1,27 @@ +// lifetimes2.rs +// +// So if the compiler is just validating the references passed to the annotated +// parameters and the return type, what do we need to change? +// +// Execute `rustlings hint lifetimes2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} + +fn main() { + let string1 = String::from("long string is long"); + let result; + { + let string2 = String::from("xyz"); + result = longest(string1.as_str(), string2.as_str()); + } + println!("The longest string is '{}'", result); +} diff --git a/exercises/16_lifetimes/lifetimes3.rs b/exercises/16_lifetimes/lifetimes3.rs new file mode 100644 index 0000000..9c59f9c --- /dev/null +++ b/exercises/16_lifetimes/lifetimes3.rs @@ -0,0 +1,21 @@ +// lifetimes3.rs +// +// Lifetimes are also needed when structs hold references. +// +// Execute `rustlings hint lifetimes3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +struct Book { + author: &str, + title: &str, +} + +fn main() { + let name = String::from("Jill Smith"); + let title = String::from("Fish Flying"); + let book = Book { author: &name, title: &title }; + + println!("{} by {}", book.title, book.author); +} diff --git a/exercises/17_tests/README.md b/exercises/17_tests/README.md new file mode 100644 index 0000000..27c6818 --- /dev/null +++ b/exercises/17_tests/README.md @@ -0,0 +1,7 @@ +# Tests + +Going out of order from the book to cover tests -- many of the following exercises will ask you to make tests pass! + +## Further information + +- [Writing Tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) diff --git a/exercises/17_tests/tests1.rs b/exercises/17_tests/tests1.rs new file mode 100644 index 0000000..810277a --- /dev/null +++ b/exercises/17_tests/tests1.rs @@ -0,0 +1,21 @@ +// tests1.rs +// +// Tests are important to ensure that your code does what you think it should +// do. Tests can be run on this file with the following command: rustlings run +// tests1 +// +// This test has a problem with it -- make the test compile! Make the test pass! +// Make the test fail! +// +// Execute `rustlings hint tests1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[cfg(test)] +mod tests { + #[test] + fn you_can_assert() { + assert!(); + } +} diff --git a/exercises/17_tests/tests2.rs b/exercises/17_tests/tests2.rs new file mode 100644 index 0000000..f8024e9 --- /dev/null +++ b/exercises/17_tests/tests2.rs @@ -0,0 +1,17 @@ +// tests2.rs +// +// This test has a problem with it -- make the test compile! Make the test pass! +// Make the test fail! +// +// Execute `rustlings hint tests2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[cfg(test)] +mod tests { + #[test] + fn you_can_assert_eq() { + assert_eq!(); + } +} diff --git a/exercises/17_tests/tests3.rs b/exercises/17_tests/tests3.rs new file mode 100644 index 0000000..4013e38 --- /dev/null +++ b/exercises/17_tests/tests3.rs @@ -0,0 +1,29 @@ +// tests3.rs +// +// This test isn't testing our function -- make it do that in such a way that +// the test passes. Then write a second test that tests whether we get the +// result we expect to get when we call `is_even(5)`. +// +// Execute `rustlings hint tests3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +pub fn is_even(num: i32) -> bool { + num % 2 == 0 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_true_when_even() { + assert!(); + } + + #[test] + fn is_false_when_odd() { + assert!(); + } +} diff --git a/exercises/17_tests/tests4.rs b/exercises/17_tests/tests4.rs new file mode 100644 index 0000000..935d0db --- /dev/null +++ b/exercises/17_tests/tests4.rs @@ -0,0 +1,48 @@ +// tests4.rs +// +// Make sure that we're testing for the correct conditions! +// +// Execute `rustlings hint tests4` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +struct Rectangle { + width: i32, + height: i32 +} + +impl Rectangle { + // Only change the test functions themselves + pub fn new(width: i32, height: i32) -> Self { + if width <= 0 || height <= 0 { + panic!("Rectangle width and height cannot be negative!") + } + Rectangle {width, height} + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_width_and_height() { + // This test should check if the rectangle is the size that we pass into its constructor + let rect = Rectangle::new(10, 20); + assert_eq!(???, 10); // check width + assert_eq!(???, 20); // check height + } + + #[test] + fn negative_width() { + // This test should check if program panics when we try to create rectangle with negative width + let _rect = Rectangle::new(-10, 10); + } + + #[test] + fn negative_height() { + // This test should check if program panics when we try to create rectangle with negative height + let _rect = Rectangle::new(10, -10); + } +} diff --git a/exercises/18_iterators/README.md b/exercises/18_iterators/README.md new file mode 100644 index 0000000..0e8b671 --- /dev/null +++ b/exercises/18_iterators/README.md @@ -0,0 +1,8 @@ +# Iterators + +This section will teach you about Iterators. + +## Further information + +- [Iterator](https://doc.rust-lang.org/book/ch13-02-iterators.html) +- [Iterator documentation](https://doc.rust-lang.org/stable/std/iter/) diff --git a/exercises/18_iterators/iterators1.rs b/exercises/18_iterators/iterators1.rs new file mode 100644 index 0000000..31076bb --- /dev/null +++ b/exercises/18_iterators/iterators1.rs @@ -0,0 +1,26 @@ +// iterators1.rs +// +// When performing operations on elements within a collection, iterators are +// essential. This module helps you get familiar with the structure of using an +// iterator and how to go through elements within an iterable collection. +// +// Make me compile by filling in the `???`s +// +// Execute `rustlings hint iterators1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[test] +fn main() { + let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"]; + + let mut my_iterable_fav_fruits = ???; // TODO: Step 1 + + assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana")); + assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 2 + assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado")); + assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 3 + assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry")); + assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 4 +} diff --git a/exercises/18_iterators/iterators2.rs b/exercises/18_iterators/iterators2.rs new file mode 100644 index 0000000..dda82a0 --- /dev/null +++ b/exercises/18_iterators/iterators2.rs @@ -0,0 +1,63 @@ +// iterators2.rs +// +// In this exercise, you'll learn some of the unique advantages that iterators +// can offer. Follow the steps to complete the exercise. +// +// Execute `rustlings hint iterators2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +// Step 1. +// Complete the `capitalize_first` function. +// "hello" -> "Hello" +pub fn capitalize_first(input: &str) -> String { + let mut c = input.chars(); + match c.next() { + None => String::new(), + Some(first) => ???, + } +} + +// Step 2. +// Apply the `capitalize_first` function to a slice of string slices. +// Return a vector of strings. +// ["hello", "world"] -> ["Hello", "World"] +pub fn capitalize_words_vector(words: &[&str]) -> Vec { + vec![] +} + +// Step 3. +// Apply the `capitalize_first` function again to a slice of string slices. +// Return a single string. +// ["hello", " ", "world"] -> "Hello World" +pub fn capitalize_words_string(words: &[&str]) -> String { + String::new() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_success() { + assert_eq!(capitalize_first("hello"), "Hello"); + } + + #[test] + fn test_empty() { + assert_eq!(capitalize_first(""), ""); + } + + #[test] + fn test_iterate_string_vec() { + let words = vec!["hello", "world"]; + assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]); + } + + #[test] + fn test_iterate_into_string() { + let words = vec!["hello", " ", "world"]; + assert_eq!(capitalize_words_string(&words), "Hello World"); + } +} diff --git a/exercises/18_iterators/iterators3.rs b/exercises/18_iterators/iterators3.rs new file mode 100644 index 0000000..29fa23a --- /dev/null +++ b/exercises/18_iterators/iterators3.rs @@ -0,0 +1,90 @@ +// iterators3.rs +// +// This is a bigger exercise than most of the others! You can do it! Here is +// your mission, should you choose to accept it: +// 1. Complete the divide function to get the first four tests to pass. +// 2. Get the remaining tests to pass by completing the result_with_list and +// list_of_results functions. +// +// Execute `rustlings hint iterators3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[derive(Debug, PartialEq, Eq)] +pub enum DivisionError { + NotDivisible(NotDivisibleError), + DivideByZero, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct NotDivisibleError { + dividend: i32, + divisor: i32, +} + +// Calculate `a` divided by `b` if `a` is evenly divisible by `b`. +// Otherwise, return a suitable error. +pub fn divide(a: i32, b: i32) -> Result { + todo!(); +} + +// Complete the function and return a value of the correct type so the test +// passes. +// Desired output: Ok([1, 11, 1426, 3]) +fn result_with_list() -> () { + let numbers = vec![27, 297, 38502, 81]; + let division_results = numbers.into_iter().map(|n| divide(n, 27)); +} + +// Complete the function and return a value of the correct type so the test +// passes. +// Desired output: [Ok(1), Ok(11), Ok(1426), Ok(3)] +fn list_of_results() -> () { + let numbers = vec![27, 297, 38502, 81]; + let division_results = numbers.into_iter().map(|n| divide(n, 27)); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_success() { + assert_eq!(divide(81, 9), Ok(9)); + } + + #[test] + fn test_not_divisible() { + assert_eq!( + divide(81, 6), + Err(DivisionError::NotDivisible(NotDivisibleError { + dividend: 81, + divisor: 6 + })) + ); + } + + #[test] + fn test_divide_by_0() { + assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); + } + + #[test] + fn test_divide_0_by_something() { + assert_eq!(divide(0, 81), Ok(0)); + } + + #[test] + fn test_result_with_list() { + assert_eq!(format!("{:?}", result_with_list()), "Ok([1, 11, 1426, 3])"); + } + + #[test] + fn test_list_of_results() { + assert_eq!( + format!("{:?}", list_of_results()), + "[Ok(1), Ok(11), Ok(1426), Ok(3)]" + ); + } +} diff --git a/exercises/18_iterators/iterators4.rs b/exercises/18_iterators/iterators4.rs new file mode 100644 index 0000000..79e1692 --- /dev/null +++ b/exercises/18_iterators/iterators4.rs @@ -0,0 +1,42 @@ +// iterators4.rs +// +// Execute `rustlings hint iterators4` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +pub fn factorial(num: u64) -> u64 { + // Complete this function to return the factorial of num + // Do not use: + // - return + // Try not to use: + // - imperative style loops (for, while) + // - additional variables + // For an extra challenge, don't use: + // - recursion + // Execute `rustlings hint iterators4` for hints. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn factorial_of_0() { + assert_eq!(1, factorial(0)); + } + + #[test] + fn factorial_of_1() { + assert_eq!(1, factorial(1)); + } + #[test] + fn factorial_of_2() { + assert_eq!(2, factorial(2)); + } + + #[test] + fn factorial_of_4() { + assert_eq!(24, factorial(4)); + } +} diff --git a/exercises/18_iterators/iterators5.rs b/exercises/18_iterators/iterators5.rs new file mode 100644 index 0000000..a062ee4 --- /dev/null +++ b/exercises/18_iterators/iterators5.rs @@ -0,0 +1,156 @@ +// iterators5.rs +// +// Let's define a simple model to track Rustlings exercise progress. Progress +// will be modelled using a hash map. The name of the exercise is the key and +// the progress is the value. Two counting functions were created to count the +// number of exercises with a given progress. Recreate this counting +// functionality using iterators. Try not to use imperative loops (for, while). +// Only the two iterator methods (count_iterator and count_collection_iterator) +// need to be modified. +// +// Execute `rustlings hint iterators5` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::collections::HashMap; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Progress { + None, + Some, + Complete, +} + +fn count_for(map: &HashMap, value: Progress) -> usize { + let mut count = 0; + for val in map.values() { + if val == &value { + count += 1; + } + } + count +} + +fn count_iterator(map: &HashMap, value: Progress) -> usize { + // map is a hashmap with String keys and Progress values. + // map = { "variables1": Complete, "from_str": None, ... } + todo!(); +} + +fn count_collection_for(collection: &[HashMap], value: Progress) -> usize { + let mut count = 0; + for map in collection { + for val in map.values() { + if val == &value { + count += 1; + } + } + } + count +} + +fn count_collection_iterator(collection: &[HashMap], value: Progress) -> usize { + // collection is a slice of hashmaps. + // collection = [{ "variables1": Complete, "from_str": None, ... }, + // { "variables2": Complete, ... }, ... ] + todo!(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn count_complete() { + let map = get_map(); + assert_eq!(3, count_iterator(&map, Progress::Complete)); + } + + #[test] + fn count_some() { + let map = get_map(); + assert_eq!(1, count_iterator(&map, Progress::Some)); + } + + #[test] + fn count_none() { + let map = get_map(); + assert_eq!(2, count_iterator(&map, Progress::None)); + } + + #[test] + fn count_complete_equals_for() { + let map = get_map(); + let progress_states = vec![Progress::Complete, Progress::Some, Progress::None]; + for progress_state in progress_states { + assert_eq!( + count_for(&map, progress_state), + count_iterator(&map, progress_state) + ); + } + } + + #[test] + fn count_collection_complete() { + let collection = get_vec_map(); + assert_eq!( + 6, + count_collection_iterator(&collection, Progress::Complete) + ); + } + + #[test] + fn count_collection_some() { + let collection = get_vec_map(); + assert_eq!(1, count_collection_iterator(&collection, Progress::Some)); + } + + #[test] + fn count_collection_none() { + let collection = get_vec_map(); + assert_eq!(4, count_collection_iterator(&collection, Progress::None)); + } + + #[test] + fn count_collection_equals_for() { + let progress_states = vec![Progress::Complete, Progress::Some, Progress::None]; + let collection = get_vec_map(); + + for progress_state in progress_states { + assert_eq!( + count_collection_for(&collection, progress_state), + count_collection_iterator(&collection, progress_state) + ); + } + } + + fn get_map() -> HashMap { + use Progress::*; + + let mut map = HashMap::new(); + map.insert(String::from("variables1"), Complete); + map.insert(String::from("functions1"), Complete); + map.insert(String::from("hashmap1"), Complete); + map.insert(String::from("arc1"), Some); + map.insert(String::from("as_ref_mut"), None); + map.insert(String::from("from_str"), None); + + map + } + + fn get_vec_map() -> Vec> { + use Progress::*; + + let map = get_map(); + + let mut other = HashMap::new(); + other.insert(String::from("variables2"), Complete); + other.insert(String::from("functions2"), Complete); + other.insert(String::from("if1"), Complete); + other.insert(String::from("from_into"), None); + other.insert(String::from("try_from_into"), None); + + vec![map, other] + } +} diff --git a/exercises/19_smart_pointers/README.md b/exercises/19_smart_pointers/README.md new file mode 100644 index 0000000..d56d2b6 --- /dev/null +++ b/exercises/19_smart_pointers/README.md @@ -0,0 +1,12 @@ +# Smart Pointers + +In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities. +Smart pointers in Rust often own the data they point to, while references only borrow data. + +## Further Information + +- [Smart Pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) +- [Using Box to Point to Data on the Heap](https://doc.rust-lang.org/book/ch15-01-box.html) +- [Rc\, the Reference Counted Smart Pointer](https://doc.rust-lang.org/book/ch15-04-rc.html) +- [Shared-State Concurrency](https://doc.rust-lang.org/book/ch16-03-shared-state.html) +- [Cow Documentation](https://doc.rust-lang.org/std/borrow/enum.Cow.html) diff --git a/exercises/19_smart_pointers/arc1.rs b/exercises/19_smart_pointers/arc1.rs new file mode 100644 index 0000000..3526ddc --- /dev/null +++ b/exercises/19_smart_pointers/arc1.rs @@ -0,0 +1,45 @@ +// arc1.rs +// +// In this exercise, we are given a Vec of u32 called "numbers" with values +// ranging from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ] We would like to use this +// set of numbers within 8 different threads simultaneously. Each thread is +// going to get the sum of every eighth value, with an offset. +// +// The first thread (offset 0), will sum 0, 8, 16, ... +// The second thread (offset 1), will sum 1, 9, 17, ... +// The third thread (offset 2), will sum 2, 10, 18, ... +// ... +// The eighth thread (offset 7), will sum 7, 15, 23, ... +// +// Because we are using threads, our values need to be thread-safe. Therefore, +// we are using Arc. We need to make a change in each of the two TODOs. +// +// Make this code compile by filling in a value for `shared_numbers` where the +// first TODO comment is, and create an initial binding for `child_numbers` +// where the second TODO comment is. Try not to create any copies of the +// `numbers` Vec! +// +// Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +#![forbid(unused_imports)] // Do not change this, (or the next) line. +use std::sync::Arc; +use std::thread; + +fn main() { + let numbers: Vec<_> = (0..100u32).collect(); + let shared_numbers = // TODO + let mut joinhandles = Vec::new(); + + for offset in 0..8 { + let child_numbers = // TODO + joinhandles.push(thread::spawn(move || { + let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); + println!("Sum of offset {} is {}", offset, sum); + })); + } + for handle in joinhandles.into_iter() { + handle.join().unwrap(); + } +} diff --git a/exercises/19_smart_pointers/box1.rs b/exercises/19_smart_pointers/box1.rs new file mode 100644 index 0000000..513e7da --- /dev/null +++ b/exercises/19_smart_pointers/box1.rs @@ -0,0 +1,58 @@ +// box1.rs +// +// At compile time, Rust needs to know how much space a type takes up. This +// becomes problematic for recursive types, where a value can have as part of +// itself another value of the same type. To get around the issue, we can use a +// `Box` - a smart pointer used to store data on the heap, which also allows us +// to wrap a recursive type. +// +// The recursive type we're implementing in this exercise is the `cons list` - a +// data structure frequently found in functional programming languages. Each +// item in a cons list contains two elements: the value of the current item and +// the next item. The last item is a value called `Nil`. +// +// Step 1: use a `Box` in the enum definition to make the code compile +// Step 2: create both empty and non-empty cons lists by replacing `todo!()` +// +// Note: the tests should not be changed +// +// Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +#[derive(PartialEq, Debug)] +pub enum List { + Cons(i32, List), + Nil, +} + +fn main() { + println!("This is an empty cons list: {:?}", create_empty_list()); + println!( + "This is a non-empty cons list: {:?}", + create_non_empty_list() + ); +} + +pub fn create_empty_list() -> List { + todo!() +} + +pub fn create_non_empty_list() -> List { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_empty_list() { + assert_eq!(List::Nil, create_empty_list()) + } + + #[test] + fn test_create_non_empty_list() { + assert_ne!(create_empty_list(), create_non_empty_list()) + } +} diff --git a/exercises/19_smart_pointers/cow1.rs b/exercises/19_smart_pointers/cow1.rs new file mode 100644 index 0000000..fcd3e0b --- /dev/null +++ b/exercises/19_smart_pointers/cow1.rs @@ -0,0 +1,78 @@ +// cow1.rs +// +// This exercise explores the Cow, or Clone-On-Write type. Cow is a +// clone-on-write smart pointer. It can enclose and provide immutable access to +// borrowed data, and clone the data lazily when mutation or ownership is +// required. The type is designed to work with general borrowed data via the +// Borrow trait. +// +// This exercise is meant to show you what to expect when passing data to Cow. +// Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the +// TODO markers. +// +// Execute `rustlings hint cow1` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +use std::borrow::Cow; + +fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> { + for i in 0..input.len() { + let v = input[i]; + if v < 0 { + // Clones into a vector if not already owned. + input.to_mut()[i] = -v; + } + } + input +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reference_mutation() -> Result<(), &'static str> { + // Clone occurs because `input` needs to be mutated. + let slice = [-1, 0, 1]; + let mut input = Cow::from(&slice[..]); + match abs_all(&mut input) { + Cow::Owned(_) => Ok(()), + _ => Err("Expected owned value"), + } + } + + #[test] + fn reference_no_mutation() -> Result<(), &'static str> { + // No clone occurs because `input` doesn't need to be mutated. + let slice = [0, 1, 2]; + let mut input = Cow::from(&slice[..]); + match abs_all(&mut input) { + // TODO + } + } + + #[test] + fn owned_no_mutation() -> Result<(), &'static str> { + // We can also pass `slice` without `&` so Cow owns it directly. In this + // case no mutation occurs and thus also no clone, but the result is + // still owned because it was never borrowed or mutated. + let slice = vec![0, 1, 2]; + let mut input = Cow::from(slice); + match abs_all(&mut input) { + // TODO + } + } + + #[test] + fn owned_mutation() -> Result<(), &'static str> { + // Of course this is also the case if a mutation does occur. In this + // case the call to `to_mut()` in the abs_all() function returns a + // reference to the same data as before. + let slice = vec![-1, 0, 1]; + let mut input = Cow::from(slice); + match abs_all(&mut input) { + // TODO + } + } +} diff --git a/exercises/19_smart_pointers/rc1.rs b/exercises/19_smart_pointers/rc1.rs new file mode 100644 index 0000000..1b90346 --- /dev/null +++ b/exercises/19_smart_pointers/rc1.rs @@ -0,0 +1,105 @@ +// rc1.rs +// +// In this exercise, we want to express the concept of multiple owners via the +// Rc type. This is a model of our solar system - there is a Sun type and +// multiple Planets. The Planets take ownership of the sun, indicating that they +// revolve around the sun. +// +// Make this code compile by using the proper Rc primitives to express that the +// sun has multiple owners. +// +// Execute `rustlings hint rc1` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +use std::rc::Rc; + +#[derive(Debug)] +struct Sun {} + +#[derive(Debug)] +enum Planet { + Mercury(Rc), + Venus(Rc), + Earth(Rc), + Mars(Rc), + Jupiter(Rc), + Saturn(Rc), + Uranus(Rc), + Neptune(Rc), +} + +impl Planet { + fn details(&self) { + println!("Hi from {:?}!", self) + } +} + +#[test] +fn main() { + let sun = Rc::new(Sun {}); + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + let mercury = Planet::Mercury(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + mercury.details(); + + let venus = Planet::Venus(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + venus.details(); + + let earth = Planet::Earth(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + earth.details(); + + let mars = Planet::Mars(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + mars.details(); + + let jupiter = Planet::Jupiter(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + jupiter.details(); + + // TODO + let saturn = Planet::Saturn(Rc::new(Sun {})); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + saturn.details(); + + // TODO + let uranus = Planet::Uranus(Rc::new(Sun {})); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + uranus.details(); + + // TODO + let neptune = Planet::Neptune(Rc::new(Sun {})); + println!("reference count = {}", Rc::strong_count(&sun)); // 9 references + neptune.details(); + + assert_eq!(Rc::strong_count(&sun), 9); + + drop(neptune); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + + drop(uranus); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + + drop(saturn); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + + drop(jupiter); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + + drop(mars); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + + // TODO + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + + // TODO + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + + // TODO + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + assert_eq!(Rc::strong_count(&sun), 1); +} diff --git a/exercises/20_threads/README.md b/exercises/20_threads/README.md new file mode 100644 index 0000000..dbe6664 --- /dev/null +++ b/exercises/20_threads/README.md @@ -0,0 +1,9 @@ +# Threads + +In most current operating systems, an executed program's code is run in a process, and the operating system manages multiple processes at once. +Within your program, you can also have independent parts that run simultaneously. The features that run these independent parts are called threads. + +## Further information + +- [Dining Philosophers example](https://doc.rust-lang.org/1.4.0/book/dining-philosophers.html) +- [Using Threads to Run Code Simultaneously](https://doc.rust-lang.org/book/ch16-01-threads.html) diff --git a/exercises/20_threads/threads1.rs b/exercises/20_threads/threads1.rs new file mode 100644 index 0000000..80b6def --- /dev/null +++ b/exercises/20_threads/threads1.rs @@ -0,0 +1,40 @@ +// threads1.rs +// +// This program spawns multiple threads that each run for at least 250ms, and +// each thread returns how much time they took to complete. The program should +// wait until all the spawned threads have finished and should collect their +// return values into a vector. +// +// Execute `rustlings hint threads1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::thread; +use std::time::{Duration, Instant}; + +fn main() { + let mut handles = vec![]; + for i in 0..10 { + handles.push(thread::spawn(move || { + let start = Instant::now(); + thread::sleep(Duration::from_millis(250)); + println!("thread {} is complete", i); + start.elapsed().as_millis() + })); + } + + let mut results: Vec = vec![]; + for handle in handles { + // TODO: a struct is returned from thread::spawn, can you use it? + } + + if results.len() != 10 { + panic!("Oh no! All the spawned threads did not finish!"); + } + + println!(); + for (i, result) in results.into_iter().enumerate() { + println!("thread {} took {}ms", i, result); + } +} diff --git a/exercises/20_threads/threads2.rs b/exercises/20_threads/threads2.rs new file mode 100644 index 0000000..62dad80 --- /dev/null +++ b/exercises/20_threads/threads2.rs @@ -0,0 +1,39 @@ +// threads2.rs +// +// Building on the last exercise, we want all of the threads to complete their +// work but this time the spawned threads need to be in charge of updating a +// shared value: JobStatus.jobs_completed +// +// Execute `rustlings hint threads2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +struct JobStatus { + jobs_completed: u32, +} + +fn main() { + let status = Arc::new(JobStatus { jobs_completed: 0 }); + let mut handles = vec![]; + for _ in 0..10 { + let status_shared = Arc::clone(&status); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(250)); + // TODO: You must take an action before you update a shared value + status_shared.jobs_completed += 1; + }); + handles.push(handle); + } + for handle in handles { + handle.join().unwrap(); + // TODO: Print the value of the JobStatus.jobs_completed. Did you notice + // anything interesting in the output? Do you have to 'join' on all the + // handles? + println!("jobs completed {}", ???); + } +} diff --git a/exercises/20_threads/threads3.rs b/exercises/20_threads/threads3.rs new file mode 100644 index 0000000..91006bb --- /dev/null +++ b/exercises/20_threads/threads3.rs @@ -0,0 +1,67 @@ +// threads3.rs +// +// Execute `rustlings hint threads3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +struct Queue { + length: u32, + first_half: Vec, + second_half: Vec, +} + +impl Queue { + fn new() -> Self { + Queue { + length: 10, + first_half: vec![1, 2, 3, 4, 5], + second_half: vec![6, 7, 8, 9, 10], + } + } +} + +fn send_tx(q: Queue, tx: mpsc::Sender) -> () { + let qc = Arc::new(q); + let qc1 = Arc::clone(&qc); + let qc2 = Arc::clone(&qc); + + thread::spawn(move || { + for val in &qc1.first_half { + println!("sending {:?}", val); + tx.send(*val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + thread::spawn(move || { + for val in &qc2.second_half { + println!("sending {:?}", val); + tx.send(*val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); +} + +#[test] +fn main() { + let (tx, rx) = mpsc::channel(); + let queue = Queue::new(); + let queue_length = queue.length; + + send_tx(queue, tx); + + let mut total_received: u32 = 0; + for received in rx { + println!("Got: {}", received); + total_received += 1; + } + + println!("total numbers received: {}", total_received); + assert_eq!(total_received, queue_length) +} diff --git a/exercises/21_macros/README.md b/exercises/21_macros/README.md new file mode 100644 index 0000000..337816d --- /dev/null +++ b/exercises/21_macros/README.md @@ -0,0 +1,14 @@ +# Macros + +Rust's macro system is very powerful, but also kind of difficult to wrap your +head around. We're not going to teach you how to write your own fully-featured +macros. Instead, we'll show you how to use and create them. + +If you'd like to learn more about writing your own macros, the +[macrokata](https://github.com/tfpk/macrokata) project has a similar style +of exercises to Rustlings, but is all about learning to write Macros. + +## Further information + +- [Macros](https://doc.rust-lang.org/book/ch19-06-macros.html) +- [The Little Book of Rust Macros](https://veykril.github.io/tlborm/) diff --git a/exercises/21_macros/macros1.rs b/exercises/21_macros/macros1.rs new file mode 100644 index 0000000..678de6e --- /dev/null +++ b/exercises/21_macros/macros1.rs @@ -0,0 +1,16 @@ +// macros1.rs +// +// Execute `rustlings hint macros1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} + +fn main() { + my_macro(); +} diff --git a/exercises/21_macros/macros2.rs b/exercises/21_macros/macros2.rs new file mode 100644 index 0000000..788fc16 --- /dev/null +++ b/exercises/21_macros/macros2.rs @@ -0,0 +1,16 @@ +// macros2.rs +// +// Execute `rustlings hint macros2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + my_macro!(); +} + +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} diff --git a/exercises/21_macros/macros3.rs b/exercises/21_macros/macros3.rs new file mode 100644 index 0000000..b795c14 --- /dev/null +++ b/exercises/21_macros/macros3.rs @@ -0,0 +1,20 @@ +// macros3.rs +// +// Make me compile, without taking the macro out of the module! +// +// Execute `rustlings hint macros3` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +mod macros { + macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; + } +} + +fn main() { + my_macro!(); +} diff --git a/exercises/21_macros/macros4.rs b/exercises/21_macros/macros4.rs new file mode 100644 index 0000000..71b45a0 --- /dev/null +++ b/exercises/21_macros/macros4.rs @@ -0,0 +1,21 @@ +// macros4.rs +// +// Execute `rustlings hint macros4` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +#[rustfmt::skip] +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + } + ($val:expr) => { + println!("Look at this other macro: {}", $val); + } +} + +fn main() { + my_macro!(); + my_macro!(7777); +} diff --git a/exercises/22_clippy/README.md b/exercises/22_clippy/README.md new file mode 100644 index 0000000..55438af --- /dev/null +++ b/exercises/22_clippy/README.md @@ -0,0 +1,10 @@ +# Clippy + +The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code. + +If you used the installation script for Rustlings, Clippy should be already installed. +If not you can install it manually via `rustup component add clippy`. + +## Further information + +- [GitHub Repository](https://github.com/rust-lang/rust-clippy). diff --git a/exercises/22_clippy/clippy1.rs b/exercises/22_clippy/clippy1.rs new file mode 100644 index 0000000..e0c6ce7 --- /dev/null +++ b/exercises/22_clippy/clippy1.rs @@ -0,0 +1,26 @@ +// clippy1.rs +// +// The Clippy tool is a collection of lints to analyze your code so you can +// catch common mistakes and improve your Rust code. +// +// For these exercises the code will fail to compile when there are Clippy +// warnings. Check Clippy's suggestions from the output to solve the exercise. +// +// Execute `rustlings hint clippy1` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +use std::f32; + +fn main() { + let pi = 3.14f32; + let radius = 5.00f32; + + let area = pi * f32::powi(radius, 2); + + println!( + "The area of a circle with radius {:.2} is {:.5}!", + radius, area + ) +} diff --git a/exercises/22_clippy/clippy2.rs b/exercises/22_clippy/clippy2.rs new file mode 100644 index 0000000..9b87a0b --- /dev/null +++ b/exercises/22_clippy/clippy2.rs @@ -0,0 +1,15 @@ +// clippy2.rs +// +// Execute `rustlings hint clippy2` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn main() { + let mut res = 42; + let option = Some(12); + for x in option { + res += x; + } + println!("{}", res); +} diff --git a/exercises/22_clippy/clippy3.rs b/exercises/22_clippy/clippy3.rs new file mode 100644 index 0000000..5a95f5b --- /dev/null +++ b/exercises/22_clippy/clippy3.rs @@ -0,0 +1,30 @@ +// clippy3.rs +// +// Here's a couple more easy Clippy fixes, so you can see its utility. +// No hints. + +// I AM NOT DONE + +#[allow(unused_variables, unused_assignments)] +fn main() { + let my_option: Option<()> = None; + if my_option.is_none() { + my_option.unwrap(); + } + + let my_arr = &[ + -1, -2, -3 + -4, -5, -6 + ]; + println!("My array! Here it is: {:?}", my_arr); + + let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); + println!("This Vec is empty, see? {:?}", my_empty_vec); + + let mut value_a = 45; + let mut value_b = 66; + // Let's swap these two! + value_a = value_b; + value_b = value_a; + println!("value a: {}; value b: {}", value_a, value_b); +} diff --git a/exercises/23_conversions/README.md b/exercises/23_conversions/README.md new file mode 100644 index 0000000..619a78c --- /dev/null +++ b/exercises/23_conversions/README.md @@ -0,0 +1,23 @@ +# Type conversions + +Rust offers a multitude of ways to convert a value of a given type into another type. + +The simplest form of type conversion is a type cast expression. It is denoted with the binary operator `as`. For instance, `println!("{}", 1 + 1.0);` would not compile, since `1` is an integer while `1.0` is a float. However, `println!("{}", 1 as f32 + 1.0)` should compile. The exercise [`using_as`](using_as.rs) tries to cover this. + +Rust also offers traits that facilitate type conversions upon implementation. These traits can be found under the [`convert`](https://doc.rust-lang.org/std/convert/index.html) module. +The traits are the following: + +- `From` and `Into` covered in [`from_into`](from_into.rs) +- `TryFrom` and `TryInto` covered in [`try_from_into`](try_from_into.rs) +- `AsRef` and `AsMut` covered in [`as_ref_mut`](as_ref_mut.rs) + +Furthermore, the `std::str` module offers a trait called [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) which helps with converting strings into target types via the `parse` method on strings. If properly implemented for a given type `Person`, then `let p: Person = "Mark,20".parse().unwrap()` should both compile and run without panicking. + +These should be the main ways ***within the standard library*** to convert data into your desired types. + +## Further information + +These are not directly covered in the book, but the standard library has a great documentation for it. + +- [conversions](https://doc.rust-lang.org/std/convert/index.html) +- [`FromStr` trait](https://doc.rust-lang.org/std/str/trait.FromStr.html) diff --git a/exercises/23_conversions/as_ref_mut.rs b/exercises/23_conversions/as_ref_mut.rs new file mode 100644 index 0000000..2ba9e3f --- /dev/null +++ b/exercises/23_conversions/as_ref_mut.rs @@ -0,0 +1,65 @@ +// as_ref_mut.rs +// +// AsRef and AsMut allow for cheap reference-to-reference conversions. Read more +// about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and +// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively. +// +// Execute `rustlings hint as_ref_mut` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +// Obtain the number of bytes (not characters) in the given argument. +// TODO: Add the AsRef trait appropriately as a trait bound. +fn byte_counter(arg: T) -> usize { + arg.as_ref().as_bytes().len() +} + +// Obtain the number of characters (not bytes) in the given argument. +// TODO: Add the AsRef trait appropriately as a trait bound. +fn char_counter(arg: T) -> usize { + arg.as_ref().chars().count() +} + +// Squares a number using as_mut(). +// TODO: Add the appropriate trait bound. +fn num_sq(arg: &mut T) { + // TODO: Implement the function body. + ??? +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn different_counts() { + let s = "Café au lait"; + assert_ne!(char_counter(s), byte_counter(s)); + } + + #[test] + fn same_counts() { + let s = "Cafe au lait"; + assert_eq!(char_counter(s), byte_counter(s)); + } + + #[test] + fn different_counts_using_string() { + let s = String::from("Café au lait"); + assert_ne!(char_counter(s.clone()), byte_counter(s)); + } + + #[test] + fn same_counts_using_string() { + let s = String::from("Cafe au lait"); + assert_eq!(char_counter(s.clone()), byte_counter(s)); + } + + #[test] + fn mut_box() { + let mut num: Box = Box::new(3); + num_sq(&mut num); + assert_eq!(*num, 9); + } +} diff --git a/exercises/23_conversions/from_into.rs b/exercises/23_conversions/from_into.rs new file mode 100644 index 0000000..60911f3 --- /dev/null +++ b/exercises/23_conversions/from_into.rs @@ -0,0 +1,140 @@ +// from_into.rs +// +// The From trait is used for value-to-value conversions. If From is implemented +// correctly for a type, the Into trait should work conversely. You can read +// more about it at https://doc.rust-lang.org/std/convert/trait.From.html +// +// Execute `rustlings hint from_into` or use the `hint` watch subcommand for a +// hint. + +#[derive(Debug)] +struct Person { + name: String, + age: usize, +} + +// We implement the Default trait to use it as a fallback +// when the provided string is not convertible into a Person object +impl Default for Person { + fn default() -> Person { + Person { + name: String::from("John"), + age: 30, + } + } +} + +// Your task is to complete this implementation in order for the line `let p = +// Person::from("Mark,20")` to compile Please note that you'll need to parse the +// age component into a `usize` with something like `"4".parse::()`. The +// outcome of this needs to be handled appropriately. +// +// Steps: +// 1. If the length of the provided string is 0, then return the default of +// Person. +// 2. Split the given string on the commas present in it. +// 3. Extract the first element from the split operation and use it as the name. +// 4. If the name is empty, then return the default of Person. +// 5. Extract the other element from the split operation and parse it into a +// `usize` as the age. +// If while parsing the age, something goes wrong, then return the default of +// Person Otherwise, then return an instantiated Person object with the results + +// I AM NOT DONE + +impl From<&str> for Person { + fn from(s: &str) -> Person { + } +} + +fn main() { + // Use the `from` function + let p1 = Person::from("Mark,20"); + // Since From is implemented for Person, we should be able to use Into + let p2: Person = "Gerald,70".into(); + println!("{:?}", p1); + println!("{:?}", p2); +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_default() { + // Test that the default person is 30 year old John + let dp = Person::default(); + assert_eq!(dp.name, "John"); + assert_eq!(dp.age, 30); + } + #[test] + fn test_bad_convert() { + // Test that John is returned when bad string is provided + let p = Person::from(""); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + #[test] + fn test_good_convert() { + // Test that "Mark,20" works + let p = Person::from("Mark,20"); + assert_eq!(p.name, "Mark"); + assert_eq!(p.age, 20); + } + #[test] + fn test_bad_age() { + // Test that "Mark,twenty" will return the default person due to an + // error in parsing age + let p = Person::from("Mark,twenty"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_comma_and_age() { + let p: Person = Person::from("Mark"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_age() { + let p: Person = Person::from("Mark,"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name() { + let p: Person = Person::from(",1"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name_and_age() { + let p: Person = Person::from(","); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name_and_invalid_age() { + let p: Person = Person::from(",one"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_trailing_comma() { + let p: Person = Person::from("Mike,32,"); + assert_eq!(p.name, "Mike"); + assert_eq!(p.age, 32); + } + + #[test] + fn test_trailing_comma_and_some_string() { + let p: Person = Person::from("Mike,32,man"); + assert_eq!(p.name, "Mike"); + assert_eq!(p.age, 32); + } +} diff --git a/exercises/23_conversions/from_str.rs b/exercises/23_conversions/from_str.rs new file mode 100644 index 0000000..34472c3 --- /dev/null +++ b/exercises/23_conversions/from_str.rs @@ -0,0 +1,133 @@ +// from_str.rs +// +// This is similar to from_into.rs, but this time we'll implement `FromStr` and +// return errors instead of falling back to a default value. Additionally, upon +// implementing FromStr, you can use the `parse` method on strings to generate +// an object of the implementor type. You can read more about it at +// https://doc.rust-lang.org/std/str/trait.FromStr.html +// +// Execute `rustlings hint from_str` or use the `hint` watch subcommand for a +// hint. + +use std::num::ParseIntError; +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +struct Person { + name: String, + age: usize, +} + +// We will use this error type for the `FromStr` implementation. +#[derive(Debug, PartialEq)] +enum ParsePersonError { + // Empty input string + Empty, + // Incorrect number of fields + BadLen, + // Empty name field + NoName, + // Wrapped error from parse::() + ParseInt(ParseIntError), +} + +// I AM NOT DONE + +// Steps: +// 1. If the length of the provided string is 0, an error should be returned +// 2. Split the given string on the commas present in it +// 3. Only 2 elements should be returned from the split, otherwise return an +// error +// 4. Extract the first element from the split operation and use it as the name +// 5. Extract the other element from the split operation and parse it into a +// `usize` as the age with something like `"4".parse::()` +// 6. If while extracting the name and the age something goes wrong, an error +// should be returned +// If everything goes well, then return a Result of a Person object +// +// As an aside: `Box` implements `From<&'_ str>`. This means that if +// you want to return a string error message, you can do so via just using +// return `Err("my error message".into())`. + +impl FromStr for Person { + type Err = ParsePersonError; + fn from_str(s: &str) -> Result { + } +} + +fn main() { + let p = "Mark,20".parse::().unwrap(); + println!("{:?}", p); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_input() { + assert_eq!("".parse::(), Err(ParsePersonError::Empty)); + } + #[test] + fn good_input() { + let p = "John,32".parse::(); + assert!(p.is_ok()); + let p = p.unwrap(); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 32); + } + #[test] + fn missing_age() { + assert!(matches!( + "John,".parse::(), + Err(ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn invalid_age() { + assert!(matches!( + "John,twenty".parse::(), + Err(ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn missing_comma_and_age() { + assert_eq!("John".parse::(), Err(ParsePersonError::BadLen)); + } + + #[test] + fn missing_name() { + assert_eq!(",1".parse::(), Err(ParsePersonError::NoName)); + } + + #[test] + fn missing_name_and_age() { + assert!(matches!( + ",".parse::(), + Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn missing_name_and_invalid_age() { + assert!(matches!( + ",one".parse::(), + Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn trailing_comma() { + assert_eq!("John,32,".parse::(), Err(ParsePersonError::BadLen)); + } + + #[test] + fn trailing_comma_and_some_string() { + assert_eq!( + "John,32,man".parse::(), + Err(ParsePersonError::BadLen) + ); + } +} diff --git a/exercises/23_conversions/try_from_into.rs b/exercises/23_conversions/try_from_into.rs new file mode 100644 index 0000000..32d6ef3 --- /dev/null +++ b/exercises/23_conversions/try_from_into.rs @@ -0,0 +1,193 @@ +// try_from_into.rs +// +// TryFrom is a simple and safe type conversion that may fail in a controlled +// way under some circumstances. Basically, this is the same as From. The main +// difference is that this should return a Result type instead of the target +// type itself. You can read more about it at +// https://doc.rust-lang.org/std/convert/trait.TryFrom.html +// +// Execute `rustlings hint try_from_into` or use the `hint` watch subcommand for +// a hint. + +use std::convert::{TryFrom, TryInto}; + +#[derive(Debug, PartialEq)] +struct Color { + red: u8, + green: u8, + blue: u8, +} + +// We will use this error type for these `TryFrom` conversions. +#[derive(Debug, PartialEq)] +enum IntoColorError { + // Incorrect length of slice + BadLen, + // Integer conversion error + IntConversion, +} + +// I AM NOT DONE + +// Your task is to complete this implementation and return an Ok result of inner +// type Color. You need to create an implementation for a tuple of three +// integers, an array of three integers, and a slice of integers. +// +// Note that the implementation for tuple and array will be checked at compile +// time, but the slice implementation needs to check the slice length! Also note +// that correct RGB color values must be integers in the 0..=255 range. + +// Tuple implementation +impl TryFrom<(i16, i16, i16)> for Color { + type Error = IntoColorError; + fn try_from(tuple: (i16, i16, i16)) -> Result { + } +} + +// Array implementation +impl TryFrom<[i16; 3]> for Color { + type Error = IntoColorError; + fn try_from(arr: [i16; 3]) -> Result { + } +} + +// Slice implementation +impl TryFrom<&[i16]> for Color { + type Error = IntoColorError; + fn try_from(slice: &[i16]) -> Result { + } +} + +fn main() { + // Use the `try_from` function + let c1 = Color::try_from((183, 65, 14)); + println!("{:?}", c1); + + // Since TryFrom is implemented for Color, we should be able to use TryInto + let c2: Result = [183, 65, 14].try_into(); + println!("{:?}", c2); + + let v = vec![183, 65, 14]; + // With slice we should use `try_from` function + let c3 = Color::try_from(&v[..]); + println!("{:?}", c3); + // or take slice within round brackets and use TryInto + let c4: Result = (&v[..]).try_into(); + println!("{:?}", c4); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tuple_out_of_range_positive() { + assert_eq!( + Color::try_from((256, 1000, 10000)), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_tuple_out_of_range_negative() { + assert_eq!( + Color::try_from((-1, -10, -256)), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_tuple_sum() { + assert_eq!( + Color::try_from((-1, 255, 255)), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_tuple_correct() { + let c: Result = (183, 65, 14).try_into(); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14 + } + ); + } + #[test] + fn test_array_out_of_range_positive() { + let c: Result = [1000, 10000, 256].try_into(); + assert_eq!(c, Err(IntoColorError::IntConversion)); + } + #[test] + fn test_array_out_of_range_negative() { + let c: Result = [-10, -256, -1].try_into(); + assert_eq!(c, Err(IntoColorError::IntConversion)); + } + #[test] + fn test_array_sum() { + let c: Result = [-1, 255, 255].try_into(); + assert_eq!(c, Err(IntoColorError::IntConversion)); + } + #[test] + fn test_array_correct() { + let c: Result = [183, 65, 14].try_into(); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14 + } + ); + } + #[test] + fn test_slice_out_of_range_positive() { + let arr = [10000, 256, 1000]; + assert_eq!( + Color::try_from(&arr[..]), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_slice_out_of_range_negative() { + let arr = [-256, -1, -10]; + assert_eq!( + Color::try_from(&arr[..]), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_slice_sum() { + let arr = [-1, 255, 255]; + assert_eq!( + Color::try_from(&arr[..]), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_slice_correct() { + let v = vec![183, 65, 14]; + let c: Result = Color::try_from(&v[..]); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14 + } + ); + } + #[test] + fn test_slice_excess_length() { + let v = vec![0, 0, 0, 0]; + assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); + } + #[test] + fn test_slice_insufficient_length() { + let v = vec![0, 0]; + assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); + } +} diff --git a/exercises/23_conversions/using_as.rs b/exercises/23_conversions/using_as.rs new file mode 100644 index 0000000..414cef3 --- /dev/null +++ b/exercises/23_conversions/using_as.rs @@ -0,0 +1,33 @@ +// using_as.rs +// +// Type casting in Rust is done via the usage of the `as` operator. Please note +// that the `as` operator is not only used when type casting. It also helps with +// renaming imports. +// +// The goal is to make sure that the division does not fail to compile and +// returns the proper type. +// +// Execute `rustlings hint using_as` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn average(values: &[f64]) -> f64 { + let total = values.iter().sum::(); + total / values.len() +} + +fn main() { + let values = [3.5, 0.3, 13.0, 11.7]; + println!("{}", average(&values)); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn returns_proper_type_and_value() { + assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); + } +} diff --git a/exercises/README.md b/exercises/README.md new file mode 100644 index 0000000..c7effa9 --- /dev/null +++ b/exercises/README.md @@ -0,0 +1,27 @@ +# Exercise to Book Chapter mapping + +| Exercise | Book Chapter | +| ---------------------- | ------------------- | +| variables | §3.1 | +| functions | §3.3 | +| if | §3.5 | +| primitive_types | §3.2, §4.3 | +| vecs | §8.1 | +| move_semantics | §4.1-2 | +| structs | §5.1, §5.3 | +| enums | §6, §18.3 | +| strings | §8.2 | +| modules | §7 | +| hashmaps | §8.3 | +| options | §10.1 | +| error_handling | §9 | +| generics | §10 | +| traits | §10.2 | +| tests | §11.1 | +| lifetimes | §10.3 | +| iterators | §13.2-4 | +| threads | §16.1-3 | +| smart_pointers | §15, §16.3 | +| macros | §19.6 | +| clippy | §21.4 | +| conversions | n/a | diff --git a/exercises/quiz1.rs b/exercises/quiz1.rs new file mode 100644 index 0000000..4ee5ada --- /dev/null +++ b/exercises/quiz1.rs @@ -0,0 +1,33 @@ +// quiz1.rs +// +// This is a quiz for the following sections: +// - Variables +// - Functions +// - If +// +// Mary is buying apples. The price of an apple is calculated as follows: +// - An apple costs 2 rustbucks. +// - If Mary buys more than 40 apples, each apple only costs 1 rustbuck! +// Write a function that calculates the price of an order of apples given the +// quantity bought. +// +// No hints this time ;) + +// I AM NOT DONE + +// Put your function here! +// fn calculate_price_of_apples { + +// Don't modify this function! +#[test] +fn verify_test() { + let price1 = calculate_price_of_apples(35); + let price2 = calculate_price_of_apples(40); + let price3 = calculate_price_of_apples(41); + let price4 = calculate_price_of_apples(65); + + assert_eq!(70, price1); + assert_eq!(80, price2); + assert_eq!(41, price3); + assert_eq!(65, price4); +} diff --git a/exercises/quiz2.rs b/exercises/quiz2.rs new file mode 100644 index 0000000..29925ca --- /dev/null +++ b/exercises/quiz2.rs @@ -0,0 +1,64 @@ +// quiz2.rs +// +// This is a quiz for the following sections: +// - Strings +// - Vecs +// - Move semantics +// - Modules +// - Enums +// +// Let's build a little machine in the form of a function. As input, we're going +// to give a list of strings and commands. These commands determine what action +// is going to be applied to the string. It can either be: +// - Uppercase the string +// - Trim the string +// - Append "bar" to the string a specified amount of times +// The exact form of this will be: +// - The input is going to be a Vector of a 2-length tuple, +// the first element is the string, the second one is the command. +// - The output element is going to be a Vector of strings. +// +// No hints this time! + +// I AM NOT DONE + +pub enum Command { + Uppercase, + Trim, + Append(usize), +} + +mod my_module { + use super::Command; + + // TODO: Complete the function signature! + pub fn transformer(input: ???) -> ??? { + // TODO: Complete the output declaration! + let mut output: ??? = vec![]; + for (string, command) in input.iter() { + // TODO: Complete the function body. You can do it! + } + output + } +} + +#[cfg(test)] +mod tests { + // TODO: What do we need to import to have `transformer` in scope? + use ???; + use super::Command; + + #[test] + fn it_works() { + let output = transformer(vec![ + ("hello".into(), Command::Uppercase), + (" all roads lead to rome! ".into(), Command::Trim), + ("foo".into(), Command::Append(1)), + ("bar".into(), Command::Append(5)), + ]); + assert_eq!(output[0], "HELLO"); + assert_eq!(output[1], "all roads lead to rome!"); + assert_eq!(output[2], "foobar"); + assert_eq!(output[3], "barbarbarbarbarbar"); + } +} diff --git a/exercises/quiz3.rs b/exercises/quiz3.rs new file mode 100644 index 0000000..3b01d31 --- /dev/null +++ b/exercises/quiz3.rs @@ -0,0 +1,64 @@ +// quiz3.rs +// +// This quiz tests: +// - Generics +// - Traits +// +// An imaginary magical school has a new report card generation system written +// in Rust! Currently the system only supports creating report cards where the +// student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the +// school also issues alphabetical grades (A+ -> F-) and needs to be able to +// print both types of report card! +// +// Make the necessary code changes in the struct ReportCard and the impl block +// to support alphabetical report cards. Change the Grade in the second test to +// "A+" to show that your changes allow alphabetical grades. +// +// Execute `rustlings hint quiz3` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +pub struct ReportCard { + pub grade: f32, + pub student_name: String, + pub student_age: u8, +} + +impl ReportCard { + pub fn print(&self) -> String { + format!("{} ({}) - achieved a grade of {}", + &self.student_name, &self.student_age, &self.grade) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generate_numeric_report_card() { + let report_card = ReportCard { + grade: 2.1, + student_name: "Tom Wriggle".to_string(), + student_age: 12, + }; + assert_eq!( + report_card.print(), + "Tom Wriggle (12) - achieved a grade of 2.1" + ); + } + + #[test] + fn generate_alphabetic_report_card() { + // TODO: Make sure to change the grade here after you finish the exercise. + let report_card = ReportCard { + grade: 2.1, + student_name: "Gary Plotter".to_string(), + student_age: 11, + }; + assert_eq!( + report_card.print(), + "Gary Plotter (11) - achieved a grade of A+" + ); + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1523898 --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1694183432, + "narHash": "sha256-YyPGNapgZNNj51ylQMw9lAgvxtM2ai1HZVUu3GS8Fng=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "db9208ab987cdeeedf78ad9b4cf3c55f5ebd269b", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..152d38e --- /dev/null +++ b/flake.nix @@ -0,0 +1,78 @@ +{ + description = "Small exercises to get you used to reading and writing Rust code"; + + inputs = { + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + }; + + outputs = { self, flake-utils, nixpkgs, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + cargoBuildInputs = with pkgs; lib.optionals stdenv.isDarwin [ + darwin.apple_sdk.frameworks.CoreServices + ]; + + rustlings = + pkgs.rustPlatform.buildRustPackage { + name = "rustlings"; + version = "5.6.1"; + + buildInputs = cargoBuildInputs; + nativeBuildInputs = [pkgs.git]; + + src = with pkgs.lib; cleanSourceWith { + src = self; + # a function that returns a bool determining if the path should be included in the cleaned source + filter = path: type: + let + # filename + baseName = builtins.baseNameOf (toString path); + # path from root directory + path' = builtins.replaceStrings [ "${self}/" ] [ "" ] path; + # checks if path is in the directory + inDirectory = directory: hasPrefix directory path'; + in + inDirectory "src" || + inDirectory "tests" || + hasPrefix "Cargo" baseName || + baseName == "info.toml"; + }; + + cargoLock.lockFile = ./Cargo.lock; + }; + in + { + devShell = pkgs.mkShell { + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + + buildInputs = with pkgs; [ + cargo + rustc + rust-analyzer + rustlings + rustfmt + clippy + ] ++ cargoBuildInputs; + }; + apps = let + rustlings-app = { + type = "app"; + program = "${rustlings}/bin/rustlings"; + }; + in { + default = rustlings-app; + rustlings = rustlings-app; + }; + packages = { + inherit rustlings; + default = rustlings; + }; + }); +} diff --git a/info.toml b/info.toml new file mode 100644 index 0000000..887662a --- /dev/null +++ b/info.toml @@ -0,0 +1,1321 @@ +# INTRO + +[[exercises]] +name = "intro1" +path = "exercises/00_intro/intro1.rs" +mode = "compile" +hint = """ +Remove the `I AM NOT DONE` comment in the `exercises/intro00/intro1.rs` file +to move on to the next exercise.""" + +[[exercises]] +name = "intro2" +path = "exercises/00_intro/intro2.rs" +mode = "compile" +hint = """ +The compiler is informing us that we've got the name of the print macro wrong, and has suggested an alternative.""" + +# VARIABLES + +[[exercises]] +name = "variables1" +path = "exercises/01_variables/variables1.rs" +mode = "compile" +hint = """ +The declaration in the first line in the main function is missing a keyword +that is needed in Rust to create a new variable binding.""" + +[[exercises]] +name = "variables2" +path = "exercises/01_variables/variables2.rs" +mode = "compile" +hint = """ +The compiler message is saying that Rust cannot infer the type that the +variable binding `x` has with what is given here. + +What happens if you annotate the first line in the main function with a type +annotation? + +What if you give `x` a value? + +What if you do both? + +What type should `x` be, anyway? + +What if `x` is the same type as `10`? What if it's a different type?""" + +[[exercises]] +name = "variables3" +path = "exercises/01_variables/variables3.rs" +mode = "compile" +hint = """ +Oops! In this exercise, we have a variable binding that we've created on in the +first line in the `main` function, and we're trying to use it in the next line, +but we haven't given it a value. + +We can't print out something that isn't there; try giving `x` a value! + +This is an error that can cause bugs that's very easy to make in any +programming language -- thankfully the Rust compiler has caught this for us!""" + +[[exercises]] +name = "variables4" +path = "exercises/01_variables/variables4.rs" +mode = "compile" +hint = """ +In Rust, variable bindings are immutable by default. But here we're trying +to reassign a different value to `x`! There's a keyword we can use to make +a variable binding mutable instead.""" + +[[exercises]] +name = "variables5" +path = "exercises/01_variables/variables5.rs" +mode = "compile" +hint = """ +In `variables4` we already learned how to make an immutable variable mutable +using a special keyword. Unfortunately this doesn't help us much in this +exercise because we want to assign a different typed value to an existing +variable. Sometimes you may also like to reuse existing variable names because +you are just converting values to different types like in this exercise. + +Fortunately Rust has a powerful solution to this problem: 'Shadowing'! +You can read more about 'Shadowing' in the book's section 'Variables and +Mutability': +https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing + +Try to solve this exercise afterwards using this technique.""" + +[[exercises]] +name = "variables6" +path = "exercises/01_variables/variables6.rs" +mode = "compile" +hint = """ +We know about variables and mutability, but there is another important type of +variable available: constants. + +Constants are always immutable and they are declared with keyword `const` rather +than keyword `let`. + +Constants types must also always be annotated. + +Read more about constants and the differences between variables and constants +under 'Constants' in the book's section 'Variables and Mutability': +https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants +""" + +# FUNCTIONS + +[[exercises]] +name = "functions1" +path = "exercises/02_functions/functions1.rs" +mode = "compile" +hint = """ +This main function is calling a function that it expects to exist, but the +function doesn't exist. It expects this function to have the name `call_me`. +It expects this function to not take any arguments and not return a value. +Sounds a lot like `main`, doesn't it?""" + +[[exercises]] +name = "functions2" +path = "exercises/02_functions/functions2.rs" +mode = "compile" +hint = """ +Rust requires that all parts of a function's signature have type annotations, +but `call_me` is missing the type annotation of `num`.""" + +[[exercises]] +name = "functions3" +path = "exercises/02_functions/functions3.rs" +mode = "compile" +hint = """ +This time, the function *declaration* is okay, but there's something wrong +with the place where we're calling the function. + +As a reminder, you can freely play around with different solutions in Rustlings! +Watch mode will only jump to the next exercise if you remove the `I AM NOT +DONE` comment.""" + +[[exercises]] +name = "functions4" +path = "exercises/02_functions/functions4.rs" +mode = "compile" +hint = """ +The error message points to the function `sale_price` and says it expects a type +after the `->`. This is where the function's return type should be -- take a +look at the `is_even` function for an example! + +Also: Did you figure out that, technically, `u32` would be the more fitting type +for the prices here, since they can't be negative? If so, kudos!""" + +[[exercises]] +name = "functions5" +path = "exercises/02_functions/functions5.rs" +mode = "compile" +hint = """ +This is a really common error that can be fixed by removing one character. +It happens because Rust distinguishes between expressions and statements: +expressions return a value based on their operand(s), and statements simply +return a `()` type which behaves just like `void` in C/C++ language. + +We want to return a value of `i32` type from the `square` function, but it is +returning a `()` type... + +They are not the same. There are two solutions: +1. Add a `return` ahead of `num * num;` +2. remove `;`, make it to be `num * num`""" + +# IF + +[[exercises]] +name = "if1" +path = "exercises/03_if/if1.rs" +mode = "test" +hint = """ +It's possible to do this in one line if you would like! + +Some similar examples from other languages: +- In C(++) this would be: `a > b ? a : b` +- In Python this would be: `a if a > b else b` + +Remember in Rust that: +- the `if` condition does not need to be surrounded by parentheses +- `if`/`else` conditionals are expressions +- Each condition is followed by a `{}` block.""" + +[[exercises]] +name = "if2" +path = "exercises/03_if/if2.rs" +mode = "test" +hint = """ +For that first compiler error, it's important in Rust that each conditional +block returns the same type! To get the tests passing, you will need a couple +conditions checking different input values.""" + +[[exercises]] +name = "if3" +path = "exercises/03_if/if3.rs" +mode = "test" +hint = """ +In Rust, every arm of an `if` expression has to return the same type of value. +Make sure the type is consistent across all arms.""" + +# QUIZ 1 + +[[exercises]] +name = "quiz1" +path = "exercises/quiz1.rs" +mode = "test" +hint = "No hints this time ;)" + +# PRIMITIVE TYPES + +[[exercises]] +name = "primitive_types1" +path = "exercises/04_primitive_types/primitive_types1.rs" +mode = "compile" +hint = "No hints this time ;)" + +[[exercises]] +name = "primitive_types2" +path = "exercises/04_primitive_types/primitive_types2.rs" +mode = "compile" +hint = "No hints this time ;)" + +[[exercises]] +name = "primitive_types3" +path = "exercises/04_primitive_types/primitive_types3.rs" +mode = "compile" +hint = """ +There's a shorthand to initialize Arrays with a certain size that does not +require you to type in 100 items (but you certainly can if you want!). + +For example, you can do: +``` +let array = ["Are we there yet?"; 10]; +``` + +Bonus: what are some other things you could have that would return `true` +for `a.len() >= 100`?""" + +[[exercises]] +name = "primitive_types4" +path = "exercises/04_primitive_types/primitive_types4.rs" +mode = "test" +hint = """ +Take a look at the 'Understanding Ownership -> Slices -> Other Slices' section +of the book: https://doc.rust-lang.org/book/ch04-03-slices.html and use the +starting and ending (plus one) indices of the items in the `Array` that you +want to end up in the slice. + +If you're curious why the first argument of `assert_eq!` does not have an +ampersand for a reference since the second argument is areference, take a look +at the coercion chapter of the nomicon: +https://doc.rust-lang.org/nomicon/coercions.html""" + +[[exercises]] +name = "primitive_types5" +path = "exercises/04_primitive_types/primitive_types5.rs" +mode = "compile" +hint = """ +Take a look at the 'Data Types -> The Tuple Type' section of the book: +https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type +Particularly the part about destructuring (second to last example in the +section). + +You'll need to make a pattern to bind `name` and `age` to the appropriate parts +of the tuple. You can do it!!""" + +[[exercises]] +name = "primitive_types6" +path = "exercises/04_primitive_types/primitive_types6.rs" +mode = "test" +hint = """ +While you could use a destructuring `let` for the tuple here, try +indexing into it instead, as explained in the last example of the +'Data Types -> The Tuple Type' section of the book: +https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type +Now you have another tool in your toolbox!""" + +# VECS + +[[exercises]] +name = "vecs1" +path = "exercises/05_vecs/vecs1.rs" +mode = "test" +hint = """ +In Rust, there are two ways to define a Vector. +1. One way is to use the `Vec::new()` function to create a new vector + and fill it with the `push()` method. +2. The second way, which is simpler is to use the `vec![]` macro and + define your elements inside the square brackets. + +Check this chapter: https://doc.rust-lang.org/stable/book/ch08-01-vectors.html +of the Rust book to learn more. +""" + +[[exercises]] +name = "vecs2" +path = "exercises/05_vecs/vecs2.rs" +mode = "test" +hint = """ +In the first function we are looping over the Vector and getting a reference to +one `element` at a time. + +To modify the value of that `element` we need to use the `*` dereference +operator. You can learn more in this chapter of the Rust book: +https://doc.rust-lang.org/stable/book/ch08-01-vectors.html#iterating-over-the-values-in-a-vector + +In the second function this dereferencing is not necessary, because the `map` +function expects the new value to be returned. + +After you've completed both functions, decide for yourself which approach you +like better. + +What do you think is the more commonly used pattern under Rust developers? +""" + +# MOVE SEMANTICS + +[[exercises]] +name = "move_semantics1" +path = "exercises/06_move_semantics/move_semantics1.rs" +mode = "test" +hint = """ +So you've got the "cannot borrow immutable local variable `vec` as mutable" +error on the line where we push an element to the vector, right? + +The fix for this is going to be adding one keyword, and the addition is NOT on +the line where we push to the vector (where the error is). + +Also: Try accessing `vec0` after having called `fill_vec()`. See what +happens!""" + +[[exercises]] +name = "move_semantics2" +path = "exercises/06_move_semantics/move_semantics2.rs" +mode = "test" +hint = """ +When running this exercise for the first time, you'll notice an error about +"borrow of moved value". In Rust, when an argument is passed to a function and +it's not explicitly returned, you can't use the original variable anymore. +We call this "moving" a variable. When we pass `vec0` into `fill_vec`, it's +being "moved" into `vec1`, meaning we can't access `vec0` anymore after the +fact. + +Rust provides a couple of different ways to mitigate this issue, feel free to +try them all: +1. You could make another, separate version of the data that's in `vec0` and + pass that to `fill_vec` instead. +2. Make `fill_vec` borrow its argument instead of taking ownership of it, + and then copy the data within the function (`vec.clone()`) in order to + return an owned `Vec`. +""" + +[[exercises]] +name = "move_semantics3" +path = "exercises/06_move_semantics/move_semantics3.rs" +mode = "test" +hint = """ +The difference between this one and the previous ones is that the first line +of `fn fill_vec` that had `let mut vec = vec;` is no longer there. You can, +instead of adding that line back, add `mut` in one place that will change +an existing binding to be a mutable binding instead of an immutable one :)""" + +[[exercises]] +name = "move_semantics4" +path = "exercises/06_move_semantics/move_semantics4.rs" +mode = "test" +hint = """ +Stop reading whenever you feel like you have enough direction :) Or try +doing one step and then fixing the compiler errors that result! +So the end goal is to: + - get rid of the first line in main that creates the new vector + - so then `vec0` doesn't exist, so we can't pass it to `fill_vec` + - `fill_vec` has had its signature changed, which our call should reflect + - since we're not creating a new vec in `main` anymore, we need to create + a new vec in `fill_vec`, and fill it with the expected values""" + +[[exercises]] +name = "move_semantics5" +path = "exercises/06_move_semantics/move_semantics5.rs" +mode = "test" +hint = """ +Carefully reason about the range in which each mutable reference is in +scope. Does it help to update the value of referent (`x`) immediately after +the mutable reference is taken? Read more about 'Mutable References' +in the book's section 'References and Borrowing': +https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references. +""" + +[[exercises]] +name = "move_semantics6" +path = "exercises/06_move_semantics/move_semantics6.rs" +mode = "compile" +hint = """ +To find the answer, you can consult the book section "References and Borrowing": +https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html + +The first problem is that `get_char` is taking ownership of the string. So +`data` is moved and can't be used for `string_uppercase`. `data` is moved to +`get_char` first, meaning that `string_uppercase` cannot manipulate the data. + +Once you've fixed that, `string_uppercase`'s function signature will also need +to be adjusted. + +Can you figure out how? + +Another hint: it has to do with the `&` character.""" + +# STRUCTS + +[[exercises]] +name = "structs1" +path = "exercises/07_structs/structs1.rs" +mode = "test" +hint = """ +Rust has more than one type of struct. Three actually, all variants are used to +package related data together. + +There are normal (or classic) structs. These are named collections of related +data stored in fields. + +Tuple structs are basically just named tuples. + +Finally, Unit-like structs. These don't have any fields and are useful for +generics. + +In this exercise you need to complete and implement one of each kind. +Read more about structs in The Book: +https://doc.rust-lang.org/book/ch05-01-defining-structs.html""" + +[[exercises]] +name = "structs2" +path = "exercises/07_structs/structs2.rs" +mode = "test" +hint = """ +Creating instances of structs is easy, all you need to do is assign some values +to its fields. + +There are however some shortcuts that can be taken when instantiating structs. +Have a look in The Book, to find out more: +https://doc.rust-lang.org/stable/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax""" + +[[exercises]] +name = "structs3" +path = "exercises/07_structs/structs3.rs" +mode = "test" +hint = """ +For `is_international`: What makes a package international? Seems related to +the places it goes through right? + +For `get_fees`: This method takes an additional argument, is there a field in +the `Package` struct that this relates to? + +Have a look in The Book, to find out more about method implementations: +https://doc.rust-lang.org/book/ch05-03-method-syntax.html""" + +# ENUMS + +[[exercises]] +name = "enums1" +path = "exercises/08_enums/enums1.rs" +mode = "compile" +hint = "No hints this time ;)" + +[[exercises]] +name = "enums2" +path = "exercises/08_enums/enums2.rs" +mode = "compile" +hint = """ +You can create enumerations that have different variants with different types +such as no data, anonymous structs, a single string, tuples, ...etc""" + +[[exercises]] +name = "enums3" +path = "exercises/08_enums/enums3.rs" +mode = "test" +hint = """ +As a first step, you can define enums to compile this code without errors. + +And then create a match expression in `process()`. + +Note that you need to deconstruct some message variants in the match expression +to get value in the variant.""" + +# STRINGS + +[[exercises]] +name = "strings1" +path = "exercises/09_strings/strings1.rs" +mode = "compile" +hint = """ +The `current_favorite_color` function is currently returning a string slice +with the `'static` lifetime. We know this because the data of the string lives +in our code itself -- it doesn't come from a file or user input or another +program -- so it will live as long as our program lives. + +But it is still a string slice. There's one way to create a `String` by +converting a string slice covered in the Strings chapter of the book, and +another way that uses the `From` trait.""" + +[[exercises]] +name = "strings2" +path = "exercises/09_strings/strings2.rs" +mode = "compile" +hint = """ +Yes, it would be really easy to fix this by just changing the value bound to +`word` to be a string slice instead of a `String`, wouldn't it?? There is a way +to add one character to the `if` statement, though, that will coerce the +`String` into a string slice. + +Side note: If you're interested in learning about how this kind of reference +conversion works, you can jump ahead in the book and read this part in the +smart pointers chapter: +https://doc.rust-lang.org/stable/book/ch15-02-deref.html#implicit-deref-coercions-with-functions-and-methods""" + +[[exercises]] +name = "strings3" +path = "exercises/09_strings/strings3.rs" +mode = "test" +hint = """ +There's tons of useful standard library functions for strings. Let's try and use some of them: +https://doc.rust-lang.org/std/string/struct.String.html#method.trim + +For the `compose_me` method: You can either use the `format!` macro, or convert +the string slice into an owned string, which you can then freely extend.""" + +[[exercises]] +name = "strings4" +path = "exercises/09_strings/strings4.rs" +mode = "compile" +hint = "No hints this time ;)" + +# MODULES + +[[exercises]] +name = "modules1" +path = "exercises/10_modules/modules1.rs" +mode = "compile" +hint = """ +Everything is private in Rust by default-- but there's a keyword we can use +to make something public! The compiler error should point to the thing that +needs to be public.""" + +[[exercises]] +name = "modules2" +path = "exercises/10_modules/modules2.rs" +mode = "compile" +hint = """ +The delicious_snacks module is trying to present an external interface that is +different than its internal structure (the `fruits` and `veggies` modules and +associated constants). Complete the `use` statements to fit the uses in main and +find the one keyword missing for both constants. + +Learn more at https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#re-exporting-names-with-pub-use""" + +[[exercises]] +name = "modules3" +path = "exercises/10_modules/modules3.rs" +mode = "compile" +hint = """ +`UNIX_EPOCH` and `SystemTime` are declared in the `std::time` module. Add a +`use` statement for these two to bring them into scope. You can use nested +paths or the glob operator to bring these two in using only one line.""" + +# HASHMAPS + +[[exercises]] +name = "hashmaps1" +path = "exercises/11_hashmaps/hashmaps1.rs" +mode = "test" +hint = """ +Hint 1: Take a look at the return type of the function to figure out + the type for the `basket`. + +Hint 2: Number of fruits should be at least 5. And you have to put + at least three different types of fruits. +""" + +[[exercises]] +name = "hashmaps2" +path = "exercises/11_hashmaps/hashmaps2.rs" +mode = "test" +hint = """ +Use the `entry()` and `or_insert()` methods of `HashMap` to achieve this. +Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value +""" + +[[exercises]] +name = "hashmaps3" +path = "exercises/11_hashmaps/hashmaps3.rs" +mode = "test" +hint = """ +Hint 1: Use the `entry()` and `or_insert()` methods of `HashMap` to insert + entries corresponding to each team in the scores table. + +Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value + +Hint 2: If there is already an entry for a given key, the value returned by + `entry()` can be updated based on the existing value. + +Learn more at https://doc.rust-lang.org/book/ch08-03-hash-maps.html#updating-a-value-based-on-the-old-value +""" + +# QUIZ 2 + +[[exercises]] +name = "quiz2" +path = "exercises/quiz2.rs" +mode = "test" +hint = "No hints this time ;)" + +# OPTIONS + +[[exercises]] +name = "options1" +path = "exercises/12_options/options1.rs" +mode = "test" +hint = """ +Options can have a `Some` value, with an inner value, or a `None` value, +without an inner value. + +There's multiple ways to get at the inner value, you can use `unwrap`, or +pattern match. Unwrapping is the easiest, but how do you do it safely so that +it doesn't panic in your face later?""" + +[[exercises]] +name = "options2" +path = "exercises/12_options/options2.rs" +mode = "test" +hint = """ +Check out: + +- https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html +- https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html + +Remember that `Option`s can be stacked in `if let` and `while let`. + +For example: `Some(Some(variable)) = variable2` + +Also see `Option::flatten` +""" + +[[exercises]] +name = "options3" +path = "exercises/12_options/options3.rs" +mode = "compile" +hint = """ +The compiler says a partial move happened in the `match` statement. How can +this be avoided? The compiler shows the correction needed. + +After making the correction as suggested by the compiler, do read: +https://doc.rust-lang.org/std/keyword.ref.html""" + +# ERROR HANDLING + +[[exercises]] +name = "errors1" +path = "exercises/13_error_handling/errors1.rs" +mode = "test" +hint = """ +`Ok` and `Err` are the two variants of `Result`, so what the tests are saying +is that `generate_nametag_text` should return a `Result` instead of an `Option`. + +To make this change, you'll need to: + - update the return type in the function signature to be a `Result` that could be the variants `Ok(String)` and `Err(String)` + - change the body of the function to return `Ok(stuff)` where it currently + returns `Some(stuff)` + - change the body of the function to return `Err(error message)` where it + currently returns `None`""" + +[[exercises]] +name = "errors2" +path = "exercises/13_error_handling/errors2.rs" +mode = "test" +hint = """ +One way to handle this is using a `match` statement on +`item_quantity.parse::()` where the cases are `Ok(something)` and +`Err(something)`. + +This pattern is very common in Rust, though, so there's a `?` operator that +does pretty much what you would make that match statement do for you! + +Take a look at this section of the 'Error Handling' chapter: +https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator +and give it a try!""" + +[[exercises]] +name = "errors3" +path = "exercises/13_error_handling/errors3.rs" +mode = "compile" +hint = """ +If other functions can return a `Result`, why shouldn't `main`? It's a fairly +common convention to return something like `Result<(), ErrorType>` from your +main function. + +The unit (`()`) type is there because nothing is really needed in terms of +positive results.""" + +[[exercises]] +name = "errors4" +path = "exercises/13_error_handling/errors4.rs" +mode = "test" +hint = """ +`PositiveNonzeroInteger::new` is always creating a new instance and returning +an `Ok` result. + +It should be doing some checking, returning an `Err` result if those checks +fail, and only returning an `Ok` result if those checks determine that +everything is... okay :)""" + +[[exercises]] +name = "errors5" +path = "exercises/13_error_handling/errors5.rs" +mode = "compile" +hint = """ +There are two different possible `Result` types produced within `main()`, which +are propagated using `?` operators. How do we declare a return type from +`main()` that allows both? + +Under the hood, the `?` operator calls `From::from` on the error value to +convert it to a boxed trait object, a `Box`. This boxed trait +object is polymorphic, and since all errors implement the `error::Error` trait, +we can capture lots of different errors in one "Box" object. + +Check out this section of the book: +https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator + +Read more about boxing errors: +https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html + +Read more about using the `?` operator with boxed errors: +https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html +""" + +[[exercises]] +name = "errors6" +path = "exercises/13_error_handling/errors6.rs" +mode = "test" +hint = """ +This exercise uses a completed version of `PositiveNonzeroInteger` from +errors4. + +Below the line that `TODO` asks you to change, there is an example of using +the `map_err()` method on a `Result` to transform one type of error into +another. Try using something similar on the `Result` from `parse()`. You +might use the `?` operator to return early from the function, or you might +use a `match` expression, or maybe there's another way! + +You can create another function inside `impl ParsePosNonzeroError` to use +with `map_err()`. + +Read more about `map_err()` in the `std::result` documentation: +https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err""" + +# Generics + +[[exercises]] +name = "generics1" +path = "exercises/14_generics/generics1.rs" +mode = "compile" +hint = """ +Vectors in Rust make use of generics to create dynamically sized arrays of any +type. + +You need to tell the compiler what type we are pushing onto this vector.""" + +[[exercises]] +name = "generics2" +path = "exercises/14_generics/generics2.rs" +mode = "test" +hint = """ +Currently we are wrapping only values of type `u32`. + +Maybe we could update the explicit references to this data type somehow? + +If you are still stuck https://doc.rust-lang.org/stable/book/ch10-01-syntax.html#in-method-definitions +""" + +# TRAITS + +[[exercises]] +name = "traits1" +path = "exercises/15_traits/traits1.rs" +mode = "test" +hint = """ +A discussion about Traits in Rust can be found at: +https://doc.rust-lang.org/book/ch10-02-traits.html +""" + +[[exercises]] +name = "traits2" +path = "exercises/15_traits/traits2.rs" +mode = "test" +hint = """ +Notice how the trait takes ownership of `self`, and returns `Self`. + +Try mutating the incoming string vector. Have a look at the tests to see +what the result should look like! + +Vectors provide suitable methods for adding an element at the end. See +the documentation at: https://doc.rust-lang.org/std/vec/struct.Vec.html""" + +[[exercises]] +name = "traits3" +path = "exercises/15_traits/traits3.rs" +mode = "test" +hint = """ +Traits can have a default implementation for functions. Structs that implement +the trait can then use the default version of these functions if they choose not +to implement the function themselves. + +See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations +""" + +[[exercises]] +name = "traits4" +path = "exercises/15_traits/traits4.rs" +mode = "test" +hint = """ +Instead of using concrete types as parameters you can use traits. Try replacing +the '??' with 'impl ' + +See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters +""" + +[[exercises]] +name = "traits5" +path = "exercises/15_traits/traits5.rs" +mode = "compile" +hint = """ +To ensure a parameter implements multiple traits use the '+ syntax'. Try +replacing the '??' with 'impl <> + <>'. + +See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#specifying-multiple-trait-bounds-with-the--syntax +""" + +# QUIZ 3 + +[[exercises]] +name = "quiz3" +path = "exercises/quiz3.rs" +mode = "test" +hint = """ +To find the best solution to this challenge you're going to need to think back +to your knowledge of traits, specifically 'Trait Bound Syntax' + +You may also need this: `use std::fmt::Display;`.""" + +# LIFETIMES + +[[exercises]] +name = "lifetimes1" +path = "exercises/16_lifetimes/lifetimes1.rs" +mode = "compile" +hint = """ +Let the compiler guide you. Also take a look at the book if you need help: +https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html""" + +[[exercises]] +name = "lifetimes2" +path = "exercises/16_lifetimes/lifetimes2.rs" +mode = "compile" +hint = """ +Remember that the generic lifetime `'a` will get the concrete lifetime that is +equal to the smaller of the lifetimes of `x` and `y`. + +You can take at least two paths to achieve the desired result while keeping the +inner block: +1. Move the `string2` declaration to make it live as long as `string1` (how is + `result` declared?) +2. Move `println!` into the inner block""" + +[[exercises]] +name = "lifetimes3" +path = "exercises/16_lifetimes/lifetimes3.rs" +mode = "compile" +hint = """ +If you use a lifetime annotation in a struct's fields, where else does it need +to be added?""" + +# TESTS + +[[exercises]] +name = "tests1" +path = "exercises/17_tests/tests1.rs" +mode = "test" +hint = """ +You don't even need to write any code to test -- you can just test values and +run that, even though you wouldn't do that in real life. :) + +`assert!` is a macro that needs an argument. Depending on the value of the +argument, `assert!` will do nothing (in which case the test will pass) or +`assert!` will panic (in which case the test will fail). + +So try giving different values to `assert!` and see which ones compile, which +ones pass, and which ones fail :)""" + +[[exercises]] +name = "tests2" +path = "exercises/17_tests/tests2.rs" +mode = "test" +hint = """ +Like the previous exercise, you don't need to write any code to get this test +to compile and run. + +`assert_eq!` is a macro that takes two arguments and compares them. Try giving +it two values that are equal! Try giving it two arguments that are different! +Try giving it two values that are of different types! Try switching which +argument comes first and which comes second!""" + +[[exercises]] +name = "tests3" +path = "exercises/17_tests/tests3.rs" +mode = "test" +hint = """ +You can call a function right where you're passing arguments to `assert!`. So +you could do something like `assert!(having_fun())`. + +If you want to check that you indeed get `false`, you can negate the result of +what you're doing using `!`, like `assert!(!having_fun())`.""" + +[[exercises]] +name = "tests4" +path = "exercises/17_tests/tests4.rs" +mode = "test" +hint = """ +We expect method `Rectangle::new()` to panic for negative values. + +To handle that you need to add a special attribute to the test function. + +You can refer to the docs: +https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html#checking-for-panics-with-should_panic""" + +# STANDARD LIBRARY TYPES + +[[exercises]] +name = "iterators1" +path = "exercises/18_iterators/iterators1.rs" +mode = "test" +hint = """ +Step 1: + +We need to apply something to the collection `my_fav_fruits` before we start to +go through it. What could that be? Take a look at the struct definition for a +vector for inspiration: +https://doc.rust-lang.org/std/vec/struct.Vec.html + +Step 2 & step 3: + +Very similar to the lines above and below. You've got this! + +Step 4: + +An iterator goes through all elements in a collection, but what if we've run +out of elements? What should we expect here? If you're stuck, take a look at +https://doc.rust-lang.org/std/iter/trait.Iterator.html for some ideas. +""" + +[[exercises]] +name = "iterators2" +path = "exercises/18_iterators/iterators2.rs" +mode = "test" +hint = """ +Step 1: + +The variable `first` is a `char`. It needs to be capitalized and added to the +remaining characters in `c` in order to return the correct `String`. + +The remaining characters in `c` can be viewed as a string slice using the +`as_str` method. + +The documentation for `char` contains many useful methods. +https://doc.rust-lang.org/std/primitive.char.html + +Step 2: + +Create an iterator from the slice. Transform the iterated values by applying +the `capitalize_first` function. Remember to `collect` the iterator. + +Step 3: + +This is surprisingly similar to the previous solution. `collect` is very +powerful and very general. Rust just needs to know the desired type.""" + +[[exercises]] +name = "iterators3" +path = "exercises/18_iterators/iterators3.rs" +mode = "test" +hint = """ +The `divide` function needs to return the correct error when even division is +not possible. + +The `division_results` variable needs to be collected into a collection type. + +The `result_with_list` function needs to return a single `Result` where the +success case is a vector of integers and the failure case is a `DivisionError`. + +The `list_of_results` function needs to return a vector of results. + +See https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect for +how the `FromIterator` trait is used in `collect()`. This trait is REALLY +powerful! It can make the solution to this exercise infinitely easier.""" + +[[exercises]] +name = "iterators4" +path = "exercises/18_iterators/iterators4.rs" +mode = "test" +hint = """ +In an imperative language, you might write a `for` loop that updates a mutable +variable. Or, you might write code utilizing recursion and a match clause. In +Rust you can take another functional approach, computing the factorial +elegantly with ranges and iterators. + +Hint 2: Check out the `fold` and `rfold` methods!""" + +[[exercises]] +name = "iterators5" +path = "exercises/18_iterators/iterators5.rs" +mode = "test" +hint = """ +The documentation for the `std::iter::Iterator` trait contains numerous methods +that would be helpful here. + +The `collection` variable in `count_collection_iterator` is a slice of +`HashMap`s. It needs to be converted into an iterator in order to use the +iterator methods. + +The `fold` method can be useful in the `count_collection_iterator` function. + +For a further challenge, consult the documentation for `Iterator` to find +a different method that could make your code more compact than using `fold`.""" + +# SMART POINTERS + +[[exercises]] +name = "box1" +path = "exercises/19_smart_pointers/box1.rs" +mode = "test" +hint = """ +Step 1: + +The compiler's message should help: since we cannot store the value of the +actual type when working with recursive types, we need to store a reference +(pointer) to its value. + +We should, therefore, place our `List` inside a `Box`. More details in the book +here: https://doc.rust-lang.org/book/ch15-01-box.html#enabling-recursive-types-with-boxes + +Step 2: + +Creating an empty list should be fairly straightforward (hint: peek at the +assertions). + +For a non-empty list keep in mind that we want to use our `Cons` "list builder". +Although the current list is one of integers (`i32`), feel free to change the +definition and try other types! +""" + +[[exercises]] +name = "rc1" +path = "exercises/19_smart_pointers/rc1.rs" +mode = "test" +hint = """ +This is a straightforward exercise to use the `Rc` type. Each `Planet` has +ownership of the `Sun`, and uses `Rc::clone()` to increment the reference count +of the `Sun`. + +After using `drop()` to move the `Planet`s out of scope individually, the +reference count goes down. + +In the end the `Sun` only has one reference again, to itself. + +See more at: https://doc.rust-lang.org/book/ch15-04-rc.html + +* Unfortunately Pluto is no longer considered a planet :( +""" + +[[exercises]] +name = "arc1" +path = "exercises/19_smart_pointers/arc1.rs" +mode = "compile" +hint = """ +Make `shared_numbers` be an `Arc` from the numbers vector. Then, in order +to avoid creating a copy of `numbers`, you'll need to create `child_numbers` +inside the loop but still in the main thread. + +`child_numbers` should be a clone of the `Arc` of the numbers instead of a +thread-local copy of the numbers. + +This is a simple exercise if you understand the underlying concepts, but if this +is too much of a struggle, consider reading through all of Chapter 16 in the +book: +https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html +""" + +[[exercises]] +name = "cow1" +path = "exercises/19_smart_pointers/cow1.rs" +mode = "test" +hint = """ +If `Cow` already owns the data it doesn't need to clone it when `to_mut()` is +called. + +Check out https://doc.rust-lang.org/std/borrow/enum.Cow.html for documentation +on the `Cow` type. +""" + +# THREADS + +[[exercises]] +name = "threads1" +path = "exercises/20_threads/threads1.rs" +mode = "compile" +hint = """ +`JoinHandle` is a struct that is returned from a spawned thread: +https://doc.rust-lang.org/std/thread/fn.spawn.html + +A challenge with multi-threaded applications is that the main thread can +finish before the spawned threads are completed. +https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles + +Use the `JoinHandle`s to wait for each thread to finish and collect their +results. + +https://doc.rust-lang.org/std/thread/struct.JoinHandle.html +""" + +[[exercises]] +name = "threads2" +path = "exercises/20_threads/threads2.rs" +mode = "compile" +hint = """ +`Arc` is an Atomic Reference Counted pointer that allows safe, shared access +to **immutable** data. But we want to *change* the number of `jobs_completed` +so we'll need to also use another type that will only allow one thread to +mutate the data at a time. Take a look at this section of the book: +https://doc.rust-lang.org/book/ch16-03-shared-state.html#atomic-reference-counting-with-arct +and keep reading if you'd like more hints :) + +Do you now have an `Arc` `Mutex` `JobStatus` at the beginning of main? Like: +``` +let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 })); +``` + +Similar to the code in the example in the book that happens after the text +that says 'Sharing a Mutex Between Multiple Threads'. If not, give that a +try! If you do and would like more hints, keep reading!! + +Make sure neither of your threads are holding onto the lock of the mutex +while they are sleeping, since this will prevent the other thread from +being allowed to get the lock. Locks are automatically released when +they go out of scope. + +If you've learned from the sample solutions, I encourage you to come +back to this exercise and try it again in a few days to reinforce +what you've learned :)""" + +[[exercises]] +name = "threads3" +path = "exercises/20_threads/threads3.rs" +mode = "test" +hint = """ +An alternate way to handle concurrency between threads is to use an `mpsc` +(multiple producer, single consumer) channel to communicate. + +With both a sending end and a receiving end, it's possible to send values in +one thread and receive them in another. + +Multiple producers are possible by using clone() to create a duplicate of the +original sending end. + +See https://doc.rust-lang.org/book/ch16-02-message-passing.html for more info. +""" + +# MACROS + +[[exercises]] +name = "macros1" +path = "exercises/21_macros/macros1.rs" +mode = "compile" +hint = """ +When you call a macro, you need to add something special compared to a +regular function call. If you're stuck, take a look at what's inside +`my_macro`.""" + +[[exercises]] +name = "macros2" +path = "exercises/21_macros/macros2.rs" +mode = "compile" +hint = """ +Macros don't quite play by the same rules as the rest of Rust, in terms of +what's available where. + +Unlike other things in Rust, the order of "where you define a macro" versus +"where you use it" actually matters.""" + +[[exercises]] +name = "macros3" +path = "exercises/21_macros/macros3.rs" +mode = "compile" +hint = """ +In order to use a macro outside of its module, you need to do something +special to the module to lift the macro out into its parent. + +The same trick also works on "extern crate" statements for crates that have +exported macros, if you've seen any of those around.""" + +[[exercises]] +name = "macros4" +path = "exercises/21_macros/macros4.rs" +mode = "compile" +hint = """ +You only need to add a single character to make this compile. + +The way macros are written, it wants to see something between each "macro arm", +so it can separate them. + +That's all the macro exercises we have in here, but it's barely even scratching +the surface of what you can do with Rust's macros. For a more thorough +introduction, you can have a read through 'The Little Book of Rust Macros': +https://veykril.github.io/tlborm/""" + +# CLIPPY + +[[exercises]] +name = "clippy1" +path = "exercises/22_clippy/clippy1.rs" +mode = "clippy" +hint = """ +Rust stores the highest precision version of any long or infinite precision +mathematical constants in the Rust standard library: +https://doc.rust-lang.org/stable/std/f32/consts/index.html + +We may be tempted to use our own approximations for certain mathematical +constants, but clippy recognizes those imprecise mathematical constants as a +source of potential error. + +See the suggestions of the clippy warning in compile output and use the +appropriate replacement constant from `std::f32::consts`...""" + +[[exercises]] +name = "clippy2" +path = "exercises/22_clippy/clippy2.rs" +mode = "clippy" +hint = """ +`for` loops over `Option` values are more clearly expressed as an `if let`""" + +[[exercises]] +name = "clippy3" +path = "exercises/22_clippy/clippy3.rs" +mode = "clippy" +hint = "No hints this time!" + +# TYPE CONVERSIONS + +[[exercises]] +name = "using_as" +path = "exercises/23_conversions/using_as.rs" +mode = "test" +hint = """ +Use the `as` operator to cast one of the operands in the last line of the +`average` function into the expected return type.""" + +[[exercises]] +name = "from_into" +path = "exercises/23_conversions/from_into.rs" +mode = "test" +hint = """ +Follow the steps provided right before the `From` implementation""" + +[[exercises]] +name = "from_str" +path = "exercises/23_conversions/from_str.rs" +mode = "test" +hint = """ +The implementation of `FromStr` should return an `Ok` with a `Person` object, +or an `Err` with an error if the string is not valid. + +This is almost like the `from_into` exercise, but returning errors instead +of falling back to a default value. + +Look at the test cases to see which error variants to return. + +Another hint: You can use the `map_err` method of `Result` with a function +or a closure to wrap the error from `parse::`. + +Yet another hint: If you would like to propagate errors by using the `?` +operator in your solution, you might want to look at +https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html +""" + +[[exercises]] +name = "try_from_into" +path = "exercises/23_conversions/try_from_into.rs" +mode = "test" +hint = """ +Follow the steps provided right before the `TryFrom` implementation. +You can also use the example at +https://doc.rust-lang.org/std/convert/trait.TryFrom.html + +Is there an implementation of `TryFrom` in the standard library that +can both do the required integer conversion and check the range of the input? + +Another hint: Look at the test cases to see which error variants to return. + +Yet another hint: You can use the `map_err` or `or` methods of `Result` to +convert errors. + +Yet another hint: If you would like to propagate errors by using the `?` +operator in your solution, you might want to look at +https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html + +Challenge: Can you make the `TryFrom` implementations generic over many integer types?""" + +[[exercises]] +name = "as_ref_mut" +path = "exercises/23_conversions/as_ref_mut.rs" +mode = "test" +hint = """ +Add `AsRef` or `AsMut` as a trait bound to the functions.""" diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..844b013 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,94 @@ +#!/usr/bin/env pwsh + +#Requires -Version 5 +param($path = "$home/rustlings") + +Write-Host "Let's get you set up with Rustlings!" + +Write-Host "Checking requirements..." +if (Get-Command git -ErrorAction SilentlyContinue) { + Write-Host "SUCCESS: Git is installed" +} else { + Write-Host "WARNING: Git does not seem to be installed." + Write-Host "Please download Git using your package manager or over https://git-scm.com/!" + exit 1 +} + +if (Get-Command rustc -ErrorAction SilentlyContinue) { + Write-Host "SUCCESS: Rust is installed" +} else { + Write-Host "WARNING: Rust does not seem to be installed." + Write-Host "Please download Rust using https://rustup.rs!" + exit 1 +} + +if (Get-Command cargo -ErrorAction SilentlyContinue) { + Write-Host "SUCCESS: Cargo is installed" +} else { + Write-Host "WARNING: Cargo does not seem to be installed." + Write-Host "Please download Rust and Cargo using https://rustup.rs!" + exit 1 +} + +# Function that compares two versions strings v1 and v2 given in arguments (e.g 1.31 and 1.33.0). +# Returns 1 if v1 > v2, 0 if v1 == v2, 2 if v1 < v2. +function vercomp($v1, $v2) { + if ($v1 -eq $v2) { + return 0 + } + + $v1 = $v1.Replace(".", "0") + $v2 = $v2.Replace(".", "0") + if ($v1.Length -gt $v2.Length) { + $v2 = $v2.PadRight($v1.Length, "0") + } else { + $v1 = $v1.PadRight($v2.Length, "0") + } + + if ($v1 -gt $v2) { + return 1 + } else { + return 2 + } +} + +$rustVersion = $(rustc --version).Split(" ")[1] +$minRustVersion = "1.70" +if ((vercomp $rustVersion $minRustVersion) -eq 2) { + Write-Host "WARNING: Rust version is too old: $rustVersion - needs at least $minRustVersion" + Write-Host "Please update Rust with 'rustup update'" + exit 1 +} else { + Write-Host "SUCCESS: Rust is up to date" +} + +Write-Host "Cloning Rustlings at $path" +git clone -q https://github.com/rust-lang/rustlings $path +if (!($LASTEXITCODE -eq 0)) { + exit 1 +} + +# UseBasicParsing is deprecated, pwsh 6 or above will automatically use it, +# but anyone running pwsh 5 will have to pass the argument. +$version = Invoke-WebRequest -UseBasicParsing https://api.github.com/repos/rust-lang/rustlings/releases/latest ` + | ConvertFrom-Json | Select-Object -ExpandProperty tag_name + +Write-Host "Checking out version $version..." +Set-Location $path +git checkout -q tags/$version + +Write-Host "Installing the 'rustlings' executable..." +cargo install --force --path . +if (!(Get-Command rustlings -ErrorAction SilentlyContinue)) { + Write-Host "WARNING: Please check that you have '~/.cargo/bin' in your PATH environment variable!" +} + +# Checking whether Clippy is installed. +# Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514 +$clippy = (rustup component list | Select-String "clippy" | Select-String "installed") | Out-String +if (!$clippy) { + Write-Host "Installing the 'cargo-clippy' executable..." + rustup component add clippy +} + +Write-Host "All done! Navigate to $path and run 'rustlings' to get started!" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..5915a33 --- /dev/null +++ b/install.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo -e "\nLet's get you set up with Rustlings!" + +echo "Checking requirements..." +if [ -x "$(command -v git)" ] +then + echo "SUCCESS: Git is installed" +else + echo "ERROR: Git does not seem to be installed." + echo "Please download Git using your package manager or over https://git-scm.com/!" + exit 1 +fi + +if [ -x "$(command -v cc)" ] +then + echo "SUCCESS: cc is installed" +else + echo "ERROR: cc does not seem to be installed." + echo "Please download (g)cc using your package manager." + echo "OSX: xcode-select --install" + echo "Deb: sudo apt install gcc" + echo "Yum: sudo yum -y install gcc" + exit 1 +fi + +if [ -x "$(command -v rustup)" ] +then + echo "SUCCESS: rustup is installed" +else + echo "ERROR: rustup does not seem to be installed." + echo "Please download rustup using https://rustup.rs!" + exit 1 +fi + +if [ -x "$(command -v rustc)" ] +then + echo "SUCCESS: Rust is installed" +else + echo "ERROR: Rust does not seem to be installed." + echo "Please download Rust using rustup!" + exit 1 +fi + +if [ -x "$(command -v cargo)" ] +then + echo "SUCCESS: Cargo is installed" +else + echo "ERROR: Cargo does not seem to be installed." + echo "Please download Rust and Cargo using rustup!" + exit 1 +fi + +# Look up python installations, starting with 3 with a fallback of 2 +if [ -x "$(command -v python3)" ] +then + PY="$(command -v python3)" +elif [ -x "$(command -v python)" ] +then + PY="$(command -v python)" +elif [ -x "$(command -v python2)" ] +then + PY="$(command -v python2)" +else + echo "ERROR: No working python installation was found" + echo "Please install python and add it to the PATH variable" + exit 1 +fi + +# Function that compares two versions strings v1 and v2 given in arguments (e.g 1.31 and 1.33.0). +# Returns 1 if v1 > v2, 0 if v1 == v2, 2 if v1 < v2. +function vercomp() { + if [[ $1 == $2 ]] + then + return 0 + fi + v1=( ${1//./ } ) + v2=( ${2//./ } ) + len1=${#v1[@]} + len2=${#v2[@]} + max_len=$len1 + if [[ $max_len -lt $len2 ]] + then + max_len=$len2 + fi + + #pad right in short arr + if [[ len1 -gt len2 ]]; + then + for ((i = len2; i < len1; i++)); + do + v2[$i]=0 + done + else + for ((i = len1; i < len2; i++)); + do + v1[$i]=0 + done + fi + + for i in `seq 0 $((max_len-1))` + do + # Fill empty fields with zeros in v1 + if [ -z "${v1[$i]}" ] + then + v1[$i]=0 + fi + # And in v2 + if [ -z "${v2[$i]}" ] + then + v2[$i]=0 + fi + if [ ${v1[$i]} -gt ${v2[$i]} ] + then + return 1 + fi + if [ ${v1[$i]} -lt ${v2[$i]} ] + then + return 2 + fi + done + return 0 +} + +RustVersion=$(rustc --version | cut -d " " -f 2) +MinRustVersion=1.70 +vercomp "$RustVersion" $MinRustVersion || ec=$? +if [ ${ec:-0} -eq 2 ] +then + echo "ERROR: Rust version is too old: $RustVersion - needs at least $MinRustVersion" + echo "Please update Rust with 'rustup update'" + exit 1 +else + echo "SUCCESS: Rust is up to date" +fi + +Path=${1:-rustlings/} +echo "Cloning Rustlings at $Path..." +git clone -q https://github.com/rust-lang/rustlings "$Path" + +cd "$Path" + +Version=$(curl -s https://api.github.com/repos/rust-lang/rustlings/releases/latest | ${PY} -c "import json,sys;obj=json.load(sys.stdin);print(obj['tag_name']) if 'tag_name' in obj else sys.exit(f\"Error: {obj['message']}\");") +CargoBin="${CARGO_HOME:-$HOME/.cargo}/bin" + +if [[ -z ${Version} ]] +then + echo "The latest tag version could not be fetched remotely." + echo "Using the local git repository..." + Version=$(ls -tr .git/refs/tags/ | tail -1) + if [[ -z ${Version} ]] + then + echo "No valid tag version found" + echo "Rustlings will be installed using the main branch" + Version="main" + else + Version="tags/${Version}" + fi +else + Version="tags/${Version}" +fi + +echo "Checking out version $Version..." +git checkout -q ${Version} + +echo "Installing the 'rustlings' executable..." +cargo install --force --path . + +if ! [ -x "$(command -v rustlings)" ] +then + echo "WARNING: Please check that you have '$CargoBin' in your PATH environment variable!" +fi + +# Checking whether Clippy is installed. +# Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514 +Clippy=$(rustup component list | grep "clippy" | grep "installed") +if [ -z "$Clippy" ] +then + echo "Installing the 'cargo-clippy' executable..." + rustup component add clippy +fi + +echo "All done! Run 'rustlings' to get started." diff --git a/oranda.json b/oranda.json new file mode 100644 index 0000000..be88e5e --- /dev/null +++ b/oranda.json @@ -0,0 +1,24 @@ +{ + "project": { + "homepage": "https://rustlings.cool", + "repository": "https://github.com/rust-lang/rustlings" + }, + "marketing": { + "analytics": { + "plausible": { + "domain": "rustlings.cool" + } + } + }, + "components": { + "artifacts": { + "auto": true, + "package_managers": { + "preferred": { + "macos/linux/unix": "curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash", + "windows": "Start-BitsTransfer -Source https://raw.githubusercontent.com/rust-lang/rustlings/main/install.ps1 -Destination $env:TMP/install_rustlings.ps1; Unblock-File $env:TMP/install_rustlings.ps1; Invoke-Expression $env:TMP/install_rustlings.ps1" + } + } + } + } +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..fa2a56c --- /dev/null +++ b/shell.nix @@ -0,0 +1,6 @@ +(import (let lock = builtins.fromJSON (builtins.readFile ./flake.lock); +in fetchTarball { + url = + "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; +}) { src = ./.; }).shellNix diff --git a/src/exercise.rs b/src/exercise.rs new file mode 100644 index 0000000..664b362 --- /dev/null +++ b/src/exercise.rs @@ -0,0 +1,378 @@ +use regex::Regex; +use serde::Deserialize; +use std::env; +use std::fmt::{self, Display, Formatter}; +use std::fs::{self, remove_file, File}; +use std::io::Read; +use std::path::PathBuf; +use std::process::{self, Command}; + +const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"]; +const RUSTC_EDITION_ARGS: &[&str] = &["--edition", "2021"]; +const RUSTC_NO_DEBUG_ARGS: &[&str] = &["-C", "strip=debuginfo"]; +const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE"; +const CONTEXT: usize = 2; +const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/22_clippy/Cargo.toml"; + +// Get a temporary file name that is hopefully unique +#[inline] +fn temp_file() -> String { + let thread_id: String = format!("{:?}", std::thread::current().id()) + .chars() + .filter(|c| c.is_alphanumeric()) + .collect(); + + format!("./temp_{}_{thread_id}", process::id()) +} + +// The mode of the exercise. +#[derive(Deserialize, Copy, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum Mode { + // Indicates that the exercise should be compiled as a binary + Compile, + // Indicates that the exercise should be compiled as a test harness + Test, + // Indicates that the exercise should be linted with clippy + Clippy, +} + +#[derive(Deserialize)] +pub struct ExerciseList { + pub exercises: Vec, +} + +// A representation of a rustlings exercise. +// This is deserialized from the accompanying info.toml file +#[derive(Deserialize, Debug)] +pub struct Exercise { + // Name of the exercise + pub name: String, + // The path to the file containing the exercise's source code + pub path: PathBuf, + // The mode of the exercise (Test, Compile, or Clippy) + pub mode: Mode, + // The hint text associated with the exercise + pub hint: String, +} + +// An enum to track of the state of an Exercise. +// An Exercise can be either Done or Pending +#[derive(PartialEq, Debug)] +pub enum State { + // The state of the exercise once it's been completed + Done, + // The state of the exercise while it's not completed yet + Pending(Vec), +} + +// The context information of a pending exercise +#[derive(PartialEq, Debug)] +pub struct ContextLine { + // The source code that is still pending completion + pub line: String, + // The line number of the source code still pending completion + pub number: usize, + // Whether or not this is important + pub important: bool, +} + +// The result of compiling an exercise +pub struct CompiledExercise<'a> { + exercise: &'a Exercise, + _handle: FileHandle, +} + +impl<'a> CompiledExercise<'a> { + // Run the compiled exercise + pub fn run(&self) -> Result { + self.exercise.run() + } +} + +// A representation of an already executed binary +#[derive(Debug)] +pub struct ExerciseOutput { + // The textual contents of the standard output of the binary + pub stdout: String, + // The textual contents of the standard error of the binary + pub stderr: String, +} + +struct FileHandle; + +impl Drop for FileHandle { + fn drop(&mut self) { + clean(); + } +} + +impl Exercise { + pub fn compile(&self) -> Result { + let cmd = match self.mode { + Mode::Compile => Command::new("rustc") + .args([self.path.to_str().unwrap(), "-o", &temp_file()]) + .args(RUSTC_COLOR_ARGS) + .args(RUSTC_EDITION_ARGS) + .args(RUSTC_NO_DEBUG_ARGS) + .output(), + Mode::Test => Command::new("rustc") + .args(["--test", self.path.to_str().unwrap(), "-o", &temp_file()]) + .args(RUSTC_COLOR_ARGS) + .args(RUSTC_EDITION_ARGS) + .args(RUSTC_NO_DEBUG_ARGS) + .output(), + Mode::Clippy => { + let cargo_toml = format!( + r#"[package] +name = "{}" +version = "0.0.1" +edition = "2021" +[[bin]] +name = "{}" +path = "{}.rs""#, + self.name, self.name, self.name + ); + let cargo_toml_error_msg = if env::var("NO_EMOJI").is_ok() { + "Failed to write Clippy Cargo.toml file." + } else { + "Failed to write 📎 Clippy 📎 Cargo.toml file." + }; + fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml).expect(cargo_toml_error_msg); + // To support the ability to run the clippy exercises, build + // an executable, in addition to running clippy. With a + // compilation failure, this would silently fail. But we expect + // clippy to reflect the same failure while compiling later. + Command::new("rustc") + .args([self.path.to_str().unwrap(), "-o", &temp_file()]) + .args(RUSTC_COLOR_ARGS) + .args(RUSTC_EDITION_ARGS) + .args(RUSTC_NO_DEBUG_ARGS) + .output() + .expect("Failed to compile!"); + // Due to an issue with Clippy, a cargo clean is required to catch all lints. + // See https://github.com/rust-lang/rust-clippy/issues/2604 + // This is already fixed on Clippy's master branch. See this issue to track merging into Cargo: + // https://github.com/rust-lang/rust-clippy/issues/3837 + Command::new("cargo") + .args(["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) + .args(RUSTC_COLOR_ARGS) + .output() + .expect("Failed to run 'cargo clean'"); + Command::new("cargo") + .args(["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) + .args(RUSTC_COLOR_ARGS) + .args(["--", "-D", "warnings", "-D", "clippy::float_cmp"]) + .output() + } + } + .expect("Failed to run 'compile' command."); + + if cmd.status.success() { + Ok(CompiledExercise { + exercise: self, + _handle: FileHandle, + }) + } else { + clean(); + Err(ExerciseOutput { + stdout: String::from_utf8_lossy(&cmd.stdout).to_string(), + stderr: String::from_utf8_lossy(&cmd.stderr).to_string(), + }) + } + } + + fn run(&self) -> Result { + let arg = match self.mode { + Mode::Test => "--show-output", + _ => "", + }; + let cmd = Command::new(temp_file()) + .arg(arg) + .output() + .expect("Failed to run 'run' command"); + + let output = ExerciseOutput { + stdout: String::from_utf8_lossy(&cmd.stdout).to_string(), + stderr: String::from_utf8_lossy(&cmd.stderr).to_string(), + }; + + if cmd.status.success() { + Ok(output) + } else { + Err(output) + } + } + + pub fn state(&self) -> State { + let mut source_file = File::open(&self.path).unwrap_or_else(|e| { + panic!( + "We were unable to open the exercise file {}! {e}", + self.path.display() + ) + }); + + let source = { + let mut s = String::new(); + source_file.read_to_string(&mut s).unwrap_or_else(|e| { + panic!( + "We were unable to read the exercise file {}! {e}", + self.path.display() + ) + }); + s + }; + + let re = Regex::new(I_AM_DONE_REGEX).unwrap(); + + if !re.is_match(&source) { + return State::Done; + } + + let matched_line_index = source + .lines() + .enumerate() + .find_map(|(i, line)| if re.is_match(line) { Some(i) } else { None }) + .expect("This should not happen at all"); + + let min_line = ((matched_line_index as i32) - (CONTEXT as i32)).max(0) as usize; + let max_line = matched_line_index + CONTEXT; + + let context = source + .lines() + .enumerate() + .filter(|&(i, _)| i >= min_line && i <= max_line) + .map(|(i, line)| ContextLine { + line: line.to_string(), + number: i + 1, + important: i == matched_line_index, + }) + .collect(); + + State::Pending(context) + } + + // Check that the exercise looks to be solved using self.state() + // This is not the best way to check since + // the user can just remove the "I AM NOT DONE" string from the file + // without actually having solved anything. + // The only other way to truly check this would to compile and run + // the exercise; which would be both costly and counterintuitive + pub fn looks_done(&self) -> bool { + self.state() == State::Done + } +} + +impl Display for Exercise { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.path.to_str().unwrap()) + } +} + +#[inline] +fn clean() { + let _ignored = remove_file(temp_file()); +} + +#[cfg(test)] +mod test { + use super::*; + use std::path::Path; + + #[test] + fn test_clean() { + File::create(temp_file()).unwrap(); + let exercise = Exercise { + name: String::from("example"), + path: PathBuf::from("tests/fixture/state/pending_exercise.rs"), + mode: Mode::Compile, + hint: String::from(""), + }; + let compiled = exercise.compile().unwrap(); + drop(compiled); + assert!(!Path::new(&temp_file()).exists()); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_no_pdb_file() { + [Mode::Compile, Mode::Test] // Clippy doesn't like to test + .iter() + .for_each(|mode| { + let exercise = Exercise { + name: String::from("example"), + // We want a file that does actually compile + path: PathBuf::from("tests/fixture/state/pending_exercise.rs"), + mode: *mode, + hint: String::from(""), + }; + let _ = exercise.compile().unwrap(); + assert!(!Path::new(&format!("{}.pdb", temp_file())).exists()); + }); + } + + #[test] + fn test_pending_state() { + let exercise = Exercise { + name: "pending_exercise".into(), + path: PathBuf::from("tests/fixture/state/pending_exercise.rs"), + mode: Mode::Compile, + hint: String::new(), + }; + + let state = exercise.state(); + let expected = vec![ + ContextLine { + line: "// fake_exercise".to_string(), + number: 1, + important: false, + }, + ContextLine { + line: "".to_string(), + number: 2, + important: false, + }, + ContextLine { + line: "// I AM NOT DONE".to_string(), + number: 3, + important: true, + }, + ContextLine { + line: "".to_string(), + number: 4, + important: false, + }, + ContextLine { + line: "fn main() {".to_string(), + number: 5, + important: false, + }, + ]; + + assert_eq!(state, State::Pending(expected)); + } + + #[test] + fn test_finished_exercise() { + let exercise = Exercise { + name: "finished_exercise".into(), + path: PathBuf::from("tests/fixture/state/finished_exercise.rs"), + mode: Mode::Compile, + hint: String::new(), + }; + + assert_eq!(exercise.state(), State::Done); + } + + #[test] + fn test_exercise_with_output() { + let exercise = Exercise { + name: "exercise_with_output".into(), + path: PathBuf::from("tests/fixture/success/testSuccess.rs"), + mode: Mode::Test, + hint: String::new(), + }; + let out = exercise.compile().unwrap().run().unwrap(); + assert!(out.stdout.contains("THIS TEST TOO SHALL PASS")); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a06f0c5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,479 @@ +use crate::exercise::{Exercise, ExerciseList}; +use crate::project::RustAnalyzerProject; +use crate::run::{reset, run}; +use crate::verify::verify; +use clap::{Parser, Subcommand}; +use console::Emoji; +use notify_debouncer_mini::notify::{self, RecursiveMode}; +use notify_debouncer_mini::{new_debouncer, DebouncedEventKind}; +use std::ffi::OsStr; +use std::fs; +use std::io::{self, prelude::*}; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{channel, RecvTimeoutError}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +#[macro_use] +mod ui; + +mod exercise; +mod project; +mod run; +mod verify; + +/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code +#[derive(Parser)] +#[command(version)] +struct Args { + /// Show outputs from the test exercises + #[arg(long)] + nocapture: bool, + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Subcommands { + /// Verify all exercises according to the recommended order + Verify, + /// Rerun `verify` when files were edited + Watch { + /// Show hints on success + #[arg(long)] + success_hints: bool, + }, + /// Run/Test a single exercise + Run { + /// The name of the exercise + name: String, + }, + /// Reset a single exercise using "git stash -- " + Reset { + /// The name of the exercise + name: String, + }, + /// Return a hint for the given exercise + Hint { + /// The name of the exercise + name: String, + }, + /// List the exercises available in Rustlings + List { + /// Show only the paths of the exercises + #[arg(short, long)] + paths: bool, + /// Show only the names of the exercises + #[arg(short, long)] + names: bool, + /// Provide a string to match exercise names. + /// Comma separated patterns are accepted + #[arg(short, long)] + filter: Option, + /// Display only exercises not yet solved + #[arg(short, long)] + unsolved: bool, + /// Display only exercises that have been solved + #[arg(short, long)] + solved: bool, + }, + /// Enable rust-analyzer for exercises + Lsp, +} + +fn main() { + let args = Args::parse(); + + if args.command.is_none() { + println!("\n{WELCOME}\n"); + } + + if !Path::new("info.toml").exists() { + println!( + "{} must be run from the rustlings directory", + std::env::current_exe().unwrap().to_str().unwrap() + ); + println!("Try `cd rustlings/`!"); + std::process::exit(1); + } + + if !rustc_exists() { + println!("We cannot find `rustc`."); + println!("Try running `rustc --version` to diagnose your problem."); + println!("For instructions on how to install Rust, check the README."); + std::process::exit(1); + } + + let toml_str = &fs::read_to_string("info.toml").unwrap(); + let exercises = toml::from_str::(toml_str).unwrap().exercises; + let verbose = args.nocapture; + + let command = args.command.unwrap_or_else(|| { + println!("{DEFAULT_OUT}\n"); + std::process::exit(0); + }); + + match command { + Subcommands::List { + paths, + names, + filter, + unsolved, + solved, + } => { + if !paths && !names { + println!("{:<17}\t{:<46}\t{:<7}", "Name", "Path", "Status"); + } + let mut exercises_done: u16 = 0; + let filters = filter.clone().unwrap_or_default().to_lowercase(); + exercises.iter().for_each(|e| { + let fname = format!("{}", e.path.display()); + let filter_cond = filters + .split(',') + .filter(|f| !f.trim().is_empty()) + .any(|f| e.name.contains(f) || fname.contains(f)); + let status = if e.looks_done() { + exercises_done += 1; + "Done" + } else { + "Pending" + }; + let solve_cond = { + (e.looks_done() && solved) + || (!e.looks_done() && unsolved) + || (!solved && !unsolved) + }; + if solve_cond && (filter_cond || filter.is_none()) { + let line = if paths { + format!("{fname}\n") + } else if names { + format!("{}\n", e.name) + } else { + format!("{:<17}\t{fname:<46}\t{status:<7}\n", e.name) + }; + // Somehow using println! leads to the binary panicking + // when its output is piped. + // So, we're handling a Broken Pipe error and exiting with 0 anyway + let stdout = std::io::stdout(); + { + let mut handle = stdout.lock(); + handle.write_all(line.as_bytes()).unwrap_or_else(|e| { + match e.kind() { + std::io::ErrorKind::BrokenPipe => std::process::exit(0), + _ => std::process::exit(1), + }; + }); + } + } + }); + let percentage_progress = exercises_done as f32 / exercises.len() as f32 * 100.0; + println!( + "Progress: You completed {} / {} exercises ({:.1} %).", + exercises_done, + exercises.len(), + percentage_progress + ); + std::process::exit(0); + } + + Subcommands::Run { name } => { + let exercise = find_exercise(&name, &exercises); + + run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1)); + } + + Subcommands::Reset { name } => { + let exercise = find_exercise(&name, &exercises); + + reset(exercise).unwrap_or_else(|_| std::process::exit(1)); + } + + Subcommands::Hint { name } => { + let exercise = find_exercise(&name, &exercises); + + println!("{}", exercise.hint); + } + + Subcommands::Verify => { + verify(&exercises, (0, exercises.len()), verbose, false) + .unwrap_or_else(|_| std::process::exit(1)); + } + + Subcommands::Lsp => { + let mut project = RustAnalyzerProject::new(); + project + .get_sysroot_src() + .expect("Couldn't find toolchain path, do you have `rustc` installed?"); + project + .exercises_to_json() + .expect("Couldn't parse rustlings exercises files"); + + if project.crates.is_empty() { + println!("Failed find any exercises, make sure you're in the `rustlings` folder"); + } else if project.write_to_disk().is_err() { + println!("Failed to write rust-project.json to disk for rust-analyzer"); + } else { + println!("Successfully generated rust-project.json"); + println!("rust-analyzer will now parse exercises, restart your language server or editor") + } + } + + Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) { + Err(e) => { + println!( + "Error: Could not watch your progress. Error message was {:?}.", + e + ); + println!("Most likely you've run out of disk space or your 'inotify limit' has been reached."); + std::process::exit(1); + } + Ok(WatchStatus::Finished) => { + println!( + "{emoji} All exercises completed! {emoji}", + emoji = Emoji("🎉", "★") + ); + println!("\n{FENISH_LINE}\n"); + } + Ok(WatchStatus::Unfinished) => { + println!("We hope you're enjoying learning about Rust!"); + println!("If you want to continue working on the exercises at a later point, you can simply run `rustlings watch` again"); + } + }, + } +} + +fn spawn_watch_shell( + failed_exercise_hint: &Arc>>, + should_quit: Arc, +) { + let failed_exercise_hint = Arc::clone(failed_exercise_hint); + println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here."); + thread::spawn(move || loop { + let mut input = String::new(); + match io::stdin().read_line(&mut input) { + Ok(_) => { + let input = input.trim(); + if input == "hint" { + if let Some(hint) = &*failed_exercise_hint.lock().unwrap() { + println!("{hint}"); + } + } else if input == "clear" { + println!("\x1B[2J\x1B[1;1H"); + } else if input.eq("quit") { + should_quit.store(true, Ordering::SeqCst); + println!("Bye!"); + } else if input.eq("help") { + println!("Commands available to you in watch mode:"); + println!(" hint - prints the current exercise's hint"); + println!(" clear - clears the screen"); + println!(" quit - quits watch mode"); + println!(" ! - executes a command, like `!rustc --explain E0381`"); + println!(" help - displays this help message"); + println!(); + println!("Watch mode automatically re-evaluates the current exercise"); + println!("when you edit a file's contents.") + } else if let Some(cmd) = input.strip_prefix('!') { + let parts: Vec<&str> = cmd.split_whitespace().collect(); + if parts.is_empty() { + println!("no command provided"); + } else if let Err(e) = Command::new(parts[0]).args(&parts[1..]).status() { + println!("failed to execute command `{}`: {}", cmd, e); + } + } else { + println!("unknown command: {input}"); + } + } + Err(error) => println!("error reading command: {error}"), + } + }); +} + +fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise { + if name.eq("next") { + exercises + .iter() + .find(|e| !e.looks_done()) + .unwrap_or_else(|| { + println!("🎉 Congratulations! You have done all the exercises!"); + println!("🔚 There are no more exercises to do next!"); + std::process::exit(1) + }) + } else { + exercises + .iter() + .find(|e| e.name == name) + .unwrap_or_else(|| { + println!("No exercise found for '{name}'!"); + std::process::exit(1) + }) + } +} + +enum WatchStatus { + Finished, + Unfinished, +} + +fn watch( + exercises: &[Exercise], + verbose: bool, + success_hints: bool, +) -> notify::Result { + /* Clears the terminal with an ANSI escape code. + Works in UNIX and newer Windows terminals. */ + fn clear_screen() { + println!("\x1Bc"); + } + + let (tx, rx) = channel(); + let should_quit = Arc::new(AtomicBool::new(false)); + + let mut debouncer = new_debouncer(Duration::from_secs(1), tx)?; + debouncer + .watcher() + .watch(Path::new("./exercises"), RecursiveMode::Recursive)?; + + clear_screen(); + + let to_owned_hint = |t: &Exercise| t.hint.to_owned(); + let failed_exercise_hint = match verify( + exercises.iter(), + (0, exercises.len()), + verbose, + success_hints, + ) { + Ok(_) => return Ok(WatchStatus::Finished), + Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))), + }; + spawn_watch_shell(&failed_exercise_hint, Arc::clone(&should_quit)); + loop { + match rx.recv_timeout(Duration::from_secs(1)) { + Ok(event) => match event { + Ok(events) => { + for event in events { + let event_path = event.path; + if event.kind == DebouncedEventKind::Any + && event_path.extension() == Some(OsStr::new("rs")) + && event_path.exists() + { + let filepath = event_path.as_path().canonicalize().unwrap(); + let pending_exercises = + exercises + .iter() + .find(|e| filepath.ends_with(&e.path)) + .into_iter() + .chain(exercises.iter().filter(|e| { + !e.looks_done() && !filepath.ends_with(&e.path) + })); + let num_done = exercises + .iter() + .filter(|e| e.looks_done() && !filepath.ends_with(&e.path)) + .count(); + clear_screen(); + match verify( + pending_exercises, + (num_done, exercises.len()), + verbose, + success_hints, + ) { + Ok(_) => return Ok(WatchStatus::Finished), + Err(exercise) => { + let mut failed_exercise_hint = + failed_exercise_hint.lock().unwrap(); + *failed_exercise_hint = Some(to_owned_hint(exercise)); + } + } + } + } + } + Err(e) => println!("watch error: {e:?}"), + }, + Err(RecvTimeoutError::Timeout) => { + // the timeout expired, just check the `should_quit` variable below then loop again + } + Err(e) => println!("watch error: {e:?}"), + } + // Check if we need to exit + if should_quit.load(Ordering::SeqCst) { + return Ok(WatchStatus::Unfinished); + } + } +} + +fn rustc_exists() -> bool { + Command::new("rustc") + .args(["--version"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .and_then(|mut child| child.wait()) + .map(|status| status.success()) + .unwrap_or(false) +} + +const DEFAULT_OUT: &str = r#"Thanks for installing Rustlings! + +Is this your first time? Don't worry, Rustlings was made for beginners! We are +going to teach you a lot of things about Rust, but before we can get +started, here's a couple of notes about how Rustlings operates: + +1. The central concept behind Rustlings is that you solve exercises. These + exercises usually have some sort of syntax error in them, which will cause + them to fail compilation or testing. Sometimes there's a logic error instead + of a syntax error. No matter what error, it's your job to find it and fix it! + You'll know when you fixed it because then, the exercise will compile and + Rustlings will be able to move on to the next exercise. +2. If you run Rustlings in watch mode (which we recommend), it'll automatically + start with the first exercise. Don't get confused by an error message popping + up as soon as you run Rustlings! This is part of the exercise that you're + supposed to solve, so open the exercise file in an editor and start your + detective work! +3. If you're stuck on an exercise, there is a helpful hint you can view by typing + 'hint' (in watch mode), or running `rustlings hint exercise_name`. +4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub! + (https://github.com/rust-lang/rustlings/issues/new). We look at every issue, + and sometimes, other learners do too so you can help each other out! +5. If you want to use `rust-analyzer` with exercises, which provides features like + autocompletion, run the command `rustlings lsp`. + +Got all that? Great! To get started, run `rustlings watch` in order to get the first +exercise. Make sure to have your editor open!"#; + +const FENISH_LINE: &str = "+----------------------------------------------------+ +| You made it to the Fe-nish line! | ++-------------------------- ------------------------+ + \\/\x1b[31m + ▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒ + ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒ + ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒ + ░░▒▒▒▒░░▒▒ ▒▒ ▒▒ ▒▒ ▒▒░░▒▒▒▒ + ▓▓▓▓▓▓▓▓ ▓▓ ▓▓██ ▓▓ ▓▓██ ▓▓ ▓▓▓▓▓▓▓▓ + ▒▒▒▒ ▒▒ ████ ▒▒ ████ ▒▒░░ ▒▒▒▒ + ▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒ + ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▓▓▒▒▓▓▒▒▒▒▒▒▒▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▒▒▒▒▒▒▒▒▒▒██▒▒▒▒▒▒██▒▒▒▒▒▒▒▒▒▒ + ▒▒ ▒▒▒▒▒▒▒▒▒▒██████▒▒▒▒▒▒▒▒▒▒ ▒▒ + ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ + ▒▒ ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ▒▒ + ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ + ▒▒ ▒▒ ▒▒ ▒▒\x1b[0m + +We hope you enjoyed learning about the various aspects of Rust! +If you noticed any issues, please don't hesitate to report them to our repo. +You can also contribute your own exercises to help the greater community! + +Before reporting an issue or contributing, please read our guidelines: +https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md"; + +const WELCOME: &str = r" welcome to... + _ _ _ + _ __ _ _ ___| |_| (_)_ __ __ _ ___ + | '__| | | / __| __| | | '_ \ / _` / __| + | | | |_| \__ \ |_| | | | | | (_| \__ \ + |_| \__,_|___/\__|_|_|_| |_|\__, |___/ + |___/"; diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..bcbd7ad --- /dev/null +++ b/src/project.rs @@ -0,0 +1,99 @@ +use glob::glob; +use serde::{Deserialize, Serialize}; +use std::env; +use std::error::Error; +use std::path::PathBuf; +use std::process::Command; + +/// Contains the structure of resulting rust-project.json file +/// and functions to build the data required to create the file +#[derive(Serialize, Deserialize)] +pub struct RustAnalyzerProject { + sysroot_src: String, + pub crates: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Crate { + root_module: String, + edition: String, + deps: Vec, + cfg: Vec, +} + +impl RustAnalyzerProject { + pub fn new() -> RustAnalyzerProject { + RustAnalyzerProject { + sysroot_src: String::new(), + crates: Vec::new(), + } + } + + /// Write rust-project.json to disk + pub fn write_to_disk(&self) -> Result<(), std::io::Error> { + std::fs::write( + "./rust-project.json", + serde_json::to_vec(&self).expect("Failed to serialize to JSON"), + )?; + Ok(()) + } + + /// If path contains .rs extension, add a crate to `rust-project.json` + fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box> { + if let Some(ext) = path.extension() { + if ext == "rs" { + self.crates.push(Crate { + root_module: path.display().to_string(), + edition: "2021".to_string(), + deps: Vec::new(), + // This allows rust_analyzer to work inside #[test] blocks + cfg: vec!["test".to_string()], + }) + } + } + + Ok(()) + } + + /// Parse the exercises folder for .rs files, any matches will create + /// a new `crate` in rust-project.json which allows rust-analyzer to + /// treat it like a normal binary + pub fn exercises_to_json(&mut self) -> Result<(), Box> { + for path in glob("./exercises/**/*")? { + self.path_to_json(path?)?; + } + Ok(()) + } + + /// Use `rustc` to determine the default toolchain + pub fn get_sysroot_src(&mut self) -> Result<(), Box> { + // check if RUST_SRC_PATH is set + if let Ok(path) = env::var("RUST_SRC_PATH") { + self.sysroot_src = path; + return Ok(()); + } + + let toolchain = Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output()? + .stdout; + + let toolchain = String::from_utf8_lossy(&toolchain); + let mut whitespace_iter = toolchain.split_whitespace(); + + let toolchain = whitespace_iter.next().unwrap_or(&toolchain); + + println!("Determined toolchain: {}\n", &toolchain); + + self.sysroot_src = (std::path::Path::new(toolchain) + .join("lib") + .join("rustlib") + .join("src") + .join("rust") + .join("library") + .to_string_lossy()) + .to_string(); + Ok(()) + } +} diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 0000000..e0ada4c --- /dev/null +++ b/src/run.rs @@ -0,0 +1,74 @@ +use std::process::Command; +use std::time::Duration; + +use crate::exercise::{Exercise, Mode}; +use crate::verify::test; +use indicatif::ProgressBar; + +// Invoke the rust compiler on the path of the given exercise, +// and run the ensuing binary. +// The verbose argument helps determine whether or not to show +// the output from the test harnesses (if the mode of the exercise is test) +pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> { + match exercise.mode { + Mode::Test => test(exercise, verbose)?, + Mode::Compile => compile_and_run(exercise)?, + Mode::Clippy => compile_and_run(exercise)?, + } + Ok(()) +} + +// Resets the exercise by stashing the changes. +pub fn reset(exercise: &Exercise) -> Result<(), ()> { + let command = Command::new("git") + .args(["stash", "--"]) + .arg(&exercise.path) + .spawn(); + + match command { + Ok(_) => Ok(()), + Err(_) => Err(()), + } +} + +// Invoke the rust compiler on the path of the given exercise +// and run the ensuing binary. +// This is strictly for non-test binaries, so output is displayed +fn compile_and_run(exercise: &Exercise) -> Result<(), ()> { + let progress_bar = ProgressBar::new_spinner(); + progress_bar.set_message(format!("Compiling {exercise}...")); + progress_bar.enable_steady_tick(Duration::from_millis(100)); + + let compilation_result = exercise.compile(); + let compilation = match compilation_result { + Ok(compilation) => compilation, + Err(output) => { + progress_bar.finish_and_clear(); + warn!( + "Compilation of {} failed!, Compiler error message:\n", + exercise + ); + println!("{}", output.stderr); + return Err(()); + } + }; + + progress_bar.set_message(format!("Running {exercise}...")); + let result = compilation.run(); + progress_bar.finish_and_clear(); + + match result { + Ok(output) => { + println!("{}", output.stdout); + success!("Successfully ran {}", exercise); + Ok(()) + } + Err(output) => { + println!("{}", output.stdout); + println!("{}", output.stderr); + + warn!("Ran {} with errors", exercise); + Err(()) + } + } +} diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..1ee4631 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,33 @@ +macro_rules! warn { + ($fmt:literal, $ex:expr) => {{ + use console::{style, Emoji}; + use std::env; + let formatstr = format!($fmt, $ex); + if env::var("NO_EMOJI").is_ok() { + println!("{} {}", style("!").red(), style(formatstr).red()); + } else { + println!( + "{} {}", + style(Emoji("⚠️ ", "!")).red(), + style(formatstr).red() + ); + } + }}; +} + +macro_rules! success { + ($fmt:literal, $ex:expr) => {{ + use console::{style, Emoji}; + use std::env; + let formatstr = format!($fmt, $ex); + if env::var("NO_EMOJI").is_ok() { + println!("{} {}", style("✓").green(), style(formatstr).green()); + } else { + println!( + "{} {}", + style(Emoji("✅", "✓")).green(), + style(formatstr).green() + ); + } + }}; +} diff --git a/src/verify.rs b/src/verify.rs new file mode 100644 index 0000000..8a2ad49 --- /dev/null +++ b/src/verify.rs @@ -0,0 +1,234 @@ +use crate::exercise::{CompiledExercise, Exercise, Mode, State}; +use console::style; +use indicatif::{ProgressBar, ProgressStyle}; +use std::{env, time::Duration}; + +// Verify that the provided container of Exercise objects +// can be compiled and run without any failures. +// Any such failures will be reported to the end user. +// If the Exercise being verified is a test, the verbose boolean +// determines whether or not the test harness outputs are displayed. +pub fn verify<'a>( + exercises: impl IntoIterator, + progress: (usize, usize), + verbose: bool, + success_hints: bool, +) -> Result<(), &'a Exercise> { + let (num_done, total) = progress; + let bar = ProgressBar::new(total as u64); + let mut percentage = num_done as f32 / total as f32 * 100.0; + bar.set_style( + ProgressStyle::default_bar() + .template("Progress: [{bar:60.green/red}] {pos}/{len} {msg}") + .expect("Progressbar template should be valid!") + .progress_chars("#>-"), + ); + bar.set_position(num_done as u64); + bar.set_message(format!("({:.1} %)", percentage)); + + for exercise in exercises { + let compile_result = match exercise.mode { + Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints), + Mode::Compile => compile_and_run_interactively(exercise, success_hints), + Mode::Clippy => compile_only(exercise, success_hints), + }; + if !compile_result.unwrap_or(false) { + return Err(exercise); + } + percentage += 100.0 / total as f32; + bar.inc(1); + bar.set_message(format!("({:.1} %)", percentage)); + } + Ok(()) +} + +enum RunMode { + Interactive, + NonInteractive, +} + +// Compile and run the resulting test harness of the given Exercise +pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> { + compile_and_test(exercise, RunMode::NonInteractive, verbose, false)?; + Ok(()) +} + +// Invoke the rust compiler without running the resulting binary +fn compile_only(exercise: &Exercise, success_hints: bool) -> Result { + let progress_bar = ProgressBar::new_spinner(); + progress_bar.set_message(format!("Compiling {exercise}...")); + progress_bar.enable_steady_tick(Duration::from_millis(100)); + + let _ = compile(exercise, &progress_bar)?; + progress_bar.finish_and_clear(); + + Ok(prompt_for_completion(exercise, None, success_hints)) +} + +// Compile the given Exercise and run the resulting binary in an interactive mode +fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Result { + let progress_bar = ProgressBar::new_spinner(); + progress_bar.set_message(format!("Compiling {exercise}...")); + progress_bar.enable_steady_tick(Duration::from_millis(100)); + + let compilation = compile(exercise, &progress_bar)?; + + progress_bar.set_message(format!("Running {exercise}...")); + let result = compilation.run(); + progress_bar.finish_and_clear(); + + let output = match result { + Ok(output) => output, + Err(output) => { + warn!("Ran {} with errors", exercise); + println!("{}", output.stdout); + println!("{}", output.stderr); + return Err(()); + } + }; + + Ok(prompt_for_completion( + exercise, + Some(output.stdout), + success_hints, + )) +} + +// Compile the given Exercise as a test harness and display +// the output if verbose is set to true +fn compile_and_test( + exercise: &Exercise, + run_mode: RunMode, + verbose: bool, + success_hints: bool, +) -> Result { + let progress_bar = ProgressBar::new_spinner(); + progress_bar.set_message(format!("Testing {exercise}...")); + progress_bar.enable_steady_tick(Duration::from_millis(100)); + + let compilation = compile(exercise, &progress_bar)?; + let result = compilation.run(); + progress_bar.finish_and_clear(); + + match result { + Ok(output) => { + if verbose { + println!("{}", output.stdout); + } + if let RunMode::Interactive = run_mode { + Ok(prompt_for_completion(exercise, None, success_hints)) + } else { + Ok(true) + } + } + Err(output) => { + warn!( + "Testing of {} failed! Please try again. Here's the output:", + exercise + ); + println!("{}", output.stdout); + Err(()) + } + } +} + +// Compile the given Exercise and return an object with information +// about the state of the compilation +fn compile<'a>( + exercise: &'a Exercise, + progress_bar: &ProgressBar, +) -> Result, ()> { + let compilation_result = exercise.compile(); + + match compilation_result { + Ok(compilation) => Ok(compilation), + Err(output) => { + progress_bar.finish_and_clear(); + warn!( + "Compiling of {} failed! Please try again. Here's the output:", + exercise + ); + println!("{}", output.stderr); + Err(()) + } + } +} + +fn prompt_for_completion( + exercise: &Exercise, + prompt_output: Option, + success_hints: bool, +) -> bool { + let context = match exercise.state() { + State::Done => return true, + State::Pending(context) => context, + }; + match exercise.mode { + Mode::Compile => success!("Successfully ran {}!", exercise), + Mode::Test => success!("Successfully tested {}!", exercise), + Mode::Clippy => success!("Successfully compiled {}!", exercise), + } + + let no_emoji = env::var("NO_EMOJI").is_ok(); + + let clippy_success_msg = if no_emoji { + "The code is compiling, and Clippy is happy!" + } else { + "The code is compiling, and 📎 Clippy 📎 is happy!" + }; + + let success_msg = match exercise.mode { + Mode::Compile => "The code is compiling!", + Mode::Test => "The code is compiling, and the tests pass!", + Mode::Clippy => clippy_success_msg, + }; + println!(); + if no_emoji { + println!("~*~ {success_msg} ~*~") + } else { + println!("🎉 🎉 {success_msg} 🎉 🎉") + } + println!(); + + if let Some(output) = prompt_output { + println!("Output:"); + println!("{}", separator()); + println!("{output}"); + println!("{}", separator()); + println!(); + } + if success_hints { + println!("Hints:"); + println!("{}", separator()); + println!("{}", exercise.hint); + println!("{}", separator()); + println!(); + } + + println!("You can keep working on this exercise,"); + println!( + "or jump into the next one by removing the {} comment:", + style("`I AM NOT DONE`").bold() + ); + println!(); + for context_line in context { + let formatted_line = if context_line.important { + format!("{}", style(context_line.line).bold()) + } else { + context_line.line.to_string() + }; + + println!( + "{:>2} {} {}", + style(context_line.number).blue().bold(), + style("|").blue(), + formatted_line + ); + } + + false +} + +fn separator() -> console::StyledObject<&'static str> { + style("====================").bold() +} diff --git a/tests/fixture/failure/compFailure.rs b/tests/fixture/failure/compFailure.rs new file mode 100644 index 0000000..566856a --- /dev/null +++ b/tests/fixture/failure/compFailure.rs @@ -0,0 +1,3 @@ +fn main() { + let +} \ No newline at end of file diff --git a/tests/fixture/failure/compNoExercise.rs b/tests/fixture/failure/compNoExercise.rs new file mode 100644 index 0000000..f79c691 --- /dev/null +++ b/tests/fixture/failure/compNoExercise.rs @@ -0,0 +1,2 @@ +fn main() { +} diff --git a/tests/fixture/failure/info.toml b/tests/fixture/failure/info.toml new file mode 100644 index 0000000..e5949f9 --- /dev/null +++ b/tests/fixture/failure/info.toml @@ -0,0 +1,11 @@ +[[exercises]] +name = "compFailure" +path = "compFailure.rs" +mode = "compile" +hint = "" + +[[exercises]] +name = "testFailure" +path = "testFailure.rs" +mode = "test" +hint = "Hello!" diff --git a/tests/fixture/failure/testFailure.rs b/tests/fixture/failure/testFailure.rs new file mode 100644 index 0000000..b33a5d2 --- /dev/null +++ b/tests/fixture/failure/testFailure.rs @@ -0,0 +1,4 @@ +#[test] +fn passing() { + asset!(true); +} diff --git a/tests/fixture/failure/testNotPassed.rs b/tests/fixture/failure/testNotPassed.rs new file mode 100644 index 0000000..a9fe88d --- /dev/null +++ b/tests/fixture/failure/testNotPassed.rs @@ -0,0 +1,4 @@ +#[test] +fn not_passing() { + assert!(false); +} diff --git a/tests/fixture/state/finished_exercise.rs b/tests/fixture/state/finished_exercise.rs new file mode 100644 index 0000000..016b827 --- /dev/null +++ b/tests/fixture/state/finished_exercise.rs @@ -0,0 +1,5 @@ +// fake_exercise + +fn main() { + +} diff --git a/tests/fixture/state/info.toml b/tests/fixture/state/info.toml new file mode 100644 index 0000000..547b3a4 --- /dev/null +++ b/tests/fixture/state/info.toml @@ -0,0 +1,18 @@ +[[exercises]] +name = "pending_exercise" +path = "pending_exercise.rs" +mode = "compile" +hint = """""" + +[[exercises]] +name = "pending_test_exercise" +path = "pending_test_exercise.rs" +mode = "test" +hint = """""" + +[[exercises]] +name = "finished_exercise" +path = "finished_exercise.rs" +mode = "compile" +hint = """""" + diff --git a/tests/fixture/state/pending_exercise.rs b/tests/fixture/state/pending_exercise.rs new file mode 100644 index 0000000..f579d0b --- /dev/null +++ b/tests/fixture/state/pending_exercise.rs @@ -0,0 +1,7 @@ +// fake_exercise + +// I AM NOT DONE + +fn main() { + +} diff --git a/tests/fixture/state/pending_test_exercise.rs b/tests/fixture/state/pending_test_exercise.rs new file mode 100644 index 0000000..8756f02 --- /dev/null +++ b/tests/fixture/state/pending_test_exercise.rs @@ -0,0 +1,4 @@ +// I AM NOT DONE + +#[test] +fn it_works() {} diff --git a/tests/fixture/success/compSuccess.rs b/tests/fixture/success/compSuccess.rs new file mode 100644 index 0000000..f79c691 --- /dev/null +++ b/tests/fixture/success/compSuccess.rs @@ -0,0 +1,2 @@ +fn main() { +} diff --git a/tests/fixture/success/info.toml b/tests/fixture/success/info.toml new file mode 100644 index 0000000..68d3538 --- /dev/null +++ b/tests/fixture/success/info.toml @@ -0,0 +1,11 @@ +[[exercises]] +name = "compSuccess" +path = "compSuccess.rs" +mode = "compile" +hint = """""" + +[[exercises]] +name = "testSuccess" +path = "testSuccess.rs" +mode = "test" +hint = """""" diff --git a/tests/fixture/success/testSuccess.rs b/tests/fixture/success/testSuccess.rs new file mode 100644 index 0000000..7139b50 --- /dev/null +++ b/tests/fixture/success/testSuccess.rs @@ -0,0 +1,5 @@ +#[test] +fn passing() { + println!("THIS TEST TOO SHALL PASS"); + assert!(true); +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..d1694a3 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,269 @@ +use assert_cmd::prelude::*; +use glob::glob; +use predicates::boolean::PredicateBooleanExt; +use std::fs::File; +use std::io::Read; +use std::process::Command; + +#[test] +fn runs_without_arguments() { + let mut cmd = Command::cargo_bin("rustlings").unwrap(); + cmd.assert().success(); +} + +#[test] +fn fails_when_in_wrong_dir() { + Command::cargo_bin("rustlings") + .unwrap() + .current_dir("tests/") + .assert() + .code(1); +} + +#[test] +fn verify_all_success() { + Command::cargo_bin("rustlings") + .unwrap() + .arg("verify") + .current_dir("tests/fixture/success") + .assert() + .success(); +} + +#[test] +fn verify_fails_if_some_fails() { + Command::cargo_bin("rustlings") + .unwrap() + .arg("verify") + .current_dir("tests/fixture/failure") + .assert() + .code(1); +} + +#[test] +fn run_single_compile_success() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "compSuccess"]) + .current_dir("tests/fixture/success/") + .assert() + .success(); +} + +#[test] +fn run_single_compile_failure() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "compFailure"]) + .current_dir("tests/fixture/failure/") + .assert() + .code(1); +} + +#[test] +fn run_single_test_success() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "testSuccess"]) + .current_dir("tests/fixture/success/") + .assert() + .success(); +} + +#[test] +fn run_single_test_failure() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "testFailure"]) + .current_dir("tests/fixture/failure/") + .assert() + .code(1); +} + +#[test] +fn run_single_test_not_passed() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "testNotPassed.rs"]) + .current_dir("tests/fixture/failure/") + .assert() + .code(1); +} + +#[test] +fn run_single_test_no_filename() { + Command::cargo_bin("rustlings") + .unwrap() + .arg("run") + .current_dir("tests/fixture/") + .assert() + .code(2) + .stderr(predicates::str::contains( + "required arguments were not provided", + )); +} + +#[test] +fn run_single_test_no_exercise() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "compNoExercise.rs"]) + .current_dir("tests/fixture/failure") + .assert() + .code(1); +} + +#[test] +fn reset_single_exercise() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["reset", "intro1"]) + .assert() + .code(0); +} + +#[test] +fn reset_no_exercise() { + Command::cargo_bin("rustlings") + .unwrap() + .arg("reset") + .assert() + .code(2) + .stderr(predicates::str::contains( + "required arguments were not provided", + )); +} + +#[test] +fn get_hint_for_single_test() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["hint", "testFailure"]) + .current_dir("tests/fixture/failure") + .assert() + .code(0) + .stdout("Hello!\n"); +} + +#[test] +fn all_exercises_require_confirmation() { + for exercise in glob("exercises/**/*.rs").unwrap() { + let path = exercise.unwrap(); + if path.file_name().unwrap() == "mod.rs" { + continue; + } + let source = { + let mut file = File::open(&path).unwrap(); + let mut s = String::new(); + file.read_to_string(&mut s).unwrap(); + s + }; + source + .matches("// I AM NOT DONE") + .next() + .unwrap_or_else(|| { + panic!( + "There should be an `I AM NOT DONE` annotation in {:?}", + path + ) + }); + } +} + +#[test] +fn run_compile_exercise_does_not_prompt() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "pending_exercise"]) + .current_dir("tests/fixture/state") + .assert() + .code(0) + .stdout(predicates::str::contains("I AM NOT DONE").not()); +} + +#[test] +fn run_test_exercise_does_not_prompt() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "pending_test_exercise"]) + .current_dir("tests/fixture/state") + .assert() + .code(0) + .stdout(predicates::str::contains("I AM NOT DONE").not()); +} + +#[test] +fn run_single_test_success_with_output() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["--nocapture", "run", "testSuccess"]) + .current_dir("tests/fixture/success/") + .assert() + .code(0) + .stdout(predicates::str::contains("THIS TEST TOO SHALL PASS")); +} + +#[test] +fn run_single_test_success_without_output() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["run", "testSuccess"]) + .current_dir("tests/fixture/success/") + .assert() + .code(0) + .stdout(predicates::str::contains("THIS TEST TOO SHALL PASS").not()); +} + +#[test] +fn run_rustlings_list() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["list"]) + .current_dir("tests/fixture/success") + .assert() + .success(); +} + +#[test] +fn run_rustlings_list_no_pending() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["list"]) + .current_dir("tests/fixture/success") + .assert() + .success() + .stdout(predicates::str::contains("Pending").not()); +} + +#[test] +fn run_rustlings_list_both_done_and_pending() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["list"]) + .current_dir("tests/fixture/state") + .assert() + .success() + .stdout(predicates::str::contains("Done").and(predicates::str::contains("Pending"))); +} + +#[test] +fn run_rustlings_list_without_pending() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["list", "--solved"]) + .current_dir("tests/fixture/state") + .assert() + .success() + .stdout(predicates::str::contains("Pending").not()); +} + +#[test] +fn run_rustlings_list_without_done() { + Command::cargo_bin("rustlings") + .unwrap() + .args(["list", "--unsolved"]) + .current_dir("tests/fixture/state") + .assert() + .success() + .stdout(predicates::str::contains("Done").not()); +}