diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java index d4660b605369..34f5e87f1bb9 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java @@ -29,11 +29,11 @@ import java.util.Map; /** * Implementation of task which contains task files, tests, input file for tests * - * To implement new task there are 3 steps to be done: + * To implement new task there are 4 steps to be done: * - extend Task class * - go to Lesson and update elementTypes in taskList AbstractCollection. Needed for proper xml serialization - * - update com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector#typeSupported(java.lang.String) * - Update TaskSerializer and TaskDeserializer in StudySerializationUtil to handle json serialization + * - for Adaptive tasks update taskTypes in EduAdaptiveStepicConnector so new task type can be added to a course */ public abstract class Task implements StudyItem { @Expose private String name; diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 257bcd769e43..54dbb355cb07 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -1,5 +1,6 @@ package com.jetbrains.edu.learning.stepic; +import com.google.common.collect.ImmutableMap; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -13,6 +14,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; @@ -47,10 +49,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getStep; @@ -63,10 +62,6 @@ public class EduAdaptiveStepicConnector { public static final String LOADING_NEXT_RECOMMENDATION = "Loading Next Recommendation"; private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class); private static final int CONNECTION_TIMEOUT = 60 * 1000; - private static final String CODE_TASK_TYPE = "code"; - private static final String CHOICE_TYPE_TEXT = "choice"; - private static final String TEXT_STEP_TYPE = "text"; - private static final String TASK_NAME = "task"; @Nullable public static Task getNextRecommendation(@NotNull Project project, @NotNull RemoteCourse course) { @@ -111,8 +106,9 @@ public class EduAdaptiveStepicConnector { for (int stepId : realLesson.steps) { StepicWrappers.StepSource step = getStep(stepId); String stepType = step.block.name; - if (typeSupported(stepType)) { - final Task taskFromStep = getTask(course, realLesson.getName(), step, stepId, user.getId()); + StepikTaskBuilder taskBuilder = new StepikTaskBuilder(course, realLesson.getName(), step, stepId, user.getId()); + if (taskBuilder.isSupported(stepType)) { + final Task taskFromStep = taskBuilder.createTask(stepType); if (taskFromStep != null) return taskFromStep; } else { @@ -148,109 +144,6 @@ public class EduAdaptiveStepicConnector { return getNextRecommendation(project, course); } - private static boolean typeSupported(String stepType) { - return CODE_TASK_TYPE.equals(stepType) - || CHOICE_TYPE_TEXT.equals(stepType) - || TEXT_STEP_TYPE.equals(stepType) - || stepType.startsWith(EduStepicNames.PYCHARM_PREFIX); - } - - @Nullable - private static Task getTask(@NotNull RemoteCourse course, - @NotNull String name, - @NotNull StepicWrappers.StepSource step, - int stepId, int userId) throws IOException { - final String stepType = step.block.name; - Language language = course.getLanguageById(); - if (language == null) { - LOG.warn("Language is null for the course: " + course.getName()); - return null; - } - - if (stepType.equals(CODE_TASK_TYPE)) { - return getCodeTaskFromStep(language, step.block, name, stepId); - } - else if (stepType.equals(CHOICE_TYPE_TEXT)) { - return getChoiceTaskFromStep(language, name, step.block, stepId, userId); - } - else if (stepType.startsWith(EduStepicNames.PYCHARM_PREFIX)) { - return EduStepicConnector.createTask(stepId); - } - else if (stepType.equals(TEXT_STEP_TYPE)) { - return getTheoryTaskFromStep(language, name, step.block, stepId); - } - - return null; - } - - @Nullable - private static String getTaskFileName(@NotNull Language language) { - // This is a hacky way to how we should name task file. - // It's assumed that if test's name is capitalized we need to capitalize task file name too. - String testFileName = EduPluginConfigurator.INSTANCE.forLanguage(language).getTestFileName(); - boolean capitalize = !testFileName.isEmpty() && Character.isUpperCase(testFileName.charAt(0)); - - LanguageFileType type = language.getAssociatedFileType(); - if (type == null) { - LOG.warn("Failed to create task file name: associated file type for " + language + " is null"); - return null; - } - - return (capitalize ? StringUtil.capitalize(TASK_NAME) : TASK_NAME) + "." + type.getDefaultExtension(); - } - - @NotNull - private static Task getTheoryTaskFromStep(@NotNull Language language, - @NotNull String lessonName, - @NotNull StepicWrappers.Step block, - int stepId) { - final Task task = new TheoryTask(lessonName); - task.setStepId(stepId); - task.addTaskText(EduNames.TASK, block.text); - String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(language).getLineCommentPrefix(); - String taskFileName = getTaskFileName(language); - - if (taskFileName != null) { - createMockTaskFile(task, commentPrefix + " this is a theory task. You can use this editor as a playground", taskFileName); - } - return task; - } - - @NotNull - private static Task getChoiceTaskFromStep(@NotNull Language language, - @NotNull String name, - @NotNull StepicWrappers.Step block, - int stepId, int userId) { - final ChoiceTask task = new ChoiceTask(name); - task.setStepId(stepId); - task.addTaskText(EduNames.TASK, block.text); - - final StepicWrappers.AdaptiveAttemptWrapper.Attempt attempt = getAttemptForStep(stepId, userId); - if (attempt != null) { - final StepicWrappers.AdaptiveAttemptWrapper.Dataset dataset = attempt.dataset; - if (dataset != null) { - task.setChoiceVariants(dataset.options); - task.setMultipleChoice(dataset.is_multiple_choice); - } - else { - LOG.warn("Dataset for step " + stepId + " is null"); - } - } - String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(language).getLineCommentPrefix(); - String taskFileName = getTaskFileName(language); - if (taskFileName != null) { - createMockTaskFile(task, commentPrefix + " you can experiment here, it won't be checked", taskFileName); - } - return task; - } - - private static void createMockTaskFile(@NotNull Task task, @NotNull String editorText, @NotNull String taskFileName) { - final TaskFile taskFile = new TaskFile(); - taskFile.text = editorText; - taskFile.name = taskFileName; - task.taskFiles.put(taskFile.name, taskFile); - } - @Nullable private static StepicWrappers.AdaptiveAttemptWrapper.Attempt getAttemptForStep(int stepId, int userId) { try { @@ -469,66 +362,6 @@ public class EduAdaptiveStepicConnector { } } - @NotNull - private static Task getCodeTaskFromStep(@NotNull Language language, - @NotNull StepicWrappers.Step step, - @NotNull String name, - int lessonID) { - final Task task = new CodeTask(name); - task.setStepId(lessonID); - - task.setStatus(StudyStatus.Unchecked); - final StringBuilder taskDescription = new StringBuilder(step.text); - if (step.options.samples != null) { - taskDescription.append("
"); - for (List sample : step.options.samples) { - if (sample.size() == 2) { - taskDescription.append("Sample Input:
"); - taskDescription.append(StringUtil.replace(sample.get(0), "\n", "
")); - taskDescription.append("
"); - taskDescription.append("Sample Output:
"); - taskDescription.append(StringUtil.replace(sample.get(1), "\n", "
")); - taskDescription.append("

"); - } - } - } - - if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) { - taskDescription.append("
").append("Memory limit: ").append(step.options.executionMemoryLimit).append(" Mb").append("
") - .append("Time limit: ").append(step.options.executionTimeLimit).append("s").append("

"); - } - task.addTaskText(EduNames.TASK, taskDescription.toString()); - - if (step.options.test != null) { - for (StepicWrappers.FileWrapper wrapper : step.options.test) { - task.addTestsTexts(wrapper.name, wrapper.text); - } - } - else { - //TODO: to be moved - if (language.isKindOf("Python") && step.options.samples != null) { - createTestFileFromSamples(task, step.options.samples); - } - } - - task.taskFiles = new HashMap<>(); - if (step.options.files != null) { - for (TaskFile taskFile : step.options.files) { - task.taskFiles.put(taskFile.name, taskFile); - } - } - else { - final String templateForTask = getCodeTemplateForTask(language, step.options.codeTemplates); - String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(language).getLineCommentPrefix(); - String text = templateForTask == null ? (commentPrefix + " write your answer here \n") : templateForTask; - String taskFileName = getTaskFileName(language); - if (taskFileName != null) { - createMockTaskFile(task, text, taskFileName); - } - } - return task; - } - private static String getCodeTemplateForTask(@NotNull Language language, @Nullable LinkedTreeMap codeTemplates) { if (codeTemplates != null) { @@ -555,8 +388,10 @@ public class EduAdaptiveStepicConnector { if (result.getStatus() == StudyStatus.Failed) { try { createNewAttempt(task.getStepId()); - final Task updatedTask = - getTask((RemoteCourse)task.getLesson().getCourse(), task.getName(), getStep(task.getStepId()), task.getStepId(), user.getId()); + StepicWrappers.StepSource step = getStep(task.getStepId()); + StepikTaskBuilder taskBuilder = new StepikTaskBuilder((RemoteCourse)task.getLesson().getCourse(), task.getName(), + step, task.getStepId(), user.getId()); + final Task updatedTask = taskBuilder.createTask(step.block.name); if (updatedTask instanceof ChoiceTask) { final List variants = ((ChoiceTask)updatedTask).getChoiceVariants(); task.setChoiceVariants(variants); @@ -756,4 +591,171 @@ public class EduAdaptiveStepicConnector { return "
" + "Open on Stepik" + "
"; } + + private static class StepikTaskBuilder { + private static final String TASK_NAME = "task"; + private int myStepId; + private int myUserId; + private final String myName; + private final Language myLanguage; + private StepicWrappers.Step myStep; + private final Map> taskTypes = ImmutableMap.of( + "code", () -> codeTask(), + "choice", () -> choiceTask(), + "text", () -> theoryTask(), + "task", () -> pycharmTask() + ); + + public StepikTaskBuilder(@NotNull RemoteCourse course, + @NotNull String name, + @NotNull StepicWrappers.StepSource step, + int stepId, int userId) { + myName = name; + myStep = step.block; + myStepId = stepId; + myUserId = userId; + myLanguage = course.getLanguageById(); + } + + @Nullable + public Task createTask(String type) { + return taskTypes.get(type).compute(); + } + + public boolean isSupported(String type) { + return taskTypes.containsKey(type); + } + + @NotNull + private CodeTask codeTask() { + CodeTask task = new CodeTask(myName); + task.setStepId(myStepId); + + task.setStatus(StudyStatus.Unchecked); + final StringBuilder taskDescription = new StringBuilder(myStep.text); + if (myStep.options.samples != null) { + taskDescription.append("
"); + for (List sample : myStep.options.samples) { + if (sample.size() == 2) { + taskDescription.append("Sample Input:
"); + taskDescription.append(StringUtil.replace(sample.get(0), "\n", "
")); + taskDescription.append("
"); + taskDescription.append("Sample Output:
"); + taskDescription.append(StringUtil.replace(sample.get(1), "\n", "
")); + taskDescription.append("

"); + } + } + } + + if (myStep.options.executionMemoryLimit != null && myStep.options.executionTimeLimit != null) { + taskDescription.append("
").append("Memory limit: ").append(myStep.options.executionMemoryLimit).append(" Mb") + .append("
") + .append("Time limit: ").append(myStep.options.executionTimeLimit).append("s").append("

"); + } + task.addTaskText(EduNames.TASK, taskDescription.toString()); + + if (myStep.options.test != null) { + for (StepicWrappers.FileWrapper wrapper : myStep.options.test) { + task.addTestsTexts(wrapper.name, wrapper.text); + } + } + else { + if (myLanguage.isKindOf("Python") && myStep.options.samples != null) { + createTestFileFromSamples(task, myStep.options.samples); + } + } + + task.taskFiles = new HashMap<>(); + if (myStep.options.files != null) { + for (TaskFile taskFile : myStep.options.files) { + task.taskFiles.put(taskFile.name, taskFile); + } + } + else { + final String templateForTask = getCodeTemplateForTask(myLanguage, myStep.options.codeTemplates); + String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(myLanguage).getLineCommentPrefix(); + String text = templateForTask == null ? (commentPrefix + " write your answer here \n") : templateForTask; + String taskFileName = getTaskFileName(myLanguage); + if (taskFileName != null) { + createMockTaskFile(task, text, taskFileName); + } + } + return task; + } + + @NotNull + private ChoiceTask choiceTask() { + ChoiceTask task = new ChoiceTask(myName); + task.setStepId(myStepId); + task.addTaskText(EduNames.TASK, myStep.text); + + final StepicWrappers.AdaptiveAttemptWrapper.Attempt attempt = getAttemptForStep(myStepId, myUserId); + if (attempt != null) { + final StepicWrappers.AdaptiveAttemptWrapper.Dataset dataset = attempt.dataset; + if (dataset != null) { + task.setChoiceVariants(dataset.options); + task.setMultipleChoice(dataset.is_multiple_choice); + } + else { + LOG.warn("Dataset for step " + myStepId + " is null"); + } + } + String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(myLanguage).getLineCommentPrefix(); + String taskFileName = getTaskFileName(myLanguage); + if (taskFileName != null) { + createMockTaskFile(task, commentPrefix + " you can experiment here, it won't be checked", taskFileName); + } + + return task; + } + + @NotNull + private TheoryTask theoryTask() { + TheoryTask task = new TheoryTask(myName); + task.setStepId(myStepId); + task.addTaskText(EduNames.TASK, myStep.text); + String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(myLanguage).getLineCommentPrefix(); + String taskFileName = getTaskFileName(myLanguage); + + if (taskFileName != null) { + createMockTaskFile(task, commentPrefix + " this is a theory task. You can use this editor as a playground", taskFileName); + } + return task; + } + + @Nullable + private PyCharmTask pycharmTask() { + try { + return (PyCharmTask)EduStepicConnector.createTask(myStepId); + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + + return null; + } + + private static void createMockTaskFile(@NotNull Task task, @NotNull String editorText, @NotNull String taskFileName) { + final TaskFile taskFile = new TaskFile(); + taskFile.text = editorText; + taskFile.name = taskFileName; + task.taskFiles.put(taskFile.name, taskFile); + } + + @Nullable + private static String getTaskFileName(@NotNull Language language) { + // This is a hacky way to how we should name task file. + // It's assumed that if test's name is capitalized we need to capitalize task file name too. + String testFileName = EduPluginConfigurator.INSTANCE.forLanguage(language).getTestFileName(); + boolean capitalize = !testFileName.isEmpty() && Character.isUpperCase(testFileName.charAt(0)); + + LanguageFileType type = language.getAssociatedFileType(); + if (type == null) { + LOG.warn("Failed to create task file name: associated file type for " + language + " is null"); + return null; + } + + return (capitalize ? StringUtil.capitalize(TASK_NAME) : TASK_NAME) + "." + type.getDefaultExtension(); + } + } }