delete subtask
[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.IdeView;
6 import com.intellij.ide.fileTemplates.FileTemplate;
7 import com.intellij.ide.fileTemplates.FileTemplateManager;
8 import com.intellij.ide.fileTemplates.FileTemplateUtil;
9 import com.intellij.ide.util.EditorHelper;
10 import com.intellij.lang.Language;
11 import com.intellij.openapi.actionSystem.AnActionEvent;
12 import com.intellij.openapi.actionSystem.Presentation;
13 import com.intellij.openapi.application.ApplicationManager;
14 import com.intellij.openapi.diagnostic.Logger;
15 import com.intellij.openapi.module.Module;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.roots.ModuleRootModificationUtil;
18 import com.intellij.openapi.roots.ProjectRootManager;
19 import com.intellij.openapi.util.Ref;
20 import com.intellij.openapi.util.io.FileUtil;
21 import com.intellij.openapi.vfs.LocalFileSystem;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.psi.PsiDirectory;
24 import com.intellij.psi.PsiElement;
25 import com.intellij.util.Function;
26 import com.jetbrains.edu.coursecreator.settings.CCSettings;
27 import com.jetbrains.edu.learning.StudyTaskManager;
28 import com.jetbrains.edu.learning.StudyUtils;
29 import com.jetbrains.edu.learning.core.EduNames;
30 import com.jetbrains.edu.learning.core.EduUtils;
31 import com.jetbrains.edu.learning.courseFormat.Course;
32 import com.jetbrains.edu.learning.courseFormat.StudyItem;
33 import com.jetbrains.edu.learning.courseFormat.Task;
34 import com.jetbrains.edu.learning.courseFormat.TaskFile;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.Map;
44
45 public class CCUtils {
46   public static final String ANSWER_EXTENSION_DOTTED = ".answer.";
47   private static final Logger LOG = Logger.getInstance(CCUtils.class);
48   public static final String GENERATED_FILES_FOLDER = ".coursecreator";
49   public static final String COURSE_MODE = "Course Creator";
50
51   public static int getSubtaskIndex(Project project, VirtualFile file) {
52     String fileName = file.getName();
53     String name = FileUtil.getNameWithoutExtension(fileName);
54     boolean canBeSubtaskFile = isTestsFile(project, file) || StudyUtils.isTaskDescriptionFile(fileName);
55     if (!canBeSubtaskFile) {
56       return -1;
57     }
58     if (!name.contains(EduNames.SUBTASK_MARKER)) {
59       return 0;
60     }
61     int markerIndex = name.indexOf(EduNames.SUBTASK_MARKER);
62     String index = name.substring(markerIndex + EduNames.SUBTASK_MARKER.length());
63     if (index.isEmpty()) {
64       return -1;
65     }
66     try {
67       return Integer.valueOf(index);
68     } catch (NumberFormatException e) {
69       return -1;
70     }
71   }
72
73   @Nullable
74   public static CCLanguageManager getStudyLanguageManager(@NotNull final Course course) {
75     Language language = Language.findLanguageByID(course.getLanguageID());
76     return language == null ? null : CCLanguageManager.INSTANCE.forLanguage(language);
77   }
78
79   /**
80    * This method decreases index and updates directory names of
81    * all tasks/lessons that have higher index than specified object
82    *
83    * @param dirs         directories that are used to get tasks/lessons
84    * @param getStudyItem function that is used to get task/lesson from VirtualFile. This function can return null
85    * @param threshold    index is used as threshold
86    * @param prefix       task or lesson directory name prefix
87    */
88   public static void updateHigherElements(VirtualFile[] dirs,
89                                           @NotNull final Function<VirtualFile, ? extends StudyItem> getStudyItem,
90                                           final int threshold,
91                                           final String prefix,
92                                           final int delta) {
93     ArrayList<VirtualFile> dirsToRename = new ArrayList<>
94       (Collections2.filter(Arrays.asList(dirs), new Predicate<VirtualFile>() {
95         @Override
96         public boolean apply(VirtualFile dir) {
97           final StudyItem item = getStudyItem.fun(dir);
98           if (item == null) {
99             return false;
100           }
101           int index = item.getIndex();
102           return index > threshold;
103         }
104       }));
105     Collections.sort(dirsToRename, (o1, o2) -> {
106       StudyItem item1 = getStudyItem.fun(o1);
107       StudyItem item2 = getStudyItem.fun(o2);
108       //if we delete some dir we should start increasing numbers in dir names from the end
109       return (-delta) * EduUtils.INDEX_COMPARATOR.compare(item1, item2);
110     });
111
112     for (final VirtualFile dir : dirsToRename) {
113       final StudyItem item = getStudyItem.fun(dir);
114       final int newIndex = item.getIndex() + delta;
115       item.setIndex(newIndex);
116       ApplicationManager.getApplication().runWriteAction(new Runnable() {
117         @Override
118         public void run() {
119           try {
120             dir.rename(this, prefix + newIndex);
121           }
122           catch (IOException e) {
123             LOG.error(e);
124           }
125         }
126       });
127     }
128   }
129
130   public static boolean isLessonDir(PsiDirectory sourceDirectory) {
131     if (sourceDirectory == null) {
132       return false;
133     }
134     Project project = sourceDirectory.getProject();
135     Course course = StudyTaskManager.getInstance(project).getCourse();
136     if (course != null && isCourseCreator(project) && course.getLesson(sourceDirectory.getName()) != null) {
137       return true;
138     }
139     return false;
140   }
141
142
143   public static VirtualFile getGeneratedFilesFolder(@NotNull Project project, @NotNull Module module) {
144     VirtualFile baseDir = project.getBaseDir();
145     VirtualFile folder = baseDir.findChild(GENERATED_FILES_FOLDER);
146     if (folder != null) {
147       return folder;
148     }
149     final Ref<VirtualFile> generatedRoot = new Ref<>();
150     ApplicationManager.getApplication().runWriteAction(new Runnable() {
151       @Override
152       public void run() {
153         try {
154           generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER));
155           VirtualFile contentRootForFile =
156             ProjectRootManager.getInstance(module.getProject()).getFileIndex().getContentRootForFile(generatedRoot.get());
157           if (contentRootForFile == null) {
158             return;
159           }
160           ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(), Collections.singletonList(generatedRoot.get().getUrl()));
161         }
162         catch (IOException e) {
163           LOG.info("Failed to create folder for generated files", e);
164         }
165       }
166     });
167     return generatedRoot.get();
168   }
169
170   @Nullable
171   public static VirtualFile generateFolder(@NotNull Project project, @NotNull Module module, String name) {
172     VirtualFile generatedRoot = getGeneratedFilesFolder(project, module);
173     if (generatedRoot == null) {
174       return null;
175     }
176
177     final Ref<VirtualFile> folder = new Ref<>(generatedRoot.findChild(name));
178     //need to delete old folder
179     ApplicationManager.getApplication().runWriteAction(() -> {
180       try {
181         if (folder.get() != null) {
182           folder.get().delete(null);
183         }
184         folder.set(generatedRoot.createChildDirectory(null, name));
185       }
186       catch (IOException e) {
187         LOG.info("Failed to generate folder " + name, e);
188       }
189     });
190     return folder.get();
191   }
192
193   public static boolean isCourseCreator(@NotNull Project project) {
194     Course course = StudyTaskManager.getInstance(project).getCourse();
195     if (course == null) {
196       return false;
197     }
198
199     return COURSE_MODE.equals(course.getCourseMode());
200   }
201
202   public static boolean isTestsFile(@NotNull Project project, @NotNull VirtualFile file) {
203     Course course = StudyTaskManager.getInstance(project).getCourse();
204     if (course == null) {
205       return false;
206     }
207     CCLanguageManager manager = getStudyLanguageManager(course);
208     if (manager == null) {
209       return false;
210     }
211     return manager.isTestFile(file);
212   }
213
214   public static void createResourceFile(VirtualFile createdFile, Course course, VirtualFile taskVF) {
215     VirtualFile lessonVF = taskVF.getParent();
216     if (lessonVF == null) {
217       return;
218     }
219
220     String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskVF.getName());
221     File taskResourceFile = new File(taskResourcesPath);
222     if (!taskResourceFile.exists()) {
223       if (!taskResourceFile.mkdirs()) {
224         LOG.info("Failed to create resources for task " + taskResourcesPath);
225       }
226     }
227     try {
228       File toFile = new File(taskResourceFile, createdFile.getName());
229       FileUtil.copy(new File(createdFile.getPath()), toFile);
230     }
231     catch (IOException e) {
232       LOG.info("Failed to copy created task file to resources " + createdFile.getPath());
233     }
234   }
235
236
237   public static void updateResources(Project project, Task task, VirtualFile taskDir) {
238     Course course = StudyTaskManager.getInstance(project).getCourse();
239     if (course == null) {
240       return;
241     }
242     VirtualFile lessonVF = taskDir.getParent();
243     if (lessonVF == null) {
244       return;
245     }
246
247     String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskDir.getName());
248     File taskResourceFile = new File(taskResourcesPath);
249     if (!taskResourceFile.exists()) {
250       if (!taskResourceFile.mkdirs()) {
251         LOG.info("Failed to create resources for task " + taskResourcesPath);
252       }
253     }
254     VirtualFile studentDir = LocalFileSystem.getInstance().findFileByIoFile(taskResourceFile);
255     if (studentDir == null) {
256       return;
257     }
258     for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
259       String name = entry.getKey();
260       VirtualFile answerFile = taskDir.findChild(name);
261       if (answerFile == null) {
262         continue;
263       }
264       ApplicationManager.getApplication().runWriteAction(() -> {
265         EduUtils.createStudentFile(CCUtils.class, project, answerFile, studentDir, null, task.getActiveSubtaskIndex());
266       });
267     }
268   }
269
270   public static void updateActionGroup(AnActionEvent e) {
271     Presentation presentation = e.getPresentation();
272     Project project = e.getProject();
273     presentation.setEnabledAndVisible(project != null && isCourseCreator(project));
274   }
275
276   private static void createFromTemplate(@NotNull final PsiDirectory taskDirectory,
277                                          @Nullable final FileTemplate template,
278                                          @Nullable IdeView view, boolean open) {
279     if (template == null) {
280       return;
281     }
282     try {
283       final PsiElement file = FileTemplateUtil.createFromTemplate(template, template.getName(), null, taskDirectory);
284       if (view != null && open) {
285         EditorHelper.openInEditor(file, false);
286         view.selectElement(file);
287       }
288     }
289     catch (Exception e) {
290       LOG.error(e);
291     }
292   }
293
294   public static void createTaskContent(@NotNull Project project,
295                                    @Nullable IdeView view,
296                                    @NotNull Course course,
297                                    PsiDirectory taskDirectory) {
298     CCLanguageManager manager = getStudyLanguageManager(course);
299     if (manager == null) {
300       return;
301     }
302     createFromTemplate(taskDirectory, manager.getTestsTemplate(project), view, false);
303     createFromTemplate(taskDirectory, FileTemplateManager.getInstance(project)
304       .getInternalTemplate(StudyUtils.getTaskDescriptionFileName(CCSettings.getInstance().useHtmlAsDefaultTaskFormat())), view, false);
305     String defaultExtension = manager.getDefaultTaskFileExtension();
306     if (defaultExtension != null) {
307       FileTemplate taskFileTemplate = manager.getTaskFileTemplateForExtension(project, defaultExtension);
308       createFromTemplate(taskDirectory, taskFileTemplate, view, true);
309     }
310   }
311 }