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