import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
private static void tryToRenameTaskFile(PsiFile file, String oldName) {
final PsiDirectory taskDir = file.getContainingDirectory();
- Course course = StudyTaskManager.getInstance(file.getProject()).getCourse();
+ final Project project = file.getProject();
+ Course course = StudyTaskManager.getInstance(project).getCourse();
if (course == null) {
return;
}
return;
}
ApplicationManager.getApplication().runWriteAction(() -> {
- VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, oldName);
+ VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, oldName);
if (patternFile != null) {
try {
patternFile.delete(CCRefactoringElementListenerProvider.class);
if (child == null) {
continue;
}
- Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
+ Document patternDocument = StudyUtils.getPatternDocument(project, entry.getValue(), name);
Document document = FileDocumentManager.getInstance().getDocument(child);
if (document == null || patternDocument == null) {
return;
}
private static void computeInitialState(Project project, PsiFile file, TaskFile taskFile, Document document) {
- Document patternDocument = StudyUtils.getPatternDocument(taskFile, file.getName());
+ Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, file.getName());
if (patternDocument == null) {
return;
}
return;
}
String name = file.getName();
- VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, name);
+ VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, name);
ApplicationManager.getApplication().runWriteAction(() -> {
if (patternFile != null) {
try {
<applicationConfigurable groupId="tools" instance="com.jetbrains.edu.learning.settings.StudyConfigurable"
id="com.jetbrains.edu.learning.settings.StudyConfigurable"
displayName="Education"/>
- <applicationService serviceInterface="com.jetbrains.edu.learning.stepic.StudySettings"
- serviceImplementation="com.jetbrains.edu.learning.stepic.StudySettings"/>
<toolWindow id="Task Description" anchor="right" factoryClass="com.jetbrains.edu.learning.ui.StudyToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
<toolWindow id="Course Progress" anchor="left" factoryClass="com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
+ <toolWindow id="Test Results" anchor="bottom" factoryClass="com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
<fileEditorProvider implementation="com.jetbrains.edu.learning.editor.StudyFileEditorProvider"/>
<treeStructureProvider implementation="com.jetbrains.edu.learning.projectView.StudyTreeStructureProvider"/>
<highlightErrorFilter implementation="com.jetbrains.edu.learning.editor.StudyHighlightErrorFilter"/>
Task task = getTask(file);
setTaskText(task, file.getParent());
}
+ toolWindow.setBottomComponent(null);
}
@Nullable
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.*;
import com.jetbrains.edu.learning.oldCourseFormat.OldCourse;
+import com.jetbrains.edu.learning.stepic.StepicUser;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
private Course myCourse;
private OldCourse myOldCourse;
public int VERSION = 2;
+ public StepicUser myUser;
public Map<AnswerPlaceholder, StudyStatus> myStudyStatusMap = new HashMap<>();
public Map<TaskFile, StudyStatus> myTaskStatusMap = new HashMap<>();
public Map<Task, List<UserTest>> myUserTests = new HashMap<>();
@NotNull
public List<UserTest> getUserTests(@NotNull final Task task) {
final List<UserTest> userTests = myUserTests.get(task);
- return userTests != null ? userTests : Collections.<UserTest>emptyList();
+ return userTests != null ? userTests : Collections.emptyList();
}
public void removeUserTest(@NotNull final Task task, @NotNull final UserTest userTest) {
myTaskStatusMap = taskManager.myTaskStatusMap;
myStudyStatusMap = taskManager.myStudyStatusMap;
myShouldUseJavaFx = taskManager.myShouldUseJavaFx;
+ myUser = taskManager.getUser();
}
}
final Element oldCourseElement = state.getChild(COURSE_ELEMENT);
public void setTurnEditingMode(boolean turnEditingMode) {
myTurnEditingMode = turnEditingMode;
}
+
+ public String getLogin() {
+ if (myUser != null) {
+ return myUser.getEmail();
+ }
+ return "";
+ }
+
+ public String getPassword() {
+ if (myUser != null) {
+ return myUser.getPassword();
+ }
+ return "";
+ }
+
+ public void setLogin(String login) {
+ if (myUser != null) {
+ myUser.setEmail(login);
+ }
+ }
+
+ public void setPassword(String password) {
+ if (myUser != null) {
+ myUser.setPassword(password);
+ }
+ }
+
+ public StepicUser getUser() {
+ return myUser;
+ }
+
+ public void setUser(StepicUser user) {
+ myUser = user;
+ }
}
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
+import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.content.Content;
+import com.intellij.util.TimeoutUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.MarkdownUtil;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.*;
+import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
public class StudyUtils {
private StudyUtils() {
@Nullable
- public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) {
+ public static VirtualFile getPatternFile(@NotNull Project project, @NotNull TaskFile taskFile, String name) {
Task task = taskFile.getTask();
String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex());
String taskDir = EduNames.TASK + String.valueOf(task.getIndex());
Course course = task.getLesson().getCourse();
- File resourceFile = new File(course.getCourseDirectory());
+ File resourceFile = getCourseDirectory(project, course);
if (!resourceFile.exists()) {
return null;
}
}
@Nullable
- public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) {
- VirtualFile patternFile = getPatternFile(taskFile, name);
+ public static Document getPatternDocument(@NotNull Project project, @NotNull final TaskFile taskFile, String name) {
+ VirtualFile patternFile = getPatternFile(project, taskFile, name);
if (patternFile == null) {
return null;
}
return null;
}
-
+ @Nullable
public static String getTaskText(@NotNull final Project project) {
TaskFile taskFile = getSelectedTaskFile(project);
if (taskFile == null) {
}
return taskFile;
}
+
+ @Nullable
+ public static Task getCurrentTask(@NotNull final Project project) {
+ VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
+ TaskFile taskFile = null;
+ for (VirtualFile file : files) {
+ taskFile = getTaskFile(project, file);
+ if (taskFile != null) {
+ break;
+ }
+ }
+ if (taskFile != null) {
+ return taskFile.getTask();
+ }
+ return null;
+ }
public static void updateStudyToolWindow(Project project) {
final StudyToolWindow studyToolWindow = getStudyToolWindow(project);
if (studyToolWindow != null) {
String taskText = getTaskText(project);
- studyToolWindow.setTaskText(taskText, null, project);
+ if (taskText != null) {
+ studyToolWindow.setTaskText(taskText, null, project);
+ }
+ else {
+ LOG.warn("Task text is null");
+ }
}
}
return StudyTaskManager.getInstance(project).getCourse() != null;
}
+ @Nullable
+ public static Project getStudyProject() {
+ Project studyProject = null;
+ Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
+ for (Project project : openProjects) {
+ if (StudyTaskManager.getInstance(project).getCourse() != null) {
+ studyProject = project;
+ }
+ }
+ return studyProject;
+ }
+
+ @NotNull
+ public static File getCourseDirectory(@NotNull Project project, Course course) {
+ final File courseDirectory;
+ if (course.isAdaptive()) {
+ courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR,
+ StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName()
+ + "_" + StudyTaskManager.getInstance(project).getUser().getEmail());
+ }
+ else {
+ courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName());
+ }
+ return courseDirectory;
+ }
+
public static boolean hasJavaFx() {
try {
Class.forName("javafx.application.Platform");
return null;
}
+ // supposd to be called under progress
+ @Nullable
+ public static <T> T execCancelable(@NotNull final Callable<T> callable) {
+ final Future<T> future = ApplicationManager.getApplication().executeOnPooledThread(callable);
+
+ while (!future.isCancelled() && !future.isDone()) {
+ ProgressManager.checkCanceled();
+ TimeoutUtil.sleep(500);
+ }
+ T result = null;
+ try {
+ result = future.get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ LOG.warn(e.getMessage());
+ }
+ return result;
+ }
+
+ @Nullable
+ public static Task getTaskFromSelectedEditor(Project project) {
+ final StudyEditor editor = getSelectedStudyEditor(project);
+ Task task = null;
+ if (editor != null) {
+ final TaskFile file = editor.getTaskFile();
+ task = file.getTask();
+ }
+ return task;
+ }
+
private static String convertToHtml(@NotNull final String content) {
ArrayList<String> lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n"));
MarkdownUtil.replaceHeaders(lines);
}
StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project);
final StudyState studyState = new StudyState(studyEditor);
- Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
+ Document patternDocument = StudyUtils.getPatternDocument(project, answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
if (patternDocument == null) {
return;
}
@NotNull final Project project,
TaskFile taskFile,
String name) {
- if (!resetDocument(document, taskFile, name)) {
+ if (!resetDocument(project, document, taskFile, name)) {
return false;
}
taskFile.getTask().setStatus(StudyStatus.Unchecked);
}
- private static boolean resetDocument(@NotNull final Document document,
+ private static boolean resetDocument(@NotNull final Project project,
+ @NotNull final Document document,
@NotNull final TaskFile taskFile,
String fileName) {
- final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName);
+ final Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, fileName);
if (patternDocument == null) {
return false;
}
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
import com.jetbrains.edu.learning.core.EduUtils;
+import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
-import com.jetbrains.edu.learning.stepic.StudySettings;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable {
@Override
public void run(@NotNull ProgressIndicator indicator) {
+ final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+ if (course != null && course.isAdaptive()) {
+ checkForAdaptiveCourse(indicator);
+ }
+ else {
+ checkForEduCourse(indicator);
+ }
+ }
+
+ private void checkForEduCourse(@NotNull ProgressIndicator indicator) {
+ final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator);
+
+ if (testsOutput != null) {
+ if (testsOutput.isSuccess()) {
+ onTaskSolved(testsOutput.getMessage());
+ }
+ else {
+ onTaskFailed(testsOutput.getMessage());
+ }
+ runAfterTaskCheckedActions();
+ postAttemptToStepic(testsOutput);
+ }
+ }
+
+ @Nullable
+ private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) {
final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine);
final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator);
if (indicator.isCanceled()) {
ApplicationManager.getApplication().invokeLater(
() -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject));
- return;
}
-
- final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output);
- String stderr = output.getStderr();
- if (!stderr.isEmpty()) {
- ApplicationManager.getApplication().invokeLater(() ->
- StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
- MessageType.WARNING.getPopupBackground(),
- myProject));
- //log error output of tests
- LOG.info("#educational " + stderr);
- return;
+ final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+ if (course != null) {
+ final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive());
+ String stderr = output.getStderr();
+ if (!stderr.isEmpty()) {
+ ApplicationManager.getApplication().invokeLater(() ->
+ StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
+ MessageType.WARNING.getPopupBackground(),
+ myProject));
+ //log error output of tests
+ LOG.info("#educational " + stderr);
+ return null;
+ }
+ return testsOutput;
}
+ return null;
+ }
- postAttemptToStepic(testsOutput);
-
-
- if (testsOutput.isSuccess()) {
- onTaskSolved(testsOutput);
- }
- else {
- onTaskFailed(testsOutput);
+ private void checkForAdaptiveCourse(ProgressIndicator indicator) {
+ final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator);
+ if (testOutput != null) {
+ if (testOutput.isSuccess()) {
+ final Pair<Boolean, String> pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask);
+ if (pair != null && !(!pair.getFirst() && pair.getSecond().isEmpty())) {
+ if (pair.getFirst()) {
+ onTaskSolved("Congratulations! Remote tests passed.");
+ if (myStatusBeforeCheck != StudyStatus.Solved) {
+ EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, 2);
+ }
+ }
+ else {
+ final String checkMessage = pair.getSecond();
+ onTaskFailed(checkMessage);
+ }
+ runAfterTaskCheckedActions();
+ }
+ else {
+ ApplicationManager.getApplication().invokeLater(() -> StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
+ MessageType.WARNING
+ .getPopupBackground(),
+ myProject));
+ }
+ }
+ else {
+ onTaskFailed(testOutput.getMessage());
+ }
}
- runAfterTaskSolvedActions();
}
- protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
+ protected void onTaskFailed(String message) {
myTaskManger.setStatus(myTask, StudyStatus.Failed);
- ApplicationManager.getApplication().invokeLater(
- () -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), myProject));
+ final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+
+ if (course != null) {
+ if (course.isAdaptive()) {
+ ApplicationManager.getApplication().invokeLater(
+ () -> {
+ StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), myProject);
+ StudyCheckUtils.showTestResultsToolWindow(myProject, message, false);
+ });
+ }
+ else {
+ ApplicationManager.getApplication()
+ .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject));
+ }
+ }
}
- protected void onTaskSolved(StudyTestsOutputParser.TestsOutput testsOutput) {
+ protected void onTaskSolved(String message) {
myTaskManger.setStatus(myTask, StudyStatus.Solved);
- ApplicationManager.getApplication().invokeLater(
- () -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.INFO.getPopupBackground(), myProject));
+ final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+
+ if (course != null) {
+ if (course.isAdaptive()) {
+ ApplicationManager.getApplication().invokeLater(
+ () -> {
+ StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), myProject);
+ StudyCheckUtils.showTestResultsToolWindow(myProject, message, true);
+ });
+ }
+ else {
+ ApplicationManager.getApplication()
+ .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject));
+ }
+ }
}
- private void runAfterTaskSolvedActions() {
+ private void runAfterTaskCheckedActions() {
StudyPluginConfigurator configurator = StudyUtils.getConfigurator(myProject);
if (configurator != null) {
StudyAfterCheckAction[] checkActions = configurator.getAfterCheckActions();
if (checkActions != null) {
- for (StudyAfterCheckAction action: checkActions) {
+ for (StudyAfterCheckAction action : checkActions) {
action.run(myProject, myTask, myStatusBeforeCheck);
}
}
}
}
- protected void postAttemptToStepic(StudyTestsOutputParser.TestsOutput testsOutput) {
- final StudySettings studySettings = StudySettings.getInstance();
+ protected void postAttemptToStepic(@NotNull StudyTestsOutputParser.TestsOutput testsOutput) {
+ final StudyTaskManager studySettings = StudyTaskManager.getInstance(myProject);
final String login = studySettings.getLogin();
final String password = StringUtil.isEmptyOrSpaces(login) ? "" : studySettings.getPassword();
EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), login, password);
package com.jetbrains.edu.learning.checker;
+import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.wm.IdeFocusManager;
-import com.intellij.openapi.wm.IdeFrame;
-import com.intellij.openapi.wm.WindowManager;
+import com.intellij.openapi.wm.*;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.openapi.wm.ex.WindowManagerEx;
+import com.intellij.ui.content.Content;
+import com.jetbrains.edu.learning.StudyState;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduDocumentListener;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.learning.StudyState;
-import com.jetbrains.edu.learning.StudyTaskManager;
-import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
+import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory;
+import com.jetbrains.python.console.PythonConsoleView;
import org.jetbrains.annotations.NotNull;
+import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.util.List;
}
public static void navigateToFailedPlaceholder(@NotNull final StudyState studyState,
- @NotNull final Task task,
- @NotNull final VirtualFile taskDir,
- @NotNull final Project project) {
+ @NotNull final Task task,
+ @NotNull final VirtualFile taskDir,
+ @NotNull final Project project) {
TaskFile selectedTaskFile = studyState.getTaskFile();
Editor editor = studyState.getEditor();
TaskFile taskFileToNavigate = selectedTaskFile;
public static void runSmartTestProcess(@NotNull final VirtualFile taskDir,
- @NotNull final StudyTestRunner testRunner,
- final String taskFileName,
- @NotNull final TaskFile taskFile,
- @NotNull final Project project) {
+ @NotNull final StudyTestRunner testRunner,
+ final String taskFileName,
+ @NotNull final TaskFile taskFile,
+ @NotNull final Project project) {
final TaskFile answerTaskFile = new TaskFile();
answerTaskFile.name = taskFileName;
final VirtualFile virtualFile = taskDir.findChild(taskFileName);
}
-
private static VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir,
- @NotNull final VirtualFile file,
- @NotNull final TaskFile source,
- @NotNull final TaskFile target) {
+ @NotNull final VirtualFile file,
+ @NotNull final TaskFile source,
+ @NotNull final TaskFile target) {
VirtualFile copy = null;
try {
EduUtils.flushWindows(taskFile, virtualFile, true);
}
}
+
+ public static void showTestResultsToolWindow(@NotNull final Project project, @NotNull final String message, boolean solved) {
+ final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
+ ToolWindow window = toolWindowManager.getToolWindow("Test Results");
+ if (window == null) {
+ toolWindowManager.registerToolWindow("Test Results", true, ToolWindowAnchor.BOTTOM);
+ window = toolWindowManager.getToolWindow("Test Results");
+ new StudyTestResultsToolWindowFactory().createToolWindowContent(project, window);
+ }
+
+ final Content[] contents = window.getContentManager().getContents();
+ for (Content content : contents) {
+ final JComponent component = content.getComponent();
+ if (component instanceof PythonConsoleView) {
+ ((PythonConsoleView)component).clear();
+ if (!solved) {
+ ((PythonConsoleView)component).print(message, ConsoleViewContentType.ERROR_OUTPUT);
+ }
+ else {
+ ((PythonConsoleView)component).print(message, ConsoleViewContentType.NORMAL_OUTPUT);
+ }
+ window.setAvailable(true, () -> {});
+ window.show(() -> {});
+ return;
+ }
+ }
+ }
}
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
+import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyTaskManager;
Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath());
final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess, null, windowCopy.getPath());
final ProcessOutput output = handler.runProcess();
- boolean res = StudyTestsOutputParser.getTestsOutput(output).isSuccess();
- StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
- StudyUtils.deleteFile(windowCopy);
- if (fileWindows != null) {
- StudyUtils.deleteFile(fileWindows);
- }
- if (!resourceFile.delete()) {
- LOG.error("failed to delete", resourceFile.getPath());
+ final Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course != null) {
+ boolean res = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive()).isSuccess();
+ StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
+ StudyUtils.deleteFile(windowCopy);
+ if (fileWindows != null) {
+ StudyUtils.deleteFile(fileWindows);
+ }
+ if (!resourceFile.delete()) {
+ LOG.error("failed to delete", resourceFile.getPath());
+ }
}
}
}
import com.intellij.execution.process.ProcessOutput;
import org.jetbrains.annotations.NotNull;
+import java.util.List;
+
public class StudyTestsOutputParser {
- private static final String ourStudyPrefix = "#educational_plugin";
+ private static final String STUDY_PREFIX = "#educational_plugin";
public static final String TEST_OK = "test OK";
private static final String TEST_FAILED = "FAILED + ";
private static final String CONGRATS_MESSAGE = "CONGRATS_MESSAGE ";
}
@NotNull
- public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) {
+ public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput, final boolean isAdaptive) {
String congratulations = CONGRATULATIONS;
- for (String line : processOutput.getStdoutLines()) {
- if (line.startsWith(ourStudyPrefix)) {
+ final List<String> lines = processOutput.getStdoutLines();
+ for (int i = 0; i < lines.size(); i++) {
+ final String line = lines.get(i);
+ if (line.startsWith(STUDY_PREFIX)) {
if (line.contains(TEST_OK)) {
continue;
}
}
if (line.contains(TEST_FAILED)) {
- return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
+ if (!isAdaptive) {
+ return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
+ }
+ else {
+ final StringBuilder builder = new StringBuilder(line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()) + "\n");
+ for (int j = i + 1; j < lines.size(); j++) {
+ final String failedTextLine = lines.get(j);
+ if (!failedTextLine.contains(STUDY_PREFIX) || !failedTextLine.contains(CONGRATS_MESSAGE)) {
+ builder.append(failedTextLine);
+ builder.append("\n");
+ }
+ else {
+ break;
+ }
+ }
+ return new TestsOutput(false, builder.toString());
+ }
}
}
}
import com.intellij.util.Function;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.stepic.CourseInfo;
+import com.jetbrains.edu.learning.stepic.StepicUser;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class Course {
- @Expose
- private List<Lesson> lessons = new ArrayList<Lesson>();
-
+ @Expose private List<Lesson> lessons = new ArrayList<Lesson>();
+ @Expose private List<StepicUser> authors = new ArrayList<StepicUser>();
@Expose private String description;
@Expose private String name;
- private String myCourseDirectory = "";
- @Expose private List<CourseInfo.Author> authors = new ArrayList<CourseInfo.Author>();
- private boolean myUpToDate;
-
- @Expose @SerializedName("language")
- private String myLanguage = "Python";
+ @Expose private String myCourseDirectory = "";
+ @Expose private int id;
+ @Expose private boolean myUpToDate;
+ @Expose private boolean isAdaptive;
+ @Expose @SerializedName("language") private String myLanguage = "Python";
- //this field is used to distinguish ordinary and CheckIO projects
+ //this field is used to distinguish ordinary and CheckIO projects,
//"PyCharm" is used here for historical reasons
- private String courseType = EduNames.PYCHARM;
-
- //this field is used to distinguish study and course creator modes
- private String courseMode = EduNames.STUDY;
+ @Expose private String courseType = EduNames.PYCHARM;
+ @Expose private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
/**
* Initializes state of course
}
@NotNull
- public List<CourseInfo.Author> getAuthors() {
+ public List<StepicUser> getAuthors() {
return authors;
}
- public static String getAuthorsString(@NotNull List<CourseInfo.Author> authors) {
+ public static String getAuthorsString(@NotNull List<StepicUser> authors) {
return StringUtil.join(authors, author -> author.getName(), ", ");
}
public void setAuthors(String[] authors) {
- this.authors = new ArrayList<CourseInfo.Author>();
+ this.authors = new ArrayList<StepicUser>();
for (String name : authors) {
final List<String> pair = StringUtil.split(name, " ");
if (!pair.isEmpty())
- this.authors.add(new CourseInfo.Author(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
+ this.authors.add(new StepicUser(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
}
}
myLanguage = language;
}
- public void setAuthors(List<CourseInfo.Author> authors) {
+ public void setAuthors(List<StepicUser> authors) {
this.authors = authors;
}
this.courseType = courseType;
}
+ public boolean isAdaptive() {
+ return isAdaptive;
+ }
+
+ public void setAdaptive(boolean adaptive) {
+ isAdaptive = adaptive;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
public String getCourseMode() {
return courseMode;
}
import java.util.List;
public class Lesson implements StudyItem {
- public int id;
- @Transient
- public List<Integer> steps;
- @Transient
- public List<String> tags;
- @Transient
- Boolean is_public;
- @Expose
- @SerializedName("title")
- private String name;
- @Expose
- @SerializedName("task_list")
- public List<Task> taskList = new ArrayList<Task>();
-
- @Transient
- private Course myCourse = null;
-
- // index is visible to user number of lesson from 1 to lesson number
- private int myIndex = -1;
+ @Expose public int id;
+ @Expose private int myIndex = -1; // index is visible to user number of lesson from 1 to lesson number
+ @Expose @Transient public List<Integer> steps;
+ @Expose @SerializedName("title") private String name;
+ @Expose @SerializedName("task_list") public List<Task> taskList = new ArrayList<Task>();
+ @Transient public List<String> tags;
+ @Transient private Course myCourse = null;
public void initLesson(final Course course, boolean isRestarted) {
setCourse(course);
* Implementation of task which contains task files, tests, input file for tests
*/
public class Task implements StudyItem {
- @Expose
- private String name;
-
- // index is visible to user number of task from 1 to task number
- private int myIndex;
- private StudyStatus myStatus = StudyStatus.Uninitialized;
-
- private int myStepicId;
-
- @Expose
- @SerializedName("task_files")
- public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
-
- private String text;
- private Map<String, String> testsText = new HashMap<String, String>();
-
+ @Expose private String name;
+ @Expose private String text;
+ @Expose private int myStepicId;
+ @Expose private int myIndex; // index is visible to user number of task from 1 to task number
+ @Expose @SerializedName("task_files") public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
+ @Expose private Map<String, String> testsText = new HashMap<String, String>();
+ @Expose private StudyStatus myStatus = StudyStatus.Uninitialized;
@Transient private Lesson myLesson;
public Task() {}
*/
public class TaskFile {
- @SerializedName("placeholders")
- @Expose
- private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
- private int myIndex = -1;
-
- @Expose
- public String name;
- @Expose
- public String text;
+ @Expose public String name;
+ @Expose public String text;
+ @Expose private int myIndex = -1;
+ @Expose private boolean myUserCreated = false;
+ @Expose private boolean myTrackChanges = true;
+ @Expose private boolean myHighlightErrors = false;
+ @Expose @SerializedName("placeholders") private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
@Transient private Task myTask;
- private boolean myUserCreated = false;
- private boolean myTrackChanges = true;
- private boolean myHighlightErrors = false;
public void initTaskFile(final Task task, boolean isRestarted) {
setTask(task);
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.learning.StudyTaskManager;
-import com.jetbrains.edu.learning.StudyUtils;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot,
@NotNull final Project project) {
- try {
- final List<Lesson> lessons = course.getLessons();
- for (int i = 1; i <= lessons.size(); i++) {
- Lesson lesson = lessons.get(i - 1);
- lesson.setIndex(i);
- createLesson(lesson, baseDir, resourceRoot, project);
- }
- baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
- File[] files = resourceRoot.listFiles(
- (dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
- for (File file : files) {
- File dir = new File(baseDir.getPath(), file.getName());
- if (file.isDirectory()) {
- FileUtil.copyDir(file, dir);
- continue;
- }
-
- FileUtil.copy(file, dir);
-
- }
- }
- catch (IOException e) {
- LOG.error(e);
- }
+ try {
+ final List<Lesson> lessons = course.getLessons();
+ for (int i = 1; i <= lessons.size(); i++) {
+ Lesson lesson = lessons.get(i - 1);
+ lesson.setIndex(i);
+ createLesson(lesson, baseDir, resourceRoot, project);
+ }
+ baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
+ File[] files = resourceRoot.listFiles(
+ (dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
+ for (File file : files) {
+ File dir = new File(baseDir.getPath(), file.getName());
+ if (file.isDirectory()) {
+ FileUtil.copyDir(file, dir);
+ continue;
+ }
+
+ FileUtil.copy(file, dir);
+
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
}
}
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbModePermission;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.containers.ContainerUtil;
+import com.jetbrains.edu.learning.StudyProjectComponent;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.learning.StudyProjectComponent;
-import com.jetbrains.edu.learning.StudyTaskManager;
-import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
+import com.jetbrains.edu.learning.stepic.StepicUser;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+
+import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
public class StudyProjectGenerator {
+ public static final String AUTHOR_ATTRIBUTE = "authors";
+ public static final String LANGUAGE_ATTRIBUTE = "language";
+ public static final String ADAPTIVE_COURSE_PREFIX = "__AdaptivePyCharmPython__";
+ public static final File OUR_COURSES_DIR = new File(PathManager.getConfigPath(), "courses");
private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName());
- private final List<SettingsListener> myListeners = ContainerUtil.newArrayList();
- protected static final File ourCoursesDir = new File(PathManager.getConfigPath(), "courses");
+ private static final String COURSE_NAME_ATTRIBUTE = "name";
+ private static final String COURSE_DESCRIPTION = "description";
private static final String CACHE_NAME = "courseNames.txt";
+ private final List<SettingsListener> myListeners = ContainerUtil.newArrayList();
+ public StepicUser myUser;
private List<CourseInfo> myCourses = new ArrayList<>();
protected CourseInfo mySelectedCourseInfo;
- private static final String COURSE_NAME_ATTRIBUTE = "name";
- private static final String COURSE_DESCRIPTION = "description";
- public static final String AUTHOR_ATTRIBUTE = "authors";
- public static final String LANGUAGE_ATTRIBUTE = "language";
public void setCourses(List<CourseInfo> courses) {
myCourses = courses;
}
public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) {
- final Course course = getCourse();
+ StudyTaskManager.getInstance(project).setUser(myUser);
+ final Course course = getCourse(project);
if (course == null) {
LOG.warn("Course is null");
return;
}
+ final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
StudyTaskManager.getInstance(project).setCourse(course);
ApplicationManager.getApplication().invokeLater(
() -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND,
() -> ApplicationManager.getApplication().runWriteAction(() -> {
- course.initCourse(false);
- final File courseDirectory = new File(ourCoursesDir, course.getName());
StudyGenerator.createCourse(course, baseDir, courseDirectory, project);
- course.setCourseDirectory(new File(ourCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath());
+ course.setCourseDirectory(courseDirectory.getAbsolutePath());
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
StudyProjectComponent.getInstance(project).registerStudyToolWindow(course);
openFirstTask(course, project);
})));
}
- protected Course getCourse() {
+ @Nullable
+ protected Course getCourse(@NotNull final Project project) {
+ final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
+ if (courseFile.exists()) {
+ return readCourseFromCache(courseFile, false);
+ }
+ else if (myUser != null) {
+ final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX +
+ mySelectedCourseInfo.getName() + "_" +
+ myUser.getEmail()), EduNames.COURSE_META_FILE);
+ if (adaptiveCourseFile.exists()) {
+ return readCourseFromCache(adaptiveCourseFile, true);
+ }
+ }
+ final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
+ if (course != null) {
+ flushCourse(project, course);
+ course.initCourse(false);
+ }
+ return course;
+ }
+
+ @Nullable
+ private static Course readCourseFromCache(@NotNull File courseFile, boolean isAdaptive) {
Reader reader = null;
try {
- final File courseFile = new File(new File(ourCoursesDir, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
- if (courseFile.exists()) {
- reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
- Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
- final Course course = gson.fromJson(reader, Course.class);
- course.initCourse(false);
- return course;
- }
+ reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
+ Gson gson = new GsonBuilder().create();
+ final Course course = gson.fromJson(reader, Course.class);
+ course.initCourse(isAdaptive);
+ return course;
}
- catch (FileNotFoundException | UnsupportedEncodingException e) {
- LOG.error(e);
+ catch (UnsupportedEncodingException e) {
+ LOG.warn(e.getMessage());
+ }
+ catch (FileNotFoundException e) {
+ LOG.warn(e.getMessage());
}
finally {
StudyUtils.closeSilently(reader);
}
- final Course course = EduStepicConnector.getCourse(mySelectedCourseInfo);
- if (course != null) {
- flushCourse(course);
- }
- return course;
+ return null;
}
public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) {
final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile);
ProjectView.getInstance(project).select(file, activeVirtualFile, true);
FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
- } else {
+ }
+ else {
String first = StudyUtils.getFirst(taskFiles.keySet());
if (first != null) {
NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first);
}
}
- public void flushCourse(@NotNull final Course course) {
- final File courseDirectory = new File(ourCoursesDir, course.getName());
+ public static void flushCourse(@NotNull final Project project, @NotNull final Course course) {
+ final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
FileUtil.createDirectory(courseDirectory);
flushCourseJson(course, courseDirectory);
else {
FileUtil.writeToFile(file, taskFile.text);
}
-
}
catch (IOException e) {
LOG.error("ERROR copying file " + name);
final Map<String, String> testsText = task.getTestsText();
for (Map.Entry<String, String> entry : testsText.entrySet()) {
final File testsFile = new File(taskDirectory, entry.getKey());
+ if (testsFile.exists()) {
+ FileUtil.delete(testsFile);
+ }
FileUtil.createIfDoesntExist(testsFile);
try {
- FileUtil.writeToFile(testsFile, entry.getValue());
+ FileUtil.writeToFile(testsFile, entry.getValue());
}
catch (IOException e) {
LOG.error("ERROR copying tests file");
}
}
- private static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
- final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
+ final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
final String json = gson.toJson(course);
final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE);
final FileOutputStream fileOutputStream;
*/
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
public static void flushCache(List<CourseInfo> courses) {
- File cacheFile = new File(ourCoursesDir, CACHE_NAME);
+ File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
PrintWriter writer = null;
try {
if (!createCacheFile(cacheFile)) return;
}
private static boolean createCacheFile(File cacheFile) throws IOException {
- if (!ourCoursesDir.exists()) {
- final boolean created = ourCoursesDir.mkdirs();
+ if (!OUR_COURSES_DIR.exists()) {
+ final boolean created = OUR_COURSES_DIR.mkdirs();
if (!created) {
LOG.error("Cannot flush courses cache. Can't create courses directory");
return false;
return true;
}
- public List<CourseInfo> getCourses(boolean force) {
- if (ourCoursesDir.exists()) {
+ // Supposed to be called under progress
+ public List<CourseInfo> getCoursesAsynchronouslyIfNeeded(boolean force) {
+ if (OUR_COURSES_DIR.exists()) {
myCourses = getCoursesFromCache();
}
if (force || myCourses.isEmpty()) {
- myCourses = EduStepicConnector.getCourses();
+ myCourses = execCancelable(EduStepicConnector::getCourses);
flushCache(myCourses);
}
if (myCourses.isEmpty()) {
return myCourses;
}
+ public List<CourseInfo> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
+ try {
+ return ProgressManager.getInstance()
+ .runProcessWithProgressSynchronously(new ThrowableComputable<List<CourseInfo>, RuntimeException>() {
+ @Override
+ public List<CourseInfo> compute() throws RuntimeException {
+ ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
+ return getCoursesAsynchronouslyIfNeeded(force);
+ }
+ }, progressTitle, true, project);
+ }
+ catch (RuntimeException e) {
+ return Collections.singletonList(CourseInfo.INVALID_COURSE);
+ }
+ }
+
public void addSettingsStateListener(@NotNull SettingsListener listener) {
myListeners.add(listener);
}
}
public static List<CourseInfo> getBundledIntro() {
- final File introCourse = new File(ourCoursesDir, "Introduction to Python");
+ final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python");
if (introCourse.exists()) {
final CourseInfo courseInfo = getCourseInfo(introCourse);
public static List<CourseInfo> getCoursesFromCache() {
List<CourseInfo> courses = new ArrayList<>();
- final File cacheFile = new File(ourCoursesDir, CACHE_NAME);
+ final File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
if (!cacheFile.exists()) {
return courses;
}
while ((line = reader.readLine()) != null) {
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final CourseInfo courseInfo = gson.fromJson(line, CourseInfo.class);
- courses.add(courseInfo);
+ if (!courseInfo.isAdaptive()) {
+ courses.add(courseInfo);
+ }
}
}
catch (IOException | JsonSyntaxException e) {
finally {
StudyUtils.closeSilently(reader);
}
- } finally {
+ }
+ finally {
StudyUtils.closeSilently(inputStream);
}
}
}
return courses;
}
+
/**
* Adds course from zip archive to courses
*
try {
String fileName = file.getName();
String unzippedName = fileName.substring(0, fileName.indexOf("."));
- File courseDir = new File(ourCoursesDir, unzippedName);
+ File courseDir = new File(OUR_COURSES_DIR, unzippedName);
ZipUtil.unzip(null, courseDir, file, null, null, true);
CourseInfo courseName = addCourse(myCourses, courseDir);
flushCache(myCourses);
if (courseName != null && !courseName.getName().equals(unzippedName)) {
- courseDir.renameTo(new File(ourCoursesDir, courseName.getName()));
+ //noinspection ResultOfMethodCallIgnored
+ courseDir.renameTo(new File(OUR_COURSES_DIR, courseName.getName()));
+ //noinspection ResultOfMethodCallIgnored
courseDir.delete();
}
return courseName;
/**
* Adds course to courses specified in params
*
- *
* @param courses
* @param courseDir must be directory containing course file
* @return added course name or null if course is invalid
}
return null;
}
+
/**
* Parses course json meta file and finds course name
*
courseInfo.setName(courseName);
courseInfo.setDescription(courseDescription);
courseInfo.setType("pycharm " + language);
- final ArrayList<CourseInfo.Author> authors = new ArrayList<>();
+ final ArrayList<StepicUser> authors = new ArrayList<>();
for (JsonElement author : courseAuthors) {
final JsonObject authorAsJsonObject = author.getAsJsonObject();
- authors.add(new CourseInfo.Author(authorAsJsonObject.get("first_name").getAsString(), authorAsJsonObject.get("last_name").getAsString()));
+ final StepicUser stepicUser = new StepicUser();
+ stepicUser.setFirstName(authorAsJsonObject.get("first_name").getAsString());
+ stepicUser.setLastName(authorAsJsonObject.get("last_name").getAsString());
+ authors.add(stepicUser);
}
courseInfo.setAuthors(authors);
}
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduDocumentListener;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.learning.StudyUtils;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
private final TaskFile myTaskFile;
private static final Map<Document, EduDocumentListener> myDocumentListeners = new HashMap<Document, EduDocumentListener>();
+ public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) {
+ super(project, file, TextEditorProvider.getInstance());
+ myTaskFile = StudyUtils.getTaskFile(project, file);
+ }
+
public TaskFile getTaskFile() {
return myTaskFile;
}
myDocumentListeners.put(document, listener);
}
- public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) {
- super(project, file, TextEditorProvider.getInstance());
- myTaskFile = StudyUtils.getTaskFile(project, file);
- }
-
public static void removeListener(Document document) {
final EduDocumentListener listener = myDocumentListeners.get(document);
if (listener != null) {
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
-class StudyFileEditorProvider implements FileEditorProvider, DumbAware {
- static final private String EDITOR_TYPE_ID = "StudyEditor";
- final private FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance();
+public class StudyFileEditorProvider implements FileEditorProvider, DumbAware {
+ public static final String EDITOR_TYPE_ID = "StudyEditor";
+ private final FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance();
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
import com.google.gson.annotations.SerializedName;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
* and when project is being created
*/
public class CourseInfo {
- boolean is_public;
- public List<Integer> sections;
- @SerializedName("title")
- private String myName;
- @SerializedName("summary")
- private String myDescription;
- @SerializedName("course_format")
- //course type in format "pycharm <language>"
- private String myType = "pycharm Python";
-
- List<Integer> instructors = new ArrayList<Integer>();
+ public static CourseInfo INVALID_COURSE = new CourseInfo();
- List<Author> myAuthors = new ArrayList<Author>();
+ @SerializedName("title") private String myName;
int id;
+ boolean isAdaptive;
+ boolean isPublic;
+ List<Integer> sections;
+ List<Integer> instructors = new ArrayList<Integer>();
- public static CourseInfo INVALID_COURSE = new CourseInfo();
+ List<StepicUser> myAuthors = new ArrayList<>();
+ @SerializedName("summary") private String myDescription;
+ @SerializedName("course_format") private String myType = "pycharm Python"; //course type in format "pycharm <language>"
+ @Nullable private String username;
public String getName() {
return myName;
}
@NotNull
- public List<Author> getAuthors() {
+ public List<StepicUser> getAuthors() {
return myAuthors;
}
@Override
public String toString() {
- return myName;
+ return getName();
}
@Override
if (o == null || getClass() != o.getClass()) return false;
CourseInfo that = (CourseInfo)o;
if (that.getName() == null || that.getDescription() == null) return false;
- return that.getName().equals(myName)
+ return that.getName().equals(getName())
&& that.getDescription().equals(myDescription);
}
@Override
public int hashCode() {
- int result = myName != null ? myName.hashCode() : 0;
+ int result = getName() != null ? getName().hashCode() : 0;
result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
return result;
}
+ @Nullable
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(@Nullable String username) {
+ this.username = username;
+ }
+
public static class Author {
int id;
String first_name = "";
myName = name;
}
- public void setAuthors(List<Author> authors) {
+ public void setAuthors(List<StepicUser> authors) {
myAuthors = authors;
- for (Author author : authors) {
+ for (StepicUser author : authors) {
if (author.id > 0) {
instructors.add(author.id);
}
}
}
- public void addAuthor(Author author) {
+ public void addAuthor(StepicUser author) {
if (myAuthors == null) {
- myAuthors = new ArrayList<Author>();
+ myAuthors = new ArrayList<>();
}
myAuthors.add(author);
}
public void setType(String type) {
myType = type;
}
+
+ public boolean isAdaptive() {
+ return isAdaptive;
+ }
+
+ public void setAdaptive(boolean adaptive) {
+ isAdaptive = adaptive;
+ }
+
+ public boolean isPublic() {
+ return isPublic;
+ }
+
+ public void setPublic(boolean aPublic) {
+ isPublic = aPublic;
+ }
}
--- /dev/null
+package com.jetbrains.edu.learning.stepic;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.lang.Language;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.checker.StudyExecutor;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.*;
+import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
+import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
+import com.jetbrains.edu.learning.editor.StudyEditor;
+import com.jetbrains.edu.learning.ui.StudyToolWindow;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*;
+
+public class EduAdaptiveStepicConnector {
+ public static final String PYTHON27 = "python27";
+ public static final String PYTHON3 = "python3";
+
+ private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
+ private static final String STEPIC_URL = "https://stepic.org/";
+ private static final String STEPIC_API_URL = STEPIC_URL + "api/";
+ private static final String RECOMMENDATIONS_URL = "recommendations";
+ private static final String CONTENT_TYPE_APPL_JSON = "application/json";
+ private static final String LESSON_URL = "lessons/";
+ private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions";
+ private static final String ATTEMPTS_URL = "attempts";
+ private static final String SUBMISSION_URL = "submissions";
+ private static final String ASSIGNMENT_URL = "/assignments";
+ private static final String VIEWS_URL = "/views";
+ private static final String UNITS_URL = "/units";
+ private static final String DEFAULT_TASKFILE_NAME = "code.py";
+
+ @Nullable
+ public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) {
+ try {
+ final CloseableHttpClient client = getHttpClient(project);
+ final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL)
+ .addParameter("course", String.valueOf(course.getId()))
+ .build();
+ final HttpGet request = new HttpGet(uri);
+ setHeaders(request, CONTENT_TYPE_APPL_JSON);
+
+ final CloseableHttpResponse response = client.execute(request);
+ final StatusLine statusLine = response.getStatusLine();
+ final HttpEntity responseEntity = response.getEntity();
+ final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
+
+ if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
+ final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+ final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class);
+
+ if (recomWrapper.recommendations.length != 0) {
+ final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0];
+ final String lessonId = recommendation.lesson;
+ final StepicWrappers.LessonContainer
+ lessonContainer = getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class);
+ if (lessonContainer.lessons.size() == 1) {
+ final Lesson realLesson = lessonContainer.lessons.get(0);
+ course.getLessons().get(0).id = Integer.parseInt(lessonId);
+
+ viewAllSteps(client, realLesson.id);
+
+ for (int stepId : realLesson.steps) {
+ final StepicWrappers.Step step = getStep(stepId);
+ if (step.name.equals("code")) {
+ return getTaskFromStep(project, stepId, step, realLesson.getName());
+ }
+ }
+
+ LOG.warn("Got a lesson without code part as a recommendation");
+ }
+ else {
+ LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size());
+ }
+ }
+ }
+ else {
+ throw new IOException("Stepic returned non 200 status code: " + responseString);
+ }
+ }
+ catch (IOException e) {
+ LOG.warn(e.getMessage());
+ }
+ catch (URISyntaxException e) {
+ LOG.warn(e.getMessage());
+ }
+ return null;
+ }
+
+ private static void viewAllSteps(CloseableHttpClient client, int lessonId) throws URISyntaxException, IOException {
+ final URI unitsUrl = new URIBuilder(UNITS_URL).addParameter("lesson", String.valueOf(lessonId)).build();
+ final StepicWrappers.UnitContainer unitContainer = getFromStepic(unitsUrl.toString(), StepicWrappers.UnitContainer.class);
+ if (unitContainer.units.size() != 1) {
+ LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size());
+ return;
+ }
+
+ final URIBuilder builder = new URIBuilder(ASSIGNMENT_URL);
+ for (Integer step : unitContainer.units.get(0).assignments) {
+ builder.addParameter("ids[]", String.valueOf(step));
+ }
+ final URI assignmentUrl = builder.build();
+ final StepicWrappers.AssignmentsWrapper assignments = getFromStepic(assignmentUrl.toString(), StepicWrappers.AssignmentsWrapper.class);
+ if (assignments.assignments.size() > 0) {
+ for (StepicWrappers.Assignment assignment : assignments.assignments) {
+ final HttpPost post = new HttpPost(STEPIC_API_URL + VIEWS_URL);
+ final StepicWrappers.ViewsWrapper viewsWrapper = new StepicWrappers.ViewsWrapper(assignment.id, assignment.step);
+ post.setEntity(new StringEntity(new Gson().toJson(viewsWrapper)));
+ setHeaders(post, CONTENT_TYPE_APPL_JSON);
+ final CloseableHttpResponse viewPostResult = client.execute(post);
+ if (viewPostResult.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
+ LOG.warn("Error while Views post, code: " + viewPostResult.getStatusLine().getStatusCode());
+ }
+ }
+ }
+ else {
+ LOG.warn("Got assignments of incorrect length: " + assignments.assignments.size());
+ }
+ }
+
+ public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId,
+ @NotNull final String user, int reaction) {
+ final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL);
+ final String json = new Gson()
+ .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
+ post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
+ final CloseableHttpClient client = getHttpClient(project);
+ setHeaders(post, CONTENT_TYPE_APPL_JSON);
+ try {
+ final CloseableHttpResponse execute = client.execute(post);
+ return execute.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED;
+ }
+ catch (IOException e) {
+ LOG.warn(e.getMessage());
+ }
+ return false;
+ }
+
+ public static void addNextRecommendedTask(@NotNull final Project project, int reaction) {
+ final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project);
+ final Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course != null && editor != null && editor.getTaskFile() != null) {
+ final StepicUser user = StudyTaskManager.getInstance(project).getUser();
+
+ final boolean recommendationReaction =
+ user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id),
+ String.valueOf(user.id), reaction);
+ if (recommendationReaction) {
+ final Task task = getNextRecommendation(project, course);
+
+ if (task != null) {
+ final Lesson adaptive = course.getLessons().get(0);
+ final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
+ if (reaction == 0 || reaction == -1) {
+ unsolvedTask.setName(task.getName());
+ unsolvedTask.setStepicId(task.getStepicId());
+ unsolvedTask.setText(task.getText());
+ unsolvedTask.getTestsText().clear();
+ final Map<String, String> testsText = task.getTestsText();
+ for (String testName : testsText.keySet()) {
+ unsolvedTask.addTestsTexts(testName, testsText.get(testName));
+ }
+ final Map<String, TaskFile> taskFiles = task.getTaskFiles();
+ if (taskFiles.size() == 1) {
+ final TaskFile taskFile = editor.getTaskFile();
+ taskFile.text = ((TaskFile)taskFiles.values().toArray()[0]).text;
+ ApplicationManager.getApplication().invokeLater(() ->
+ ApplicationManager.getApplication().runWriteAction(() ->
+ editor.getEditor()
+ .getDocument()
+ .setText(
+ taskFiles.get(
+ DEFAULT_TASKFILE_NAME).text)));
+ }
+ else {
+ LOG.warn("Got task without unexpected number of task files: " + taskFiles.size());
+ }
+
+ final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+ final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size()));
+ StudyProjectGenerator.flushTask(task, taskDirectory);
+ StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
+ final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+
+ if (lessonDir != null) {
+ createTestFiles(course, task, unsolvedTask, lessonDir);
+ }
+ final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
+ if (window != null) {
+ window.setTaskText(unsolvedTask.getText(), unsolvedTask.getTaskDir(project), project);
+ }
+ }
+ else {
+ adaptive.addTask(task);
+ task.setIndex(adaptive.getTaskList().size());
+ final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+
+ if (lessonDir != null) {
+ ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
+ try {
+ StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
+ }
+ catch (IOException e) {
+ LOG.warn(e.getMessage());
+ }
+ }));
+ }
+
+ final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+ StudyProjectGenerator.flushLesson(lessonDirectory, adaptive);
+ StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
+ adaptive.initLesson(course, true);
+ }
+ }
+ ApplicationManager.getApplication().invokeLater(() -> {
+ VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
+ ProjectView.getInstance(project).refresh();
+ });
+ }
+ else {
+ LOG.warn("Recommendation reactions weren't posted");
+ }
+ }
+ }
+
+ private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) {
+ ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
+ try {
+ final VirtualFile taskDir = VfsUtil
+ .findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true);
+ final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName());
+ File newResourceRoot = null;
+ if (taskDir != null) {
+ newResourceRoot = new File(resourceRoot, taskDir.getName());
+ File[] filesInTask = newResourceRoot.listFiles();
+ if (filesInTask != null) {
+ for (File file : filesInTask) {
+ String fileName = file.getName();
+ if (!task.isTaskFile(fileName)) {
+ File resourceFile = new File(newResourceRoot, fileName);
+ File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
+ FileUtil.copy(resourceFile, fileInProject);
+ }
+ }
+ }
+ }
+ else {
+ LOG.warn("Task directory is null");
+ }
+ }
+ catch (IOException e) {
+ LOG.warn(e.getMessage());
+ }
+ }));
+ }
+
+ @NotNull
+ private static Task getTaskFromStep(Project project,
+ int lessonID,
+ @NotNull final StepicWrappers.Step step, @NotNull String name) {
+ final Task task = new Task();
+ task.setName(name);
+ task.setStepicId(lessonID);
+ task.setText(step.text);
+ task.setStatus(StudyStatus.Unchecked);
+ if (step.options.samples != null) {
+ final StringBuilder builder = new StringBuilder();
+ for (List<String> sample : step.options.samples) {
+ if (sample.size() == 2) {
+ builder.append("<b>Sample Input:</b><br>");
+ builder.append(StringUtil.replace(sample.get(0), "\n", "<br>"));
+ builder.append("<br>");
+ builder.append("<b>Sample Output:</b><br>");
+ builder.append(StringUtil.replace(sample.get(1), "\n", "<br>"));
+ builder.append("<br><br>");
+ }
+ }
+ task.setText(task.getText() + "<br>" + builder.toString());
+ }
+
+ if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) {
+ String builder = "<b>Memory limit</b>: " +
+ step.options.executionMemoryLimit + " Mb" +
+ "<br>" +
+ "<b>Time limit</b>: " +
+ step.options.executionTimeLimit + "s" +
+ "<br><br>";
+ task.setText(task.getText() + builder);
+ }
+
+ if (step.options.test != null) {
+ for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
+ task.addTestsTexts(wrapper.name, wrapper.text);
+ }
+ }
+ else {
+ if (step.options.samples != null) {
+ createTestFileFromSamples(task, step.options.samples);
+ }
+ }
+
+ task.taskFiles = new HashMap<String, TaskFile>(); // TODO: it looks like we don't need taskFiles as map anymore
+ if (step.options.files != null) {
+ for (TaskFile taskFile : step.options.files) {
+ task.taskFiles.put(taskFile.name, taskFile);
+ }
+ }
+ else {
+ final TaskFile taskFile = new TaskFile();
+ taskFile.name = "code";
+ final String templateForTask = getCodeTemplateForTask(step.options.codeTemplates, task, project);
+ taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask;
+ task.taskFiles.put("code.py", taskFile);
+ }
+ return task;
+ }
+
+ private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates,
+ @NotNull final Task task, @NotNull final Project project) {
+
+ if (codeTemplates != null) {
+ final String languageString = getLanguageString(task, project);
+ if (languageString != null) {
+ return codeTemplates.getTemplateForLanguage(languageString);
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public static Pair<Boolean, String> checkTask(@NotNull final Project project, @NotNull final Task task) {
+ int attemptId = -1;
+ try {
+ attemptId = getAttemptId(project, task, ATTEMPTS_URL);
+ }
+ catch (IOException e) {
+ LOG.warn(e.getMessage());
+ }
+ if (attemptId != -1) {
+ final Editor editor = StudyUtils.getSelectedEditor(project);
+ String language = getLanguageString(task, project);
+ if (editor != null && language != null) {
+ final CloseableHttpClient client = getHttpClient(project);
+ StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText());
+
+ final StepicUser user = StudyTaskManager.getInstance(project).getUser();
+ if (user != null) {
+ final int id = user.getId();
+ wrapper = getCheckResults(attemptId, id, client, wrapper);
+ if (wrapper.submissions.length == 1) {
+ final boolean isSolved = !wrapper.submissions[0].status.equals("wrong");
+ return Pair.create(isSolved, wrapper.submissions[0].hint);
+ }
+ else {
+ LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length);
+ }
+ }
+ else {
+ LOG.warn("User is null");
+ }
+ }
+ }
+ else {
+ LOG.warn("Got an incorrect attempt id: " + attemptId);
+ }
+ return Pair.create(false, "");
+ }
+
+ @Nullable
+ private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client,
+ final int attemptId,
+ @NotNull final String language,
+ @NotNull final String text) {
+ final CloseableHttpResponse response;
+ try {
+ final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper =
+ new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, text);
+ final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL);
+ setHeaders(httpPost, CONTENT_TYPE_APPL_JSON);
+ try {
+ httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper)));
+ }
+ catch (UnsupportedEncodingException e) {
+ LOG.warn(e.getMessage());
+ }
+ response = client.execute(httpPost);
+ return new Gson().fromJson(EntityUtils.toString(response.getEntity()), StepicWrappers.ResultSubmissionWrapper.class);
+ }
+ catch (IOException e) {
+ LOG.warn(e.getMessage());
+ }
+ return null;
+ }
+
+ @NotNull
+ private static StepicWrappers.ResultSubmissionWrapper getCheckResults(int attemptId,
+ int id,
+ CloseableHttpClient client,
+ StepicWrappers.ResultSubmissionWrapper wrapper) {
+ try {
+ while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) {
+ TimeUnit.MILLISECONDS.sleep(500);
+ final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL)
+ .addParameter("attempt", String.valueOf(attemptId))
+ .addParameter("order", "desc")
+ .addParameter("user", String.valueOf(id))
+ .build();
+ final HttpGet httpGet = new HttpGet(submissionURI);
+ setHeaders(httpGet, CONTENT_TYPE_APPL_JSON);
+ final CloseableHttpResponse httpResponse = client.execute(httpGet);
+ final String entity = EntityUtils.toString(httpResponse.getEntity());
+ wrapper = new Gson().fromJson(entity, StepicWrappers.ResultSubmissionWrapper.class);
+ }
+ }
+ catch (InterruptedException e) {
+ LOG.warn(e.getMessage());
+ }
+ catch (IOException e) {
+ LOG.warn(e.getMessage());
+ }
+ catch (URISyntaxException e) {
+ LOG.warn(e.getMessage());
+ }
+ return wrapper;
+ }
+
+ @Nullable
+ private static String getLanguageString(@NotNull Task task, @NotNull Project project) {
+ final Language pythonLanguage = Language.findLanguageByID("Python");
+ if (pythonLanguage != null) {
+ final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project);
+ if (language != null) {
+ final String versionString = language.getVersionString();
+ if (versionString != null) {
+ final List<String> versionStringParts = StringUtil.split(versionString, " ");
+ if (versionStringParts.size() == 2) {
+ return versionStringParts.get(1).startsWith("2") ? PYTHON27 : PYTHON3;
+ }
+ }
+ }
+ else {
+ StudyUtils.showNoSdkNotification(task, project);
+ }
+ }
+ return null;
+ }
+
+ private static int getAttemptId(@NotNull final Project project, @NotNull Task task, @NotNull final String attempts) throws IOException {
+ final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId());
+
+ final HttpPost post = new HttpPost(STEPIC_API_URL + attempts);
+ post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper)));
+
+ final CloseableHttpClient client = getHttpClient(project);
+ setHeaders(post, CONTENT_TYPE_APPL_JSON);
+ final CloseableHttpResponse httpResponse = client.execute(post);
+ final String entity = EntityUtils.toString(httpResponse.getEntity());
+ final StepicWrappers.AttemptContainer container =
+ new Gson().fromJson(entity, StepicWrappers.AttemptContainer.class);
+ return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1;
+ }
+
+ private static void createTestFileFromSamples(@NotNull final Task task,
+ @NotNull final List<List<String>> samples) {
+
+ String testText = "from test_helper import check_samples\n\n" +
+ "if __name__ == '__main__':\n" +
+ " check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")";
+ task.addTestsTexts("tests.py", testText);
+ }
+}
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import com.google.gson.annotations.Expose;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.util.net.ssl.CertificateManager;
+import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.apache.commons.codec.binary.Base64;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpStatus;
-import org.apache.http.NameValuePair;
-import org.apache.http.StatusLine;
+import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
-import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
public class EduStepicConnector {
+ private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
private static final String stepicUrl = "https://stepic.org/";
private static final String stepicApiUrl = stepicUrl + "api/";
- private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
private static String ourCSRFToken = "";
private static CloseableHttpClient ourClient;
private EduStepicConnector() {
}
- public static boolean login(@NotNull final String user, @NotNull final String password) {
+ public static StepicUser login(@NotNull final String username, @NotNull final String password) {
initializeClient();
- return postCredentials(user, password);
+ if (postCredentials(username, password)) {
+ final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser();
+ if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) {
+ return stepicUserWrapper.users.get(0);
+ }
+ }
+ return null;
}
@Nullable
- public static AuthorWrapper getCurrentUser() {
+ public static StepicWrappers.AuthorWrapper getCurrentUser() {
try {
- return getFromStepic("stepics/1", AuthorWrapper.class);
+ return getFromStepic("stepics/1", StepicWrappers.AuthorWrapper.class);
}
catch (IOException e) {
LOG.warn("Couldn't get author info");
final HttpPost userRequest = new HttpPost(stepicApiUrl + "users");
initializeClient();
setHeaders(userRequest, "application/json");
- String requestBody = new Gson().toJson(new UserWrapper(user, password));
+ String requestBody = new Gson().toJson(new StepicWrappers.UserWrapper(user, password));
userRequest.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
return true;
}
- private static void initializeClient() {
- final HttpGet request = new HttpGet(stepicUrl);
- request.addHeader(new BasicHeader("referer", "https://stepic.org"));
- request.addHeader(new BasicHeader("content-type", "application/json"));
+ public static void initializeClient() {
+ if (ourClient == null) {
+ final HttpGet request = new HttpGet(stepicUrl);
+ request.addHeader(new BasicHeader("referer", "https://stepic.org"));
+ request.addHeader(new BasicHeader("content-type", "application/json"));
- HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
- setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
- ourCookieStore = new BasicCookieStore();
+ HttpClientBuilder builder =
+ HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
+ setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
+ ourCookieStore = new BasicCookieStore();
- try {
- // Create a trust manager that does not validate certificate for this connection
- TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
- public X509Certificate[] getAcceptedIssuers() { return null; }
- public void checkClientTrusted(X509Certificate[] certs, String authType) {}
- public void checkServerTrusted(X509Certificate[] certs, String authType) {}
- }};
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, trustAllCerts, new SecureRandom());
- ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
-
- ourClient.execute(request);
- saveCSRFToken();
- }
- catch (IOException e) {
- LOG.error(e.getMessage());
- }
- catch (NoSuchAlgorithmException e) {
- LOG.error(e.getMessage());
- }
- catch (KeyManagementException e) {
- LOG.error(e.getMessage());
+ try {
+ // Create a trust manager that does not validate certificate for this connection
+ TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ }
+
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ }
+ }};
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, trustAllCerts, new SecureRandom());
+ ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
+
+ ourClient.execute(request);
+ saveCSRFToken();
+ }
+ catch (IOException e) {
+ LOG.error(e.getMessage());
+ }
+ catch (NoSuchAlgorithmException e) {
+ LOG.error(e.getMessage());
+ }
+ catch (KeyManagementException e) {
+ LOG.error(e.getMessage());
+ }
}
}
nvps.add(new BasicNameValuePair("password", password));
nvps.add(new BasicNameValuePair("remember", "on"));
- try {
- request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
- }
- catch (UnsupportedEncodingException e) {
- LOG.error(e.getMessage());
- ourClient = null;
- return false;
- }
+ request.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
setHeaders(request, "application/x-www-form-urlencoded");
if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
- LOG.error("Failed to login " + responseString);
+ LOG.warn("Failed to login: " + line.getStatusCode() + line.getReasonPhrase());
+ LOG.debug("Failed to login " + responseString);
ourClient = null;
return false;
}
return true;
}
- private static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
+ static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
final HttpGet request = new HttpGet(stepicApiUrl + link);
if (ourClient == null) {
initializeClient();
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
return gson.fromJson(responseString, container);
}
+
+ @NotNull
+ public static CloseableHttpClient getHttpClient(@NotNull final Project project) {
+ if (ourClient == null) {
+ login(project);
+ initializeClient();
+ }
+ return ourClient;
+ }
@NotNull
public static List<CourseInfo> getCourses() {
catch (IOException e) {
LOG.error("Cannot load course list " + e.getMessage());
}
- return Collections.emptyList();
+ return Collections.singletonList(CourseInfo.INVALID_COURSE);
}
private static boolean addCoursesFromStepic(List<CourseInfo> result, int pageNumber) throws IOException {
final String url = pageNumber == 0 ? "courses" : "courses?page=" + String.valueOf(pageNumber);
- final CoursesContainer coursesContainer = getFromStepic(url, CoursesContainer.class);
+ final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url, StepicWrappers.CoursesContainer.class);
final List<CourseInfo> courseInfos = coursesContainer.courses;
for (CourseInfo info : courseInfos) {
final String courseType = info.getType();
- if (StringUtil.isEmptyOrSpaces(courseType)) continue;
+ if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue;
final List<String> typeLanguage = StringUtil.split(courseType, " ");
- if (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0))) {
-
+ // TODO: should adaptive course be of PyCharmType ?
+ if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) {
for (Integer instructor : info.instructors) {
- final CourseInfo.Author author = getFromStepic("users/" + String.valueOf(instructor), AuthorWrapper.class).users.get(0);
+ final StepicUser author = getFromStepic("users/" + String.valueOf(instructor), StepicWrappers.AuthorWrapper.class).users.get(0);
info.addAuthor(author);
}
return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
}
- public static Course getCourse(@NotNull final CourseInfo info) {
+ public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) {
final Course course = new Course();
course.setAuthors(info.getAuthors());
course.setDescription(info.getDescription());
- course.setName(info.getName());
- String courseType = info.getType();
- course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
+ course.setAdaptive(info.isAdaptive());
+ course.setId(info.id);
course.setUpToDate(true); // TODO: get from stepic
- try {
- for (Integer section : info.sections) {
- course.addLessons(getLessons(section));
+
+ if (!course.isAdaptive()) {
+ String courseType = info.getType();
+ course.setName(info.getName());
+ course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
+ try {
+ for (Integer section : info.sections) {
+ course.addLessons(getLessons(section));
+ }
+ return course;
+ }
+ catch (IOException e) {
+ LOG.error("IOException " + e.getMessage());
}
- return course;
}
- catch (IOException e) {
- LOG.error("IOException " + e.getMessage());
+ else {
+ final Lesson lesson = new Lesson();
+ course.setName(info.getName());
+ //TODO: more specific name?
+ lesson.setName("Adaptive");
+ course.addLesson(lesson);
+ final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course);
+ if (recommendation != null) {
+ lesson.addTask(recommendation);
+ }
+
+ return course;
}
return null;
}
public static List<Lesson> getLessons(int sectionId) throws IOException {
- final SectionContainer sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), SectionContainer.class);
+ final StepicWrappers.SectionContainer
+ sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), StepicWrappers.SectionContainer.class);
List<Integer> unitIds = sectionContainer.sections.get(0).units;
final List<Lesson> lessons = new ArrayList<Lesson>();
for (Integer unitId : unitIds) {
- UnitContainer unit = getFromStepic("units/" + String.valueOf(unitId), UnitContainer.class);
+ StepicWrappers.UnitContainer
+ unit = getFromStepic("units/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class);
int lessonID = unit.units.get(0).lesson;
- LessonContainer lesson = getFromStepic("lessons/" + String.valueOf(lessonID), LessonContainer.class);
+ StepicWrappers.LessonContainer
+ lesson = getFromStepic("lessons/" + String.valueOf(lessonID), StepicWrappers.LessonContainer.class);
Lesson realLesson = lesson.lessons.get(0);
realLesson.taskList = new ArrayList<Task>();
for (Integer s : realLesson.steps) {
}
private static void createTask(Lesson lesson, Integer stepicId) throws IOException {
- final Step step = getStep(stepicId);
+ final StepicWrappers.Step step = getStep(stepicId);
if (!step.name.equals(PYCHARM_PREFIX)) return;
final Task task = new Task();
task.setStepicId(stepicId);
task.setName(step.options != null ? step.options.title : PYCHARM_PREFIX);
task.setText(step.text);
- for (TestFileWrapper wrapper : step.options.test) {
+ for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
task.addTestsTexts(wrapper.name, wrapper.text);
}
lesson.taskList.add(task);
}
- public static Step getStep(Integer step) throws IOException {
- return getFromStepic("steps/" + String.valueOf(step), StepContainer.class).steps.get(0).block;
+ public static StepicWrappers.Step getStep(Integer step) throws IOException {
+ return getFromStepic("steps/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0).block;
}
return;
}
else {
- final boolean success = login(login, password);
- if (!success) return;
+ if (login(login, password) == null) return;
}
}
final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "attempts");
setHeaders(attemptRequest, "application/json");
- String attemptRequestBody = new Gson().toJson(new AttemptWrapper(task.getStepicId()));
+ String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId()));
attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON));
try {
if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to make attempt " + attemptResponseString);
}
- final AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, AttemptContainer.class).attempts.get(0);
+ final StepicWrappers.AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0);
final Map<String, TaskFile> taskFiles = task.getTaskFiles();
- final ArrayList<SolutionFile> files = new ArrayList<SolutionFile>();
+ final ArrayList<StepicWrappers.SolutionFile> files = new ArrayList<StepicWrappers.SolutionFile>();
for (TaskFile fileEntry : taskFiles.values()) {
- files.add(new SolutionFile(fileEntry.name, fileEntry.text));
+ files.add(new StepicWrappers.SolutionFile(fileEntry.name, fileEntry.text));
}
postSubmission(passed, attempt, files);
}
}
}
- private static void postSubmission(boolean passed, AttemptWrapper.Attempt attempt, ArrayList<SolutionFile> files) throws IOException {
+ private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
final HttpPost request = new HttpPost(stepicApiUrl + "submissions");
setHeaders(request, "application/json");
- String requestBody = new Gson().toJson(new SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
+ String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
indicator.setText("Uploading course to " + stepicUrl);
final HttpPost request = new HttpPost(stepicApiUrl + "courses");
if (ourClient == null || !relogin) {
- if (!login()) return;
+ if (!login(project)) return;
}
- final AuthorWrapper user = getCurrentUser();
+ final StepicWrappers.AuthorWrapper user = getCurrentUser();
if (user != null) {
course.setAuthors(user.users);
}
setHeaders(request, "application/json");
- String requestBody = new Gson().toJson(new CourseWrapper(course));
+ String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final StatusLine line = response.getStatusLine();
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
if (!relogin) {
- login();
+ login(project);
postCourse(project, course, true, indicator);
}
LOG.error("Failed to push " + responseString);
return;
}
- final CourseInfo postedCourse = new Gson().fromJson(responseString, CoursesContainer.class).courses.get(0);
+ final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
final int sectionId = postModule(postedCourse.id, 1, String.valueOf(postedCourse.getName()));
int position = 1;
}
}
- private static boolean login() {
- final String login = StudySettings.getInstance().getLogin();
+ private static boolean login(@NotNull final Project project) {
+ final String login = StudyTaskManager.getInstance(project).getLogin();
if (StringUtil.isEmptyOrSpaces(login)) {
return showLoginDialog();
}
else {
- boolean success = login(login, StudySettings.getInstance().getPassword());
- if (!success) {
+ if (login(login, StudyTaskManager.getInstance(project).getPassword()) == null) {
return showLoginDialog();
}
}
private static void postUnit(int lessonId, int position, int sectionId) {
final HttpPost request = new HttpPost(stepicApiUrl + "units");
setHeaders(request, "application/json");
- final UnitWrapper unitWrapper = new UnitWrapper();
- unitWrapper.unit = new Unit();
+ final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
+ unitWrapper.unit = new StepicWrappers.Unit();
unitWrapper.unit.lesson = lessonId;
unitWrapper.unit.position = position;
unitWrapper.unit.section = sectionId;
private static int postModule(int courseId, int position, @NotNull final String title) {
final HttpPost request = new HttpPost(stepicApiUrl + "sections");
setHeaders(request, "application/json");
- final Section section = new Section();
+ final StepicWrappers.Section section = new StepicWrappers.Section();
section.course = courseId;
section.title = title;
section.position = position;
- final SectionWrapper sectionContainer = new SectionWrapper();
+ final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
sectionContainer.section = section;
String requestBody = new Gson().toJson(sectionContainer);
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
}
- final Section postedSection = new Gson().fromJson(responseString, SectionContainer.class).sections.get(0);
+ final StepicWrappers.Section
+ postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
return postedSection.id;
}
catch (IOException e) {
return -1;
}
- public static int updateLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
+ public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPut request = new HttpPut(stepicApiUrl + "lessons/" + String.valueOf(lesson.id));
if (ourClient == null) {
- if (!login()) {
+ if (!login(project)) {
LOG.error("Failed to push lesson");
return 0;
}
}
setHeaders(request, "application/json");
- String requestBody = new Gson().toJson(new LessonWrapper(lesson));
+ String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
return -1;
}
- public static int postLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
+ public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPost request = new HttpPost(stepicApiUrl + "lessons");
if (ourClient == null) {
- login();
+ login(project);
}
setHeaders(request, "application/json");
- String requestBody = new Gson().toJson(new LessonWrapper(lesson));
+ String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
setHeaders(request, "application/json");
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
ApplicationManager.getApplication().invokeLater(() -> {
- final String requestBody = gson.toJson(new StepSourceWrapper(project, task, lessonId));
+ final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
});
}
- private static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
+ static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
request.addHeader(new BasicHeader("referer", stepicUrl));
request.addHeader(new BasicHeader("X-CSRFToken", ourCSRFToken));
request.addHeader(new BasicHeader("content-type", contentType));
}
-
- private static class StepContainer {
- List<StepSource> steps;
- }
-
- private static class Step {
- @Expose StepOptions options;
- @Expose String text;
- @Expose String name = "pycharm";
- @Expose StepOptions source;
-
- public static Step fromTask(Project project, @NotNull final Task task) {
- final Step step = new Step();
- step.text = task.getTaskText(project);
- step.source = StepOptions.fromTask(project, task);
- return step;
- }
- }
-
- private static class StepOptions {
- @Expose List<TestFileWrapper> test;
- @Expose String title;
- @Expose List<TaskFile> files;
- @Expose String text;
-
- public static StepOptions fromTask(final Project project, @NotNull final Task task) {
- final StepOptions source = new StepOptions();
- setTests(task, source, project);
- source.files = new ArrayList<TaskFile>();
- source.title = task.getName();
- for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
- final TaskFile taskFile = new TaskFile();
- TaskFile.copy(entry.getValue(), taskFile);
- ApplicationManager.getApplication().runWriteAction(() -> {
- final VirtualFile taskDir = task.getTaskDir(project);
- assert taskDir != null;
- VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
- assert ideaDir != null;
- EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
- });
- taskFile.name = entry.getKey();
-
- VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
- if (ideaDir == null) return null;
- final VirtualFile file = ideaDir.findChild(taskFile.name);
- try {
- if (file != null) {
- if (EduUtils.isImage(taskFile.name)) {
- taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
- }
- else {
- taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
- }
- }
- }
- catch (IOException e) {
- LOG.error("Can't find file " + file.getPath());
- }
-
- source.files.add(taskFile);
- }
- return source;
- }
-
- private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) {
- final Map<String, String> testsText = task.getTestsText();
- if (testsText.isEmpty()) {
- ApplicationManager.getApplication().runReadAction(() -> {
- source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project)));
- });
- }
- else {
- source.test = new ArrayList<TestFileWrapper>();
- for (Map.Entry<String, String> entry : testsText.entrySet()) {
- source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue()));
- }
- }
- }
- }
-
- private static class CoursesContainer {
- public List<CourseInfo> courses;
- public Map meta;
- }
-
- static class StepSourceWrapper {
- @Expose
- StepSource stepSource;
-
- public StepSourceWrapper(Project project, Task task, int lessonId) {
- stepSource = new StepSource(project, task, lessonId);
- }
- }
-
- static class CourseWrapper {
- CourseInfo course;
-
- public CourseWrapper(Course course) {
- this.course = new CourseInfo();
- this.course.setName(course.getName());
- this.course.setDescription(course.getDescription());
- this.course.setAuthors(course.getAuthors());
- }
- }
-
- static class LessonWrapper {
- Lesson lesson;
-
- public LessonWrapper(Lesson lesson) {
- this.lesson = new Lesson();
- this.lesson.setName(lesson.getName());
- this.lesson.id = lesson.id;
- this.lesson.steps = new ArrayList<Integer>();
- }
- }
-
- static class LessonContainer {
- List<Lesson> lessons;
- }
-
- static class StepSource {
- @Expose Step block;
- @Expose int position = 0;
- @Expose int lesson = 0;
-
- public StepSource(Project project, Task task, int lesson) {
- this.lesson = lesson;
- position = task.getIndex();
- block = Step.fromTask(project, task);
- }
- }
-
- static class TestFileWrapper {
- @Expose private final String name;
- @Expose private final String text;
-
- public TestFileWrapper(String name, String text) {
- this.name = name;
- this.text = text;
- }
- }
-
- static class Section {
- List<Integer> units;
- int course;
- String title;
- int position;
- int id;
- }
-
- static class SectionWrapper {
- Section section;
- }
-
- static class SectionContainer {
- List<Section> sections;
- List<Lesson> lessons;
-
- List<Unit> units;
- }
-
- static class Unit {
- int id;
- int section;
- int lesson;
- int position;
- }
-
- static class UnitContainer {
-
- List<Unit> units;
- }
-
- static class UnitWrapper{
- Unit unit;
- }
-
-
- static class AttemptWrapper {
- static class Attempt {
- public Attempt(int step) {
- this.step = step;
- }
-
- int step;
- int id;
- }
- public AttemptWrapper(int step) {
- attempt = new Attempt(step);
- }
-
- Attempt attempt;
- }
-
- static class AttemptContainer {
- List<AttemptWrapper.Attempt> attempts;
- }
-
- static class SolutionFile {
- String name;
- String text;
-
- public SolutionFile(String name, String text) {
- this.name = name;
- this.text = text;
- }
- }
-
- static class AuthorWrapper {
- List<CourseInfo.Author> users;
- }
-
- static class SubmissionWrapper {
- Submission submission;
-
-
- public SubmissionWrapper(int attempt, String score, ArrayList<SolutionFile> files) {
- submission = new Submission(score, attempt, files);
- }
-
- static class Submission {
- int attempt;
-
- private final Reply reply;
-
- public Submission(String score, int attempt, ArrayList<SolutionFile> files) {
- reply = new Reply(files, score);
- this.attempt = attempt;
- }
-
- static class Reply {
- String score;
- List<SolutionFile> solution;
-
- public Reply(ArrayList<SolutionFile> files, String score) {
- this.score = score;
- solution = files;
- }
- }
- }
-
- }
-
- static class User {
- String first_name;
- String last_name;
- String email;
- String password;
-
- public User(String user, String password) {
- email = user;
- this.password = password;
- this.first_name = user;
- this.last_name = user;
- }
- }
-
- static class UserWrapper {
- User user;
-
- public UserWrapper(String user, String password) {
- this.user = new User(user, password);
- }
- }
-
}
myLoginPanel = new LoginPanel(this);
setTitle("Login to Stepic");
setOKButtonText("Login");
+ setTitle("Login to Stepic");
init();
}
@Override
protected void doOKAction() {
AuthDataHolder authData = myLoginPanel.getAuthData();
- final boolean success = EduStepicConnector.login(authData.email, authData.password);
- if (!success) {
- setErrorText("Login failed");
+ final StepicUser user = EduStepicConnector.login(authData.email, authData.password);
+ if (user != null) {
+ super.doOKAction();
}
else {
- StudySettings.getInstance().setLogin(authData.email);
- StudySettings.getInstance().setPassword(authData.password);
- super.doOKAction();
+ setErrorText("Login failed");
}
}
--- /dev/null
+package com.jetbrains.edu.learning.stepic;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
+import com.intellij.openapi.fileEditor.FileEditorManagerListener;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.progress.util.ProgressIndicatorBase;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+
+public class StepicAdaptiveReactionsPanel extends JPanel {
+ private final ReactionButtonPanel myHardPanel;
+ private final ReactionButtonPanel myBoringPanel;
+ private final Project myProject;
+ private static final int TOO_HARD_REACTION = 0;
+ private static final int TOO_BORING_REACTION = -1;
+ private static final String HARD_REACTION = "Too Hard";
+ private static final String BORING_REACTION = "Too Boring";
+ private static final String SOLVED_TASK_TOOLTIP = "Task Is Solved";
+ private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task";
+ private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task";
+
+ public StepicAdaptiveReactionsPanel(@NotNull final Project project) {
+ myProject = project;
+ setLayout(new GridBagLayout());
+ setBackground(UIUtil.getTextFieldBackground());
+
+ myHardPanel = new ReactionButtonPanel(HARD_REACTION, HARD_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_HARD_REACTION);
+ myBoringPanel = new ReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_BORING_REACTION);
+ addFileListener();
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 0;
+ c.gridy = 0;
+ add(Box.createVerticalStrut(3), c);
+ c.gridx = 1;
+ c.gridy = 1;
+ add(Box.createHorizontalStrut(3), c);
+ c.weightx = 1;
+ c.gridx = 2;
+ add(myHardPanel, c);
+ c.gridx = 3;
+ c.weightx = 0;
+ add(Box.createHorizontalStrut(3), c);
+ c.weightx = 1;
+ c.gridx = 4;
+ add(myBoringPanel, c);
+ c.gridx = 5;
+ c.weightx = 0;
+ add(Box.createHorizontalStrut(3), c);
+ }
+
+ public void setEnabledRecursive(final boolean isEnabled) {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ super.setEnabled(isEnabled);
+ myHardPanel.setEnabledRecursive(isEnabled);
+ myBoringPanel.setEnabledRecursive(isEnabled);
+ });
+ }
+
+ private void addFileListener() {
+ final FileEditorManagerListener editorManagerListener = new FileEditorManagerListener() {
+ @Override
+ public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
+ final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
+ final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
+ StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
+ }
+
+ @Override
+ public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
+
+ }
+
+ @Override
+ public void selectionChanged(@NotNull FileEditorManagerEvent event) {
+ final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
+ final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
+ StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
+ }
+ };
+ myProject.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorManagerListener);
+ }
+
+ private class ReactionButtonPanel extends JPanel {
+ private final JPanel myButtonPanel;
+ private final JLabel myLabel;
+
+ public ReactionButtonPanel(@NotNull final String text,
+ @NotNull final String enabledTooltip,
+ @NotNull final String disabledTooltip,
+ int reaction) {
+ com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
+ final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
+
+ myLabel = new JLabel(text);
+
+ myButtonPanel = new JPanel();
+ myButtonPanel.setLayout(new BoxLayout(myButtonPanel, BoxLayout.PAGE_AXIS));
+ myButtonPanel.setToolTipText(isEnabled ? enabledTooltip : disabledTooltip);
+ myButtonPanel.add(Box.createVerticalStrut(5));
+ myButtonPanel.add(myLabel);
+ myButtonPanel.add(Box.createVerticalStrut(5));
+
+ setEnabledRecursive(isEnabled);
+
+ setLayout(new GridBagLayout());
+ setBorder(BorderFactory.createEtchedBorder());
+ add(myButtonPanel);
+ addMouseListener(() -> EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, reaction));
+ }
+
+ private void addMouseListener(@NotNull Runnable onClickAction) {
+ final ReactionMouseAdapter mouseAdapter = new ReactionMouseAdapter(onClickAction, this);
+ this.addMouseListener(mouseAdapter);
+ myButtonPanel.addMouseListener(mouseAdapter);
+ myLabel.addMouseListener(mouseAdapter);
+ }
+
+
+ public void setEnabledRecursive(final boolean isEnabled) {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ super.setEnabled(isEnabled);
+ myButtonPanel.setEnabled(isEnabled);
+ myLabel.setEnabled(isEnabled);
+ });
+ }
+
+ private class ReactionMouseAdapter extends MouseAdapter {
+ private final Runnable myClickAction;
+ private final ReactionButtonPanel myPanel;
+
+ public ReactionMouseAdapter(@NotNull final Runnable onClickAction, @NotNull final ReactionButtonPanel mainPanel) {
+ myClickAction = onClickAction;
+ myPanel = mainPanel;
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 1) {
+ final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
+ if (task != null && task.getStatus() != StudyStatus.Solved) {
+ final ProgressIndicatorBase progress = new ProgressIndicatorBase();
+ progress.setText("Loading Next Recommendation");
+ ProgressManager.getInstance().run(new Task.Backgroundable(myProject,
+ "Loading Next Recommendation") {
+ @Override
+ public void run(@NotNull ProgressIndicator indicator) {
+ StepicAdaptiveReactionsPanel.this.setEnabledRecursive(false);
+ myClickAction.run();
+ }
+
+ @Override
+ public void onFinished() {
+ StepicAdaptiveReactionsPanel.this.setEnabledRecursive(true);
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
+ if (task != null && task.getStatus() != StudyStatus.Solved && myPanel.isEnabled()) {
+ setBackground(UIUtil.getButtonSelectColor());
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ setBackground(UIUtil.getLabelBackground());
+ }
+
+ private void setBackground(Color color) {
+ myPanel.setBackground(color);
+ myButtonPanel.setBackground(color);
+ }
+ }
+ }
+}
+
*/
package com.jetbrains.edu.learning.stepic;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.DocumentAdapter;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.settings.StudyOptionsProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class StepicStudyOptions implements StudyOptionsProvider {
private static final String DEFAULT_PASSWORD_TEXT = "************";
+ private static final Logger LOG = Logger.getInstance(StepicStudyOptions.class);
private JTextField myLoginTextField;
private JPasswordField myPasswordField;
private JPanel myPane;
myPasswordField.setText(StringUtil.isEmpty(password) ? null : password);
}
-
+ @Override
public void reset() {
- final StudySettings studySettings = StudySettings.getInstance();
- setLogin(studySettings.getLogin());
- setPassword(DEFAULT_PASSWORD_TEXT);
+ Project project = StudyUtils.getStudyProject();
+ if (project != null) {
+ final StudyTaskManager studySettings = StudyTaskManager.getInstance(project);
+ setLogin(studySettings.getLogin());
+ setPassword(DEFAULT_PASSWORD_TEXT);
- resetCredentialsModification();
+ resetCredentialsModification();
+ }
+ else {
+ LOG.warn("No study object is opened");
+ }
}
@Override
}
+ @Override
public void apply() {
if (myCredentialsModified) {
- final StudySettings studySettings = StudySettings.getInstance();
- studySettings.setLogin(getLogin());
- studySettings.setPassword(getPassword());
- if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) {
- EduStepicConnector.login(getLogin(), getPassword());
+ final Project project = StudyUtils.getStudyProject();
+ if (project != null) {
+ final StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
+ studyTaskManager.setLogin(getLogin());
+ studyTaskManager.setPassword(getPassword());
+ if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) {
+ EduStepicConnector.login(getLogin(), getPassword());
+ }
+ }
+ else {
+ LOG.warn("No study object is opened");
}
}
resetCredentialsModification();
--- /dev/null
+package com.jetbrains.edu.learning.stepic;
+
+import com.intellij.ide.passwordSafe.PasswordSafe;
+import com.intellij.ide.passwordSafe.PasswordSafeException;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.text.StringUtil;
+import com.jetbrains.edu.learning.StudyTaskManager;
+
+public class StepicUser {
+ private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY";
+ private static final Logger LOG = Logger.getInstance(StepicUser.class);
+ int id;
+ String firstName;
+ String last_name;
+ String email;
+
+ public StepicUser() {
+ }
+
+ public StepicUser(String email, String password) {
+ this.email = email;
+ setPassword(password);
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return last_name;
+ }
+
+ public void setLastName(String last_name) {
+ this.last_name = last_name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getPassword() {
+ final String login = getEmail();
+ if (StringUtil.isEmptyOrSpaces(login)) return "";
+
+ String password;
+ try {
+ password = PasswordSafe.getInstance().getPassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + login);
+ }
+ catch (PasswordSafeException e) {
+ LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
+ password = "";
+ }
+
+ return StringUtil.notNullize(password);
+ }
+
+ public void setPassword(String password) {
+ try {
+ PasswordSafe.getInstance().storePassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + getEmail(), password);
+ }
+ catch (PasswordSafeException e) {
+ LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + getEmail() + "]", e);
+ }
+ }
+
+ public String getName() {
+ return StringUtil.join(new String[]{firstName, last_name}, " ");
+ }
+}
--- /dev/null
+package com.jetbrains.edu.learning.stepic;
+
+import com.google.gson.annotations.Expose;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.core.EduUtils;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Lesson;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.TaskFile;
+import org.apache.commons.codec.binary.Base64;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class StepicWrappers {
+ private static final Logger LOG = Logger.getInstance(StepOptions.class);
+
+ static class StepContainer {
+ List<StepSource> steps;
+ }
+
+ public static class Step {
+ @Expose StepOptions options;
+ @Expose String text;
+ @Expose String name = "pycharm";
+ @Expose StepOptions source;
+
+ public static Step fromTask(Project project, @NotNull final Task task) {
+ final Step step = new Step();
+ step.text = task.getTaskText(project);
+ step.source = StepOptions.fromTask(project, task);
+ return step;
+ }
+ }
+
+ public static class StepOptions {
+ @Expose List<TestFileWrapper> test;
+ @Expose String title;
+ @Expose List<TaskFile> files;
+ @Expose String text;
+ @Expose List<List<String>> samples;
+ @Expose Integer executionMemoryLimit;
+ @Expose Integer executionTimeLimit;
+ @Expose CodeTemplatesWrapper codeTemplates;
+
+ public static StepOptions fromTask(final Project project, @NotNull final Task task) {
+ final StepOptions source = new StepOptions();
+ setTests(task, source, project);
+ source.files = new ArrayList<TaskFile>();
+ source.title = task.getName();
+ for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ final TaskFile taskFile = new TaskFile();
+ TaskFile.copy(entry.getValue(), taskFile);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final VirtualFile taskDir = task.getTaskDir(project);
+ assert taskDir != null;
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ assert ideaDir != null;
+ EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
+ }
+ });
+ taskFile.name = entry.getKey();
+
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ if (ideaDir == null) return null;
+ final VirtualFile file = ideaDir.findChild(taskFile.name);
+ try {
+ if (file != null) {
+ if (EduUtils.isImage(taskFile.name)) {
+ taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
+ }
+ else {
+ taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error("Can't find file " + file.getPath());
+ }
+
+ source.files.add(taskFile);
+ }
+ return source;
+ }
+
+ private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) {
+ final Map<String, String> testsText = task.getTestsText();
+ if (testsText.isEmpty()) {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ @Override
+ public void run() {
+ source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project)));
+ }
+ });
+ }
+ else {
+ source.test = new ArrayList<TestFileWrapper>();
+ for (Map.Entry<String, String> entry : testsText.entrySet()) {
+ source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue()));
+ }
+ }
+ }
+ }
+
+ static class CodeTemplatesWrapper {
+ String python3;
+ String python27;
+
+ @Nullable
+ public String getTemplateForLanguage(@NotNull final String langauge) {
+ if (langauge.equals(EduAdaptiveStepicConnector.PYTHON27)) {
+ return python27;
+ }
+
+ if (langauge.equals(EduAdaptiveStepicConnector.PYTHON3)) {
+ return python3;
+ }
+
+ return null;
+ }
+ }
+
+ static class CoursesContainer {
+ public List<CourseInfo> courses;
+ public Map meta;
+ }
+
+ static class StepSourceWrapper {
+ @Expose
+ StepSource stepSource;
+
+ public StepSourceWrapper(Project project, Task task, int lessonId) {
+ stepSource = new StepSource(project, task, lessonId);
+ }
+ }
+
+ static class CourseWrapper {
+ CourseInfo course;
+
+ public CourseWrapper(Course course) {
+ this.course = new CourseInfo();
+ this.course.setName(course.getName());
+ this.course.setDescription(course.getDescription());
+ this.course.setAuthors(course.getAuthors());
+ }
+ }
+
+ static class LessonWrapper {
+ Lesson lesson;
+
+ public LessonWrapper(Lesson lesson) {
+ this.lesson = new Lesson();
+ this.lesson.setName(lesson.getName());
+ this.lesson.id = lesson.id;
+ this.lesson.steps = new ArrayList<Integer>();
+ }
+ }
+
+ static class LessonContainer {
+ List<Lesson> lessons;
+ }
+
+ static class StepSource {
+ @Expose Step block;
+ @Expose int position = 0;
+ @Expose int lesson = 0;
+
+ public StepSource(Project project, Task task, int lesson) {
+ this.lesson = lesson;
+ position = task.getIndex();
+ block = Step.fromTask(project, task);
+ }
+ }
+
+ static class TestFileWrapper {
+ @Expose public final String name;
+ @Expose public final String text;
+
+ public TestFileWrapper(String name, String text) {
+ this.name = name;
+ this.text = text;
+ }
+ }
+
+ static class Section {
+ List<Integer> units;
+ int course;
+ String title;
+ int position;
+ int id;
+ }
+
+ static class SectionWrapper {
+ Section section;
+ }
+
+ static class SectionContainer {
+ List<Section> sections;
+ List<Lesson> lessons;
+
+ List<Unit> units;
+ }
+
+ static class Unit {
+ int id;
+ int section;
+ int lesson;
+ int position;
+ List<Integer> assignments;
+ }
+
+ static class UnitContainer {
+
+ List<Unit> units;
+ }
+
+ static class UnitWrapper {
+ Unit unit;
+ }
+
+ static class AttemptWrapper {
+ static class Attempt {
+ public Attempt(int step) {
+ this.step = step;
+ }
+
+ int step;
+ int id;
+ }
+
+ public AttemptWrapper(int step) {
+ attempt = new Attempt(step);
+ }
+
+ Attempt attempt;
+ }
+
+ static class AttemptToPostWrapper {
+ static class Attempt {
+ int step;
+ String dataset_url;
+ String status;
+ String time;
+ String time_left;
+ String user;
+ String user_id;
+
+ public Attempt(int step) {
+ this.step = step;
+ }
+ }
+
+ public AttemptToPostWrapper(int step) {
+ attempt = new Attempt(step);
+ }
+
+ Attempt attempt;
+ }
+
+ static class AttemptContainer {
+ List<AttemptWrapper.Attempt> attempts;
+ }
+
+ static class SolutionFile {
+ String name;
+ String text;
+
+ public SolutionFile(String name, String text) {
+ this.name = name;
+ this.text = text;
+ }
+ }
+
+ static class AuthorWrapper {
+ List<StepicUser> users;
+ }
+
+ static class SubmissionWrapper {
+ Submission submission;
+
+
+ public SubmissionWrapper(int attempt, String score, ArrayList<SolutionFile> files) {
+ submission = new Submission(score, attempt, files);
+ }
+
+ static class Submission {
+ int attempt;
+ private final Reply reply;
+
+ public Submission(String score, int attempt, ArrayList<SolutionFile> files) {
+ reply = new Reply(files, score);
+ this.attempt = attempt;
+ }
+
+ static class Reply {
+ String score;
+ List<SolutionFile> solution;
+
+ public Reply(ArrayList<SolutionFile> files, String score) {
+ this.score = score;
+ solution = files;
+ }
+ }
+ }
+ }
+
+ static class UserWrapper {
+ StepicUser user;
+
+ public UserWrapper(String user, String password) {
+ this.user = new StepicUser(user, password);
+ }
+ }
+
+ static class RecommendationReaction {
+ int reaction;
+ String user;
+ String lesson;
+
+ public RecommendationReaction(int reaction, String user, String lesson) {
+ this.reaction = reaction;
+ this.user = user;
+ this.lesson = lesson;
+ }
+ }
+
+ static class RecommendationReactionWrapper {
+ RecommendationReaction recommendationReaction;
+
+ public RecommendationReactionWrapper(RecommendationReaction recommendationReaction) {
+ this.recommendationReaction = recommendationReaction;
+ }
+ }
+
+ static class RecommendationWrapper {
+ Recommendation[] recommendations;
+ }
+
+ static class Recommendation {
+ String id;
+ String lesson;
+ }
+
+
+ static class SubmissionToPostWrapper {
+ Submission submission;
+
+ public SubmissionToPostWrapper(@NotNull String attemptId, @NotNull String language, @NotNull String code) {
+ submission = new Submission(attemptId, new Submission.Reply(language, code));
+ }
+
+ static class Submission {
+ String attempt;
+ Reply reply;
+
+ public Submission(String attempt, Reply reply) {
+ this.attempt = attempt;
+ this.reply = reply;
+ }
+
+ static class Reply {
+ String language;
+ String code;
+
+ public Reply(String language, String code) {
+ this.language = language;
+ this.code = code;
+ }
+ }
+ }
+ }
+
+ static class ResultSubmissionWrapper {
+ ResultSubmission[] submissions;
+
+ static class ResultSubmission {
+ int id;
+ String status;
+ String hint;
+ }
+ }
+
+ static class AssignmentsWrapper {
+ List<Assignment> assignments;
+ }
+
+ static class Assignment {
+ int id;
+ int step;
+ }
+
+ static class ViewsWrapper {
+ View view;
+
+ public ViewsWrapper(final int assignment, final int step) {
+ this.view = new View(assignment, step);
+ }
+ }
+
+ static class View {
+ int assignment;
+ int step;
+
+ public View(int assignment, int step) {
+ this.assignment = assignment;
+ this.step = step;
+ }
+ }
+}
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.edu.learning.stepic;
-
-import com.intellij.ide.passwordSafe.PasswordSafe;
-import com.intellij.ide.passwordSafe.PasswordSafeException;
-import com.intellij.openapi.components.PersistentStateComponent;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.components.State;
-import com.intellij.openapi.components.Storage;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.text.StringUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-@SuppressWarnings("MethodMayBeStatic")
-@State(name = "StudySettings", storages = @Storage("stepic_settings.xml"))
-public class StudySettings implements PersistentStateComponent<StudySettings.State> {
-
- private State myState = new State();
-
- public static class State {
- @Nullable public String LOGIN = null;
- }
-
- private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY";
- private static final Logger LOG = Logger.getInstance(StudySettings.class.getName());
-
- public static StudySettings getInstance() {
- return ServiceManager.getService(StudySettings.class);
- }
-
- @NotNull
- public String getPassword() {
- final String login = getLogin();
- if (StringUtil.isEmptyOrSpaces(login)) return "";
-
- String password;
- try {
- password = PasswordSafe.getInstance().getPassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY);
- }
- catch (PasswordSafeException e) {
- LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
- password = "";
- }
-
- return StringUtil.notNullize(password);
- }
-
- public void setPassword(@NotNull String password) {
- try {
- PasswordSafe.getInstance().storePassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY, password);
- }
- catch (PasswordSafeException e) {
- LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
- }
- }
-
- @Nullable
- public String getLogin() {
- return myState.LOGIN;
- }
-
- public void setLogin(@Nullable String login) {
- myState.LOGIN = login;
- }
- @Nullable
- @Override
- public StudySettings.State getState() {
- return myState;
- }
-
- @Override
- public void loadState(StudySettings.State state) {
- myState = state;
- }
-}
\ No newline at end of file
return myContentPanel;
}
+ public JTextField getLoginField() {
+ return myLoginField;
+ }
+
public String getPassword() {
return String.valueOf(myPasswordField.getPassword());
}
import java.io.InputStream;
import java.net.URL;
-class StudyBrowserWindow extends JFrame {
+public class StudyBrowserWindow extends JFrame {
private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
private static final String EVENT_TYPE_CLICK = "click";
private JFXPanel myPanel;
import com.intellij.openapi.project.ProjectUtil;
import com.jetbrains.edu.learning.StudyPluginConfigurator;
import com.jetbrains.edu.learning.StudyUtils;
+import org.jetbrains.annotations.NotNull;
import javax.swing.*;
}
@Override
- public void setText(String text) {
+ public void setText(@NotNull String text) {
StudyPluginConfigurator configurator = StudyUtils.getConfigurator(ProjectUtil.guessCurrentProject(this));
- myBrowserWindow.loadContent(text, configurator);
+ myBrowserWindow.loadContent(text, configurator);
}
}
import com.intellij.facet.ui.FacetValidatorsManager;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.icons.AllIcons;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.DefaultProjectFactory;
+import com.intellij.openapi.project.DefaultProjectFactoryImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.AncestorListenerAdapter;
import com.intellij.util.Consumer;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
-import com.jetbrains.edu.learning.stepic.StudySettings;
+import com.jetbrains.edu.learning.stepic.StepicUser;
import icons.InteractiveLearningIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
+import javax.swing.event.AncestorEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
* author: liana
* data: 7/31/14.
*/
-public class StudyNewProjectPanel{
+public class StudyNewProjectPanel {
+ private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class);
private List<CourseInfo> myAvailableCourses = new ArrayList<CourseInfo>();
private JButton myBrowseButton;
- private JComboBox myCoursesComboBox;
+ private JComboBox<CourseInfo> myCoursesComboBox;
private JButton myRefreshButton;
private JPanel myContentPanel;
private JLabel myAuthorLabel;
private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>";
private static final String INVALID_COURSE = "Selected course is invalid";
private FacetValidatorsManager myValidationManager;
+ private boolean isComboboxInitialized;
- public StudyNewProjectPanel(StudyProjectGenerator generator) {
+ public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator) {
myGenerator = generator;
- myAvailableCourses = myGenerator.getCourses(false);
myBrowseButton.setPreferredSize(new Dimension(28, 28));
myRefreshButton.setPreferredSize(new Dimension(28, 28));
- if (myAvailableCourses.isEmpty()) {
+ initListeners();
+ myRefreshButton.setVisible(true);
+ myRefreshButton.putClientProperty("JButton.buttonType", "null");
+ myRefreshButton.setIcon(AllIcons.Actions.Refresh);
+
+ myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
+ myContentPanel.addAncestorListener(new AncestorListenerAdapter() {
+ @Override
+ public void ancestorMoved(AncestorEvent event) {
+ if (!isComboboxInitialized && myContentPanel.isVisible()) {
+ isComboboxInitialized = true;
+ initCoursesCombobox();
+ }
+ }
+ });
+ }
+
+ private void initCoursesCombobox() {
+ myAvailableCourses =
+ myGenerator.getCoursesUnderProgress(false, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
+ if (myAvailableCourses == null || myAvailableCourses.isEmpty()) {
setError(CONNECTION_ERROR);
}
else {
- for (CourseInfo courseInfo : myAvailableCourses) {
- myCoursesComboBox.addItem(courseInfo);
- }
+ addCoursesToCombobox(myAvailableCourses);
final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses);
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
myGenerator.setSelectedCourse(selectedCourse);
setOK();
}
- initListeners();
- myRefreshButton.setVisible(true);
- myRefreshButton.putClientProperty("JButton.buttonType", "null");
- myRefreshButton.setIcon(AllIcons.Actions.Refresh);
-
- myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
-
}
private void setupBrowseButton() {
private class RefreshActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
- refreshCoursesList();
+ final List<CourseInfo> courses =
+ myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject());
+ if (courses != null) {
+ refreshCoursesList(courses);
+ }
}
}
- private void refreshCoursesList() {
- final List<CourseInfo> courses = myGenerator.getCourses(true);
+ private void refreshCoursesList(@NotNull final List<CourseInfo> courses) {
if (courses.isEmpty()) {
setError(CONNECTION_ERROR);
return;
}
myCoursesComboBox.removeAllItems();
- for (CourseInfo courseInfo : courses) {
- myCoursesComboBox.addItem(courseInfo);
- }
+ addCoursesToCombobox(courses);
myGenerator.setSelectedCourse(StudyUtils.getFirst(courses));
myGenerator.setCourses(courses);
StudyProjectGenerator.flushCache(myAvailableCourses);
}
+ private void addCoursesToCombobox(@NotNull List<CourseInfo> courses) {
+ for (CourseInfo courseInfo : courses) {
+ if (!courseInfo.isAdaptive()) {
+ myCoursesComboBox.addItem(courseInfo);
+ }
+ else {
+ final boolean isLoggedIn = myGenerator.myUser != null;
+ if (isLoggedIn) {
+ myCoursesComboBox.addItem(courseInfo);
+ }
+ }
+ }
+ }
+
/**
* Handles selecting course in combo box
if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
myAuthorLabel.setText("");
myDescriptionLabel.setText("");
+ setError(INVALID_COURSE);
return;
}
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
- myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ?"Author: " + authorsString : "");
+ myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
myDescriptionLabel.setText(selectedCourse.getDescription());
myGenerator.setSelectedCourse(selectedCourse);
protected AddRemoteDialog() {
super(null);
+ setTitle("Login To Stepic");
myRemoteCourse = new StudyAddRemoteCourse();
init();
}
return myRemoteCourse.getContentPanel();
}
+ @Nullable
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return myRemoteCourse.getLoginField();
+ }
+
@Override
protected void doOKAction() {
+ super.doOKAction();
if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) {
myRemoteCourse.setError("Please, enter your login");
return;
return;
}
- super.doOKAction();
- final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword());
- if (!isSuccess) {
- setError("Failed to log in");
- }
- StudySettings.getInstance().setLogin(myRemoteCourse.getLogin());
- StudySettings.getInstance().setPassword(myRemoteCourse.getPassword());
- refreshCoursesList();
+ ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
+ ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
+
+ final StepicUser stepicUser = StudyUtils.execCancelable(() -> EduStepicConnector.login(myRemoteCourse.getLogin(),
+ myRemoteCourse.getPassword()));
+ if (stepicUser != null) {
+ stepicUser.setEmail(myRemoteCourse.getLogin());
+ stepicUser.setPassword(myRemoteCourse.getPassword());
+ myGenerator.myUser = stepicUser;
+
+
+ final List<CourseInfo> courses = myGenerator.getCoursesAsynchronouslyIfNeeded(true);
+ if (courses != null) {
+ ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses));
+ }
+ }
+ else {
+ setError("Failed to login");
+ }
+ }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject());
}
}
-
}
import com.intellij.ui.ColorUtil;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
return scrollPane;
}
- public void setText(String text) {
+ public void setText(@NotNull String text) {
myTaskTextPane.setText(text);
}
}
--- /dev/null
+package com.jetbrains.edu.learning.ui
+
+import com.intellij.openapi.fileEditor.FileEditorManager
+import com.intellij.openapi.fileEditor.FileEditorManagerEvent
+import com.intellij.openapi.fileEditor.FileEditorManagerListener
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.wm.ToolWindow
+import com.jetbrains.edu.learning.StudyUtils
+import com.jetbrains.python.console.PythonConsoleView
+
+
+class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() {
+ override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
+ val currentTask = StudyUtils.getCurrentTask(project)
+ if (currentTask != null) {
+ val sdk = StudyUtils.findSdk(currentTask, project)
+ if (sdk != null) {
+ val testResultsToolWindow = PythonConsoleView(project, "Local test results", sdk);
+ testResultsToolWindow.isEditable = false
+ testResultsToolWindow.isConsoleEditorEnabled = false
+ testResultsToolWindow.prompt = null
+ toolWindow.isToHideOnEmptyContent = true
+
+ val contentManager = toolWindow.contentManager
+ val content = contentManager.factory.createContent(testResultsToolWindow.component, null, false)
+ contentManager.addContent(content)
+
+ project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow))
+ }
+ else {
+ StudyUtils.showNoSdkNotification(currentTask, project)
+ }
+ }
+ }
+
+ fun getFileEditorManagerListener(toolWindow: ToolWindow): FileEditorManagerListener {
+
+ return object : FileEditorManagerListener {
+
+ override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
+ }
+
+ override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
+ toolWindow.setAvailable(false, {})
+ }
+
+ override fun selectionChanged(event: FileEditorManagerEvent) {
+ toolWindow.setAvailable(false, {})
+ }
+ }
+ }
+}
+
+
import com.jetbrains.edu.learning.*;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
+import com.jetbrains.edu.learning.stepic.StepicAdaptiveReactionsPanel;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
private static final String TASK_INFO_ID = "taskInfo";
private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description";
+
private final JBCardLayout myCardLayout;
private final JPanel myContentPanel;
private final OnePixelSplitter mySplitPane;
JPanel toolbarPanel = createToolbarPanel(getActionGroup(project));
setToolbar(toolbarPanel);
- myContentPanel.add(TASK_INFO_ID, createTaskInfoPanel(project));
+ final JPanel panel = new JPanel(new BorderLayout());
+ final Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course != null && course.isAdaptive()) {
+ panel.add(new StepicAdaptiveReactionsPanel(project), BorderLayout.NORTH);
+ }
+ JComponent taskInfoPanel = createTaskInfoPanel(project);
+ panel.add(taskInfoPanel, BorderLayout.CENTER);
+ myContentPanel.add(TASK_INFO_ID, panel);
mySplitPane.setFirstComponent(myContentPanel);
addAdditionalPanels(project);
myCardLayout.show(myContentPanel, TASK_INFO_ID);
project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener);
}
- if (StudyTaskManager.getInstance(project).isTurnEditingMode() || StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
+ if (StudyTaskManager.getInstance(project).isTurnEditingMode() ||
+ StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
TaskFile file = StudyUtils.getSelectedTaskFile(project);
if (file != null) {
VirtualFile taskDir = file.getTask().getTaskDir(project);
setTaskText(taskText, taskDir, project);
-
}
- } else {
+ }
+ else {
setTaskText(taskText, null, project);
}
}
}
}
+
public void dispose() {
}
}
}
- protected abstract void setText(String text);
+ protected abstract void setText(@NotNull String text);
public void setEmptyText(@NotNull Project project) {
if (StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
+import com.jetbrains.edu.learning.StudyProjectComponent;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
final Course course = taskManager.getCourse();
if (course != null) {
-
final StudyToolWindow studyToolWindow;
if (StudyUtils.hasJavaFx() && StudyTaskManager.getInstance(project).shouldUseJavaFx()) {
studyToolWindow = new StudyJavaFxToolWindow();
<SOURCES />
</library>
</orderEntry>
+ <orderEntry type="module" module-name="platform-api" />
+ <orderEntry type="module" module-name="python-community" />
<orderEntry type="module" module-name="xml" />
<orderEntry type="library" name="markdownj" level="project" />
</component>
return text
-def get_file_output(encoding="utf-8", path=sys.argv[-1]):
+def get_file_output(encoding="utf-8", path=sys.argv[-1], arg_string=""):
"""
Returns answer file output
:param encoding: to decode output in python3
"""
import subprocess
- proc = subprocess.Popen([sys.executable, path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ proc = subprocess.Popen([sys.executable, path], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ if arg_string:
+ for arg in arg_string.split("\n"):
+ proc.stdin.write(bytearray(str(arg) + "\n", encoding))
+ proc.stdin.flush()
+
return list(map(lambda x: str(x.decode(encoding)), proc.communicate()[0].splitlines()))
parent = os.path.abspath(os.path.join(path, os.pardir))
python_files = [f for f in os.listdir(parent) if os.path.isfile(os.path.join(parent, f)) and f.endswith(".py")]
for python_file in python_files:
- if python_file == "tests.py": continue
+ if python_file == "tests.py":
+ continue
check_importable_path(os.path.join(parent, python_file))
return
check_importable_path(path)
return windows
+def check_samples(samples=()):
+ """
+ Check script output for all samples. Sample is a two element list, where the first is input and
+ the second is output.
+ """
+ for sample in samples:
+ if len(sample) == 2:
+ output = get_file_output(arg_string=str(sample[0]))
+ if "\n".join(output) != sample[1]:
+ failed(
+ "Test from samples failed: \n \n"
+ "Input:\n{}"
+ "\n \n"
+ "Expected:\n{}"
+ "\n \n"
+ "Your result:\n{}".format(str.strip(sample[0]), str.strip(sample[1]), "\n".join(output)))
+ return
+ set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.")
+
+ passed()
+
+
def run_common_tests(error_text="Please, reload file and try again"):
test_is_initial_text()
test_is_not_empty()
import com.jetbrains.edu.learning.checker.StudyCheckTask;
import com.jetbrains.edu.learning.checker.StudyCheckUtils;
import com.jetbrains.edu.learning.checker.StudyTestRunner;
-import com.jetbrains.edu.learning.checker.StudyTestsOutputParser;
import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
+import com.jetbrains.edu.learning.ui.StudyToolWindow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
}
if (StudyCheckUtils.hasBackgroundProcesses(project)) return;
-
- if (!runTask(project)) return;
+ final Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course != null && !course.isAdaptive() && !runTask(project)) return;
final Task task = studyState.getTask();
final VirtualFile taskDir = studyState.getTaskDir();
final Process testProcess,
final String commandLine) {
return new StudyCheckTask(project, studyState, myCheckInProgress, testProcess, commandLine) {
- @Override
- protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
- ApplicationManager.getApplication().invokeLater(() -> {
- if (myTaskDir == null) return;
- myTaskManger.setStatus(myTask, StudyStatus.Failed);
- for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
- final String name = entry.getKey();
- final TaskFile taskFile = entry.getValue();
- if (taskFile.getAnswerPlaceholders().size() < 2) {
- myTaskManger.setStatus(taskFile, StudyStatus.Failed);
- continue;
- }
- if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
- CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project)));
- }
- }
- StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), project);
- StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
- });
+ @Override
+ protected void onTaskFailed(String message) {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ if (myTaskDir == null) return;
+ myTaskManger.setStatus(myTask, StudyStatus.Failed);
+ for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
+ final String name = entry.getKey();
+ final TaskFile taskFile = entry.getValue();
+ if (taskFile.getAnswerPlaceholders().size() < 2) {
+ myTaskManger.setStatus(taskFile, StudyStatus.Failed);
+ continue;
+ }
+ if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
+ CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
+ StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project);
+ }));
+ }
+ }
+ final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project);
+ if (toolWindow != null) {
+ final Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course != null) {
+ if (course.isAdaptive()) {
+ StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), project);
+ StudyCheckUtils.showTestResultsToolWindow(project, message, false);
+ }
+ else {
+ StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), project);
+ }
}
- };
+ StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
+ }
+ });
+ }
+ };
}
@Nullable
private static VirtualFile getTaskVirtualFile(@NotNull final StudyState studyState,
- @NotNull final Task task,
- @NotNull final VirtualFile taskDir) {
+ @NotNull final Task task,
+ @NotNull final VirtualFile taskDir) {
VirtualFile taskVirtualFile = studyState.getVirtualFile();
for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
String name = entry.getKey();
}
return taskVirtualFile;
}
-
+
@NotNull
@Override
public String getActionId() {
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.DirectoryProjectGenerator;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
-import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
import com.jetbrains.edu.learning.stepic.CourseInfo;
+import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
import com.jetbrains.python.newProject.PythonProjectGenerator;
import icons.InteractiveLearningPythonIcons;
import org.jetbrains.annotations.Nls;
throw new UnsupportedOperationException();
}
public void validate() {
- fireStateChanged();
+ ApplicationManager.getApplication().invokeLater(()->fireStateChanged());
}
});
return settingsPanel.getContentPanel();
}
public List<CourseInfo> getCourses() {
- return myGenerator.getCourses(false);
+ return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject());
}
public void setSelectedCourse(CourseInfo course) {