imdone-core 2.0.0 → 2.0.1

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 (160) hide show
  1. package/.dependency-cruiser.cjs +412 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. package/.github/workflows/ci.yml +24 -0
  4. package/.imdoneignore +13 -0
  5. package/.mocharc.json +10 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +160 -0
  8. package/_index.md +7 -0
  9. package/_navbar.md +1 -0
  10. package/build.js +57 -0
  11. package/esm-migration.md +446 -0
  12. package/index.html +184 -0
  13. package/lib/FileProjectContext.js +13 -0
  14. package/lib/ProjectContext.js +43 -0
  15. package/lib/adapters/date-time.js +15 -0
  16. package/lib/adapters/environment.js +3 -0
  17. package/lib/adapters/file-gateway.js +154 -0
  18. package/lib/adapters/git-download.js +55 -0
  19. package/lib/adapters/index.js +9 -0
  20. package/lib/adapters/logger.js +136 -0
  21. package/lib/adapters/markdown.js +180 -0
  22. package/lib/adapters/parsers/DateLanguageParser.js +12 -0
  23. package/lib/adapters/parsers/content-transformer.js +116 -0
  24. package/lib/adapters/parsers/file/LocalFileParserFactory.js +36 -0
  25. package/lib/adapters/parsers/file/ReadStreamCodeParser.js +126 -0
  26. package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +17 -0
  27. package/lib/adapters/parsers/file/ReadStreamTaskParser.js +106 -0
  28. package/lib/adapters/parsers/file/index.js +4 -0
  29. package/lib/adapters/parsers/function-parser.js +25 -0
  30. package/lib/adapters/parsers/index.js +5 -0
  31. package/lib/adapters/parsers/task/CardContentParser.js +367 -0
  32. package/lib/adapters/parsers/task/CheckStyleTaskParser.js +28 -0
  33. package/lib/adapters/parsers/task/CodeStyleTaskParser.js +24 -0
  34. package/lib/adapters/parsers/task/HashStyleTaskParser.js +27 -0
  35. package/lib/adapters/parsers/task/MarkdownStyleTaskParser.js +24 -0
  36. package/lib/adapters/parsers/task/TaskParser.js +38 -0
  37. package/lib/adapters/parsers/task/index.js +6 -0
  38. package/lib/adapters/storage/config.js +44 -0
  39. package/lib/adapters/storage/index.js +2 -0
  40. package/lib/adapters/storage/tags.js +29 -0
  41. package/lib/adapters/yaml.js +9 -0
  42. package/lib/card.js +299 -0
  43. package/lib/checksum.js +22 -0
  44. package/lib/config.js +249 -0
  45. package/lib/constants.js +68 -0
  46. package/lib/context/ApplicationContext.js +35 -0
  47. package/lib/context/index.js +1 -0
  48. package/lib/default-settings.js +37 -0
  49. package/lib/domain/entities/View.js +23 -0
  50. package/lib/domain/entities/index.js +1 -0
  51. package/lib/domain/index.js +2 -0
  52. package/lib/domain/usecases/GetViewsUsecase.js +44 -0
  53. package/lib/domain/usecases/SaveViewUsecase.js +51 -0
  54. package/lib/domain/usecases/index.js +2 -0
  55. package/lib/file.js +1391 -0
  56. package/lib/get-tasks-to-modify.js +117 -0
  57. package/lib/languages.js +113 -0
  58. package/lib/list.js +56 -0
  59. package/lib/migrate-config.js +165 -0
  60. package/lib/mixins/index.js +2 -0
  61. package/lib/mixins/repo-fs-store.js +359 -0
  62. package/lib/mixins/repo-watched-fs-store.js +206 -0
  63. package/lib/plugins/archive-plugin.js +72 -0
  64. package/lib/plugins/default-board-actions-plugin.js +56 -0
  65. package/lib/plugins/default-board-properties-plugin.js +101 -0
  66. package/lib/plugins/default-plugins.js +7 -0
  67. package/lib/plugins/epic-plugin.js +177 -0
  68. package/lib/plugins/extension-plugin.js +94 -0
  69. package/lib/plugins/index.js +9 -0
  70. package/lib/plugins/persist-tags-plugin.js +14 -0
  71. package/lib/plugins/plugin-manager.js +458 -0
  72. package/lib/plugins/plugin-registry.js +57 -0
  73. package/lib/project-factory.js +42 -0
  74. package/lib/project.js +864 -0
  75. package/lib/repository.js +1514 -0
  76. package/lib/task.js +780 -0
  77. package/lib/tools.js +176 -0
  78. package/lib/usecases/actions.js +32 -0
  79. package/lib/usecases/get-imdone-url.js +6 -0
  80. package/lib/usecases/get-project-tags.js +7 -0
  81. package/lib/usecases/get-tasks-in-file.js +26 -0
  82. package/lib/usecases/index.js +4 -0
  83. package/package.json +2 -6
  84. package/scripts/delete-markdown-indexes.js +87 -0
  85. package/scripts/generate-index.js +35 -0
  86. package/scripts/index-markdown.js +55 -0
  87. package/tsconfig.json +15 -0
  88. package/types/FileProjectContext.d.ts +4 -0
  89. package/types/ProjectContext.d.ts +9 -0
  90. package/types/adapters/date-time.d.ts +2 -0
  91. package/types/adapters/environment.d.ts +1 -0
  92. package/types/adapters/file-gateway.d.ts +51 -0
  93. package/types/adapters/git-download.d.ts +1 -0
  94. package/types/adapters/index.d.ts +9 -0
  95. package/types/adapters/logger.d.ts +24 -0
  96. package/types/adapters/markdown.d.ts +13 -0
  97. package/types/adapters/parsers/DateLanguageParser.d.ts +1 -0
  98. package/types/adapters/parsers/content-transformer.d.ts +10 -0
  99. package/types/adapters/parsers/file/LocalFileParserFactory.d.ts +5 -0
  100. package/types/adapters/parsers/file/ReadStreamCodeParser.d.ts +13 -0
  101. package/types/adapters/parsers/file/ReadStreamMarkdownParser.d.ts +7 -0
  102. package/types/adapters/parsers/file/ReadStreamTaskParser.d.ts +19 -0
  103. package/types/adapters/parsers/file/index.d.ts +4 -0
  104. package/types/adapters/parsers/function-parser.d.ts +1 -0
  105. package/types/adapters/parsers/index.d.ts +5 -0
  106. package/types/adapters/parsers/task/CardContentParser.d.ts +45 -0
  107. package/types/adapters/parsers/task/CheckStyleTaskParser.d.ts +12 -0
  108. package/types/adapters/parsers/task/CodeStyleTaskParser.d.ts +14 -0
  109. package/types/adapters/parsers/task/HashStyleTaskParser.d.ts +13 -0
  110. package/types/adapters/parsers/task/MarkdownStyleTaskParser.d.ts +14 -0
  111. package/types/adapters/parsers/task/TaskParser.d.ts +7 -0
  112. package/types/adapters/parsers/task/index.d.ts +6 -0
  113. package/types/adapters/storage/config.d.ts +6 -0
  114. package/types/adapters/storage/index.d.ts +2 -0
  115. package/types/adapters/storage/tags.d.ts +3 -0
  116. package/types/adapters/yaml.d.ts +2 -0
  117. package/types/card.d.ts +166 -0
  118. package/types/checksum.d.ts +8 -0
  119. package/types/config.d.ts +61 -0
  120. package/types/constants.d.ts +46 -0
  121. package/types/context/ApplicationContext.d.ts +24 -0
  122. package/types/context/index.d.ts +1 -0
  123. package/types/default-settings.d.ts +2 -0
  124. package/types/domain/entities/FileProjectContext.d.ts +5 -0
  125. package/types/domain/entities/View.d.ts +17 -0
  126. package/types/domain/entities/index.d.ts +1 -0
  127. package/types/domain/index.d.ts +2 -0
  128. package/types/domain/usecases/GetViewsUsecase.d.ts +4 -0
  129. package/types/domain/usecases/SaveViewUsecase.d.ts +8 -0
  130. package/types/domain/usecases/index.d.ts +2 -0
  131. package/types/file.d.ts +344 -0
  132. package/types/get-tasks-to-modify.d.ts +3 -0
  133. package/types/index.d.ts +23 -0
  134. package/types/languages.d.ts +649 -0
  135. package/types/list.d.ts +30 -0
  136. package/types/migrate-config.d.ts +1 -0
  137. package/types/mixins/index.d.ts +2 -0
  138. package/types/mixins/repo-fs-store.d.ts +2 -0
  139. package/types/mixins/repo-watched-fs-store.d.ts +2 -0
  140. package/types/plugins/archive-plugin.d.ts +8 -0
  141. package/types/plugins/default-board-actions-plugin.d.ts +9 -0
  142. package/types/plugins/default-board-properties-plugin.d.ts +16 -0
  143. package/types/plugins/default-plugins.d.ts +6 -0
  144. package/types/plugins/epic-plugin.d.ts +19 -0
  145. package/types/plugins/extension-plugin.d.ts +15 -0
  146. package/types/plugins/index.d.ts +9 -0
  147. package/types/plugins/persist-tags-plugin.d.ts +4 -0
  148. package/types/plugins/plugin-manager.d.ts +68 -0
  149. package/types/plugins/plugin-registry.d.ts +1 -0
  150. package/types/project-factory.d.ts +14 -0
  151. package/types/project.d.ts +158 -0
  152. package/types/repository.d.ts +996 -0
  153. package/types/task.d.ts +202 -0
  154. package/types/tools.d.ts +29 -0
  155. package/types/usecases/actions.d.ts +7 -0
  156. package/types/usecases/get-imdone-url.d.ts +1 -0
  157. package/types/usecases/get-project-tags.d.ts +1 -0
  158. package/types/usecases/get-tasks-in-file.d.ts +4 -0
  159. package/types/usecases/index.d.ts +4 -0
  160. package/vitest.config.js +13 -0
