course-format-ts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/.idea/edu-format-ts.iml +10 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +28 -0
  3. package/.idea/misc.xml +4 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/AGENTS.md +1 -0
  7. package/dist/courseFormat/AnswerPlaceholder.d.ts +37 -0
  8. package/dist/courseFormat/AnswerPlaceholder.js +101 -0
  9. package/dist/courseFormat/AnswerPlaceholderComparator.d.ts +4 -0
  10. package/dist/courseFormat/AnswerPlaceholderComparator.js +8 -0
  11. package/dist/courseFormat/AnswerPlaceholderDependency.d.ts +19 -0
  12. package/dist/courseFormat/AnswerPlaceholderDependency.js +91 -0
  13. package/dist/courseFormat/CheckFeedback.d.ts +20 -0
  14. package/dist/courseFormat/CheckFeedback.js +58 -0
  15. package/dist/courseFormat/CheckResult.d.ts +33 -0
  16. package/dist/courseFormat/CheckResult.js +58 -0
  17. package/dist/courseFormat/CheckResultSeverity.d.ts +7 -0
  18. package/dist/courseFormat/CheckResultSeverity.js +17 -0
  19. package/dist/courseFormat/CheckStatus.d.ts +5 -0
  20. package/dist/courseFormat/CheckStatus.js +9 -0
  21. package/dist/courseFormat/Course.d.ts +68 -0
  22. package/dist/courseFormat/Course.js +165 -0
  23. package/dist/courseFormat/CourseMode.d.ts +4 -0
  24. package/dist/courseFormat/CourseMode.js +8 -0
  25. package/dist/courseFormat/CourseVisibility.d.ts +4 -0
  26. package/dist/courseFormat/CourseVisibility.js +8 -0
  27. package/dist/courseFormat/CourseraCourse.d.ts +5 -0
  28. package/dist/courseFormat/CourseraCourse.js +15 -0
  29. package/dist/courseFormat/DescriptionFormat.d.ts +5 -0
  30. package/dist/courseFormat/DescriptionFormat.js +9 -0
  31. package/dist/courseFormat/EduCourse.d.ts +17 -0
  32. package/dist/courseFormat/EduCourse.js +42 -0
  33. package/dist/courseFormat/EduFile.d.ts +22 -0
  34. package/dist/courseFormat/EduFile.js +110 -0
  35. package/dist/courseFormat/EduFileErrorHighlightLevel.d.ts +5 -0
  36. package/dist/courseFormat/EduFileErrorHighlightLevel.js +9 -0
  37. package/dist/courseFormat/EduFormatNames.d.ts +75 -0
  38. package/dist/courseFormat/EduFormatNames.js +80 -0
  39. package/dist/courseFormat/EduTestInfo.d.ts +28 -0
  40. package/dist/courseFormat/EduTestInfo.js +68 -0
  41. package/dist/courseFormat/EduVersions.d.ts +2 -0
  42. package/dist/courseFormat/EduVersions.js +5 -0
  43. package/dist/courseFormat/FileContents.d.ts +36 -0
  44. package/dist/courseFormat/FileContents.js +67 -0
  45. package/dist/courseFormat/FileContentsFactory.d.ts +17 -0
  46. package/dist/courseFormat/FileContentsFactory.js +2 -0
  47. package/dist/courseFormat/FrameworkLesson.d.ts +9 -0
  48. package/dist/courseFormat/FrameworkLesson.js +28 -0
  49. package/dist/courseFormat/ItemContainer.d.ts +13 -0
  50. package/dist/courseFormat/ItemContainer.js +45 -0
  51. package/dist/courseFormat/JBAccountUserInfo.d.ts +9 -0
  52. package/dist/courseFormat/JBAccountUserInfo.js +20 -0
  53. package/dist/courseFormat/Language.d.ts +4 -0
  54. package/dist/courseFormat/Language.js +33 -0
  55. package/dist/courseFormat/Lesson.d.ts +20 -0
  56. package/dist/courseFormat/Lesson.js +56 -0
  57. package/dist/courseFormat/LessonContainer.d.ts +16 -0
  58. package/dist/courseFormat/LessonContainer.js +54 -0
  59. package/dist/courseFormat/PluginInfo.d.ts +7 -0
  60. package/dist/courseFormat/PluginInfo.js +15 -0
  61. package/dist/courseFormat/Section.d.ts +8 -0
  62. package/dist/courseFormat/Section.js +27 -0
  63. package/dist/courseFormat/StudyItem.d.ts +20 -0
  64. package/dist/courseFormat/StudyItem.js +47 -0
  65. package/dist/courseFormat/Tags.d.ts +16 -0
  66. package/dist/courseFormat/Tags.js +42 -0
  67. package/dist/courseFormat/TaskFile.d.ts +26 -0
  68. package/dist/courseFormat/TaskFile.js +72 -0
  69. package/dist/courseFormat/UserInfo.d.ts +3 -0
  70. package/dist/courseFormat/UserInfo.js +2 -0
  71. package/dist/courseFormat/Vendor.d.ts +7 -0
  72. package/dist/courseFormat/Vendor.js +14 -0
  73. package/dist/courseFormat/attempts/Attempt.d.ts +12 -0
  74. package/dist/courseFormat/attempts/Attempt.js +25 -0
  75. package/dist/courseFormat/attempts/AttemptBase.d.ts +9 -0
  76. package/dist/courseFormat/attempts/AttemptBase.js +28 -0
  77. package/dist/courseFormat/attempts/DataTaskAttempt.d.ts +6 -0
  78. package/dist/courseFormat/attempts/DataTaskAttempt.js +24 -0
  79. package/dist/courseFormat/attempts/Dataset.d.ts +12 -0
  80. package/dist/courseFormat/attempts/Dataset.js +21 -0
  81. package/dist/courseFormat/fileUtils.d.ts +5 -0
  82. package/dist/courseFormat/fileUtils.js +32 -0
  83. package/dist/courseFormat/hyperskill/HyperskillCourse.d.ts +13 -0
  84. package/dist/courseFormat/hyperskill/HyperskillCourse.js +25 -0
  85. package/dist/courseFormat/hyperskill/HyperskillProject.d.ts +10 -0
  86. package/dist/courseFormat/hyperskill/HyperskillProject.js +16 -0
  87. package/dist/courseFormat/hyperskill/HyperskillStage.d.ts +8 -0
  88. package/dist/courseFormat/hyperskill/HyperskillStage.js +20 -0
  89. package/dist/courseFormat/hyperskill/HyperskillTaskType.d.ts +4 -0
  90. package/dist/courseFormat/hyperskill/HyperskillTaskType.js +26 -0
  91. package/dist/courseFormat/hyperskill/HyperskillTopic.d.ts +5 -0
  92. package/dist/courseFormat/hyperskill/HyperskillTopic.js +11 -0
  93. package/dist/courseFormat/loggerUtils.d.ts +1 -0
  94. package/dist/courseFormat/loggerUtils.js +6 -0
  95. package/dist/courseFormat/stepik/StepikCourse.d.ts +5 -0
  96. package/dist/courseFormat/stepik/StepikCourse.js +15 -0
  97. package/dist/courseFormat/stepik/StepikLesson.d.ts +6 -0
  98. package/dist/courseFormat/stepik/StepikLesson.js +16 -0
  99. package/dist/courseFormat/tasks/AnswerTask.d.ts +8 -0
  100. package/dist/courseFormat/tasks/AnswerTask.js +11 -0
  101. package/dist/courseFormat/tasks/CodeTask.d.ts +12 -0
  102. package/dist/courseFormat/tasks/CodeTask.js +21 -0
  103. package/dist/courseFormat/tasks/DataTask.d.ts +18 -0
  104. package/dist/courseFormat/tasks/DataTask.js +32 -0
  105. package/dist/courseFormat/tasks/EduTask.d.ts +12 -0
  106. package/dist/courseFormat/tasks/EduTask.js +22 -0
  107. package/dist/courseFormat/tasks/IdeTask.d.ts +9 -0
  108. package/dist/courseFormat/tasks/IdeTask.js +14 -0
  109. package/dist/courseFormat/tasks/NumberTask.d.ts +9 -0
  110. package/dist/courseFormat/tasks/NumberTask.js +14 -0
  111. package/dist/courseFormat/tasks/OutputTask.d.ts +10 -0
  112. package/dist/courseFormat/tasks/OutputTask.js +18 -0
  113. package/dist/courseFormat/tasks/OutputTaskBase.d.ts +14 -0
  114. package/dist/courseFormat/tasks/OutputTaskBase.js +19 -0
  115. package/dist/courseFormat/tasks/RemoteEduTask.d.ts +9 -0
  116. package/dist/courseFormat/tasks/RemoteEduTask.js +15 -0
  117. package/dist/courseFormat/tasks/StringTask.d.ts +9 -0
  118. package/dist/courseFormat/tasks/StringTask.js +14 -0
  119. package/dist/courseFormat/tasks/TableTask.d.ts +17 -0
  120. package/dist/courseFormat/tasks/TableTask.js +43 -0
  121. package/dist/courseFormat/tasks/Task.d.ts +45 -0
  122. package/dist/courseFormat/tasks/Task.js +155 -0
  123. package/dist/courseFormat/tasks/TheoryTask.d.ts +10 -0
  124. package/dist/courseFormat/tasks/TheoryTask.js +15 -0
  125. package/dist/courseFormat/tasks/UnsupportedTask.d.ts +9 -0
  126. package/dist/courseFormat/tasks/UnsupportedTask.js +14 -0
  127. package/dist/courseFormat/tasks/choice/ChoiceOption.d.ts +10 -0
  128. package/dist/courseFormat/tasks/choice/ChoiceOption.js +33 -0
  129. package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.d.ts +5 -0
  130. package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.js +9 -0
  131. package/dist/courseFormat/tasks/choice/ChoiceTask.d.ts +23 -0
  132. package/dist/courseFormat/tasks/choice/ChoiceTask.js +47 -0
  133. package/dist/courseFormat/tasks/matching/MatchingTask.d.ts +10 -0
  134. package/dist/courseFormat/tasks/matching/MatchingTask.js +15 -0
  135. package/dist/courseFormat/tasks/matching/SortingBasedTask.d.ts +16 -0
  136. package/dist/courseFormat/tasks/matching/SortingBasedTask.js +50 -0
  137. package/dist/courseFormat/tasks/matching/SortingTask.d.ts +9 -0
  138. package/dist/courseFormat/tasks/matching/SortingTask.js +14 -0
  139. package/dist/courseFormat/uiMessages.d.ts +3 -0
  140. package/dist/courseFormat/uiMessages.js +14 -0
  141. package/dist/disk-loader.d.ts +4 -0
  142. package/dist/disk-loader.js +389 -0
  143. package/dist/index.d.ts +31 -0
  144. package/dist/index.js +64 -0
  145. package/dist/loader.d.ts +7 -0
  146. package/dist/loader.js +435 -0
  147. package/dist/models.d.ts +49 -0
  148. package/dist/models.js +2 -0
  149. package/dist/zip-loader.d.ts +4 -0
  150. package/dist/zip-loader.js +431 -0
  151. package/example-course-project/course-info.yaml +15 -0
  152. package/example-course-project/lesson1/lesson-info.yaml +3 -0
  153. package/example-course-project/lesson1/lesson-remote-info.yaml +1 -0
  154. package/example-course-project/lesson1/task1/Task.txt +1 -0
  155. package/example-course-project/lesson1/task1/task-info.yaml +12 -0
  156. package/example-course-project/lesson1/task1/task-remote-info.yaml +1 -0
  157. package/example-course-project/lesson1/task1/task.md +47 -0
  158. package/example-course-project/lesson1/task1/tests/Tests.txt +0 -0
  159. package/example-course-project/lesson1/task2/Task.txt +1 -0
  160. package/example-course-project/lesson1/task2/task-info.yaml +12 -0
  161. package/example-course-project/lesson1/task2/task-remote-info.yaml +1 -0
  162. package/example-course-project/lesson1/task2/task.md +47 -0
  163. package/example-course-project/lesson1/task2/tests/Tests.txt +0 -0
  164. package/package.json +19 -0
  165. package/src/@types/mime-types.d.ts +3 -0
  166. package/src/courseFormat/AnswerPlaceholder.ts +121 -0
  167. package/src/courseFormat/AnswerPlaceholderComparator.ts +7 -0
  168. package/src/courseFormat/AnswerPlaceholderDependency.ts +122 -0
  169. package/src/courseFormat/CheckFeedback.ts +71 -0
  170. package/src/courseFormat/CheckResult.ts +92 -0
  171. package/src/courseFormat/CheckResultSeverity.ts +13 -0
  172. package/src/courseFormat/CheckStatus.ts +5 -0
  173. package/src/courseFormat/Course.ts +201 -0
  174. package/src/courseFormat/CourseMode.ts +4 -0
  175. package/src/courseFormat/CourseVisibility.ts +4 -0
  176. package/src/courseFormat/CourseraCourse.ts +10 -0
  177. package/src/courseFormat/DescriptionFormat.ts +5 -0
  178. package/src/courseFormat/EduCourse.ts +41 -0
  179. package/src/courseFormat/EduFile.ts +133 -0
  180. package/src/courseFormat/EduFileErrorHighlightLevel.ts +5 -0
  181. package/src/courseFormat/EduFormatNames.ts +95 -0
  182. package/src/courseFormat/EduTestInfo.ts +87 -0
  183. package/src/courseFormat/EduVersions.ts +2 -0
  184. package/src/courseFormat/FileContents.ts +97 -0
  185. package/src/courseFormat/FileContentsFactory.ts +19 -0
  186. package/src/courseFormat/FrameworkLesson.ts +29 -0
  187. package/src/courseFormat/ItemContainer.ts +47 -0
  188. package/src/courseFormat/JBAccountUserInfo.ts +21 -0
  189. package/src/courseFormat/Language.ts +31 -0
  190. package/src/courseFormat/Lesson.ts +69 -0
  191. package/src/courseFormat/LessonContainer.ts +65 -0
  192. package/src/courseFormat/PluginInfo.ts +15 -0
  193. package/src/courseFormat/Section.ts +29 -0
  194. package/src/courseFormat/StudyItem.ts +55 -0
  195. package/src/courseFormat/Tags.ts +45 -0
  196. package/src/courseFormat/TaskFile.ts +88 -0
  197. package/src/courseFormat/UserInfo.ts +3 -0
  198. package/src/courseFormat/Vendor.ts +15 -0
  199. package/src/courseFormat/attempts/Attempt.ts +28 -0
  200. package/src/courseFormat/attempts/AttemptBase.ts +24 -0
  201. package/src/courseFormat/attempts/DataTaskAttempt.ts +19 -0
  202. package/src/courseFormat/attempts/Dataset.ts +13 -0
  203. package/src/courseFormat/fileUtils.ts +31 -0
  204. package/src/courseFormat/hyperskill/HyperskillCourse.ts +24 -0
  205. package/src/courseFormat/hyperskill/HyperskillProject.ts +10 -0
  206. package/src/courseFormat/hyperskill/HyperskillStage.ts +15 -0
  207. package/src/courseFormat/hyperskill/HyperskillTaskType.ts +23 -0
  208. package/src/courseFormat/hyperskill/HyperskillTopic.ts +5 -0
  209. package/src/courseFormat/loggerUtils.ts +3 -0
  210. package/src/courseFormat/stepik/StepikCourse.ts +10 -0
  211. package/src/courseFormat/stepik/StepikLesson.ts +11 -0
  212. package/src/courseFormat/tasks/AnswerTask.ts +13 -0
  213. package/src/courseFormat/tasks/CodeTask.ts +42 -0
  214. package/src/courseFormat/tasks/DataTask.ts +37 -0
  215. package/src/courseFormat/tasks/EduTask.ts +26 -0
  216. package/src/courseFormat/tasks/IdeTask.ts +17 -0
  217. package/src/courseFormat/tasks/NumberTask.ts +17 -0
  218. package/src/courseFormat/tasks/OutputTask.ts +21 -0
  219. package/src/courseFormat/tasks/OutputTaskBase.ts +23 -0
  220. package/src/courseFormat/tasks/RemoteEduTask.ts +18 -0
  221. package/src/courseFormat/tasks/StringTask.ts +17 -0
  222. package/src/courseFormat/tasks/TableTask.ts +51 -0
  223. package/src/courseFormat/tasks/Task.ts +181 -0
  224. package/src/courseFormat/tasks/TheoryTask.ts +19 -0
  225. package/src/courseFormat/tasks/UnsupportedTask.ts +17 -0
  226. package/src/courseFormat/tasks/choice/ChoiceOption.ts +37 -0
  227. package/src/courseFormat/tasks/choice/ChoiceOptionStatus.ts +5 -0
  228. package/src/courseFormat/tasks/choice/ChoiceTask.ts +57 -0
  229. package/src/courseFormat/tasks/matching/MatchingTask.ts +19 -0
  230. package/src/courseFormat/tasks/matching/SortingBasedTask.ts +59 -0
  231. package/src/courseFormat/tasks/matching/SortingTask.ts +17 -0
  232. package/src/courseFormat/uiMessages.ts +12 -0
  233. package/src/disk-loader.ts +463 -0
  234. package/src/index.ts +33 -0
  235. package/src/models.ts +54 -0
  236. package/src/zip-loader.ts +583 -0
  237. package/test/load-course.test.js +279 -0
  238. package/test/load-zip-course.test.js +73 -0
  239. package/tsconfig.json +15 -0
