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,41 @@
1
+ import { Course } from "./Course"
2
+ import { MARKETPLACE } from "./EduFormatNames"
3
+ import { JSON_FORMAT_VERSION } from "./EduVersions"
4
+
5
+ export class EduCourse extends Course {
6
+ get itemType(): string {
7
+ return this.isMarketplace ? MARKETPLACE : super.itemType
8
+ }
9
+
10
+ get isStepikRemote(): boolean {
11
+ return this.id !== 0 && !this.isMarketplace
12
+ }
13
+
14
+ get isMarketplaceRemote(): boolean {
15
+ return this.id !== 0 && this.isMarketplace
16
+ }
17
+
18
+ formatVersion = JSON_FORMAT_VERSION
19
+ isUpToDate = true
20
+ learnersCount = 0
21
+ reviewScore = 0.0
22
+ generatedEduId?: string
23
+ isPreview = false
24
+ sectionIds: number[] = []
25
+ instructors: number[] = []
26
+ isStepikPublic = false
27
+ reviewSummary = 0
28
+
29
+ convertToLocal(): void {
30
+ if (this.isMarketplace) {
31
+ this.marketplaceCourseVersion = 1
32
+ }
33
+ else {
34
+ this.isStepikPublic = false
35
+ this.sectionIds = []
36
+ this.instructors = []
37
+ }
38
+ this.id = 0
39
+ this.updateDate = new Date(0)
40
+ }
41
+ }
@@ -0,0 +1,133 @@
1
+ import {
2
+ BinaryContents,
3
+ FileContents,
4
+ InMemoryTextualContents,
5
+ InMemoryUndeterminedContents,
6
+ TextualContents,
7
+ UndeterminedContents,
8
+ } from "./FileContents"
9
+ import { EduFileErrorHighlightLevel } from "./EduFileErrorHighlightLevel"
10
+ import { exceedsBase64ContentLimit, getBinaryFileLimit, isBinary, mimeFileType } from "./fileUtils"
11
+ import { logger } from "./loggerUtils"
12
+ import type { TaskFile } from "./TaskFile"
13
+
14
+ export class EduFile {
15
+ name = ""
16
+
17
+ private _contents: FileContents = UndeterminedContentsEmpty
18
+
19
+ get text(): string {
20
+ return this.contents.textualRepresentation
21
+ }
22
+
23
+ set text(value: string) {
24
+ this.contents = new InMemoryUndeterminedContents(value)
25
+ }
26
+
27
+ get contents(): FileContents {
28
+ return this._contents
29
+ }
30
+
31
+ set contents(value: FileContents) {
32
+ this._contents = value
33
+ }
34
+
35
+ setContentsIfEquals(expectedValue: FileContents, newValue: FileContents): boolean {
36
+ if (this._contents === expectedValue) {
37
+ this._contents = newValue
38
+ return true
39
+ }
40
+ return false
41
+ }
42
+
43
+ get isBinary(): boolean | undefined {
44
+ if (isTextualContents(this.contents)) return false
45
+ if (isBinaryContents(this.contents)) return true
46
+ return undefined
47
+ }
48
+
49
+ isTrackChanges = true
50
+ errorHighlightLevel: EduFileErrorHighlightLevel = EduFileErrorHighlightLevel.TEMPORARY_SUPPRESSION
51
+ isVisible = false
52
+ isEditable = true
53
+ isPropagatable = true
54
+ isLearnerCreated = false
55
+
56
+ constructor(name?: string, contents?: FileContents | string) {
57
+ if (name !== undefined) {
58
+ this.name = name
59
+ }
60
+ if (contents !== undefined) {
61
+ if (typeof contents === "string") {
62
+ this.contents = new InMemoryTextualContents(contents)
63
+ }
64
+ else {
65
+ this.contents = contents
66
+ }
67
+ }
68
+ }
69
+
70
+ getTextToSerialize(): string | null {
71
+ if (!(this instanceof requireTaskFile().TaskFile) || !this.task.course.needWriteYamlText) {
72
+ return null
73
+ }
74
+
75
+ if (isBinaryContents(this.contents)) return null
76
+ if (isUndeterminedContents(this.contents)) {
77
+ const contentType = mimeFileType(this.name)
78
+ if (contentType && isBinary(String(contentType))) return null
79
+ }
80
+
81
+ const text = this.contents.textualRepresentation
82
+ if (exceedsBase64ContentLimit(text)) {
83
+ logger("EduFile").warn(
84
+ `Base64 encoding of \`${this.name}\` file exceeds limit (${getBinaryFileLimit()}), its content isn't serialized`
85
+ )
86
+ return null
87
+ }
88
+
89
+ return text
90
+ }
91
+
92
+ get pathInCourse(): string {
93
+ const pathPrefix = this instanceof requireTaskFile().TaskFile ? this.task.pathInCourse : ""
94
+ const pathInCourse = `${pathPrefix}/${this.name}`
95
+ return pathInCourse.startsWith("/") ? pathInCourse.substring(1) : pathInCourse
96
+ }
97
+
98
+ get pathInArchive(): string {
99
+ return this.pathInCourse
100
+ }
101
+ }
102
+
103
+ function requireTaskFile(): { TaskFile: typeof TaskFile } {
104
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
105
+ return require("./TaskFile")
106
+ }
107
+
108
+ function isTextualContents(contents: FileContents): contents is TextualContents {
109
+ return (contents as TextualContents).text !== undefined
110
+ }
111
+
112
+ function isBinaryContents(contents: FileContents): contents is BinaryContents {
113
+ return (contents as BinaryContents).bytes !== undefined
114
+ }
115
+
116
+ function isUndeterminedContents(contents: FileContents): contents is UndeterminedContents {
117
+ return (contents as UndeterminedContents).textualRepresentation !== undefined && !isTextualContents(contents)
118
+ }
119
+
120
+ const UndeterminedContentsEmpty: UndeterminedContents = {
121
+ textualRepresentation: "",
122
+ get text() {
123
+ return this.textualRepresentation
124
+ },
125
+ get bytes() {
126
+ try {
127
+ return Buffer.from(this.textualRepresentation, "base64")
128
+ }
129
+ catch {
130
+ return new Uint8Array()
131
+ }
132
+ },
133
+ }
@@ -0,0 +1,5 @@
1
+ export enum EduFileErrorHighlightLevel {
2
+ NONE = "NONE",
3
+ ALL_PROBLEMS = "ALL_PROBLEMS",
4
+ TEMPORARY_SUPPRESSION = "TEMPORARY_SUPPRESSION",
5
+ }
@@ -0,0 +1,95 @@
1
+ import { message } from "./uiMessages"
2
+
3
+ export const EDU_PREFIX = "edu"
4
+ export const REST_PREFIX = "api"
5
+ export const CODE_ARGUMENT = "code"
6
+
7
+ export const MARKETPLACE = "Marketplace"
8
+ export const COURSE_META_FILE = "course.json"
9
+ export const COURSE_ICON_FILE = "courseIcon.svg"
10
+ export const COURSE_CONTENTS_FOLDER = "contents"
11
+
12
+ export const COURSE = "course"
13
+ export const SECTION = "section"
14
+ export const LESSON = "lesson"
15
+ export const FRAMEWORK = "framework"
16
+ export const TASK = "task"
17
+ export const ITEM = "item"
18
+
19
+ export const SOLUTION = "solution"
20
+ export const STATES_ON_CLOSE = "states_on_close"
21
+ export const SUBMISSIONS = "submissions"
22
+
23
+ export const EMAIL = "email"
24
+ export const NAME = "name"
25
+ export const URL = "url"
26
+
27
+ export const CORRECT = "correct"
28
+ export const WRONG = "wrong"
29
+ export const UNCHECKED = "unchecked"
30
+
31
+ export const TIME_LEFT = "time_left"
32
+ export const ID = "id"
33
+ export const TIME = "time"
34
+ export const STEP = "step"
35
+ export const DATASET = "dataset"
36
+ export const STATUS = "status"
37
+ export const USER = "user"
38
+
39
+ export const IS_MULTIPLE_CHOICE = "is_multiple_choice"
40
+ export const OPTIONS = "options"
41
+ export const PAIRS = "pairs"
42
+ export const ROWS = "rows"
43
+ export const COLUMNS = "columns"
44
+ export const IS_CHECKBOX = "is_checkbox"
45
+
46
+ export const DEFAULT_ENVIRONMENT = ""
47
+
48
+ export const PYCHARM = "PyCharm"
49
+
50
+ export const TITLE = "title"
51
+ export const THEORY_ID = "theory"
52
+ export const STEP_ID = "step"
53
+ export const IS_COMPLETED = "is_completed"
54
+ export const DESCRIPTION = "description"
55
+ export const IDE_FILES = "ide_files"
56
+ export const USE_IDE = "use_ide"
57
+ export const LANGUAGE = "language"
58
+ export const ENVIRONMENT = "environment"
59
+ export const IS_TEMPLATE_BASED = "is_template_based"
60
+ export const HYPERSKILL_PROBLEMS = "Problems"
61
+ export const HYPERSKILL_TOPICS = "Topics"
62
+ export const TOPICS = "topics"
63
+ export const HYPERSKILL_PROJECTS_URL = "https://hyperskill.org/projects"
64
+ export const HYPERSKILL = "Hyperskill"
65
+
66
+ export const COURSERA = "Coursera"
67
+
68
+ export const STEPIK = "Stepik"
69
+ export const ATTEMPT = "attempt"
70
+ export const CHECK_PROFILE = "check_profile"
71
+
72
+ export const JAVA = "JAVA"
73
+ export const KOTLIN = "kotlin"
74
+ export const PYTHON = "Python"
75
+ export const SCALA = "Scala"
76
+ export const JAVASCRIPT = "JavaScript"
77
+ export const RUST = "Rust"
78
+ export const SHELL = "Shell Script"
79
+ export const CPP = "C++"
80
+ export const OBJECTIVE_C = "ObjectiveC"
81
+ export const GO = "go"
82
+ export const PHP = "PHP"
83
+ export const CSHARP = "C#"
84
+
85
+ export const PYTHON_2_VERSION = "2.x"
86
+ export const PYTHON_3_VERSION = "3.x"
87
+
88
+ export const TROUBLESHOOTING_GUIDE_URL =
89
+ "https://plugins.jetbrains.com/plugin/10081-jetbrains-academy/docs/troubleshooting-guide.html"
90
+ export const NO_TESTS_URL = `${TROUBLESHOOTING_GUIDE_URL}#no_tests_have_run`
91
+ export const FAILED_TO_CHECK_URL = `${TROUBLESHOOTING_GUIDE_URL}#failed_to_launch_checking`
92
+ export const FAILED_TO_DELETE_SUBMISSIONS =
93
+ `${TROUBLESHOOTING_GUIDE_URL}#failed_to_delete_submissions`
94
+
95
+ export const LOGIN_NEEDED_MESSAGE = message("check.error.login.needed")
@@ -0,0 +1,87 @@
1
+ import { CheckResultDiff } from "./CheckResult"
2
+
3
+ export class EduTestInfo {
4
+ readonly name: string
5
+ readonly status: number
6
+ readonly message: string
7
+ readonly details?: string
8
+ readonly checkResultDiff?: CheckResultDiff
9
+
10
+ private readonly isSuccessInternal: boolean
11
+
12
+ constructor(
13
+ name = "",
14
+ status = -1,
15
+ message = "",
16
+ details?: string | null,
17
+ isFinishedSuccessfully?: boolean | null,
18
+ checkResultDiff?: CheckResultDiff | null
19
+ ) {
20
+ this.name = name
21
+ this.status = status
22
+ this.message = message
23
+ this.details = details ?? undefined
24
+ this.checkResultDiff = checkResultDiff ?? undefined
25
+ this.isSuccessInternal =
26
+ isFinishedSuccessfully === true ||
27
+ (EduTestInfo.get(status) !== undefined && EduTestInfo.isSuccess(EduTestInfo.get(status)!))
28
+ }
29
+
30
+ get isSuccess(): boolean {
31
+ return this.isSuccessInternal
32
+ }
33
+
34
+ toString(): string {
35
+ return `[${EduTestInfo.getPresentableStatus(this.status)}] ${this.name}`
36
+ }
37
+
38
+ static firstFailed(tests: EduTestInfo[]): EduTestInfo | undefined {
39
+ return tests.find((test) => !test.isSuccess)
40
+ }
41
+ }
42
+
43
+ export namespace EduTestInfo {
44
+ export enum PresentableStatus {
45
+ SKIPPED = 0,
46
+ COMPLETED = 1,
47
+ NOT_RUN = 2,
48
+ RUNNING = 3,
49
+ TERMINATED = 4,
50
+ IGNORED = 5,
51
+ FAILED = 6,
52
+ ERROR = 8,
53
+ }
54
+
55
+ const STATUS_TITLES: Record<PresentableStatus, string> = {
56
+ [PresentableStatus.SKIPPED]: "Skipped",
57
+ [PresentableStatus.COMPLETED]: "Completed",
58
+ [PresentableStatus.NOT_RUN]: "Not run",
59
+ [PresentableStatus.RUNNING]: "Running",
60
+ [PresentableStatus.TERMINATED]: "Terminated",
61
+ [PresentableStatus.IGNORED]: "Ignored",
62
+ [PresentableStatus.FAILED]: "Failed",
63
+ [PresentableStatus.ERROR]: "Error",
64
+ }
65
+
66
+ export function get(status: number): PresentableStatus | undefined {
67
+ return (Object.values(PresentableStatus) as Array<string | number>)
68
+ .filter((value) => typeof value === "number")
69
+ .map((value) => value as number)
70
+ .includes(status)
71
+ ? (status as PresentableStatus)
72
+ : undefined
73
+ }
74
+
75
+ export function getPresentableStatus(status: number): string {
76
+ const value = get(status)
77
+ return value === undefined ? "Unknown" : STATUS_TITLES[value]
78
+ }
79
+
80
+ export function isSuccess(status: PresentableStatus): boolean {
81
+ return (
82
+ status === PresentableStatus.COMPLETED ||
83
+ status === PresentableStatus.SKIPPED ||
84
+ status === PresentableStatus.IGNORED
85
+ )
86
+ }
87
+ }
@@ -0,0 +1,2 @@
1
+ export const JSON_FORMAT_VERSION = 22
2
+ export const JSON_FORMAT_VERSION_WITH_NEW_LANGUAGE_VERSION = 16
@@ -0,0 +1,97 @@
1
+ export interface FileContents {
2
+ readonly textualRepresentation: string
3
+ }
4
+
5
+ export interface DeterminedContents extends FileContents {}
6
+
7
+ export interface TextualContents extends DeterminedContents {
8
+ readonly text: string
9
+ }
10
+
11
+ export interface BinaryContents extends DeterminedContents {
12
+ readonly bytes: Uint8Array
13
+ }
14
+
15
+ export interface UndeterminedContents extends FileContents {
16
+ readonly textualRepresentation: string
17
+ readonly text: string
18
+ readonly bytes: Uint8Array
19
+ }
20
+
21
+ export const TextualContentsEmpty: TextualContents = {
22
+ text: "",
23
+ get textualRepresentation() {
24
+ return this.text
25
+ },
26
+ }
27
+
28
+ export const BinaryContentsEmpty: BinaryContents = {
29
+ bytes: new Uint8Array(),
30
+ get textualRepresentation() {
31
+ return Buffer.from(this.bytes).toString("base64")
32
+ },
33
+ }
34
+
35
+ export const UndeterminedContentsEmpty: UndeterminedContents = {
36
+ textualRepresentation: "",
37
+ get text() {
38
+ return this.textualRepresentation
39
+ },
40
+ get bytes() {
41
+ try {
42
+ return Buffer.from(this.textualRepresentation, "base64")
43
+ }
44
+ catch {
45
+ return new Uint8Array()
46
+ }
47
+ },
48
+ }
49
+
50
+ export class InMemoryBinaryContents implements BinaryContents {
51
+ readonly bytes: Uint8Array
52
+
53
+ constructor(bytes: Uint8Array) {
54
+ this.bytes = bytes
55
+ }
56
+
57
+ get textualRepresentation(): string {
58
+ return Buffer.from(this.bytes).toString("base64")
59
+ }
60
+
61
+ static parseBase64Encoding(base64: string): InMemoryBinaryContents {
62
+ return new InMemoryBinaryContents(Buffer.from(base64, "base64"))
63
+ }
64
+ }
65
+
66
+ export class InMemoryTextualContents implements TextualContents {
67
+ readonly text: string
68
+
69
+ constructor(text: string) {
70
+ this.text = text
71
+ }
72
+
73
+ get textualRepresentation(): string {
74
+ return this.text
75
+ }
76
+ }
77
+
78
+ export class InMemoryUndeterminedContents implements UndeterminedContents {
79
+ readonly textualRepresentation: string
80
+
81
+ constructor(textualRepresentation: string) {
82
+ this.textualRepresentation = textualRepresentation
83
+ }
84
+
85
+ get text(): string {
86
+ return this.textualRepresentation
87
+ }
88
+
89
+ get bytes(): Uint8Array {
90
+ try {
91
+ return Buffer.from(this.textualRepresentation, "base64")
92
+ }
93
+ catch {
94
+ return new Uint8Array()
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,19 @@
1
+ import type { EduFile } from "./EduFile"
2
+ import type { BinaryContents, TextualContents } from "./FileContents"
3
+
4
+ /**
5
+ * Factory used to create file contents during deserialization of EduFile, TaskFile.
6
+ * Used when json or yaml do not have the contents themselves (text field is empty).
7
+ */
8
+ export interface FileContentsFactory {
9
+ /**
10
+ * [file] is the EduFile object that will contain these contents.
11
+ * This file may not be fully initialized at the moment of the call.
12
+ */
13
+ createBinaryContents(file: EduFile): BinaryContents
14
+
15
+ /**
16
+ * see createBinaryContents
17
+ */
18
+ createTextualContents(file: EduFile): TextualContents
19
+ }
@@ -0,0 +1,29 @@
1
+ import { Lesson } from "./Lesson"
2
+ import { FRAMEWORK } from "./EduFormatNames"
3
+ import { Task } from "./tasks/Task"
4
+
5
+ export class FrameworkLesson extends Lesson {
6
+ currentTaskIndex = 0
7
+ isTemplateBased = true
8
+
9
+ constructor(lesson?: Lesson) {
10
+ super()
11
+ if (lesson) {
12
+ this.id = lesson.id
13
+ this.updateDate = lesson.updateDate
14
+ this.name = lesson.name
15
+ this.items = lesson.items
16
+ this.parent = lesson.parent
17
+ this.index = lesson.index
18
+ this.customPresentableName = lesson.customPresentableName
19
+ }
20
+ }
21
+
22
+ currentTask(): Task | undefined {
23
+ return this.taskList[this.currentTaskIndex]
24
+ }
25
+
26
+ get itemType(): string {
27
+ return FRAMEWORK
28
+ }
29
+ }
@@ -0,0 +1,47 @@
1
+ import { StudyItem } from "./StudyItem"
2
+
3
+ export abstract class ItemContainer extends StudyItem {
4
+ private _items: StudyItem[] = []
5
+
6
+ get items(): StudyItem[] {
7
+ return this._items
8
+ }
9
+
10
+ set items(value: StudyItem[]) {
11
+ this._items = [...value]
12
+ }
13
+
14
+ override init(parentItem: ItemContainer, isRestarted: boolean): void {
15
+ this.parent = parentItem
16
+ this.items.forEach((item, index) => {
17
+ item.index = index + 1
18
+ item.init(this, isRestarted)
19
+ })
20
+ }
21
+
22
+ getItem(name: string): StudyItem | undefined {
23
+ return this.items.find((item) => item.name === name)
24
+ }
25
+
26
+ addItem(item: StudyItem): void {
27
+ this._items.push(item)
28
+ }
29
+
30
+ addItemAt(index: number, item: StudyItem): void {
31
+ this._items.splice(index, 0, item)
32
+ }
33
+
34
+ replaceItem(existingItem: StudyItem, newItem: StudyItem): void {
35
+ const index = this._items.indexOf(existingItem)
36
+ if (index < 0) return
37
+ this._items[index] = newItem
38
+ }
39
+
40
+ removeItem(item: StudyItem): void {
41
+ this._items = this._items.filter((value) => value !== item)
42
+ }
43
+
44
+ sortItems(): void {
45
+ this._items.sort((a, b) => a.index - b.index)
46
+ }
47
+ }
@@ -0,0 +1,21 @@
1
+ import { UserInfo } from "./UserInfo"
2
+
3
+ export class JBAccountUserInfo implements UserInfo {
4
+ email = ""
5
+ name = ""
6
+ jbaLogin = ""
7
+
8
+ constructor(userName?: string) {
9
+ if (userName) {
10
+ this.name = userName
11
+ }
12
+ }
13
+
14
+ getFullName(): string {
15
+ return this.name
16
+ }
17
+
18
+ toString(): string {
19
+ return this.getFullName()
20
+ }
21
+ }
@@ -0,0 +1,31 @@
1
+ const languages: Record<string, string> = {
2
+ Python: "Python",
3
+ "C++": "C/C++",
4
+ ObjectiveC: "C/C++",
5
+ go: "Go",
6
+ JAVA: "Java",
7
+ kotlin: "Kotlin",
8
+ Scala: "Scala",
9
+ JavaScript: "JavaScript",
10
+ Rust: "Rust",
11
+ PHP: "PHP",
12
+ "Shell Script": "Shell Script",
13
+ SQL: "SQL",
14
+ "C#": "C#",
15
+ unity: "unity",
16
+ TEXT: "Plain text",
17
+ FakeGradleBasedLanguage: "FakeGradleBasedLanguage",
18
+ }
19
+
20
+ export const Language = {
21
+ findLanguageByID(id: string): string | undefined {
22
+ return languages[id]
23
+ },
24
+ findLanguageByName(name: string): string | undefined {
25
+ if (name === "C/C++") {
26
+ return "C++"
27
+ }
28
+ const entry = Object.entries(languages).find(([, value]) => value === name)
29
+ return entry ? entry[0] : undefined
30
+ },
31
+ }
@@ -0,0 +1,69 @@
1
+ import { LessonContainer } from "./LessonContainer"
2
+ import type { Course } from "./Course"
3
+ import type { Section } from "./Section"
4
+ import { Task } from "./tasks/Task"
5
+ import { LESSON } from "./EduFormatNames"
6
+ import { ItemContainer } from "./ItemContainer"
7
+
8
+ function getLessonContainerModule(): { Section: typeof Section } {
9
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
10
+ return require("./Section")
11
+ }
12
+
13
+ export class Lesson extends LessonContainer {
14
+ override init(parentItem: ItemContainer, isRestarted: boolean): void {
15
+ if (!(parentItem instanceof LessonContainer)) {
16
+ throw new Error(`Parent for lesson ${this.name} should be either course or section`)
17
+ }
18
+ super.init(parentItem, isRestarted)
19
+ }
20
+
21
+ get taskList(): Task[] {
22
+ return this.items.filter((item): item is Task => item instanceof Task)
23
+ }
24
+
25
+ get course(): Course {
26
+ return this.parent.course
27
+ }
28
+
29
+ get section(): Section | undefined {
30
+ return this.parent instanceof getLessonContainerModule().Section ? this.parent : undefined
31
+ }
32
+
33
+ get content(): Task[] {
34
+ return this.items.filter((item): item is Task => item instanceof Task)
35
+ }
36
+
37
+ get itemType(): string {
38
+ return LESSON
39
+ }
40
+
41
+ addTask(task: Task): void {
42
+ this.addItem(task)
43
+ }
44
+
45
+ addTaskAt(index: number, task: Task): void {
46
+ this.addItemAt(index, task)
47
+ }
48
+
49
+ removeTask(task: Task): void {
50
+ this.removeItem(task)
51
+ }
52
+
53
+ getTask(name: string): Task | undefined
54
+ getTask(id: number): Task | undefined
55
+ getTask(param: string | number): Task | undefined {
56
+ if (typeof param === "string") {
57
+ return this.taskList.find((task) => task.name === param)
58
+ }
59
+ return this.taskList.find((task) => task.id === param)
60
+ }
61
+
62
+ get container(): LessonContainer {
63
+ return this.section ?? this.course
64
+ }
65
+
66
+ visitTasks(visit: (task: Task) => void): void {
67
+ this.taskList.forEach(visit)
68
+ }
69
+ }