1 package com.jetbrains.edu.coursecreator;
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;
39 import java.io.IOException;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
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";
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) {
58 if (!name.contains(EduNames.SUBTASK_MARKER)) {
61 int markerIndex = name.indexOf(EduNames.SUBTASK_MARKER);
62 String index = name.substring(markerIndex + EduNames.SUBTASK_MARKER.length());
63 if (index.isEmpty()) {
67 return Integer.valueOf(index);
68 } catch (NumberFormatException e) {
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);
80 * This method decreases index and updates directory names of
81 * all tasks/lessons that have higher index than specified object
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
88 public static void updateHigherElements(VirtualFile[] dirs,
89 @NotNull final Function<VirtualFile, ? extends StudyItem> getStudyItem,
93 ArrayList<VirtualFile> dirsToRename = new ArrayList<>
94 (Collections2.filter(Arrays.asList(dirs), new Predicate<VirtualFile>() {
96 public boolean apply(VirtualFile dir) {
97 final StudyItem item = getStudyItem.fun(dir);
101 int index = item.getIndex();
102 return index > threshold;
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);
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() {
120 dir.rename(this, prefix + newIndex);
122 catch (IOException e) {
130 public static boolean isLessonDir(PsiDirectory sourceDirectory) {
131 if (sourceDirectory == null) {
134 Project project = sourceDirectory.getProject();
135 Course course = StudyTaskManager.getInstance(project).getCourse();
136 if (course != null && isCourseCreator(project) && course.getLesson(sourceDirectory.getName()) != null) {
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) {
149 final Ref<VirtualFile> generatedRoot = new Ref<>();
150 ApplicationManager.getApplication().runWriteAction(new Runnable() {
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) {
160 ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(), Collections.singletonList(generatedRoot.get().getUrl()));
162 catch (IOException e) {
163 LOG.info("Failed to create folder for generated files", e);
167 return generatedRoot.get();
171 public static VirtualFile generateFolder(@NotNull Project project, @NotNull Module module, String name) {
172 VirtualFile generatedRoot = getGeneratedFilesFolder(project, module);
173 if (generatedRoot == null) {
177 final Ref<VirtualFile> folder = new Ref<>(generatedRoot.findChild(name));
178 //need to delete old folder
179 ApplicationManager.getApplication().runWriteAction(() -> {
181 if (folder.get() != null) {
182 folder.get().delete(null);
184 folder.set(generatedRoot.createChildDirectory(null, name));
186 catch (IOException e) {
187 LOG.info("Failed to generate folder " + name, e);
193 public static boolean isCourseCreator(@NotNull Project project) {
194 Course course = StudyTaskManager.getInstance(project).getCourse();
195 if (course == null) {
199 return COURSE_MODE.equals(course.getCourseMode());
202 public static boolean isTestsFile(@NotNull Project project, @NotNull VirtualFile file) {
203 Course course = StudyTaskManager.getInstance(project).getCourse();
204 if (course == null) {
207 CCLanguageManager manager = getStudyLanguageManager(course);
208 if (manager == null) {
211 return manager.isTestFile(file);
214 public static void createResourceFile(VirtualFile createdFile, Course course, VirtualFile taskVF) {
215 VirtualFile lessonVF = taskVF.getParent();
216 if (lessonVF == null) {
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);
228 File toFile = new File(taskResourceFile, createdFile.getName());
229 FileUtil.copy(new File(createdFile.getPath()), toFile);
231 catch (IOException e) {
232 LOG.info("Failed to copy created task file to resources " + createdFile.getPath());
237 public static void updateResources(Project project, Task task, VirtualFile taskDir) {
238 Course course = StudyTaskManager.getInstance(project).getCourse();
239 if (course == null) {
242 VirtualFile lessonVF = taskDir.getParent();
243 if (lessonVF == null) {
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);
254 VirtualFile studentDir = LocalFileSystem.getInstance().findFileByIoFile(taskResourceFile);
255 if (studentDir == null) {
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) {
264 ApplicationManager.getApplication().runWriteAction(() -> {
265 EduUtils.createStudentFile(CCUtils.class, project, answerFile, studentDir, null, task.getActiveSubtaskIndex());
270 public static void updateActionGroup(AnActionEvent e) {
271 Presentation presentation = e.getPresentation();
272 Project project = e.getProject();
273 presentation.setEnabledAndVisible(project != null && isCourseCreator(project));
276 private static void createFromTemplate(@NotNull final PsiDirectory taskDirectory,
277 @Nullable final FileTemplate template,
278 @Nullable IdeView view, boolean open) {
279 if (template == null) {
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);
289 catch (Exception e) {
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) {
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);