EDU-338 Add supported python versions info to the course.json
[idea/community.git] / python / educational-core / course-creator / src / com / jetbrains / edu / coursecreator / CCUtils.java
1 package com.jetbrains.edu.coursecreator;
2
3 import com.google.common.base.Predicate;
4 import com.google.common.collect.Collections2;
5 import com.intellij.ide.projectView.actions.MarkRootActionBase;
6 import com.intellij.lang.Language;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.diagnostic.Logger;
9 import com.intellij.openapi.module.Module;
10 import com.intellij.openapi.project.DumbModePermission;
11 import com.intellij.openapi.project.DumbService;
12 import com.intellij.openapi.project.Project;
13 import com.intellij.openapi.roots.ContentEntry;
14 import com.intellij.openapi.roots.ModifiableRootModel;
15 import com.intellij.openapi.roots.ModuleRootManager;
16 import com.intellij.openapi.util.Ref;
17 import com.intellij.openapi.util.io.FileUtil;
18 import com.intellij.openapi.vfs.LocalFileSystem;
19 import com.intellij.openapi.vfs.VirtualFile;
20 import com.intellij.psi.PsiDirectory;
21 import com.intellij.util.Function;
22 import com.jetbrains.edu.learning.StudyTaskManager;
23 import com.jetbrains.edu.learning.StudyUtils;
24 import com.jetbrains.edu.learning.core.EduUtils;
25 import com.jetbrains.edu.learning.courseFormat.Course;
26 import com.jetbrains.edu.learning.courseFormat.StudyItem;
27 import com.jetbrains.edu.learning.courseFormat.Task;
28 import com.jetbrains.edu.learning.courseFormat.TaskFile;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.Map;
38
39 public class CCUtils {
40   public static final String ANSWER_EXTENSION_DOTTED = ".answer.";
41   private static final Logger LOG = Logger.getInstance(CCUtils.class);
42   public static final String GENERATED_FILES_FOLDER = ".coursecreator";
43   public static final String COURSE_MODE = "Course Creator";
44
45   @Nullable
46   public static CCLanguageManager getStudyLanguageManager(@NotNull final Course course) {
47     Language language = Language.findLanguageByID(course.getLanguageID());
48     return language == null ? null : CCLanguageManager.INSTANCE.forLanguage(language);
49   }
50
51   /**
52    * This method decreases index and updates directory names of
53    * all tasks/lessons that have higher index than specified object
54    *
55    * @param dirs         directories that are used to get tasks/lessons
56    * @param getStudyItem function that is used to get task/lesson from VirtualFile. This function can return null
57    * @param threshold    index is used as threshold
58    * @param prefix       task or lesson directory name prefix
59    */
60   public static void updateHigherElements(VirtualFile[] dirs,
61                                           @NotNull final Function<VirtualFile, ? extends StudyItem> getStudyItem,
62                                           final int threshold,
63                                           final String prefix,
64                                           final int delta) {
65     ArrayList<VirtualFile> dirsToRename = new ArrayList<VirtualFile>
66       (Collections2.filter(Arrays.asList(dirs), new Predicate<VirtualFile>() {
67         @Override
68         public boolean apply(VirtualFile dir) {
69           final StudyItem item = getStudyItem.fun(dir);
70           if (item == null) {
71             return false;
72           }
73           int index = item.getIndex();
74           return index > threshold;
75         }
76       }));
77     Collections.sort(dirsToRename, (o1, o2) -> {
78       StudyItem item1 = getStudyItem.fun(o1);
79       StudyItem item2 = getStudyItem.fun(o2);
80       //if we delete some dir we should start increasing numbers in dir names from the end
81       return (-delta) * EduUtils.INDEX_COMPARATOR.compare(item1, item2);
82     });
83
84     for (final VirtualFile dir : dirsToRename) {
85       final StudyItem item = getStudyItem.fun(dir);
86       final int newIndex = item.getIndex() + delta;
87       item.setIndex(newIndex);
88       ApplicationManager.getApplication().runWriteAction(new Runnable() {
89         @Override
90         public void run() {
91           try {
92             dir.rename(this, prefix + newIndex);
93           }
94           catch (IOException e) {
95             LOG.error(e);
96           }
97         }
98       });
99     }
100   }
101
102   public static boolean isLessonDir(PsiDirectory sourceDirectory) {
103     if (sourceDirectory == null) {
104       return false;
105     }
106     Project project = sourceDirectory.getProject();
107     Course course = StudyTaskManager.getInstance(project).getCourse();
108     if (course != null && isCourseCreator(project) && course.getLesson(sourceDirectory.getName()) != null) {
109       return true;
110     }
111     return false;
112   }
113
114
115   public static VirtualFile getGeneratedFilesFolder(@NotNull Project project, @NotNull Module module) {
116     VirtualFile baseDir = project.getBaseDir();
117     VirtualFile folder = baseDir.findChild(GENERATED_FILES_FOLDER);
118     if (folder != null) {
119       return folder;
120     }
121     final Ref<VirtualFile> generatedRoot = new Ref<>();
122     DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
123       @Override
124       public void run() {
125         ApplicationManager.getApplication().runWriteAction(new Runnable() {
126           @Override
127           public void run() {
128             try {
129               generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER));
130               final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel();
131               ContentEntry entry = MarkRootActionBase.findContentEntry(model, generatedRoot.get());
132               if (entry == null) {
133                 LOG.info("Failed to find contentEntry for archive folder");
134                 return;
135               }
136               entry.addExcludeFolder(generatedRoot.get());
137               model.commit();
138               module.getProject().save();
139             }
140             catch (IOException e) {
141               LOG.info("Failed to create folder for generated files", e);
142             }
143           }
144         });
145       }
146     });
147     return generatedRoot.get();
148   }
149
150   @Nullable
151   public static VirtualFile generateFolder(@NotNull Project project, @NotNull Module module, String name) {
152     VirtualFile generatedRoot = getGeneratedFilesFolder(project, module);
153     if (generatedRoot == null) {
154       return null;
155     }
156
157     final Ref<VirtualFile> folder = new Ref<>(generatedRoot.findChild(name));
158     //need to delete old folder
159     ApplicationManager.getApplication().runWriteAction(() -> {
160       try {
161         if (folder.get() != null) {
162           folder.get().delete(null);
163         }
164         folder.set(generatedRoot.createChildDirectory(null, name));
165       }
166       catch (IOException e) {
167         LOG.info("Failed to generate folder " + name, e);
168       }
169     });
170     return folder.get();
171   }
172
173   public static boolean isCourseCreator(@NotNull Project project) {
174     Course course = StudyTaskManager.getInstance(project).getCourse();
175     if (course == null) {
176       return false;
177     }
178
179     return COURSE_MODE.equals(course.getCourseMode());
180   }
181
182   public static boolean isTestsFile(@NotNull Project project, @NotNull VirtualFile file) {
183     Course course = StudyTaskManager.getInstance(project).getCourse();
184     if (course == null) {
185       return false;
186     }
187     CCLanguageManager manager = getStudyLanguageManager(course);
188     if (manager == null) {
189       return false;
190     }
191     return manager.isTestFile(file);
192   }
193
194   public static void createResourceFile(VirtualFile createdFile, Course course, VirtualFile taskVF) {
195     VirtualFile lessonVF = taskVF.getParent();
196     if (lessonVF == null) {
197       return;
198     }
199
200     String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskVF.getName());
201     File taskResourceFile = new File(taskResourcesPath);
202     if (!taskResourceFile.exists()) {
203       if (!taskResourceFile.mkdirs()) {
204         LOG.info("Failed to create resources for task " + taskResourcesPath);
205       }
206     }
207     try {
208       File toFile = new File(taskResourceFile, createdFile.getName());
209       FileUtil.copy(new File(createdFile.getPath()), toFile);
210     }
211     catch (IOException e) {
212       LOG.info("Failed to copy created task file to resources " + createdFile.getPath());
213     }
214   }
215
216
217   public static void updateResources(Project project, Task task, VirtualFile taskDir) {
218     Course course = StudyTaskManager.getInstance(project).getCourse();
219     if (course == null) {
220       return;
221     }
222     VirtualFile lessonVF = taskDir.getParent();
223     if (lessonVF == null) {
224       return;
225     }
226
227     String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskDir.getName());
228     File taskResourceFile = new File(taskResourcesPath);
229     if (!taskResourceFile.exists()) {
230       if (!taskResourceFile.mkdirs()) {
231         LOG.info("Failed to create resources for task " + taskResourcesPath);
232       }
233     }
234     VirtualFile studentDir = LocalFileSystem.getInstance().findFileByIoFile(taskResourceFile);
235     if (studentDir == null) {
236       return;
237     }
238     Map<String, TaskFile> files = StudyUtils.getTaskFiles(task);
239     for (Map.Entry<String, TaskFile> entry : files.entrySet()) {
240       String name = entry.getKey();
241       VirtualFile answerFile = taskDir.findChild(name);
242       if (answerFile == null) {
243         continue;
244       }
245       ApplicationManager.getApplication().runWriteAction(() -> {
246         EduUtils.createStudentFile(CCUtils.class, project, answerFile, task.getActiveStepIndex(), studentDir, null);
247       });
248     }
249   }
250 }