@@ -0,0 +1,1514 @@
1
+ import _isFunction from 'lodash.isfunction'
2
+ import _isUndefined from 'lodash.isundefined'
3
+ import _isString from 'lodash.isstring'
4
+ import _omit from 'lodash.omit'
5
+ import _reject from 'lodash.reject'
6
+ import _noop from 'lodash.noop'
7
+ import _some from 'lodash.some'
8
+ import _remove from 'lodash.remove'
9
+ import _groupBy from 'lodash.groupby'
10
+ import _where from 'lodash.where'
11
+ import _union from 'lodash.union'
12
+ import Emitter from 'events'
13
+ import { languages } from './languages.js'
14
+ import util from 'util'
15
+ import { parallel, eachLimit, eachSeries, series } from 'async'
16
+ import path from 'path'
17
+ import ignore from 'ignore'
18
+ import { File } from './file.js'
19
+ import eol from 'eol'
20
+ import { tools } from './tools.js'
21
+ const { inMixinsNoop } = tools
22
+ import { constants } from './constants.js'
23
+ import debug from 'debug'
24
+ const log = debug('imdone-core:Repository')
25
+ import { List } from './list.js'
26
+ import monquery from 'monquery'
27
+ import sift from 'sift'
28
+ import { sort } from 'fast-sort'
29
+ import JSONfns from 'json-fns'
30
+ import { Task } from './task.js'
31
+ import { newCard } from './card.js'
32
+ import { replaceDateLanguage } from './adapters/parsers/DateLanguageParser.js'
33
+ import { getRawTask, isNumber, LIST_NAME_PATTERN } from './adapters/parsers/task/CardContentParser.js'
34
+ import { interpolate } from './adapters/parsers/content-transformer.js'
35
+ import XRegExp from 'xregexp'
36
+ import { appContext } from './context/ApplicationContext.js'
37
+ import { computeChecksum } from './checksum.js'
38
+ import { getTasksToModify } from './get-tasks-to-modify.js'
39
+ import { logger } from './adapters/logger.js'
40
+ const { ERRORS, ASYNC_LIMIT, DEFAULT_FILE_PATTERN} = constants
41
+ const DEFAULT_SORT = [{ asc: u => isNumber(u.order) ? u.order : Infinity }, { asc: u => u.text }]
42
+
43
+ function getTasksByList (
44
+ repo,
45
+ tasksAry,
46
+ noSort,
47
+ populateFiltered
48
+ ) {
49
+ if (!repo) return []
50
+ var tasks = {}
51
+ var allTasks = noSort ? tasksAry : sort(tasksAry).by(DEFAULT_SORT)
52
+ allTasks.forEach(function (task) {
53
+ if (!tasks[task.list]) tasks[task.list] = []
54
+ tasks[task.list].push(task)
55
+ })
56
+ var lists = repo.getLists()
57
+ lists.forEach((list) => {
58
+ if (list.filter && populateFiltered)
59
+ return Repository.populateFilteredList(list, tasksAry)
60
+ list.tasks = tasks[list.name] || []
61
+ })
62
+ return lists
63
+ }
64
+
65
+ function populateFilteredList (list, tasks) {
66
+ try {
67
+ list.tasks = Repository.query(tasks, list.filter).map((task) => {
68
+ task.filteredListName = list.name
69
+ return task
70
+ })
71
+ } catch (e) {
72
+ list.tasks = []
73
+ }
74
+ }
75
+
76
+ function regexQuery (tasks, queryString) {
77
+ return tasks
78
+ .map((task) => {
79
+ const escapedQueryString = tools.escapeRegExp(queryString.trim());
80
+
81
+ // Regex to match occurrences not part of a URL and not already surrounded by `==`
82
+ const regex = new RegExp(
83
+ `(?<!https?:\\/\\/[^\\s]*?)\\b${escapedQueryString}\\b(?![^\\s]*\\/)(?<!==)(?!==)`,
84
+ 'gi'
85
+ );
86
+
87
+ // Regex to detect Markdown links `[title](url)` where `queryString` is in title or URL
88
+ const markdownLinkRegex = new RegExp(
89
+ `\\[([^\\]]*${escapedQueryString}[^\\]]*)\\]\\(([^)]+${escapedQueryString}[^)]*)\\)`,
90
+ 'gi'
91
+ );
92
+
93
+ let match = task.content.match(regex) || task.content.match(markdownLinkRegex);
94
+ task.match = match;
95
+
96
+ if (match && queryString.trim() && task.interpretedContent) {
97
+ // Replace standalone occurrences
98
+ task.interpretedContent = task.interpretedContent.replace(
99
+ regex,
100
+ '==$&=='
101
+ );
102
+
103
+ // Replace Markdown links where queryString is in title or URL
104
+ task.interpretedContent = task.interpretedContent.replace(
105
+ markdownLinkRegex,
106
+ (match) => `==${match}==`
107
+ );
108
+
109
+ task.updateDescriptionData();
110
+ }
111
+
112
+ return task;
113
+ })
114
+ .filter(({ match }) => match);
115
+ };
116
+
117
+
118
+ function parseHideListsFromQueryString (queryString) {
119
+ const hideLists = []
120
+ const regex = `hide:(\\s*((${LIST_NAME_PATTERN},?\\s?)+))?`
121
+ queryString = queryString.replaceAll(
122
+ // hide lists example `hide: list1, list2`
123
+ new XRegExp(regex, 'g'),
124
+ (match, allLists, lists) => {
125
+ lists && lists.split(',').forEach((list) => {
126
+ hideLists.push(list.trim())
127
+ })
128
+ return ''
129
+ }
130
+ ).trim()
131
+ return {
132
+ hideLists,
133
+ queryString,
134
+ }
135
+ }
136
+
137
+ function parseSortFromQueryString (queryString) {
138
+ const sort = []
139
+ queryString = queryString.replace(
140
+ /\s([+-])([A-Za-z.]+)/g,
141
+ (match, order, attr) => {
142
+ const direction = order === '+' ? 'asc' : 'desc'
143
+ const sortString = `{ "${direction}": "function (o) { return o.${attr};}" }`
144
+ sort.push(JSONfns.parse(sortString))
145
+ return ''
146
+ }
147
+ )
148
+ return {
149
+ sort,
150
+ queryString,
151
+ }
152
+ }
153
+
154
+ function parseSortFromMongoQuery (mongoQuery) {
155
+ const sort = []
156
+ for (const [key, value] of Object.entries(mongoQuery.sort)) {
157
+ if (value > 0) sort.push({ asc: key })
158
+ else sort.push({ desc: key })
159
+ }
160
+ return sort
161
+ }
162
+
163
+ // function sortByQuery (tasks, queryString = '') {
164
+ // queryString = replaceDateLanguage(queryString)
165
+ // let { sort } = Repository.parseSortFromQueryString(queryString)
166
+ // if (!sort || sort.length === 0) sort = DEFAULT_SORT
167
+ // sort(tasks).by(sort)
168
+ // }
169
+
170
+ function filterCards (tasks, _queryString = '') {
171
+ let query
172
+ _queryString = replaceDateLanguage(_queryString)
173
+ let { sort: userSort, queryString } = Repository.parseSortFromQueryString(_queryString);
174
+ const hideListsQuery = Repository.parseHideListsFromQueryString(queryString);
175
+ queryString = hideListsQuery.queryString;
176
+ const hideLists = hideListsQuery.hideLists;
177
+ try {
178
+ query = monquery(queryString)
179
+ } catch (e) {
180
+ log(`Unable to parse ${_queryString} using monquery`)
181
+ }
182
+ let result = []
183
+ if (query) result = tasks.filter(sift.default(query))
184
+ if (!query || result.length === 0) {
185
+ result = Repository.regexQuery(tasks, queryString)
186
+ }
187
+ if (!userSort || userSort.length === 0) userSort = DEFAULT_SORT
188
+ const sorted = sort(result).by(userSort)
189
+ return {
190
+ result: sorted,
191
+ query,
192
+ sort: userSort,
193
+ hideLists
194
+ }
195
+ }
196
+
197
+ function query (tasks, queryString) {
198
+ return Repository.filterCards(tasks, queryString).result
199
+ }
200
+
201
+ function replaceDatesInQuery (query) {
202
+ return Repository.filterObjectValues(query, (key, value) => {
203
+ let date = Date.parse(value)
204
+ if (date && /(\d{4})-(\d{2})-(\d{2})/.test(value)) {
205
+ return date
206
+ }
207
+ return value
208
+ })
209
+ }
210
+
211
+ function filterObjectValues (o, cb) {
212
+ if (o && typeof o === 'object') {
213
+ for (const [key, value] of Object.entries(o)) {
214
+ if (value && typeof value === 'object') {
215
+ Repository.filterObjectValues(value, cb)
216
+ } else {
217
+ o[key] = cb(key, value)
218
+ }
219
+ }
220
+ }
221
+ return o
222
+ }
223
+
224
+ // Emits task.found, list.found, file.update and file.delete, file.processed, files.saved
225
+ export class Repository extends Emitter {
226
+
227
+ constructor(_path, config) {
228
+ super()
229
+ this.config = config
230
+ this.path = _path
231
+ this.files = []
232
+ this.languages = languages
233
+ this.allMeta = {}
234
+ this.metaKeys = new Set()
235
+ this.allTags = new Set()
236
+ this.allTopics = new Set()
237
+ this.allContexts = new Set()
238
+ }
239
+
240
+ static getTasksByList = getTasksByList
241
+
242
+ static populateFilteredList = populateFilteredList
243
+
244
+ static regexQuery = regexQuery
245
+
246
+ static parseHideListsFromQueryString = parseHideListsFromQueryString
247
+
248
+ static parseSortFromQueryString = parseSortFromQueryString
249
+
250
+ static parseSortFromMongoQuery = parseSortFromMongoQuery
251
+
252
+ // static sortByQuery = sortByQuery
253
+
254
+ static filterCards = filterCards
255
+
256
+ static query = query
257
+
258
+ static replaceDatesInQuery = replaceDatesInQuery
259
+
260
+ static filterObjectValues = filterObjectValues
261
+
262
+ init () {
263
+ inMixinsNoop()
264
+ }
265
+
266
+ // READY Refactor to use async/await
267
+ // #esm-migration #urgent #important
268
+ // <!--
269
+ // order:-240
270
+ // -->
271
+ async refresh () {
272
+ this.files = []
273
+ this.allMeta = {}
274
+ this.metaKeys = new Set()
275
+ this.allTags = new Set()
276
+ this.allTopics = new Set()
277
+ this.allContexts = new Set()
278
+ this.config.dirty = true
279
+
280
+ let files = await this.getFilesInPath(false)
281
+ try {
282
+ const config = await this.loadConfig()
283
+ this.config = config
284
+ files = await this.readFiles()
285
+ this.emit('initialized', { ok: true, lists: this.getTasksByList() })
286
+ } catch (err) {
287
+ this.emit('initialized', { ok: false })
288
+ throw err
289
+ }
290
+ return files
291
+ }
292
+
293
+ /**
294
+ * Description
295
+ * @method destroy
296
+ * @return
297
+ */
298
+ async destroy () {
299
+ this.destroyed = true
300
+ this.removeAllListeners()
301
+ }
302
+
303
+ /**
304
+ * Description
305
+ * @method getId
306
+ * @return CallExpression
307
+ */
308
+ getId () {
309
+ return this.getPath()
310
+ }
311
+
312
+ getProject () {
313
+ return this.project
314
+ }
315
+
316
+ getDisplayName () {
317
+ return path.basename(this.path)
318
+ }
319
+
320
+ emitFileUpdate (file, force) {
321
+ if (force || this.shouldEmitFileUpdate(file)) this.emit('file.update', file)
322
+ }
323
+
324
+ shouldEmitFileUpdate (file) {
325
+ if (this.moving) return
326
+ if (this.lastMovedFiles) {
327
+ var index = this.lastMovedFiles.indexOf(file)
328
+ if (index > -1) {
329
+ this.lastMovedFiles.splice(index, 1)
330
+ } else {
331
+ if (file && file.updated) return true
332
+ }
333
+ } else {
334
+ if (file && file.updated) return true
335
+ }
336
+ }
337
+
338
+ emitConfigUpdate (data) {
339
+ if (this.savingConfig) return
340
+ process.nextTick(() => {
341
+ this.emit('config.update', data)
342
+ })
343
+ }
344
+
345
+ // READY Refactor createListeners to use async/await
346
+ // #esm-migration
347
+ // <!--
348
+ // order:-120
349
+ // -->
350
+ createListeners () {
351
+ if (this.taskListener) return
352
+
353
+ this.taskListener = async (event, task) => {
354
+ if (!this.listExists(task.list) && this.config.includeList(task.list)) {
355
+ const list = new List({ name: task.list })
356
+ await this.addList(list)
357
+ this.emit('list.found', list)
358
+ await this.saveConfig()
359
+ }
360
+ Object.keys(task.allMeta).forEach(key => this.metaKeys.add(key))
361
+ task.topics.forEach(topic => this.allTopics.add(topic))
362
+ task.allTags.forEach(tag => this.allTags.add(tag))
363
+ task.allContext.forEach(context => this.allContexts.add(context))
364
+ this.allMeta = this.addAllMeta(task.allMeta)
365
+ this.emit(event, task)
366
+ }
367
+
368
+ this.taskFoundListener = (task) => {
369
+ this.taskListener('task.found', task)
370
+ }
371
+
372
+ this.taskModifiedListener = (task) => {
373
+ this.taskListener('task.modified', task)
374
+ }
375
+ }
376
+
377
+ addAllMeta (meta) {
378
+ const allMeta = Object.assign({}, this.allMeta)
379
+ Object.keys(meta).forEach((key) => {
380
+ if (!allMeta[key]) {
381
+ allMeta[key] = meta[key]
382
+ return
383
+ }
384
+ allMeta[key] = _union(allMeta[key], meta[key])
385
+ })
386
+ return allMeta
387
+ }
388
+
389
+ /**
390
+ * Description
391
+ * @method addList
392
+ * @param {} list
393
+ * @return
394
+ */
395
+ async addList (list) {
396
+ if (this.listExists(list.name)) return;
397
+
398
+ list = _omit(list, 'tasks');
399
+ this.config.lists.push(new List(list));
400
+
401
+ if (!list.filter && !/[a-z]/.test(list.name)) {
402
+ const codeList = list.name.replace(/\s+/g, '-').toUpperCase();
403
+ if (!this.config.code.include_lists.find((name) => name === codeList)) {
404
+ this.config.code.include_lists.push(codeList);
405
+ }
406
+ }
407
+
408
+ await this.saveConfig()
409
+ this.emit('list.modified', list);
410
+ };
411
+
412
+ // READY Refactor removeList to use async/await
413
+ // #esm-migration
414
+ // <!--
415
+ // order:-220
416
+ // -->
417
+ async removeList (list) {
418
+ if (!this.listExists(list)) return
419
+
420
+ var lists = _reject(this.getLists(), { name: list })
421
+ if (this.config.code && this.config.code.include_lists) {
422
+ this.config.code.include_lists = _reject(this.config.code.include_lists, list)
423
+ }
424
+ this.setLists(lists)
425
+ await this.saveConfig()
426
+ this.emit('list.modified', list)
427
+ }
428
+
429
+ /**
430
+ * Description
431
+ * @method getPath
432
+ * @return MemberExpression
433
+ */
434
+ getPath () {
435
+ return this.path
436
+ }
437
+
438
+ /**
439
+ * Description
440
+ * @method getConfig
441
+ * @return MemberExpression
442
+ */
443
+ getConfig () {
444
+ return this.config
445
+ }
446
+
447
+ /**
448
+ * Description
449
+ * @method getLists
450
+ * @return MemberExpression
451
+ */
452
+ getLists () {
453
+ const config = this.getConfig()
454
+ return config ? config.lists.map(obj => ({...obj})) : []
455
+ }
456
+
457
+ getVisibleLists () {
458
+ return _reject(this.getLists(), 'hidden')
459
+ }
460
+
461
+ isListVisible (name) {
462
+ return this.getVisibleLists.find((list) => list.name === name)
463
+ }
464
+
465
+ /**
466
+ * Description
467
+ * @method setLists
468
+ * @param {} lists
469
+ * @return ThisExpression
470
+ */
471
+ setLists (lists) {
472
+ this.config.lists = lists.map((list) => {
473
+ return new List(list)
474
+ })
475
+ return this
476
+ }
477
+
478
+ /**
479
+ * Description
480
+ * @method listExists
481
+ * @param {} name
482
+ * @return BinaryExpression
483
+ */
484
+ listExists (name) {
485
+ return this.getConfig().listExists(name)
486
+ }
487
+
488
+ /**
489
+ * Save the config file (Implemented in mixins)
490
+ *
491
+ * @method saveConfig
492
+ * @return
493
+ */
494
+ async saveConfig () {
495
+ inMixinsNoop()
496
+ }
497
+
498
+ /**
499
+ * Load the config file (Implemented in mixins)
500
+ *
501
+ * @method loadConfig
502
+ * @return MemberExpression
503
+ */
504
+ // READY Refactor loadConfig to use async/await
505
+ // #esm-migration #urgent
506
+ // <!--
507
+ // order:-160
508
+ // -->
509
+ async loadConfig () {
510
+ inMixinsNoop()
511
+ }
512
+
513
+ async migrateTasksByConfig (oldConfig, newConfig) {
514
+ return new Promise((resolve, reject) => {
515
+ if (!oldConfig || !newConfig) return resolve()
516
+ const oldMetaSep = oldConfig.getMetaSep()
517
+ const newMetaSep = newConfig.getMetaSep()
518
+ if (oldMetaSep === newMetaSep) return resolve()
519
+ eachLimit(
520
+ this.getFiles(),
521
+ ASYNC_LIMIT,
522
+ (file, cb) => {
523
+ const tasks = sort(file.tasks).desc(u => u.line)
524
+ eachSeries(
525
+ tasks,
526
+ async (task, cb) => {
527
+ if (!Task.isTask(task)) return cb()
528
+ try {
529
+ task.replaceMetaSep(oldMetaSep, newMetaSep)
530
+ await this.modifyTask(task, false)
531
+ cb()
532
+ } catch (err) {
533
+ cb(err)
534
+ }
535
+ },
536
+ async (err) => {
537
+ if (err) return cb(err)
538
+ if (!file.isModified() || file.getContent().trim() === '') return cb()
539
+ try {
540
+ const file = await this.writeFile(file)
541
+ this.resetFile(file)
542
+ cb(null, file)
543
+ } catch (err) {
544
+ cb(err)
545
+ }
546
+ }
547
+ )
548
+ },
549
+ (err) => {
550
+ if (err) return reject(err)
551
+ resolve()
552
+ }
553
+ )
554
+ })
555
+ }
556
+
557
+ /**
558
+ * Get the full path from a relative path
559
+ *
560
+ * @method getFullPath
561
+ * @param {} file
562
+ * @return String
563
+ */
564
+ getFullPath (file) {
565
+ if (File.isFile(file)) file = file.path
566
+ if (file.indexOf(this.path) === 0) return file
567
+ try {
568
+ var fullPath = path.join(this.path, file)
569
+ return fullPath
570
+ } catch (e) {
571
+ throw new Error(
572
+ util.format(
573
+ 'Error getting full path for file:%s and repo path:%s',
574
+ file,
575
+ this.path
576
+ )
577
+ )
578
+ }
579
+ }
580
+
581
+ /**
582
+ * Get the relative path from repository root
583
+ *
584
+ * @method getRelativePath
585
+ * @param {} fullPath
586
+ * @return String
587
+ */
588
+ getRelativePath (fullPath) {
589
+ if (path.sep === '\\') {
590
+ fullPath = fullPath.replace(/\//g, path.sep)
591
+ }
592
+ if (!fullPath.startsWith(this.path)) return fullPath
593
+ try {
594
+ var relPath = path.relative(this.path, fullPath)
595
+ return relPath
596
+ } catch (e) {
597
+ throw new Error(
598
+ util.format(
599
+ 'Error getting relative path for file:%s and repo path:%s',
600
+ fullPath,
601
+ this.path
602
+ )
603
+ )
604
+ }
605
+ }
606
+
607
+ /**
608
+ * Is this file OK? Implemented in mixins
609
+ *
610
+ * @method fileOK
611
+ * @param {} file
612
+ * @param {} includeDirs
613
+ * @return stat
614
+ */
615
+ // READY Refactor fileOK to use async/await
616
+ // #esm-migration #urgent
617
+ async fileOK (file, includeDirs) {
618
+ inMixinsNoop()
619
+ }
620
+
621
+ setIgnores (ignores) {
622
+ // logger.info('ignore patterns:', ignores)
623
+ this.ignorePatterns = ignores
624
+ this.ignore = ignore().add(ignores)
625
+ }
626
+
627
+ /**
628
+ * Should the relative path be included.
629
+ *
630
+ * @method shouldInclude
631
+ * @param {} relPath
632
+ * @return exclude
633
+ */
634
+ shouldInclude (relPath) {
635
+ relPath = this.getRelativePath(relPath)
636
+ let include = true
637
+ if (this.config.markdownOnly && relPath) {
638
+ include = File.isMarkdown(relPath)
639
+ }
640
+ if (this.ignore) {
641
+ try {
642
+ include = include && (relPath === '' || !this.ignore.ignores(relPath))
643
+ } catch (e) {
644
+ logger.error(
645
+ `Failed to check ignore status for dir: [${relPath}] in: [${this.path}]. It will be included.`,
646
+ e
647
+ )
648
+ }
649
+ }
650
+
651
+ return include
652
+ }
653
+
654
+ /**
655
+ * Add or replace a file in the files reference array
656
+ *
657
+ * @method addFile
658
+ * @param {} file
659
+ * @return MemberExpression
660
+ */
661
+ // READY Refactor addFile to use async/await
662
+ // #esm-migration #urgent
663
+ async addFile (file) {
664
+ if (this.destroyed) throw new Error('destroyed')
665
+ const ok = await this.fileOK(file)
666
+ if (!ok) return this.files
667
+ let index = this.files.findIndex(({ path }) => path === file.path)
668
+ if (index > -1) {
669
+ this.files[index] = file
670
+ } else {
671
+ this.files.push(file)
672
+ }
673
+
674
+ return this.files
675
+ }
676
+
677
+ /**
678
+ * Remove a file from the files reference array
679
+ *
680
+ * @method removeFile
681
+ * @param {} file
682
+ * @return MemberExpression
683
+ */
684
+ removeFile (file) {
685
+ if (!File.isFile(file)) throw new Error(ERRORS.NOT_A_FILE)
686
+ _remove(this.files, f => f.path === file.path)
687
+
688
+ return this.files
689
+ }
690
+
691
+ /**
692
+ * Description
693
+ * @method getFile
694
+ * @param {} path
695
+ * @return CallExpression
696
+ */
697
+ getFile (path) {
698
+ path = this.getRelativePath(path)
699
+ return this.files.find((file) => file.path === path)
700
+ }
701
+
702
+ getTask (id) {
703
+ if (Task.isTask(id)) {
704
+ let task = id
705
+ if (!task.meta.id) return
706
+ return this.getTasks().find((existingTask) => {
707
+ return existingTask.meta.id && existingTask.meta.id[0] == task.meta.id[0]
708
+ })
709
+ } else {
710
+ let task = this.getTasks().find((task) => task.id === id)
711
+ if (!task) return
712
+ return this.getFile(task.source.path)
713
+ .getTasks()
714
+ .find((task) => task.id === id)
715
+ }
716
+ }
717
+
718
+ /**
719
+ * Description
720
+ * @method getFileForTask
721
+ * @param {} task
722
+ * @return CallExpression
723
+ */
724
+ getFileForTask (task) {
725
+ return this.getFile(task.getSource().path)
726
+ }
727
+
728
+ /**
729
+ * Descriptione
730
+ * @method getFiles
731
+ * @param {} paths
732
+ * @return CallExpression
733
+ */
734
+ getFiles (paths) {
735
+ if (_isUndefined(paths)) return sort(this.files).asc(u => u.path)
736
+ return this.files.filter((file) => {
737
+ return paths.includes(file.path)
738
+ })
739
+ }
740
+
741
+ getFilesWithTasks () {
742
+ const files = this.files.filter((file) => file.getTasks().length > 0)
743
+ return sort(files).asc(u => u.path)
744
+ }
745
+
746
+ resetFile (file) {
747
+ file.reset()
748
+ file.removeListener('task.found', this.taskFoundListener)
749
+ file.removeListener('task.modified', this.taskModifiedListener)
750
+ }
751
+
752
+ // READY refactor extractTasks to use async/await
753
+ // #esm-migration #urgent #important
754
+ // <!--
755
+ // order:-100
756
+ // -->
757
+ async extractTasks (file) {
758
+ if (file.content === null) {
759
+ await this.readFileContent(file)
760
+ }
761
+ file.on('task.found', this.taskFoundListener)
762
+ file.on('task.modified', this.taskModifiedListener)
763
+ const fileContent = file.content
764
+ file.extractAndTransformTasks(this.getConfig())
765
+ if (!file.isModified() || fileContent === file.content) {
766
+ this.resetFile(file)
767
+ return file
768
+ }
769
+ file.extractTasks(this.getConfig())
770
+ if (file.modified) await this.writeFile(file)
771
+ this.resetFile(file)
772
+ return file
773
+ }
774
+
775
+ /**
776
+ * Implemented in mixins
777
+ * @method writeFile
778
+ * @param {} file
779
+ * @param {} cb
780
+ * @return
781
+ */
782
+ async writeFile (file) {
783
+ inMixinsNoop()
784
+ }
785
+
786
+ /**
787
+ * Implemented in mixins
788
+ * @method getFilesInPath
789
+ * @param {} includeDirs
790
+ * @return CallExpression
791
+ */
792
+
793
+ async getFilesInPath (includeDirs) {
794
+ inMixinsNoop()
795
+ }
796
+
797
+ /**
798
+ * Implemented in mixins
799
+ * @method readFileContent
800
+ * @param {} file
801
+ * @return
802
+ */
803
+ async readFileContent (file) {
804
+ inMixinsNoop()
805
+ }
806
+
807
+ // READY Refactor readFile to use async/await
808
+ // #esm-migration #important #urgent
809
+ // <!--
810
+ // order:-30
811
+ // -->
812
+ async readFile(file) {
813
+ if (!File.isFile(file)) throw new Error(ERRORS.NOT_A_FILE)
814
+ if (file.deleted) return file
815
+
816
+ var currentChecksum = file.checksum
817
+ const filePath = file.path
818
+ if (/\.\.(\/|\\)/.test(filePath)) throw new Error('Unable to read file:' + file)
819
+
820
+ await this.readFileContent(file)
821
+ file.checksum = computeChecksum(file.getContent())
822
+ file.updated = currentChecksum !== file.checksum
823
+ if (!file.updated) return file
824
+
825
+ await this.extractTasks(file)
826
+ await this.addFile()
827
+
828
+ return file
829
+ }
830
+
831
+ /**
832
+ * Description
833
+ * @method readFiles
834
+ * @param {} files
835
+ * @param {} cb
836
+ * @return
837
+ */
838
+ // READY Refactor readFiles to use async/await
839
+ // #esm-migration #urgent #important
840
+ // <!--
841
+ // order:-10
842
+ // -->
843
+ readFiles (files = this.files) {
844
+ this.allMeta = {}
845
+ this.metaKeys = new Set()
846
+ this.allTags = new Set()
847
+ this.allContexts = new Set()
848
+
849
+ return new Promise(async (resolve, reject) => {
850
+
851
+ if (files.length < 1) {
852
+ files = this.files = await this.getFilesInPath(false)
853
+ const filesToInclude = files.map((file) => file.path)
854
+ const fileStats = {
855
+ count: filesToInclude.length,
856
+ files: filesToInclude,
857
+ }
858
+ this.emit('files.found', fileStats)
859
+ }
860
+ let completed = 0
861
+
862
+ if (files.length < 1) return resolve(files)
863
+ eachLimit(
864
+ files,
865
+ ASYNC_LIMIT,
866
+ async (file) => {
867
+ this.emit('file.reading', { path: file.path })
868
+ await this.readFile(file)
869
+ completed++
870
+ this.emit('file.read', {path: file.path, completed: completed})
871
+ },
872
+ function (err) {
873
+ if (err) return reject(err)
874
+ resolve(files)
875
+ }
876
+ )
877
+ })
878
+ }
879
+
880
+ /**
881
+ * Implemented in mixins
882
+ * @method deleteFile
883
+ * @param {} path
884
+ * @param {} cb
885
+ * @return
886
+ */
887
+ async deleteFile (path, cb) {
888
+ inMixinsNoop(cb)
889
+ }
890
+
891
+ /**
892
+ * Description
893
+ * @method hasDefaultFile
894
+ * @return CallExpression
895
+ */
896
+ hasDefaultFile () {
897
+ return _some(this.getFiles(), function (file) {
898
+ var regex = new RegExp(DEFAULT_FILE_PATTERN, 'i')
899
+ return regex.test(file.path)
900
+ })
901
+ }
902
+
903
+ /**
904
+ * Description
905
+ * @method getDefaultFile
906
+ * @return file
907
+ */
908
+ getDefaultFile () {
909
+ var files = sort(this.getFiles()).asc(u => u.path)
910
+ var file = files.reverse().find((file) => {
911
+ var regex = new RegExp(DEFAULT_FILE_PATTERN, 'i')
912
+ return regex.test(file.path)
913
+ })
914
+ return file
915
+ }
916
+
917
+ getList (name) {
918
+ return this.getLists().find((list) => list.name === name)
919
+ }
920
+
921
+
922
+ getListById(id, lists = this.getLists()) {
923
+ return lists.find((list) => list.id === id)
924
+ }
925
+
926
+ // READY Refactor hideList to use async/await
927
+ // #esm-migration
928
+ // <!--
929
+ // order:-150
930
+ // -->
931
+ async hideList (name) {
932
+ await this.setListHidden(name)
933
+ }
934
+
935
+ // READY Refactor showList to use async/await
936
+ // #esm-migration
937
+ // <!--
938
+ // order:-230
939
+ // -->
940
+ async showList (name) {
941
+ await this.setListHidden(name, false)
942
+ }
943
+
944
+ async setListHidden (name, hidden = true) {
945
+ const list = this.getList(name)
946
+ if (list) {
947
+ list.hidden = hidden
948
+ await this.saveConfig()
949
+ this.emit('list.modified', name)
950
+ }
951
+ }
952
+
953
+ /**
954
+ * Description
955
+ * @method moveList
956
+ * @param {} name
957
+ * @param {} pos
958
+ * @param {} cb
959
+ * @return
960
+ */
961
+ // READY Refactor moveList to use async/await
962
+ // #esm-migration
963
+ // <!--
964
+ // order:-190
965
+ // -->
966
+ async moveList (name, pos) {
967
+ var list = this.getList(name)
968
+ if (list) {
969
+ _remove(this.getLists(), { name: name })
970
+ this.getLists().splice(pos, 0, list)
971
+ await this.saveConfig()
972
+ this.emit('list.modified', name)
973
+ }
974
+ }
975
+
976
+ // READY Refactor toggleListIgnore to use async/await
977
+ // #esm-migration
978
+ // <!--
979
+ // order:-250
980
+ // -->
981
+ async toggleListIgnore (name) {
982
+ var list = this.getList(name)
983
+ if (!list) return reject(new Error('List not found'))
984
+ list.ignore = !list.ignore
985
+ await this.updateList(list.id, list)
986
+ await this.saveConfig()
987
+ this.emit('list.modified', name)
988
+ }
989
+
990
+ async toggleList (name) {
991
+ const list = this.getList(name)
992
+ if (!list) throw new Error('List not found')
993
+ list.hidden = !list.hidden
994
+ await this.updateList(list.id, list)
995
+ await this.saveConfig()
996
+ this.emit('list.modified', name)
997
+ }
998
+
999
+ // READY Refactor updateList to use async/await
1000
+ // #esm-migration
1001
+ // <!--
1002
+ // order:-90
1003
+ // -->
1004
+ async updateList(id, {name, hidden, ignore, filter}) {
1005
+ const lists = this.getLists()
1006
+ const list = this.getListById(id, lists)
1007
+ const oldName = list.name
1008
+ const hasFilter = list.filter
1009
+ list.name = name
1010
+ list.hidden = hidden
1011
+ list.ignore = ignore
1012
+ if (hasFilter !== undefined) list.filter = filter
1013
+ this.setLists(lists)
1014
+
1015
+ if (oldName === name || hasFilter) {
1016
+ return await this.saveConfig()
1017
+ }
1018
+
1019
+ await this.moveTasksBetweenLists(oldName, name)
1020
+ }
1021
+
1022
+ getTasksByFile(tasks) {
1023
+ const tasksByFile = {} // path: [files]
1024
+ tasks.forEach((task) => {
1025
+ const filePath = task.path
1026
+ if (!tasksByFile[filePath]) {
1027
+ tasksByFile[filePath] = { file: this.getFileForTask(task), tasks: [] }
1028
+ }
1029
+ tasksByFile[filePath].tasks.push(task)
1030
+ tasksByFile[filePath].tasks = sort(tasksByFile[filePath].tasks).desc(u => u.line)
1031
+ })
1032
+ return tasksByFile
1033
+ }
1034
+ // READY Refactor moveTasksBetweenLists to use async/await
1035
+ // <!--
1036
+ // #esm-migration #needs-testing
1037
+ // order:-80
1038
+ // -->
1039
+ // ## Tasks
1040
+ // - [x] Refactor
1041
+ // - [ ] Test
1042
+ async moveTasksBetweenLists(oldName, newName) {
1043
+ return new Promise((resolve, reject) => {
1044
+ const tasksToModify = this.getTasksInList(oldName)
1045
+ const tasksByFile = this.getTasksByFile(tasksToModify) // { <path/to/file>: {file, tasks} }
1046
+
1047
+ const modifyTasksFunctions = Object.values(tasksByFile).map(({ file, tasks }) => {
1048
+ return async () => {
1049
+ await this.readFileContent(file)
1050
+ tasks.forEach((task) => {
1051
+ task.list = newName
1052
+ file.modifyTask(task, this.getConfig(), true)
1053
+ file.modified = true
1054
+ })
1055
+ return file
1056
+ }
1057
+ })
1058
+
1059
+ if (modifyTasksFunctions.length < 1) return resolve()
1060
+ this.moving = true
1061
+ parallel(modifyTasksFunctions, async (err, files) => {
1062
+ this.moving = false
1063
+ if (err) {
1064
+ files.forEach(file => this.resetFile(file))
1065
+ return reject(err)
1066
+ }
1067
+ try {
1068
+ await this.saveConfig()
1069
+ await this.saveModifiedFiles()
1070
+ resolve(files)
1071
+ } catch (err) {
1072
+ reject(err)
1073
+ }
1074
+ })
1075
+ })
1076
+ }
1077
+
1078
+ // READY Migrate writeAndExtract to use async/await
1079
+ // #esm-migration
1080
+ // <!--
1081
+ // order:-80
1082
+ // -->
1083
+ async writeAndExtract (file, emit) {
1084
+ try {
1085
+ await this.writeFile(file, emit)
1086
+ } catch (err) {
1087
+ new Error('Unable to write file:' + file.path, {cause: err})
1088
+ }
1089
+
1090
+ try {
1091
+ await this.extractTasks(file)
1092
+ } catch (err) {
1093
+ new Error('Unable to extract tasks for file:' + file.path, { cause: err })
1094
+ }
1095
+
1096
+ try {
1097
+ await this.addFile(file)
1098
+ } catch (err) {
1099
+ new Error('Unable to add file after extracting tasks: ' + file.path, { cause: err })
1100
+ }
1101
+
1102
+ return file
1103
+ }
1104
+
1105
+ // READY Refactor writeAndAdd to use async/await
1106
+ // #esm-migration
1107
+ // <!--
1108
+ // order:-70
1109
+ // -->
1110
+ async writeAndAdd (file, emit) {
1111
+ await this.writeFile(file, emit)
1112
+ await this.addFile(file)
1113
+ return file
1114
+ }
1115
+
1116
+ // READY Refactor deleteTask to use async/await
1117
+ // #esm-migration
1118
+ // <!--
1119
+ // order:-130
1120
+ // -->
1121
+ async deleteTask (task, cb) {
1122
+ let file = this.getFileForTask(task)
1123
+ if (!file) return
1124
+
1125
+ if (!file.getContent()) {
1126
+ file = await this.readFileContent(file)
1127
+ }
1128
+
1129
+ file.deleteTask(task, this.getConfig())
1130
+ if (file.getContentForFile().trim() === '' && file.isMarkDownFile()) {
1131
+ if (file.deleted) return
1132
+ logger.log('Deleting empty file:', file.path)
1133
+ this.deleteFile(file.path)
1134
+ file.deleted = true
1135
+ return
1136
+ }
1137
+ await this.writeAndExtract(file, true)
1138
+ }
1139
+
1140
+ // READY Refactor deleteTasks to use async/await
1141
+ // #esm-migration
1142
+ // <!--
1143
+ // order:-140
1144
+ // -->
1145
+ async deleteTasks(tasks) {
1146
+ const files = {}
1147
+ tasks.forEach((task) => {
1148
+ if (!files[task.source.path]) {
1149
+ files[task.source.path] = []
1150
+ }
1151
+ files[task.source.path].push(task)
1152
+ files[task.source.path] = sort(files[task.source.path]).desc(u => u.line)
1153
+ })
1154
+
1155
+ await Promise.all(
1156
+ Object.keys(files).map(async (path) => {
1157
+ const tasksInFile = files[path]
1158
+ if (!tasksInFile) return
1159
+ for (const task of tasksInFile) {
1160
+ const taskToDelete = newCard(task, this.project, true)
1161
+ await this.deleteTask(taskToDelete)
1162
+ }
1163
+ })
1164
+ )
1165
+
1166
+ this.emit('tasks.updated', tasks)
1167
+ }
1168
+
1169
+ // READY Refactor modifyTaskFromHtml to use async/await
1170
+ // #esm-migration
1171
+ // <!--
1172
+ // order:-170
1173
+ // -->
1174
+ async modifyTaskFromHtml (task, html) {
1175
+ var file = this.getFileForTask(task)
1176
+ if (!file.getContent()) {
1177
+ file = await this.readFileContent(file)
1178
+ }
1179
+ file.modifyTaskFromHtml(task, html)
1180
+ return await this.writeAndExtract(file, true)
1181
+ }
1182
+
1183
+ // READY Refactor modifyTaskFromContent to use async/await
1184
+ // #esm-migration
1185
+ // <!--
1186
+ // order:-180
1187
+ // -->
1188
+ async modifyTaskFromContent (task, content, cb) {
1189
+ var file = this.getFileForTask(task)
1190
+ if (!file.getContent()) {
1191
+ file = await this.readFileContent(file)
1192
+ }
1193
+
1194
+ file.modifyTaskFromContent(task, content, this.getConfig())
1195
+ return await this.writeAndExtract(file, true)
1196
+ }
1197
+
1198
+ getTaskContent({
1199
+ description,
1200
+ order,
1201
+ text,
1202
+ taskPrefix,
1203
+ taskSyntax,
1204
+ }) {
1205
+ const task = newCard(
1206
+ {
1207
+ description,
1208
+ meta: {},
1209
+ order,
1210
+ text,
1211
+ beforeText: taskPrefix,
1212
+ type: taskSyntax,
1213
+ },
1214
+ this.project
1215
+ )
1216
+ task.updateOrderMeta(this.config)
1217
+ return task.description.join(eol.lf)
1218
+ }
1219
+
1220
+ // READY Refactor appendTask to use async/await
1221
+ // #esm-migration
1222
+ // <!--
1223
+ // order:-300
1224
+ // -->
1225
+ async appendTask({file, content, list}) {
1226
+ const config = this.getConfig()
1227
+ const interpretedTaskPrefix = interpolate(
1228
+ config.getTaskPrefix(),
1229
+ { date: new Date() }
1230
+ ).content.trimEnd()
1231
+ const lines = eol.split(content)
1232
+ const text = lines[0]
1233
+ const taskSyntax = config.getNewCardSyntax()
1234
+ const taskPrefix = interpretedTaskPrefix ? `${interpretedTaskPrefix} ` : ''
1235
+ let fileContent = file.getContent()
1236
+ const journalTemplate = this.getConfig().journalTemplate
1237
+ fileContent = fileContent.trim()
1238
+ ? fileContent
1239
+ : journalTemplate
1240
+ && journalTemplate.trim()
1241
+ ? journalTemplate + String(eol.lf)
1242
+ : '';
1243
+
1244
+ const length = fileContent.length
1245
+ const fileIsEmpty = length < 1
1246
+ const crlf = String(eol.crlf)
1247
+ let sep = fileContent.indexOf(crlf) > -1 ? crlf : String(eol.lf)
1248
+ if (fileContent.endsWith(sep) || fileIsEmpty) sep = ''
1249
+ const order = appContext().projectContext.getOrder(list)
1250
+ const description = lines.length > 1 ? Task.padDescription(lines.slice(1), taskPrefix) : []
1251
+ const { orderMeta, tokenPrefix } = config
1252
+ const rawTask = getRawTask({tokenPrefix, orderMeta, list, order, text, type: taskSyntax})
1253
+ const taskContent = config.orderMeta
1254
+ ? this.getTaskContent({
1255
+ description,
1256
+ order,
1257
+ text,
1258
+ taskPrefix,
1259
+ taskSyntax,
1260
+ })
1261
+ : description.join(eol.lf)
1262
+
1263
+ let appendContent = File.trimBlankLines(
1264
+ `${taskPrefix}${rawTask}${eol.lf}${taskContent}`
1265
+ )
1266
+
1267
+ fileContent = `${fileContent}${sep}`
1268
+ const cardTerminator = "\n".repeat(2)
1269
+ file.setContent(`${fileContent}${appendContent}${cardTerminator}`)
1270
+
1271
+ await this.writeAndExtract(file)
1272
+ const task = file.getTasks().find(task => task.text === text && task.list === list)
1273
+ return { file, task }
1274
+ }
1275
+
1276
+ // READY Refactor addTaskToFile to use async/await
1277
+ // #esm-migration
1278
+ // <!--
1279
+ // order:-290
1280
+ // -->
1281
+ async addTaskToFile (filePath, list, content) {
1282
+ const relPath = this.getRelativePath(filePath)
1283
+ let file = this.getFile(relPath)
1284
+
1285
+ if (file) {
1286
+ await this.readFileContent(file)
1287
+ return this.appendTask({file, content, list})
1288
+ } else {
1289
+ const modifiedTime = new Date()
1290
+ const createdTime = new Date()
1291
+ file = new File({
1292
+ repoId: this.path,
1293
+ filePath: relPath,
1294
+ content: '',
1295
+ modifiedTime,
1296
+ createdTime,
1297
+ project: this.project,
1298
+ })
1299
+ return this.appendTask({file, content, list})
1300
+ }
1301
+ }
1302
+
1303
+ /**
1304
+ * Description
1305
+ * @method modifyTask
1306
+ * @param {} task
1307
+ * @return CallExpression
1308
+ */
1309
+ // READY Refactor modifyTask to use async/await
1310
+ // #esm-migration #important #urgent
1311
+ // <!--
1312
+ // order:-60
1313
+ // -->
1314
+ async modifyTask (task, writeFile = false) {
1315
+ if (!Task.isTask(task)) return
1316
+ const config = this.getConfig()
1317
+ log(
1318
+ 'Modifying Task... text:%s list:%s order:%d path:%s id:%s line:%d',
1319
+ task.text,
1320
+ task.list,
1321
+ task.order,
1322
+ task.source.path,
1323
+ task.id,
1324
+ task.line
1325
+ )
1326
+ let beforeModifyContent
1327
+ const file = this.getFileForTask(task)
1328
+ try {
1329
+ beforeModifyContent = file.getContent()
1330
+ } catch (e) {
1331
+ logger.error(`Can't get file for task: {text:'${task.text}', path:'${task.source.path}', line:${task.line}}`)
1332
+ throw e
1333
+ }
1334
+
1335
+ if (!beforeModifyContent) await this.readFileContent(file)
1336
+ file.modifyTask(task, config, true)
1337
+ file.extractTasks(config)
1338
+ file.transformTask({config, modify:true, task})
1339
+ file.extractTasks(config)
1340
+
1341
+ if (!writeFile || beforeModifyContent === file.getContent()) return task
1342
+
1343
+ return await this.writeAndAdd(file)
1344
+ }
1345
+
1346
+ // READY Refactor moveTask to use async/await
1347
+ // #esm-migration
1348
+ // <!--
1349
+ // order:-200
1350
+ // -->
1351
+ async moveTask ({ task, newList, newPos }) {
1352
+ if (!Task.isTask(task)) {
1353
+ task = newCard(task, this.project, true)
1354
+ }
1355
+ task = this.getTask(task.id)
1356
+
1357
+ var toListTasks = this.getTasksInList(newList)
1358
+ if (toListTasks === undefined)
1359
+ throw new Error(ERRORS.LIST_NOT_FOUND, newList)
1360
+
1361
+ var fromListTasks = this.getTasksInList(task.list)
1362
+ if (fromListTasks === undefined)
1363
+ throw new Error(ERRORS.LIST_NOT_FOUND, task.list)
1364
+
1365
+ var sameList = newList == task.list
1366
+ if (!sameList) task.oldList = task.list
1367
+ task.list = newList
1368
+
1369
+ // Move the task to the correct position in the list
1370
+ fromListTasks = _reject(fromListTasks, function (_task) {
1371
+ return _task.equals(task)
1372
+ })
1373
+ toListTasks = _reject(toListTasks, function (_task) {
1374
+ return _task.equals(task)
1375
+ })
1376
+
1377
+ const tasksToModify = getTasksToModify(task, toListTasks, newPos)
1378
+ task.updateOrderMeta(this.config)
1379
+ toListTasks.splice(newPos, 0, task)
1380
+
1381
+ const tasksToModifySorted = sort(tasksToModify).by([{ desc: u => u.line }, { asc: u => u.path }])
1382
+
1383
+ await eachSeries(
1384
+ tasksToModifySorted,
1385
+ async (task) => {
1386
+ await this.modifyTask(task, true)
1387
+ },
1388
+ )
1389
+
1390
+ return task
1391
+ }
1392
+
1393
+ // READY Refactor moveTasks to use async/await
1394
+ // #esm-migration #important
1395
+ // <!--
1396
+ // order:-350
1397
+ // -->
1398
+ async moveTasks (tasks, newList, newPos = 0, noEmit = false) {
1399
+ const log = require('debug')('moveTasks')
1400
+ if (this.getList(newList).filter)
1401
+ throw new Error(`Tasks can\'t be moved to a filtered list ${newList}.`)
1402
+
1403
+ const listsModified = [newList]
1404
+ this.moving = true
1405
+
1406
+ log('Move tasks to list:%s at position:%d : %j', newList, newPos, tasks)
1407
+ log(
1408
+ 'newList before mods:',
1409
+ JSON.stringify(this.getTasksInList(newList), null, 3)
1410
+ )
1411
+ await series(
1412
+ sort(tasks).by({desc: t => t.line}).map((task, i) => {
1413
+ return async () =>{
1414
+ const foundTask = this.getTasks().find(({source, line}) => task.source.path === source.path && task.line === line)
1415
+ if (foundTask) {
1416
+ if (listsModified.indexOf(foundTask.list) < 0) listsModified.push(foundTask.list)
1417
+ await this.moveTask({ task: foundTask, newList, newPos: newPos + i, noEmit: true })
1418
+ }
1419
+ }
1420
+ })
1421
+ )
1422
+
1423
+ const results = await this.saveModifiedFiles()
1424
+
1425
+ this.lastMovedFiles = results
1426
+ this.moving = false
1427
+
1428
+ if (!noEmit) this.emit('tasks.moved', tasks)
1429
+ return listsModified.map((list) => {
1430
+ return {
1431
+ list: list,
1432
+ tasks: this.getTasksInList(list),
1433
+ }
1434
+ })
1435
+ }
1436
+
1437
+ getModifiedFiles () {
1438
+ var filesToSave = []
1439
+ this.getFiles().forEach((file) => {
1440
+ if (file.isModified()) filesToSave.push(file)
1441
+ })
1442
+ return filesToSave
1443
+ }
1444
+
1445
+ // READY Refactor saveModifiedFiles to use async/await
1446
+ // #esm-migration #urgent #important
1447
+ // <!--
1448
+ // order:-110
1449
+ // -->
1450
+ async saveModifiedFiles () {
1451
+ var filesToSave = this.getModifiedFiles()
1452
+ var funcs = filesToSave.map((file) => {
1453
+ return async () => {
1454
+ file.checksum = computeChecksum(file.getContent())
1455
+ await this.writeAndExtract(file, false)
1456
+ }
1457
+ })
1458
+
1459
+ if (funcs.length < 1) return
1460
+
1461
+ this.savingFiles = true
1462
+ await Promise.all(funcs)
1463
+ this.savingFiles = false
1464
+ this.emit('files.saved', filesToSave)
1465
+ }
1466
+
1467
+ /**
1468
+ * Description
1469
+ * @method getTasks
1470
+ * @return tasks
1471
+ */
1472
+ getTasks () {
1473
+ const tasks = []
1474
+ this.getFiles().forEach((file) => {
1475
+ Array.prototype.push.apply(tasks, file.getTasks())
1476
+ })
1477
+
1478
+ return tasks
1479
+ }
1480
+
1481
+ getTasksByList (noSort) {
1482
+ return Repository.getTasksByList(this, this.getTasks(), noSort, true)
1483
+ }
1484
+
1485
+ /**
1486
+ * Description
1487
+ * @method getTasksInList
1488
+ * @param {} name
1489
+ * @return ConditionalExpression
1490
+ */
1491
+ getTasksInList (name, offset, limit) {
1492
+ if (!_isString(name)) return []
1493
+ var tasks = _where(this.getTasks(), { list: name })
1494
+ if (tasks.length === 0) return []
1495
+ var allTasks = sort(tasks).by(DEFAULT_SORT)
1496
+ if (isNumber(offset) && isNumber(limit))
1497
+ return allTasks.slice(offset, offset + limit)
1498
+ return allTasks
1499
+ }
1500
+
1501
+ getTaskIndex (task) {
1502
+ const tasks = this.getTasksInList(task.list)
1503
+ let index = 0
1504
+ tasks.forEach((t, i) => {
1505
+ if (t.id === task.id) index = i
1506
+ })
1507
+ return index
1508
+ }
1509
+
1510
+ query (queryString) {
1511
+ const result = Repository.query(this.getTasks(), queryString)
1512
+ return Repository.getTasksByList(this, result, true)
1513
+ }
1514
+ }