From 28de7cdb8ac93430d79c49d1d8dd815957ffc7ff Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Mon, 16 Feb 2015 21:14:44 +0300 Subject: [PATCH] push lesson to stepic --- .../resources/META-INF/plugin.xml | 5 + .../edu/coursecreator/CCProjectService.java | 1 + .../actions/CCCreateCourseArchive.java | 99 +------- .../coursecreator/actions/CCPushLesson.java | 49 ++++ .../actions/CCRunTestsAction.java | 2 +- .../coursecreator/actions/CCShowPreview.java | 3 +- python/educational/educational.iml | 1 + .../edu/learning/StudyProjectComponent.java | 3 +- .../edu/learning/StudyTaskManager.java | 4 +- .../courseGeneration/StudyGenerator.java | 55 +---- .../StudyProjectGenerator.java | 26 +- .../src/com/jetbrains/edu/EduUtils.java | 144 ++++++++++- .../jetbrains/edu/courseFormat/Lesson.java | 3 +- .../com/jetbrains/edu/courseFormat/Task.java | 54 ++++- .../jetbrains/edu/courseFormat/TaskFile.java | 4 +- .../edu/stepic/EduStepicConnector.java | 226 +++++++++++++++--- 16 files changed, 476 insertions(+), 203 deletions(-) create mode 100644 python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java diff --git a/python/educational/course-creator/resources/META-INF/plugin.xml b/python/educational/course-creator/resources/META-INF/plugin.xml index ed6c1ef33d23..0bc5838e6dca 100644 --- a/python/educational/course-creator/resources/META-INF/plugin.xml +++ b/python/educational/course-creator/resources/META-INF/plugin.xml @@ -83,6 +83,11 @@ + + + + + \ No newline at end of file diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java index 42d3e08c9758..23ae0bed57a5 100644 --- a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java +++ b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java @@ -173,6 +173,7 @@ public class CCProjectService implements PersistentStateComponent taskFileEntry) { - final String name = taskFileEntry.getKey(); - final TaskFile taskFile = taskFileEntry.getValue(); - VirtualFile file = userFileDir.findChild(name); - if (file != null) { - try { - file.delete(project); - } - catch (IOException e) { - LOG.error(e); - } - } - try { - userFileDir.createChildData(project, name); - } - catch (IOException e) { - LOG.error(e); - } - - file = userFileDir.findChild(name); - assert file != null; - String answerFileName = file.getNameWithoutExtension() + ".answer." + file.getExtension(); - VirtualFile answerFile = answerFileDir.findChild(answerFileName); - if (answerFile == null) { - return; - } - final Document answerDocument = FileDocumentManager.getInstance().getDocument(answerFile); - if (answerDocument == null) { - return; - } - final Document document = FileDocumentManager.getInstance().getDocument(file); - if (document == null) return; - - CommandProcessor.getInstance().executeCommand(project, new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - document.replaceString(0, document.getTextLength(), answerDocument.getCharsSequence()); - } - }); - } - }, "x", "qwe"); - EduDocumentListener listener = new EduDocumentListener(taskFile, false); - document.addDocumentListener(listener); - taskFile.sortAnswerPlaceholders(); - for (int i = taskFile.getAnswerPlaceholders().size() - 1; i >= 0; i--) { - final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i); - replaceAnswerPlaceholder(project, document, answerPlaceholder); - } - CommandProcessor.getInstance().executeCommand(project, new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - FileDocumentManager.getInstance().saveDocument(document); - } - }); - } - }, "x", "qwe"); - document.removeDocumentListener(listener); - } - - private static void replaceAnswerPlaceholder(@NotNull final Project project, - @NotNull final Document document, - @NotNull final AnswerPlaceholder answerPlaceholder) { - final String taskText = answerPlaceholder.getTaskText(); - final int offset = answerPlaceholder.getRealStartOffset(document); - CommandProcessor.getInstance().executeCommand(project, new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - document.replaceString(offset, offset + answerPlaceholder.getPossibleAnswerLength(), taskText); - FileDocumentManager.getInstance().saveDocument(document); - } - }); - } - }, "x", "qwe"); - } - private static void synchronize(@NotNull final Project project) { VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); ProjectView.getInstance(project).refresh(); diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java new file mode 100644 index 000000000000..c48d802150c8 --- /dev/null +++ b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java @@ -0,0 +1,49 @@ +package com.jetbrains.edu.coursecreator.actions; + +import com.intellij.ide.IdeView; +import com.intellij.ide.util.DirectoryChooserUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Lesson; +import com.jetbrains.edu.coursecreator.CCProjectService; +import com.jetbrains.edu.stepic.EduStepicConnector; +import org.jetbrains.annotations.NotNull; + +public class CCPushLesson extends DumbAwareAction { + public CCPushLesson() { + super("Push lesson to Stepic", "Push lesson to Stepic", null); + } + + @Override + public void update(@NotNull AnActionEvent e) { + CCProjectService.setCCActionAvailable(e); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final IdeView view = e.getData(LangDataKeys.IDE_VIEW); + final Project project = e.getData(CommonDataKeys.PROJECT); + if (view == null || project == null) { + return; + } + final Course course = CCProjectService.getInstance(project).getCourse(); + if (course == null) { + return; + } + PsiDirectory lessonDir = DirectoryChooserUtil.getOrChooseDirectory(view);; + if (lessonDir == null || !lessonDir.getName().contains("lesson")) { + return; + } + Lesson lesson = course.getLesson(lessonDir.getName()); + if (lesson == null) { + return; + } + EduStepicConnector.postLesson(project, lesson); + } + +} \ No newline at end of file diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java index 1c69540c6945..f1d9d6cba063 100644 --- a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java +++ b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java @@ -242,7 +242,7 @@ public abstract class CCRunTestsAction extends AnAction { for (Map.Entry entry : task.getTaskFiles().entrySet()) { TaskFile taskFileCopy = new TaskFile(); TaskFile.copy(entry.getValue(), taskFileCopy); - CCCreateCourseArchive.createUserFile(project, taskResourceDir, taskDir, + EduUtils.createStudentFileFromAnswer(project, taskResourceDir, taskDir, new AbstractMap.SimpleEntry(entry.getKey(), taskFileCopy)); } } diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java index f95938961a8b..8f24ed50a13c 100644 --- a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java +++ b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java @@ -35,6 +35,7 @@ import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; import com.intellij.ui.JBColor; import com.jetbrains.edu.EduAnswerPlaceholderPainter; +import com.jetbrains.edu.EduUtils; import com.jetbrains.edu.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.courseFormat.Course; import com.jetbrains.edu.courseFormat.TaskFile; @@ -102,7 +103,7 @@ public class CCShowPreview extends DumbAwareAction { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - CCCreateCourseArchive.createUserFile(project, taskDir.getVirtualFile(), taskDir.getVirtualFile(), + EduUtils.createStudentFileFromAnswer(project, taskDir.getVirtualFile(), taskDir.getVirtualFile(), new AbstractMap.SimpleEntry(taskFileName, taskFileCopy)); } }); diff --git a/python/educational/educational.iml b/python/educational/educational.iml index 7e07d8259f41..4af7e0062118 100644 --- a/python/educational/educational.iml +++ b/python/educational/educational.iml @@ -12,5 +12,6 @@ + \ No newline at end of file diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectComponent.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectComponent.java index 0327f3d3663f..74a28b45de2b 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectComponent.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectComponent.java @@ -32,7 +32,6 @@ import com.jetbrains.edu.courseFormat.Lesson; import com.jetbrains.edu.courseFormat.Task; import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.actions.*; -import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener; import com.jetbrains.edu.learning.ui.StudyCondition; import com.jetbrains.edu.learning.ui.StudyToolWindowFactory; @@ -303,7 +302,7 @@ public class StudyProjectComponent implements ProjectComponent { if (StudyUtils.indexIsValid(taskIndex, tasks)) { final Task task = tasks.get(taskIndex); final TaskFile taskFile = new TaskFile(); - StudyGenerator.initTaskFile(taskFile, task, false); + EduUtils.initTaskFile(taskFile, task, false); taskFile.setUserCreated(true); task.getTaskFiles().put(createdFile.getName(), taskFile); } diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java index 549a6b7d2fff..7044951a9255 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java @@ -12,10 +12,10 @@ import com.intellij.openapi.project.Project; import com.intellij.ui.JBColor; import com.intellij.util.containers.hash.HashMap; import com.intellij.util.xmlb.XmlSerializerUtil; +import com.jetbrains.edu.EduUtils; import com.jetbrains.edu.courseFormat.*; import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.UserTest; -import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -172,7 +172,7 @@ public class StudyTaskManager implements PersistentStateComponent taskList = lesson.getTaskList(); - for (Task task : taskList) { - initTask(task, lesson, isRestarted); - } - } - - /** - * Initializes state of task file - * - * @param lesson lesson which task belongs to - */ - public static void initTask(@NotNull final Task task, final Lesson lesson, boolean isRestarted) { - task.setLesson(lesson); - for (TaskFile taskFile : task.getTaskFiles().values()) { - initTaskFile(taskFile, task, isRestarted); - } - } - - public static void initTaskFile(@NotNull final TaskFile taskFile, final Task task, boolean isRestarted) { - taskFile.setTask(task); - final List answerPlaceholders = taskFile.getAnswerPlaceholders(); - for (AnswerPlaceholder answerPlaceholder : answerPlaceholders) { - initAnswerPlaceholder(answerPlaceholder, taskFile, isRestarted); - } - Collections.sort(answerPlaceholders, new AnswerPlaceholderComparator()); - for (int i = 0; i < answerPlaceholders.size(); i++) { - answerPlaceholders.get(i).setIndex(i); - } - } - - public static void initAnswerPlaceholder(@NotNull final AnswerPlaceholder placeholder, final TaskFile file, boolean isRestarted) { - if (!isRestarted) { - placeholder.setInitialState(new AnswerPlaceholder.MyInitialState(placeholder.getLine(), placeholder.getLength(), - placeholder.getStart())); - } - placeholder.setTaskFile(file); - } - } diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 3969c6977085..278c1d6d8fde 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -20,11 +20,11 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.edu.EduNames; +import com.jetbrains.edu.EduUtils; import com.jetbrains.edu.courseFormat.Course; import com.jetbrains.edu.courseFormat.Lesson; import com.jetbrains.edu.courseFormat.Task; import com.jetbrains.edu.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyLanguageManager; import com.jetbrains.edu.learning.StudyProjectComponent; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; @@ -65,7 +65,7 @@ public class StudyProjectGenerator { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - StudyGenerator.initCourse(course, false); + EduUtils.initCourse(course, false); final File courseDirectory = new File(myCoursesDir, course.getName()); StudyGenerator.createCourse(course, baseDir, courseDirectory, project); course.setCourseDirectory(new File(myCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath()); @@ -138,18 +138,16 @@ public class StudyProjectGenerator { LOG.error("ERROR copying file " + name); } } - StudyLanguageManager languageManager = StudyUtils.getLanguageManager(course); - if (languageManager == null) { - LOG.info("Language manager is null for " + course.getLanguageById().getDisplayName()); - return; - } - final File testsFile = new File(taskDirectory, languageManager.getTestFileName()); - FileUtil.createIfDoesntExist(testsFile); - try { - FileUtil.writeToFile(testsFile, task.getTestsText()); - } - catch (IOException e) { - LOG.error("ERROR copying tests file"); + final Map testsText = task.getTestsText(); + for (Map.Entry entry : testsText.entrySet()) { + final File testsFile = new File(taskDirectory, entry.getKey()); + FileUtil.createIfDoesntExist(testsFile); + try { + FileUtil.writeToFile(testsFile, entry.getValue()); + } + catch (IOException e) { + LOG.error("ERROR copying tests file"); + } } final File taskText = new File(taskDirectory, "task.html"); FileUtil.createIfDoesntExist(taskText); diff --git a/python/educational/src/com/jetbrains/edu/EduUtils.java b/python/educational/src/com/jetbrains/edu/EduUtils.java index 184de8b94a4c..f1fc804c6dca 100644 --- a/python/educational/src/com/jetbrains/edu/EduUtils.java +++ b/python/educational/src/com/jetbrains/edu/EduUtils.java @@ -5,6 +5,7 @@ import com.intellij.ide.projectView.actions.MarkRootActionBase; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; @@ -17,8 +18,7 @@ import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; -import com.jetbrains.edu.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.courseFormat.TaskFile; +import com.jetbrains.edu.courseFormat.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,6 +26,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; public class EduUtils { private EduUtils() { @@ -133,4 +136,141 @@ public class EduUtils { VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); } + public static void createStudentFileFromAnswer(@NotNull final Project project, + @NotNull final VirtualFile userFileDir, + @NotNull final VirtualFile answerFileDir, + @NotNull final Map.Entry taskFileEntry) { + final String name = taskFileEntry.getKey(); + final TaskFile taskFile = taskFileEntry.getValue(); + VirtualFile file = userFileDir.findChild(name); + if (file != null) { + try { + file.delete(project); + } + catch (IOException e) { + LOG.error(e); + } + } + try { + userFileDir.createChildData(project, name); + } + catch (IOException e) { + LOG.error(e); + } + + file = userFileDir.findChild(name); + assert file != null; + String answerFileName = file.getNameWithoutExtension() + ".answer." + file.getExtension(); + VirtualFile answerFile = answerFileDir.findChild(answerFileName); + if (answerFile == null) { + return; + } + final Document answerDocument = FileDocumentManager.getInstance().getDocument(answerFile); + if (answerDocument == null) { + return; + } + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document == null) return; + + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + document.replaceString(0, document.getTextLength(), answerDocument.getCharsSequence()); + } + }); + } + }, "Create Student File", "Create Student File"); + EduDocumentListener listener = new EduDocumentListener(taskFile, false); + document.addDocumentListener(listener); + taskFile.sortAnswerPlaceholders(); + for (int i = taskFile.getAnswerPlaceholders().size() - 1; i >= 0; i--) { + final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i); + replaceAnswerPlaceholder(project, document, answerPlaceholder); + } + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + FileDocumentManager.getInstance().saveDocument(document); + } + }); + } + }, "Create Student File", "Create Student File"); + document.removeDocumentListener(listener); + } + + private static void replaceAnswerPlaceholder(@NotNull final Project project, + @NotNull final Document document, + @NotNull final AnswerPlaceholder answerPlaceholder) { + final String taskText = answerPlaceholder.getTaskText(); + final int offset = answerPlaceholder.getRealStartOffset(document); + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + document.replaceString(offset, offset + answerPlaceholder.getPossibleAnswerLength(), taskText); + FileDocumentManager.getInstance().saveDocument(document); + } + }); + } + }, "Replace Answer Placeholders", "Replace Answer Placeholders"); + } + + + /** + * Initializes state of course + */ + public static void initCourse(@NotNull final Course course, boolean isRestarted) { + for (Lesson lesson : course.getLessons()) { + initLesson(lesson, course, isRestarted); + } + } + + public static void initLesson(@NotNull final Lesson lesson, final Course course, boolean isRestarted) { + lesson.setCourse(course); + final List taskList = lesson.getTaskList(); + for (Task task : taskList) { + initTask(task, lesson, isRestarted); + } + } + + /** + * Initializes state of task file + * + * @param lesson lesson which task belongs to + */ + public static void initTask(@NotNull final Task task, final Lesson lesson, boolean isRestarted) { + task.setLesson(lesson); + for (TaskFile taskFile : task.getTaskFiles().values()) { + initTaskFile(taskFile, task, isRestarted); + } + } + + public static void initTaskFile(@NotNull final TaskFile taskFile, final Task task, boolean isRestarted) { + taskFile.setTask(task); + final List answerPlaceholders = taskFile.getAnswerPlaceholders(); + for (AnswerPlaceholder answerPlaceholder : answerPlaceholders) { + initAnswerPlaceholder(answerPlaceholder, taskFile, isRestarted); + } + Collections.sort(answerPlaceholders, new AnswerPlaceholderComparator()); + for (int i = 0; i < answerPlaceholders.size(); i++) { + answerPlaceholders.get(i).setIndex(i); + } + } + + public static void initAnswerPlaceholder(@NotNull final AnswerPlaceholder placeholder, final TaskFile file, boolean isRestarted) { + if (!isRestarted) { + placeholder.setInitialState(new AnswerPlaceholder.MyInitialState(placeholder.getLine(), placeholder.getLength(), + placeholder.getStart())); + } + placeholder.setTaskFile(file); + } + } diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java b/python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java index e33ba3d397d5..f687fde80ca1 100644 --- a/python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java +++ b/python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java @@ -12,7 +12,7 @@ import java.util.List; public class Lesson { @Transient - String id; + public int id; @Transient public List steps; @Transient @@ -20,6 +20,7 @@ public class Lesson { @Transient Boolean is_public; @Expose + @SerializedName("title") private String name; @Expose @SerializedName("task_list") diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/Task.java b/python/educational/src/com/jetbrains/edu/courseFormat/Task.java index b398feb1e865..e381c6d61697 100644 --- a/python/educational/src/com/jetbrains/edu/courseFormat/Task.java +++ b/python/educational/src/com/jetbrains/edu/courseFormat/Task.java @@ -2,6 +2,8 @@ package com.jetbrains.edu.courseFormat; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.xmlb.annotations.Transient; @@ -24,7 +26,7 @@ public class Task { public Map taskFiles = new HashMap(); private String text; - private String testsText; + private Map testsText = new HashMap(); @Transient private Lesson myLesson; @@ -58,12 +60,12 @@ public class Task { myIndex = index; } - public String getTestsText() { + public Map getTestsText() { return testsText; } - public void setTestsText(@NotNull final String testsText) { - this.testsText = testsText; + public void setTestsTexts(String name, String text) { + testsText.put(name, text); } public Map getTaskFiles() { @@ -101,9 +103,9 @@ public class Task { } @Nullable - public VirtualFile getTaskDir(Project project) { - String lessonDirName = EduNames.LESSON + String.valueOf(myLesson.getIndex() + 1); - String taskDirName = EduNames.TASK + String.valueOf(myIndex + 1); + public VirtualFile getTaskDir(@NotNull final Project project) { + String lessonDirName = EduNames.LESSON + String.valueOf(myLesson.getIndex()); + String taskDirName = EduNames.TASK + String.valueOf(myIndex); VirtualFile courseDir = project.getBaseDir(); if (courseDir != null) { VirtualFile lessonDir = courseDir.findChild(lessonDirName); @@ -114,6 +116,44 @@ public class Task { return null; } + @Nullable + public String getTaskText(@NotNull final Project project) { + final VirtualFile taskDir = getTaskDir(project); + if (taskDir != null) { + final VirtualFile file = taskDir.findChild(EduNames.TASK_HTML); + if (file == null) return null; + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document != null) { + return document.getImmutableCharSequence().toString(); + } + } + + return null; + } + + @Nullable + public String getTestsText(@NotNull final Project project) { + final VirtualFile taskDir = getTaskDir(project); + if (taskDir != null) { + final VirtualFile file = taskDir.findChild("tests.py"); + if (file == null) return null; + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document != null) { + return document.getImmutableCharSequence().toString(); + } + } + + return null; + } + + public Document getDocument(Project project, String name) { + final VirtualFile taskDirectory = getTaskDir(project); + if (taskDirectory == null) return null; + final VirtualFile file = taskDirectory.findChild(name); + if (file == null) return null; + return FileDocumentManager.getInstance().getDocument(file); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java b/python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java index 9ad9d96ab78e..1e6fa65bf5ab 100644 --- a/python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java +++ b/python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java @@ -18,12 +18,14 @@ import java.util.List; */ public class TaskFile { - @SerializedName("task_windows") + @SerializedName("placeholders") @Expose private List myAnswerPlaceholders = new ArrayList(); private int myIndex = -1; + @Expose public String name; + @Expose public String text; @Transient private Task myTask; @Transient diff --git a/python/educational/src/com/jetbrains/edu/stepic/EduStepicConnector.java b/python/educational/src/com/jetbrains/edu/stepic/EduStepicConnector.java index c3e781e876d8..e91c633834eb 100644 --- a/python/educational/src/com/jetbrains/edu/stepic/EduStepicConnector.java +++ b/python/educational/src/com/jetbrains/edu/stepic/EduStepicConnector.java @@ -3,31 +3,52 @@ package com.jetbrains.edu.stepic; 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.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.io.HttpRequests; +import com.intellij.util.net.ssl.CertificateManager; +import com.jetbrains.edu.EduUtils; import com.jetbrains.edu.courseFormat.Course; import com.jetbrains.edu.courseFormat.Lesson; import com.jetbrains.edu.courseFormat.Task; import com.jetbrains.edu.courseFormat.TaskFile; +import org.apache.http.Header; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.cookie.BasicClientCookie; +import org.apache.http.message.BasicHeader; +import org.apache.http.util.EntityUtils; import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; +import java.util.*; public class EduStepicConnector { private static final String stepicApiUrl = "https://stepic.org/api/"; private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName()); + private static final String ourDomain = "stepic.org"; + private static String ourSessionId = "524iethiwju2tjywaqmf7tbwx0p0jk1b"; + private static String ourCSRFToken = "LJ9n6OyLVA7hxU94dlYWUu65MF51Nx37"; - private EduStepicConnector() {} + private EduStepicConnector() { + } @NotNull public static List getCourses() { try { - return HttpRequests.request(stepicApiUrl + "courses/93").connect(new HttpRequests.RequestProcessor>() { + return HttpRequests.request(stepicApiUrl + "courses/99").connect(new HttpRequests.RequestProcessor>() { @Override public List process(@NotNull HttpRequests.Request request) throws IOException { @@ -79,16 +100,16 @@ public class EduStepicConnector { public static List getLessons(int sectionId) throws IOException { final List lessons = - HttpRequests.request(stepicApiUrl + "sections/" + String.valueOf(sectionId)) - .connect(new HttpRequests.RequestProcessor>() { + HttpRequests.request(stepicApiUrl + "sections/" + String.valueOf(sectionId)) + .connect(new HttpRequests.RequestProcessor>() { - @Override - public List process(@NotNull HttpRequests.Request request) throws IOException { - final BufferedReader reader = request.getReader(); - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - return gson.fromJson(reader, Section.class).lessons; - } - }); + @Override + public List process(@NotNull HttpRequests.Request request) throws IOException { + final BufferedReader reader = request.getReader(); + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + return gson.fromJson(reader, Course.class).getLessons(); + } + }); for (Lesson lesson : lessons) { lesson.taskList = new ArrayList(); for (Integer s : lesson.steps) { @@ -96,7 +117,9 @@ public class EduStepicConnector { final Task task = new Task(); task.setName(step.name); task.setText(step.text); - task.setTestsText(step.options.test); + for (TestFileWrapper wrapper : step.options.test) { + task.setTestsTexts(wrapper.name, wrapper.text); + } task.taskFiles = new HashMap(); // TODO: it looks like we don't need taskFiles as map anymore if (step.options.files != null) { @@ -120,34 +143,181 @@ public class EduStepicConnector { return gson.fromJson(reader, StepContainer.class).steps.get(0).block; } }); - } - private static class Section { - public List lessons; + + + public static boolean postLesson(Project project, @NotNull final Lesson lesson) { + final HttpPost request = new HttpPost(stepicApiUrl + "lessons"); + final ArrayList
headers = getHeaders(request); + HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()); + final BasicCookieStore cookieStore = getCookies(); + final CloseableHttpClient client = builder.setDefaultHeaders(headers).setDefaultCookieStore(cookieStore).build(); + + String requestBody = new Gson().toJson(new LessonWrapper(lesson)); + request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); + + try { + final CloseableHttpResponse response = client.execute(request); + final String responseString = EntityUtils.toString(response.getEntity()); + final StatusLine line = response.getStatusLine(); + if (line.getStatusCode() != 201) { + LOG.error("Failed to push " + EntityUtils.toString(response.getEntity())); + return false; + } + final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0); + for (Task task : lesson.getTaskList()) { + postTask(project, task, postedLesson.id); + } + } + catch (IOException e) { + LOG.error(e.getMessage()); + } + return false; + } + + public static boolean postTask(Project project, @NotNull final Task task, int id) { + final HttpPost request = new HttpPost(stepicApiUrl + "step-sources"); + final ArrayList
headers = getHeaders(request); + HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()); + final BasicCookieStore cookieStore = getCookies(); + final CloseableHttpClient client = builder.setDefaultHeaders(headers).setDefaultCookieStore(cookieStore).build(); + final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); + String requestBody = gson.toJson(new StepSourceWrapper(project, task, id)); + request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); + + try { + final CloseableHttpResponse response = client.execute(request); + final StatusLine line = response.getStatusLine(); + if (line.getStatusCode() != 201) { + LOG.error("Failed to push " + EntityUtils.toString(response.getEntity())); + } + return line.getStatusCode() == 201; + } + catch (IOException e) { + LOG.error(e.getMessage()); + } + + return false; + } + + private static BasicCookieStore getCookies() { + final BasicCookieStore cookieStore = new BasicCookieStore(); + final BasicClientCookie sessionid = new BasicClientCookie("sessionid", ourSessionId); + sessionid.setDomain(ourDomain); + sessionid.setPath("/"); + + cookieStore.addCookie(sessionid); + final BasicClientCookie csrfToken = new BasicClientCookie("csrftoken", ourCSRFToken); + csrfToken.setDomain(ourDomain); + csrfToken.setPath("/"); + + cookieStore.addCookie(csrfToken); + return cookieStore; + } + + private static ArrayList
getHeaders(HttpPost request) { + final ArrayList
headers = new ArrayList
(); + headers.add(new BasicHeader("referer", "https://stepic.org")); + headers.add(new BasicHeader("X-CSRFToken", ourCSRFToken)); + headers.add(new BasicHeader("content-type", "application/json")); + request.setHeaders(headers.toArray(new Header[headers.size()])); + return headers; } private static class StepContainer { - List steps; + List steps; } private static class Step { - StepOptions options; - String text; - String name; + @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 { - String test; - List files; - } + @Expose List test; + @Expose String title; //HERE + @Expose List files; + @Expose String text; - private static class StepRaw { - Step block; + public static StepOptions fromTask(final Project project, @NotNull final Task task) { + final StepOptions source = new StepOptions(); + + final String text = task.getTestsText(project); + source.test = Collections.singletonList(new TestFileWrapper("tests.py", text)); + source.files = new ArrayList(); + source.title = task.getName(); + for (final Map.Entry entry : task.getTaskFiles().entrySet()) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final VirtualFile taskDir = task.getTaskDir(project); + EduUtils.createStudentFileFromAnswer(project, taskDir, taskDir, entry); + } + }); + final TaskFile taskFile = entry.getValue(); + taskFile.name = entry.getKey(); + final Document document = task.getDocument(project, taskFile.name); + if (document != null) { + source.text = document.getImmutableCharSequence().toString(); + taskFile.text = document.getImmutableCharSequence().toString(); + } + source.files.add(taskFile); + } + return source; + } } private static class CoursesContainer { public List courses; } + static class StepSourceWrapper { + @Expose + StepSource stepSource; + + public StepSourceWrapper(Project project, Task task, int id) { + stepSource = new StepSource(project, task, id); + } + } + + static class LessonWrapper { + Lesson lesson; + + public LessonWrapper(Lesson lesson) { + this.lesson = lesson; + } + } + + static class StepSource { + @Expose Step block; + @Expose int position = 0; + @Expose int lesson = 0; + + public StepSource(Project project, Task task, int id) { + lesson = id; + 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; + } + } + }