-
-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 96: Submit exercises via HTTP patch method #110
Conversation
ExercismSubmit >> buildRequest [ | ||
| solutionEntity solutionPart multiPartFormDataEntity solutionId | | ||
solutionEntity := ZnByteArrayEntity bytes: exerciseContents. | ||
solutionPart := ZnMimePart exercismFieldName: 'files[]' fileName: 'Acronym.class.st' entity: solutionEntity. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard coded file name. There might be multiple files for a solution i.e. the class side and instance side of a class. I'm not that familiar with using Smalltalk on file systems. This is a problem I haven't solved yet.
| path | | ||
exercise := aStringExercise. | ||
|
||
path := (Path from: (aStringExercise asKebabCase)) / '.pharo' / aStringExercise capitalized , 'class.st'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again solution hard coded here: class.st
. Seems my number one problem is getting data from all the possible files a exercise could have. I figure for an exercise we will need to grab all files in the directory ending with .st
and make a mime part for each, excluding the test class and the package file. I don't have much experience walking file systems so it's a new problem to me.
Also this code assumes that Pharo is being run from the Exercism/pharo
directory.
I havent had a chance to look at your code (family duties are in the way),but from your comments I worry you may have gone astray... aren’t we now divorced from the file system? You should be able to iterate over the classes in an exercise sub-pkg/tag and generate the source for each and attach it as a fake “file” (no real file system should be needed). possibly I’m missing something - but that’s what went through my head as a soln. (Just as retrieve doesn’t get any real file either). There is also some metadata stuff we need to do as well - by again it should be file-less |
which means I could not test submitting because I have not downloaded the
exercise metadata, which I can't do because I haven't unlocked the exercise
¯_(ツ)_/¯ .
Leave the track and then rejoin in independent mode, then all exercises are
unlocked.
During my testing I've done that multiple times.
|
@macta I did have my intent wrong with using the filesystem. That helps a lot as that was my number one headache.
The idea is to have a string of source code in Tonel format for each class excluding the test class, correct? We were originally doing submitting of the exercises from On generating the source for each exercise 'file': I figure the |
I'm very stuck on this at the moment. I've been trying to get the source code of a exercise in Tonel format before adding it to the submit client as Mime parts (no file system involved, just in the image memory). My main problem is making a "Using exercise acronym as an example."
"Trying to get the source code in Tonel format as a string."
packageTag := (RPackageOrganizer default packageNamed: 'Exercism') classTagForClass: Acronym.
"Problem here: a working copy (.mcz file) is expected in the pharo-local\package-cache\"
version := MCVersion package: packageTag.
"Won't work because of the above issue."
TonelWriter fileOut: version on: (String new writeStream). |
Having a poke at this myself, I feel your pain. Tonel doesn't look written to be independent of filesystem.
so better way of getting a snapshot could be...
but then
which presumes a filesystem and is no use for us to directly get Strings. At which point I see two options:
** Separately we can submit a PR for this to Tonel. |
I've been playing around. No time for a PR at 3:30am, but perhaps something like...
TonelWriterTest remains green. |
@bencoman thanks for digging into that. I can understand most of it. It gets a good result. We could adopt the methods above as extension methods. We also already have a |
Nice job moving this forward guys - it shouldn’t have been quite this hard, but the Tonel classes are still quite 1.0 and this usecase probably helps shape them. Sadly my laptop has died in my travels so I’m even less able to help at the moment - maybe in Sydney I might be able to get it fixed in a few weeks. Anyway, keep pushing. |
I'll have a PR for our own extensions shortly.
|
This change allows submitting exercises without needing to read/write files to the file system. It assumes a TonelWriter capable of using an in memory file system (not included in this change).
Just a quick update. I have another commit to push using the changes to the TonelWriter shown above, though the TonelWriter changes are not a part of the commit. I'm having trouble pushing things using Git on Windows at the moment. Hopefully I'll get them sorted out by tomorrow. |
Here is the latest I have on this. Definitely not finished at this stage, but this should be enough for you to see weather I am on the right track or not. @bencoman this change makes use of the changes to There is a random merge commit in there. I find Iceberg far less clear to use compared to Git on the command line, and I ended up making a commit that removed the changes I wanted instead of adding them. I'll leave some comments on my thoughts in the code changes for you to see. Still more work to do but hopefully this should be closer. I think I have the major steps in place but let me know if I've left anything out. Other than handling iterations on solutions, I don't think I've left out anything else. |
cmd := 'exercism submit ' , filesToSubmit. | ||
result := PipeableOSProcess waitForCommand: cmd. | ||
result succeeded ifFalse: [ self error: 'Unable to submit solution, CLI reported ', result outputAndError printString ]. | ||
ExercismSubmit exercise: exerciseName. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've placed this here as it seems like the easiest way to plug in to what we already have. I don't know if we intend to keep using the ExercismManager
.
{ #category : #internal } | ||
ExercismSubmit >> buildRequest [ | ||
| solutionEntity solutionPart multiPartFormDataEntity solutionId | | ||
"Assumption: There is only one entity to make which is from the class with the same name as the exercise." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made an assumption as per the comment. Handling a single class with the same name as the exercise might be enough for minimum viable product. I assume we don't need to submit the test class. If needs be I don't think it is too much trouble to handle more than one class either as a part of this change or later on.
@macta @bencoman I hope you two are doing well. It's been a while and I had hoped either of you had managed to review this pull request. I've been looking at how different iterations of exercise submissions are handled as it is the only thing I'm not sure of for this solution at the moment. Looking around at the Rust and Ruby exercises on my computer didn't give me any clues other than the fact that it doesn't look like iteration tracking is done on the users computer. In any case I think we are finally getting close to having the whole user cycle of download, work, and submit complete. I'm keen to keep pushing this project along. Give me any feed back if you can. I'll keep making progress where possible. I might merge this soon anyway to keep things moving. |
Sorry, I’ll try and have a look tomorrow night. Managed to get my computer fixed in Sydney, so I can better look at code (using an iPhone is too hard to keep the context in my head) |
@samWson Sorry for the delay in reviewing this. I've started a new contract that is stretching my capabilities (rerouting a 66kV transmission line). Managed to find time this weekend to have a good crack at it and got it working. I pushed the above update. First I need to correct some of my own previous work... The CLI submit semantics are... So even if most exercises so far only need one class, I think in #buildRequest your assumption "There is only one entity to make which is from the class with the same name as the exercise" is too narrow. Our "classes" are more equivalent to "files" and an "exercise" relates more to our "packages". I've reworked it to allow submitting single & multiple classes as well as a whole exercise/package by name. I found... |
@@ -71,7 +71,7 @@ ExercismCommand class >> configureToken [ | |||
ExercismCommand class >> configureToken: your_CLI_token [ | |||
"Get your_CLI_token at https://exercism.io/my/settings" | |||
ApiToken := your_CLI_token. | |||
ApiPath := '/v1/solutions/latest'. | |||
ApiPath := '/v1/solutions'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed to strip "/latest" to work with SUBMIT.
exercismPackage ensureProperties at: tag put: solution. | ||
]. | ||
]. | ||
parser document do: [:def | def load]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solution data now stored in ExercismManager
packageNamed: 'Exercism' | ||
ifAbsent: [ self error: 'No exercises downloaded.' ]. | ||
classTag := exercismPackage classTagForClass: aClass. | ||
^ exercismPackage ensureProperties at: classTag. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solution data moved to ExercismManager.
exercismPackage ensureProperties | ||
detect: [ :solutionData | ((solutionData at: 'exercise') at: 'id') = exerciseString ] | ||
ifFound: [ :solutionData | ^ solutionData ] | ||
ifNone: [ self error: 'Exercise ''' , exerciseString , ''' not downloaded.']. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer required. Solution data moved to ExercismManager.
ExercismSubmit class >> exercise: exerciseId [ | ||
"Submit whole package-tag specified by exerciseId." | ||
"By default, don't submit TestCases with solution (if needed use #classes: direct)" | ||
|rootPackage packageTag solutionClasses| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samWson I changed your semantics here. exerciseId to represent a whole package-tag rather than a single class. Added #class:
for submitting single classes.
|
||
httpclient | ||
url: ApiPath , '/' , solutionId; | ||
entity: multiPartFormDataEntity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed '/latest' from ApiPath to make this work here.
|solutionData| | ||
ExercismDownload exercise: 'hello-world'. | ||
solutionData := ExercismSubmit solutionDataForClass: HelloWorld. | ||
self assert: ((solutionData at: 'exercise') at: 'id') equals: 'hello-world'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer required. Solution data moved to ExercismManager.
{ #category : #'*ExercismTools' } | ||
String >> kebabAsCamelCase [ | ||
"Answer a String that converts the CamelCase input to camel-case kebab output | ||
used by exercism" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, copy-paste error with the comment, should be...
used by exercism" | |
"Answer a new String converted from Exercism's kebab-case to CamelCase." |
@bencoman thanks for the help. I see in the commit message you say that Otherwise I think the only thing left to do is to incorporate the new submit code into the Nautilus menu when you right click on an exercise. |
On Tue, 23 Oct 2018 at 15:23, Samuel ***@***.***> wrote:
@bencoman <https://github.com/bencoman> thanks for the help. I see in the
commit message you say that ExercismSubmit is now working, so I take it
that you have tested this?
Yes, I tested in a fresh Pharo 6.1 image.
Note it wasn't a completely fresh download. A couple of months ago I
downloaded a new Pharo 6.1 image,
updated only Iceberg and archived that as my base for Exercism dev.
I'm having trouble submitting or downloading anything so I'm about to try
again with a clean image to be sure.
You don't mention whether you did ```ExercismCommand reset``` ?
Without that, ```ApiPath``` variable is not updated to the required value,
so both both downloading and submitting will be broken.
Otherwise I think the only thing left to do is to incorporate the new
submit code into the Nautilus menu when you right click on an exercise.
Once you observe it working, lets merge this PR. Then you can add the GUI
part if you like. I am caught up on other things for a few days.
|
@bencoman thanks. It works now. I'm happy to merge this thing at long last. I'll get started on new issue for adding the new submit code to the GUI part. |
@samWson sorry its been just too difficult to contribute while on the road. I did however try downloading the latest image a few days ago expecting to see it use the work you guys have been forging on with - and it still seemed to be using the OS shell stuff? Possibly user error - but master does have this stuff for both pull and submit right? I did see @bencoman 's improved token prompt but when submitting a solution it seemed to be using os shell. I wanted to confirm its all supposed to be in there, and I'll try again when I get a moment. |
I don't think the direct-submit functionality has made it through to the
GUI yet.
Try the (minimal) unit test.
|
@macta we are almost there. Replacing the original code at the GUI is the last bit for this issue. It doesn't look like much work at all, however that's when I ran into my latest problem with Tonel.
That's OK. I don't mind real life taking priority over this project any day. Have a good holiday. I'm happy to keep working on the code as I have time available, and I've always got the Pharo Discord available if I can't get help here :) |
I broadly understand what this thing needs to do now. I won't be able to spend anytime on this tomorrow so I'm placing this pull request here to make sure I'm on the right track. Hopefully I'll be able to get back to it this weekend.
This is definitely a work in progress. A few more helper methods will probably be made, plus getting rid of some hard coded values.
I used the Acronym exercise as an example, which means I could not test submitting because I have not downloaded the exercise metadata, which I can't do because I haven't unlocked the exercise ¯_(ツ)_/¯ .
I've left a few comments in the source code with my thoughts and a few problems I haven't yet solved. Let me know what you think.
I'm also irritates me that I don't have any tests of the public interface. For lack of a good mocking framework it will take a bit of work to mock Exercisms API, and the OS file system.