@@ -0,0 +1,65 @@
1
+ import { ItemContainer } from "./ItemContainer"
2
+ import type { Lesson } from "./Lesson"
3
+ import type { Section } from "./Section"
4
+ import { Task } from "./tasks/Task"
5
+
6
+ function getLessonModule(): { Lesson: typeof Lesson; Section: typeof Section } {
7
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
8
+ const SectionModule = require("./Section")
9
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
10
+ const LessonModule = require("./Lesson")
11
+ return { Lesson: LessonModule.Lesson, Section: SectionModule.Section }
12
+ }
13
+
14
+ export abstract class LessonContainer extends ItemContainer {
15
+ get lessons(): Lesson[] {
16
+ const mod = getLessonModule()
17
+ return this.items.filter((item): item is Lesson => item instanceof mod.Lesson)
18
+ }
19
+
20
+ getLesson(name: string): Lesson | undefined
21
+ getLesson(id: number): Lesson | undefined
22
+ getLesson(check: (lesson: Lesson) => boolean): Lesson | undefined
23
+ getLesson(param: string | number | ((lesson: Lesson) => boolean)): Lesson | undefined {
24
+ if (typeof param === "string") {
25
+ return this.getLesson((lesson) => lesson.name === param)
26
+ }
27
+ if (typeof param === "number") {
28
+ return this.getLesson((lesson) => lesson.id === param)
29
+ }
30
+ return this.lessons.find(param)
31
+ }
32
+
33
+ addLessons(lessons: Lesson[]): void {
34
+ lessons.forEach((lesson) => this.addItem(lesson))
35
+ }
36
+
37
+ addLesson(lesson: Lesson): void {
38
+ this.addItem(lesson)
39
+ }
40
+
41
+ removeLesson(lesson: Lesson): void {
42
+ this.removeItem(lesson)
43
+ }
44
+
45
+ visitLessons(visit: (lesson: Lesson) => void): void {
46
+ const mod = getLessonModule()
47
+ for (const item of this.items) {
48
+ if (item instanceof mod.Lesson) {
49
+ visit(item)
50
+ }
51
+ else if (item instanceof mod.Section) {
52
+ item.lessons.forEach(visit)
53
+ }
54
+ }
55
+ }
56
+
57
+ visitSections(visit: (section: Section) => void): void {
58
+ const mod = getLessonModule()
59
+ this.items.filter((item): item is Section => item instanceof mod.Section).forEach(visit)
60
+ }
61
+
62
+ visitTasks(visit: (task: Task) => void): void {
63
+ this.visitLessons((lesson) => lesson.visitTasks(visit))
64
+ }
65
+ }
@@ -0,0 +1,15 @@
1
+ export class PluginInfo {
2
+ stringId = ""
3
+ displayName?: string
4
+ minVersion?: string
5
+ maxVersion?: string
6
+
7
+ constructor(stringId?: string, displayName?: string | null, minVersion?: string | null, maxVersion?: string | null) {
8
+ if (stringId !== undefined) {
9
+ this.stringId = stringId
10
+ }
11
+ this.displayName = displayName ?? undefined
12
+ this.minVersion = minVersion ?? undefined
13
+ this.maxVersion = maxVersion ?? undefined
14
+ }
15
+ }
@@ -0,0 +1,29 @@
1
+ import { LessonContainer } from "./LessonContainer"
2
+ import type { Course } from "./Course"
3
+ import { SECTION } from "./EduFormatNames"
4
+ import { ItemContainer } from "./ItemContainer"
5
+
6
+ function getCourseModule(): { Course: typeof Course } {
7
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
8
+ return require("./Course")
9
+ }
10
+
11
+ export class Section extends LessonContainer {
12
+ override init(parentItem: ItemContainer, isRestarted: boolean): void {
13
+ if (!(parentItem instanceof getCourseModule().Course)) {
14
+ throw new Error(`Course is null for section ${this.name}`)
15
+ }
16
+ super.init(parentItem, isRestarted)
17
+ }
18
+
19
+ get course(): Course {
20
+ if (!(this.parent instanceof getCourseModule().Course)) {
21
+ throw new Error(`Course is null for section ${this.name}`)
22
+ }
23
+ return this.parent as Course
24
+ }
25
+
26
+ get itemType(): string {
27
+ return SECTION
28
+ }
29
+ }
@@ -0,0 +1,55 @@
1
+ import { ItemContainer } from "./ItemContainer"
2
+ import type { Course } from "./Course"
3
+
4
+ export abstract class StudyItem {
5
+ index = -1
6
+ name = ""
7
+ updateDate: Date = new Date(0)
8
+ id = 0
9
+ contentTags: string[] = []
10
+ customPresentableName?: string
11
+ protected _parent?: ItemContainer
12
+
13
+ constructor(name?: string) {
14
+ if (name) {
15
+ this.name = name
16
+ }
17
+ }
18
+
19
+ get parent(): ItemContainer {
20
+ if (!this._parent) {
21
+ throw new Error(`Parent is null for StudyItem ${this.name}`)
22
+ }
23
+ return this._parent
24
+ }
25
+
26
+ set parent(value: ItemContainer) {
27
+ this._parent = value
28
+ }
29
+
30
+ get presentableName(): string {
31
+ return this.customPresentableName ?? this.name
32
+ }
33
+
34
+ abstract get course(): Course
35
+ abstract get itemType(): string
36
+ abstract init(parentItem: ItemContainer, isRestarted: boolean): void
37
+
38
+ getRelativePath(root: StudyItem): string {
39
+ if (this === root) return ""
40
+ const parents: string[] = []
41
+ let currentParent = this.parent
42
+ while (currentParent !== root) {
43
+ parents.push(currentParent.name)
44
+ currentParent = currentParent.parent
45
+ }
46
+ parents.reverse()
47
+ if (parents.length === 0) return this.name
48
+ parents.push(this.name)
49
+ return parents.join("/")
50
+ }
51
+
52
+ get pathInCourse(): string {
53
+ return this.getRelativePath(this.course)
54
+ }
55
+ }
@@ -0,0 +1,45 @@
1
+ import { message } from "./uiMessages"
2
+
3
+ const PROGRAMMING_LANGUAGE_TAG_SEARCH_OPTION = "programming_language"
4
+ const LANGUAGE_TAG_SEARCH_OPTION = "language"
5
+
6
+ export class Tag {
7
+ readonly text: string
8
+ private readonly searchOption: string
9
+
10
+ constructor(text: string, searchOption = "tag") {
11
+ this.text = text
12
+ this.searchOption = searchOption
13
+ }
14
+
15
+ getSearchText(): string {
16
+ return `${this.searchOption}:${this.text}`.toLowerCase()
17
+ }
18
+
19
+ accept(filter: string): boolean {
20
+ const textInLowerCase = this.text.toLowerCase()
21
+ if (textInLowerCase.includes(filter)) {
22
+ return true
23
+ }
24
+ const searchPrefix = `${this.searchOption}:`
25
+ return filter.startsWith(searchPrefix) && textInLowerCase.includes(filter.substring(searchPrefix.length))
26
+ }
27
+ }
28
+
29
+ export class ProgrammingLanguageTag extends Tag {
30
+ constructor(language: string) {
31
+ super(language, PROGRAMMING_LANGUAGE_TAG_SEARCH_OPTION)
32
+ }
33
+ }
34
+
35
+ export class HumanLanguageTag extends Tag {
36
+ constructor(languageName: string) {
37
+ super(languageName, LANGUAGE_TAG_SEARCH_OPTION)
38
+ }
39
+ }
40
+
41
+ export class FeaturedTag extends Tag {
42
+ constructor() {
43
+ super(message("course.dialog.tags.featured"))
44
+ }
45
+ }
@@ -0,0 +1,88 @@
1
+ import { EduFile } from "./EduFile"
2
+ import { AnswerPlaceholder } from "./AnswerPlaceholder"
3
+ import { AnswerPlaceholderComparator } from "./AnswerPlaceholderComparator"
4
+ import { CheckStatus } from "./CheckStatus"
5
+ import type { Task } from "./tasks/Task"
6
+ import { FileContents } from "./FileContents"
7
+
8
+ export class TaskFile extends EduFile {
9
+ private _answerPlaceholders: AnswerPlaceholder[] = []
10
+ private _task?: Task
11
+ override isVisible = true
12
+
13
+ constructor()
14
+ constructor(name: string, text: string)
15
+ constructor(name: string, contents: FileContents)
16
+ constructor(name: string, text: string, isVisible: boolean)
17
+ constructor(name: string, text: string, isVisible: boolean, isLearnerCreated: boolean)
18
+ constructor(name?: string, textOrContents?: string | FileContents, isVisible?: boolean, isLearnerCreated?: boolean) {
19
+ super()
20
+ if (name !== undefined) this.name = name
21
+ if (typeof textOrContents === "string") {
22
+ this.text = textOrContents
23
+ }
24
+ else if (textOrContents) {
25
+ this.contents = textOrContents
26
+ }
27
+ if (isVisible !== undefined) this.isVisible = isVisible
28
+ if (isLearnerCreated !== undefined) this.isLearnerCreated = isLearnerCreated
29
+ }
30
+
31
+ get answerPlaceholders(): AnswerPlaceholder[] {
32
+ return this._answerPlaceholders
33
+ }
34
+
35
+ set answerPlaceholders(value: AnswerPlaceholder[]) {
36
+ this._answerPlaceholders = [...value]
37
+ }
38
+
39
+ get task(): Task {
40
+ if (!this._task) {
41
+ throw new Error(`Task is null for TaskFile ${this.name}`)
42
+ }
43
+ return this._task
44
+ }
45
+
46
+ set task(value: Task) {
47
+ this._task = value
48
+ }
49
+
50
+ initTaskFile(task: Task, isRestarted: boolean): void {
51
+ this.task = task
52
+ for (const answerPlaceholder of this._answerPlaceholders) {
53
+ answerPlaceholder.init(this, isRestarted)
54
+ }
55
+ this.sortAnswerPlaceholders()
56
+ }
57
+
58
+ addAnswerPlaceholder(answerPlaceholder: AnswerPlaceholder): void {
59
+ this._answerPlaceholders.push(answerPlaceholder)
60
+ }
61
+
62
+ removeAnswerPlaceholder(answerPlaceholder: AnswerPlaceholder): void {
63
+ this._answerPlaceholders = this._answerPlaceholders.filter((item) => item !== answerPlaceholder)
64
+ }
65
+
66
+ getAnswerPlaceholder(offset: number): AnswerPlaceholder | undefined {
67
+ return this._answerPlaceholders.find((placeholder) => offset >= placeholder.offset && offset <= placeholder.endOffset)
68
+ }
69
+
70
+ removeAllPlaceholders(): void {
71
+ this._answerPlaceholders = []
72
+ }
73
+
74
+ sortAnswerPlaceholders(): void {
75
+ this._answerPlaceholders.sort(AnswerPlaceholderComparator.compare)
76
+ this._answerPlaceholders.forEach((placeholder, index) => {
77
+ placeholder.index = index
78
+ })
79
+ }
80
+
81
+ hasFailedPlaceholders(): boolean {
82
+ return this._answerPlaceholders.some((placeholder) => placeholder.status === CheckStatus.Failed)
83
+ }
84
+
85
+ isValid(text: string): boolean {
86
+ return this._answerPlaceholders.every((placeholder) => placeholder.isValid(text.length))
87
+ }
88
+ }
@@ -0,0 +1,3 @@
1
+ export interface UserInfo {
2
+ getFullName(): string
3
+ }
@@ -0,0 +1,15 @@
1
+ export class Vendor {
2
+ readonly name: string
3
+ readonly email?: string
4
+ readonly url?: string
5
+
6
+ constructor(name = "", email?: string | null, url?: string | null) {
7
+ this.name = name
8
+ this.email = email ?? undefined
9
+ this.url = url ?? undefined
10
+ }
11
+
12
+ toString(): string {
13
+ return this.name
14
+ }
15
+ }
@@ -0,0 +1,28 @@
1
+ import { AttemptBase } from "./AttemptBase"
2
+ import { Dataset } from "./Dataset"
3
+
4
+ export class Attempt extends AttemptBase {
5
+ step = 0
6
+ dataset: Dataset | null = null
7
+ status: string | null = null
8
+ user: string | null = null
9
+
10
+ get isActive(): boolean {
11
+ return this.status === "active"
12
+ }
13
+
14
+ constructor()
15
+ constructor(step: number)
16
+ constructor(id: number, time: Date, timeLeft: number)
17
+ constructor(stepOrId?: number, time?: Date, timeLeft?: number) {
18
+ super()
19
+ if (stepOrId !== undefined && time === undefined) {
20
+ this.step = stepOrId
21
+ }
22
+ else if (stepOrId !== undefined && time !== undefined) {
23
+ this.id = stepOrId
24
+ this.time = time
25
+ this.timeLeft = timeLeft ?? null
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,24 @@
1
+ export abstract class AttemptBase {
2
+ id = 0
3
+ time: Date = new Date()
4
+ timeLeft: number | null = null
5
+
6
+ get isRunning(): boolean {
7
+ const endDateTime = this.calculateEndDateTime()
8
+ if (endDateTime === null) return true
9
+ return new Date() < endDateTime
10
+ }
11
+
12
+ protected calculateEndDateTime(): Date | null {
13
+ if (this.timeLeft === null) return null
14
+ return new Date(this.time.getTime() + this.timeLeft * 1000)
15
+ }
16
+
17
+ constructor()
18
+ constructor(id: number, time: Date, timeLeft: number | null)
19
+ constructor(id?: number, time?: Date, timeLeft?: number | null) {
20
+ if (id !== undefined) this.id = id
21
+ if (time !== undefined) this.time = time
22
+ if (timeLeft !== undefined) this.timeLeft = timeLeft
23
+ }
24
+ }
@@ -0,0 +1,19 @@
1
+ import { AttemptBase } from "./AttemptBase"
2
+
3
+ export class DataTaskAttempt extends AttemptBase {
4
+ endDateTime: Date | null = null
5
+
6
+ override get isRunning(): boolean {
7
+ if (this.endDateTime === null) return true
8
+ return new Date() < this.endDateTime
9
+ }
10
+
11
+ static fromAttempt(attempt: AttemptBase): DataTaskAttempt {
12
+ const result = new DataTaskAttempt()
13
+ result.id = attempt.id
14
+ result.time = attempt.time
15
+ result.timeLeft = attempt.timeLeft
16
+ result.endDateTime = result.calculateEndDateTime()
17
+ return result
18
+ }
19
+ }
@@ -0,0 +1,13 @@
1
+ export class Dataset {
2
+ isMultipleChoice = false
3
+ options: string[] | null = null
4
+ pairs: Pair[] | null = null
5
+ rows: string[] | null = null
6
+ columns: string[] | null = null
7
+ isCheckbox = false
8
+ }
9
+
10
+ export class Pair {
11
+ first = ""
12
+ second = ""
13
+ }
@@ -0,0 +1,31 @@
1
+ import { lookup } from "mime-types"
2
+ import { logger } from "./loggerUtils"
3
+
4
+ const mimeBinaryTypes = ["image", "audio", "video", "application", "font"].map((type) => `${type}/`)
5
+
6
+ export function isBinary(contentType: string): boolean {
7
+ return mimeBinaryTypes.some((prefix) => contentType.startsWith(prefix))
8
+ }
9
+
10
+ export function mimeFileType(path: string): string | false | undefined {
11
+ try {
12
+ return lookup(path)
13
+ }
14
+ catch (error) {
15
+ logger("fileUtils").error("Failed to determine file mimetype", error)
16
+ return undefined
17
+ }
18
+ }
19
+
20
+ export function exceedsBase64ContentLimit(base64text: string): boolean {
21
+ return Buffer.from(base64text, "utf16le").byteLength > getBinaryFileLimit()
22
+ }
23
+
24
+ export function getBinaryFileLimit(): number {
25
+ return 1024 * 1024
26
+ }
27
+
28
+ export function getExtension(fileName: string): string {
29
+ const index = fileName.lastIndexOf(".")
30
+ return index < 0 ? "" : fileName.substring(index + 1)
31
+ }
@@ -0,0 +1,24 @@
1
+ import { Course } from "../Course"
2
+ import { HyperskillProject } from "./HyperskillProject"
3
+ import { HyperskillStage } from "./HyperskillStage"
4
+ import { HyperskillTopic } from "./HyperskillTopic"
5
+ import { HYPERSKILL } from "../EduFormatNames"
6
+
7
+ export class HyperskillCourse extends Course {
8
+ taskToTopics: Map<number, HyperskillTopic[]> = new Map()
9
+ stages: HyperskillStage[] = []
10
+ hyperskillProject: HyperskillProject | null = null
11
+ selectedStage: number | null = null
12
+ selectedProblem: number | null = null
13
+
14
+ override get itemType(): string {
15
+ return HYPERSKILL
16
+ }
17
+
18
+ get isTemplateBased(): boolean {
19
+ if (!this.hyperskillProject) {
20
+ throw new Error("Disconnected Hyperskill project")
21
+ }
22
+ return this.hyperskillProject.isTemplateBased
23
+ }
24
+ }
@@ -0,0 +1,10 @@
1
+ export class HyperskillProject {
2
+ id = -1
3
+ title = ""
4
+ description = ""
5
+ ideFiles = ""
6
+ useIde = true
7
+ language = ""
8
+ environment: string | null = null
9
+ isTemplateBased = false
10
+ }
@@ -0,0 +1,15 @@
1
+ export class HyperskillStage {
2
+ id = -1
3
+ title = ""
4
+ stepId = -1
5
+ isCompleted = false
6
+
7
+ constructor()
8
+ constructor(stageId: number, stageTitle: string, stageStepId: number, isStageCompleted: boolean)
9
+ constructor(stageId?: number, stageTitle?: string, stageStepId?: number, isStageCompleted?: boolean) {
10
+ if (stageId !== undefined) this.id = stageId
11
+ if (stageTitle !== undefined) this.title = stageTitle
12
+ if (stageStepId !== undefined) this.stepId = stageStepId
13
+ if (isStageCompleted !== undefined) this.isCompleted = isStageCompleted
14
+ }
15
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Maps Hyperskill task type IDs to their human-readable labels.
3
+ */
4
+ export const HYPERSKILL_TASK_TYPE_LABELS: Record<string, string> = {
5
+ admin: "Linux",
6
+ choice: "Quiz",
7
+ code: "Programming",
8
+ dataset: "Data",
9
+ "fill-blanks": "Fill Blanks",
10
+ "free-answer": "Free Response",
11
+ "manual-score": "Manual Score",
12
+ matching: "Matching",
13
+ math: "Math",
14
+ number: "Number",
15
+ parsons: "Parsons",
16
+ pycharm: "Programming",
17
+ remote_edu: "Programming",
18
+ sorting: "Sorting",
19
+ string: "Text",
20
+ table: "Table",
21
+ text: "Theory",
22
+ video: "Video",
23
+ }
@@ -0,0 +1,5 @@
1
+ export class HyperskillTopic {
2
+ id = -1
3
+ title = ""
4
+ theoryId: number | null = null
5
+ }
@@ -0,0 +1,3 @@
1
+ export function logger(name: string): Console {
2
+ return console
3
+ }
@@ -0,0 +1,10 @@
1
+ import { EduCourse } from "../EduCourse"
2
+ import { STEPIK } from "../EduFormatNames"
3
+
4
+ export class StepikCourse extends EduCourse {
5
+ isAdaptive = false
6
+
7
+ override get itemType(): string {
8
+ return STEPIK
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ import { Lesson } from "../Lesson"
2
+ import { STEPIK } from "../EduFormatNames"
3
+
4
+ export class StepikLesson extends Lesson {
5
+ stepIds: number[] = []
6
+ unitId = 0
7
+
8
+ override get itemType(): string {
9
+ return STEPIK
10
+ }
11
+ }
@@ -0,0 +1,13 @@
1
+ import { CheckStatus } from "../CheckStatus"
2
+ import { Task } from "./Task"
3
+
4
+ export abstract class AnswerTask extends Task {
5
+ static ANSWER_FILE_NAME = "answer.txt"
6
+
7
+ constructor()
8
+ constructor(name: string)
9
+ constructor(name: string, id: number, position: number, updateDate: Date, status: CheckStatus)
10
+ constructor(name?: string, id?: number, position?: number, updateDate?: Date, status?: CheckStatus) {
11
+ super(name as string, id as number, position as number, updateDate as Date, status as CheckStatus)
12
+ }
13
+ }
@@ -0,0 +1,42 @@
1
+ import { CheckStatus } from "../CheckStatus"
2
+ import { Task } from "./Task"
3
+
4
+ export class CodeTask extends Task {
5
+ submissionLanguage?: string
6
+
7
+ constructor()
8
+ constructor(name: string)
9
+ constructor(
10
+ name: string,
11
+ id: number,
12
+ position: number,
13
+ updateDate: Date,
14
+ status: CheckStatus,
15
+ submissionLanguage?: string | null
16
+ )
17
+ constructor(
18
+ name?: string,
19
+ id?: number,
20
+ position?: number,
21
+ updateDate?: Date,
22
+ status?: CheckStatus,
23
+ submissionLanguage?: string | null
24
+ ) {
25
+ super(name as string, id as number, position as number, updateDate as Date, status as CheckStatus)
26
+ this.submissionLanguage = submissionLanguage ?? undefined
27
+ }
28
+
29
+ get itemType(): string {
30
+ return CodeTask.CODE_TASK_TYPE
31
+ }
32
+
33
+ get supportSubmissions(): boolean {
34
+ return true
35
+ }
36
+
37
+ get isPluginTaskType(): boolean {
38
+ return false
39
+ }
40
+
41
+ static CODE_TASK_TYPE = "code"
42
+ }
@@ -0,0 +1,37 @@
1
+ import { CheckStatus } from "../CheckStatus"
2
+ import { Task } from "./Task"
3
+ import type { DataTaskAttempt } from "../attempts/DataTaskAttempt"
4
+
5
+ export class DataTask extends Task {
6
+ attempt?: DataTaskAttempt
7
+
8
+ get isTimeLimited(): boolean {
9
+ return this.attempt?.endDateTime !== undefined
10
+ }
11
+
12
+ constructor()
13
+ constructor(name: string)
14
+ constructor(name: string, id: number, position: number, updateDate: Date, status: CheckStatus)
15
+ constructor(name?: string, id?: number, position?: number, updateDate?: Date, status?: CheckStatus) {
16
+ super(name as string, id as number, position as number, updateDate as Date, status as CheckStatus)
17
+ }
18
+
19
+ get itemType(): string {
20
+ return DataTask.DATA_TASK_TYPE
21
+ }
22
+
23
+ get supportSubmissions(): boolean {
24
+ return false
25
+ }
26
+
27
+ isRunning(): boolean {
28
+ if (this.status !== CheckStatus.Unchecked) return false
29
+ return this.attempt?.isRunning ?? false
30
+ }
31
+
32
+ static DATA_TASK_TYPE = "dataset"
33
+ static DATA_FOLDER_NAME = "data"
34
+ static DATA_SAMPLE_FOLDER_NAME = "sample"
35
+ static DATASET_FOLDER_NAME = "dataset"
36
+ static INPUT_FILE_NAME = "input.txt"
37
+ }
@@ -0,0 +1,26 @@
1
+ import { CheckStatus } from "../CheckStatus"
2
+ import { Task } from "./Task"
3
+
4
+ export class EduTask extends Task {
5
+ constructor()
6
+ constructor(name: string)
7
+ constructor(name: string, id: number, position: number, updateDate: Date, status: CheckStatus)
8
+ constructor(name?: string, id?: number, position?: number, updateDate?: Date, status?: CheckStatus) {
9
+ super(name as string, id as number, position as number, updateDate as Date, status as CheckStatus)
10
+ }
11
+
12
+ get itemType(): string {
13
+ return EduTask.EDU_TASK_TYPE
14
+ }
15
+
16
+ get isToSubmitToRemote(): boolean {
17
+ return this.checkStatus !== CheckStatus.Unchecked
18
+ }
19
+
20
+ get supportSubmissions(): boolean {
21
+ return true
22
+ }
23
+
24
+ static EDU_TASK_TYPE = "edu"
25
+ static PYCHARM_TASK_TYPE = "pycharm"
26
+ }