@zentered/issue-forms-body-parser 1.3.0 → 1.4.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.
- package/dist/index.js +80 -40
- package/package.json +1 -1
- package/src/parse.js +58 -35
- package/src/parsers/duration.js +21 -4
- package/test/parse-issue.test.js +31 -36
- package/test/test-issue-1.md +11 -11
package/dist/index.js
CHANGED
|
@@ -53254,15 +53254,32 @@ function time_parseTime(text) {
|
|
|
53254
53254
|
|
|
53255
53255
|
|
|
53256
53256
|
function parseDuration(text) {
|
|
53257
|
+
let matched = false
|
|
53257
53258
|
const duration = {
|
|
53258
53259
|
hours: 0,
|
|
53259
53260
|
minutes: 0
|
|
53260
53261
|
}
|
|
53261
53262
|
|
|
53262
|
-
const
|
|
53263
|
-
|
|
53264
|
-
|
|
53265
|
-
|
|
53263
|
+
const hoursAndMinutes = new RegExp(/([0-9]+)h([0-9]+)m/)
|
|
53264
|
+
const hours = new RegExp(/([0-9]+)h/)
|
|
53265
|
+
|
|
53266
|
+
if (text.match(hoursAndMinutes)) {
|
|
53267
|
+
matched = true
|
|
53268
|
+
const [, h, m] = text.match(hoursAndMinutes)
|
|
53269
|
+
duration.hours = parseInt(h)
|
|
53270
|
+
duration.minutes = parseInt(m)
|
|
53271
|
+
} else if (text.match(hours)) {
|
|
53272
|
+
matched = true
|
|
53273
|
+
const [, h] = text.match(hours)
|
|
53274
|
+
duration.hours = parseInt(h)
|
|
53275
|
+
duration.minutes = 0
|
|
53276
|
+
}
|
|
53277
|
+
|
|
53278
|
+
if (matched) {
|
|
53279
|
+
return duration
|
|
53280
|
+
} else {
|
|
53281
|
+
return null
|
|
53282
|
+
}
|
|
53266
53283
|
}
|
|
53267
53284
|
|
|
53268
53285
|
;// CONCATENATED MODULE: ./src/parsers/list.js
|
|
@@ -53322,59 +53339,82 @@ const parsers_parseList = parseList
|
|
|
53322
53339
|
|
|
53323
53340
|
|
|
53324
53341
|
|
|
53342
|
+
|
|
53325
53343
|
async function parseMD(body) {
|
|
53326
53344
|
const tokens = await unified().use(remark_parse).use(remarkGfm).parse(body)
|
|
53327
53345
|
if (!tokens) {
|
|
53328
53346
|
return []
|
|
53329
53347
|
}
|
|
53330
53348
|
|
|
53331
|
-
const
|
|
53332
|
-
let
|
|
53333
|
-
for (
|
|
53334
|
-
const
|
|
53335
|
-
|
|
53336
|
-
|
|
53337
|
-
|
|
53338
|
-
|
|
53339
|
-
|
|
53340
|
-
|
|
53341
|
-
|
|
53342
|
-
|
|
53343
|
-
|
|
53349
|
+
const structuredResponse = {}
|
|
53350
|
+
let currentHeading = null
|
|
53351
|
+
for (const token of tokens.children) {
|
|
53352
|
+
const text = await unified()
|
|
53353
|
+
.use(remarkGfm)
|
|
53354
|
+
.use(remark_stringify)
|
|
53355
|
+
.stringify(token)
|
|
53356
|
+
const cleanText = stripFinalNewline(text)
|
|
53357
|
+
|
|
53358
|
+
// issue forms uses h3 as a heading
|
|
53359
|
+
if (token.type === 'heading' && token.depth === 3) {
|
|
53360
|
+
currentHeading = slugify(token.children[0].value)
|
|
53361
|
+
structuredResponse[currentHeading] = {
|
|
53362
|
+
title: token.children[0].value,
|
|
53363
|
+
content: []
|
|
53344
53364
|
}
|
|
53365
|
+
} else if (token.type === 'paragraph' && currentHeading) {
|
|
53366
|
+
const obj = structuredResponse[currentHeading]
|
|
53345
53367
|
|
|
53346
|
-
|
|
53347
|
-
|
|
53348
|
-
|
|
53349
|
-
obj.list = parsers_parseList(next).flat()
|
|
53350
|
-
}
|
|
53351
|
-
const text = await unified()
|
|
53352
|
-
.use(remarkGfm)
|
|
53353
|
-
.use(remark_stringify)
|
|
53354
|
-
.stringify(next)
|
|
53355
|
-
obj.text = stripFinalNewline(text)
|
|
53368
|
+
const date = parsers_parseDate(cleanText)
|
|
53369
|
+
const time = parsers_parseTime(cleanText)
|
|
53370
|
+
const duration = parsers_parseDuration(cleanText)
|
|
53356
53371
|
|
|
53357
|
-
|
|
53358
|
-
|
|
53372
|
+
if (date) {
|
|
53373
|
+
obj.date = date
|
|
53374
|
+
}
|
|
53359
53375
|
|
|
53360
|
-
|
|
53361
|
-
|
|
53362
|
-
|
|
53376
|
+
if (time) {
|
|
53377
|
+
obj.time = time
|
|
53378
|
+
}
|
|
53363
53379
|
|
|
53364
|
-
|
|
53365
|
-
|
|
53366
|
-
|
|
53380
|
+
if (duration) {
|
|
53381
|
+
obj.duration = duration
|
|
53382
|
+
}
|
|
53367
53383
|
|
|
53368
|
-
|
|
53369
|
-
|
|
53370
|
-
|
|
53384
|
+
obj.content.push(cleanText)
|
|
53385
|
+
} else if (token.type === 'list') {
|
|
53386
|
+
const obj = structuredResponse[currentHeading]
|
|
53387
|
+
obj.text = cleanText
|
|
53388
|
+
obj.list = parsers_parseList(token).flat()
|
|
53389
|
+
} else if (token.type === 'html') {
|
|
53390
|
+
const obj = structuredResponse[currentHeading]
|
|
53391
|
+
obj.content.push(token.html)
|
|
53392
|
+
} else if (token.type === 'code') {
|
|
53393
|
+
const obj = structuredResponse[currentHeading]
|
|
53394
|
+
obj.lang = token.lang
|
|
53395
|
+
obj.text = cleanText
|
|
53396
|
+
} else if (token.type === 'heading' && token.depth > 3) {
|
|
53397
|
+
const obj = structuredResponse[currentHeading]
|
|
53398
|
+
obj.content.push(token.children[0].value)
|
|
53399
|
+
} else {
|
|
53400
|
+
console.log('unhandled token type')
|
|
53401
|
+
console.log(token)
|
|
53402
|
+
}
|
|
53403
|
+
}
|
|
53371
53404
|
|
|
53372
|
-
|
|
53405
|
+
for (const key in structuredResponse) {
|
|
53406
|
+
const token = structuredResponse[key]
|
|
53407
|
+
const content = token.content.filter(Boolean)
|
|
53408
|
+
if (content && content.length > 0) {
|
|
53409
|
+
if (content.length === 1) {
|
|
53410
|
+
token.text = content[0]
|
|
53373
53411
|
}
|
|
53412
|
+
token.text = content.join('\n\n')
|
|
53374
53413
|
}
|
|
53414
|
+
token.content = content
|
|
53375
53415
|
}
|
|
53376
53416
|
|
|
53377
|
-
return
|
|
53417
|
+
return structuredResponse
|
|
53378
53418
|
}
|
|
53379
53419
|
|
|
53380
53420
|
;// CONCATENATED MODULE: ./src/index.js
|
package/package.json
CHANGED
package/src/parse.js
CHANGED
|
@@ -6,6 +6,7 @@ import remarkGfm from 'remark-gfm'
|
|
|
6
6
|
import slugify from '@sindresorhus/slugify'
|
|
7
7
|
import remarkStringify from 'remark-stringify'
|
|
8
8
|
import stripFinalNewline from 'strip-final-newline'
|
|
9
|
+
|
|
9
10
|
import {
|
|
10
11
|
parseDate,
|
|
11
12
|
parseTime,
|
|
@@ -19,51 +20,73 @@ export default async function parseMD(body) {
|
|
|
19
20
|
return []
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
-
let
|
|
24
|
-
for (
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
const structuredResponse = {}
|
|
24
|
+
let currentHeading = null
|
|
25
|
+
for (const token of tokens.children) {
|
|
26
|
+
const text = await unified()
|
|
27
|
+
.use(remarkGfm)
|
|
28
|
+
.use(remarkStringify)
|
|
29
|
+
.stringify(token)
|
|
30
|
+
const cleanText = stripFinalNewline(text)
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// issue forms uses h3 as a heading
|
|
33
|
+
if (token.type === 'heading' && token.depth === 3) {
|
|
34
|
+
currentHeading = slugify(token.children[0].value)
|
|
35
|
+
structuredResponse[currentHeading] = {
|
|
36
|
+
title: token.children[0].value,
|
|
37
|
+
content: []
|
|
35
38
|
}
|
|
39
|
+
} else if (token.type === 'paragraph' && currentHeading) {
|
|
40
|
+
const obj = structuredResponse[currentHeading]
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
obj.list = parseList(next).flat()
|
|
41
|
-
}
|
|
42
|
-
const text = await unified()
|
|
43
|
-
.use(remarkGfm)
|
|
44
|
-
.use(remarkStringify)
|
|
45
|
-
.stringify(next)
|
|
46
|
-
obj.text = stripFinalNewline(text)
|
|
42
|
+
const date = parseDate(cleanText)
|
|
43
|
+
const time = parseTime(cleanText)
|
|
44
|
+
const duration = parseDuration(cleanText)
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
if (date) {
|
|
47
|
+
obj.date = date
|
|
48
|
+
}
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
if (time) {
|
|
51
|
+
obj.time = time
|
|
52
|
+
}
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
if (duration) {
|
|
55
|
+
obj.duration = duration
|
|
56
|
+
}
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
obj.content.push(cleanText)
|
|
59
|
+
} else if (token.type === 'list') {
|
|
60
|
+
const obj = structuredResponse[currentHeading]
|
|
61
|
+
obj.text = cleanText
|
|
62
|
+
obj.list = parseList(token).flat()
|
|
63
|
+
} else if (token.type === 'html') {
|
|
64
|
+
const obj = structuredResponse[currentHeading]
|
|
65
|
+
obj.content.push(token.html)
|
|
66
|
+
} else if (token.type === 'code') {
|
|
67
|
+
const obj = structuredResponse[currentHeading]
|
|
68
|
+
obj.lang = token.lang
|
|
69
|
+
obj.text = cleanText
|
|
70
|
+
} else if (token.type === 'heading' && token.depth > 3) {
|
|
71
|
+
const obj = structuredResponse[currentHeading]
|
|
72
|
+
obj.content.push(token.children[0].value)
|
|
73
|
+
} else {
|
|
74
|
+
console.log('unhandled token type')
|
|
75
|
+
console.log(token)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
62
78
|
|
|
63
|
-
|
|
79
|
+
for (const key in structuredResponse) {
|
|
80
|
+
const token = structuredResponse[key]
|
|
81
|
+
const content = token.content.filter(Boolean)
|
|
82
|
+
if (content && content.length > 0) {
|
|
83
|
+
if (content.length === 1) {
|
|
84
|
+
token.text = content[0]
|
|
64
85
|
}
|
|
86
|
+
token.text = content.join('\n\n')
|
|
65
87
|
}
|
|
88
|
+
token.content = content
|
|
66
89
|
}
|
|
67
90
|
|
|
68
|
-
return
|
|
91
|
+
return structuredResponse
|
|
69
92
|
}
|
package/src/parsers/duration.js
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
export default function parseDuration(text) {
|
|
4
|
+
let matched = false
|
|
4
5
|
const duration = {
|
|
5
6
|
hours: 0,
|
|
6
7
|
minutes: 0
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const hoursAndMinutes = new RegExp(/([0-9]+)h([0-9]+)m/)
|
|
11
|
+
const hours = new RegExp(/([0-9]+)h/)
|
|
12
|
+
|
|
13
|
+
if (text.match(hoursAndMinutes)) {
|
|
14
|
+
matched = true
|
|
15
|
+
const [, h, m] = text.match(hoursAndMinutes)
|
|
16
|
+
duration.hours = parseInt(h)
|
|
17
|
+
duration.minutes = parseInt(m)
|
|
18
|
+
} else if (text.match(hours)) {
|
|
19
|
+
matched = true
|
|
20
|
+
const [, h] = text.match(hours)
|
|
21
|
+
duration.hours = parseInt(h)
|
|
22
|
+
duration.minutes = 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (matched) {
|
|
26
|
+
return duration
|
|
27
|
+
} else {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
13
30
|
}
|
package/test/parse-issue.test.js
CHANGED
|
@@ -10,81 +10,75 @@ const test = t.test
|
|
|
10
10
|
test('parse(md) should parse GitHub Issue Form data into useful, structured data', async (t) => {
|
|
11
11
|
const expected = {
|
|
12
12
|
'event-description': {
|
|
13
|
-
order: 0,
|
|
14
13
|
title: 'Event Description',
|
|
15
|
-
|
|
14
|
+
content: [
|
|
15
|
+
'Welcome to the CDC - Cyprus Developer Community! Join us for our monthly Larnaka\nmeet & greet event. Meet likeminded people, discuss topics we would like to hear\nabout in upcoming talks, welcome potential speakers, discuss all things tech and\nhave fun!',
|
|
16
|
+
'Notice with regards to COVID:',
|
|
17
|
+
'All attendees must follow measures in accordance with Ministry of Health\ndirectives. <https://www.pio.gov.cy/coronavirus/eng>'
|
|
18
|
+
],
|
|
19
|
+
text: 'Welcome to the CDC - Cyprus Developer Community! Join us for our monthly Larnaka\nmeet & greet event. Meet likeminded people, discuss topics we would like to hear\nabout in upcoming talks, welcome potential speakers, discuss all things tech and\nhave fun!\n\nNotice with regards to COVID:\n\nAll attendees must follow measures in accordance with Ministry of Health\ndirectives. <https://www.pio.gov.cy/coronavirus/eng>'
|
|
16
20
|
},
|
|
17
21
|
location: {
|
|
18
|
-
order: 1,
|
|
19
22
|
title: 'Location',
|
|
23
|
+
content: [
|
|
24
|
+
'[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)'
|
|
25
|
+
],
|
|
20
26
|
text: '[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)'
|
|
21
27
|
},
|
|
22
28
|
date: {
|
|
23
|
-
order: 2,
|
|
24
29
|
title: 'Date',
|
|
25
|
-
|
|
26
|
-
date: '2022-03-11'
|
|
30
|
+
content: ['11.03.2022'],
|
|
31
|
+
date: '2022-03-11',
|
|
32
|
+
text: '11.03.2022'
|
|
27
33
|
},
|
|
28
|
-
time: {
|
|
34
|
+
time: { title: 'Time', content: ['16:00'], time: '16:00', text: '16:00' },
|
|
29
35
|
duration: {
|
|
30
|
-
order: 4,
|
|
31
36
|
title: 'Duration',
|
|
32
|
-
|
|
33
|
-
duration: { hours: 2, minutes: 0 }
|
|
37
|
+
content: ['2h'],
|
|
38
|
+
duration: { hours: 2, minutes: 0 },
|
|
39
|
+
text: '2h'
|
|
34
40
|
},
|
|
35
41
|
'list-item-checked': {
|
|
36
|
-
order: 5,
|
|
37
42
|
title: 'List Item Checked',
|
|
43
|
+
content: [],
|
|
44
|
+
text: "* [x] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)",
|
|
38
45
|
list: [
|
|
39
46
|
{
|
|
40
47
|
checked: true,
|
|
41
48
|
text: "I agree to follow this project's\nCode of Conduct"
|
|
42
49
|
}
|
|
43
|
-
]
|
|
44
|
-
text: "* [x] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
|
|
50
|
+
]
|
|
45
51
|
},
|
|
46
52
|
'list-item-unchecked': {
|
|
47
|
-
order: 6,
|
|
48
53
|
title: 'List Item Unchecked',
|
|
54
|
+
content: [],
|
|
55
|
+
text: "* [ ] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)",
|
|
49
56
|
list: [
|
|
50
57
|
{
|
|
51
58
|
checked: false,
|
|
52
59
|
text: "I agree to follow this project's\nCode of Conduct"
|
|
53
60
|
}
|
|
54
|
-
]
|
|
55
|
-
text: "* [ ] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
|
|
61
|
+
]
|
|
56
62
|
},
|
|
57
63
|
'mixed-task-list': {
|
|
58
|
-
order: 7,
|
|
59
64
|
title: 'Mixed Task List',
|
|
65
|
+
content: [],
|
|
66
|
+
text: '* [x] checked\n* [ ] unchecked\n* [x] checked 2\n* [x] checked 3\n* [ ] unchecked 2',
|
|
60
67
|
list: [
|
|
61
68
|
{ checked: true, text: 'checked' },
|
|
62
69
|
{ checked: false, text: 'unchecked' },
|
|
63
70
|
{ checked: true, text: 'checked 2' },
|
|
64
71
|
{ checked: true, text: 'checked 3' },
|
|
65
72
|
{ checked: false, text: 'unchecked 2' }
|
|
66
|
-
]
|
|
67
|
-
text: '* [x] checked\n* [ ] unchecked\n* [x] checked 2\n* [x] checked 3\n* [ ] unchecked 2'
|
|
68
|
-
},
|
|
69
|
-
'complex-list': {
|
|
70
|
-
order: 8,
|
|
71
|
-
title: 'Complex List',
|
|
72
|
-
list: [
|
|
73
|
-
{ checked: null, text: 'one' },
|
|
74
|
-
{ checked: null, text: 'two' }
|
|
75
|
-
],
|
|
76
|
-
text: '* one\n* two\n * three\n * four\n 1. five\n 2. six'
|
|
73
|
+
]
|
|
77
74
|
},
|
|
78
75
|
repositories: {
|
|
79
|
-
order: 9,
|
|
80
76
|
title: 'Repositories',
|
|
77
|
+
content: [],
|
|
78
|
+
lang: 'csv',
|
|
81
79
|
text: '```csv\nhttps://example.com/repository-1\nhttps://example.com/repository-2\n```'
|
|
82
80
|
},
|
|
83
|
-
visibility: {
|
|
84
|
-
order: 10,
|
|
85
|
-
title: 'Visibility',
|
|
86
|
-
text: 'Internal'
|
|
87
|
-
}
|
|
81
|
+
visibility: { title: 'Visibility', content: ['Internal'], text: 'Internal' }
|
|
88
82
|
}
|
|
89
83
|
|
|
90
84
|
const md = await readFile(
|
|
@@ -93,7 +87,8 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
93
87
|
)
|
|
94
88
|
const actual = await fn(md)
|
|
95
89
|
// console.log(JSON.stringify(actual, null, 0))
|
|
96
|
-
|
|
90
|
+
|
|
91
|
+
t.same(actual, expected)
|
|
97
92
|
})
|
|
98
93
|
|
|
99
94
|
test('parse(md) return nothing', async (t) => {
|
|
@@ -105,5 +100,5 @@ test('parse(md) return nothing', async (t) => {
|
|
|
105
100
|
)
|
|
106
101
|
const actual = await fn(md)
|
|
107
102
|
// console.log(JSON.stringify(actual, null, 0))
|
|
108
|
-
t.
|
|
103
|
+
t.same(actual, expected)
|
|
109
104
|
})
|
package/test/test-issue-1.md
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
### Event Description
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<img width="1107" alt="The Brewery Larnaka" src="https://user-images.githubusercontent.com/3581331/162574191-3c023b32-34d9-4035-90bf-7297cdccaf06.png">
|
|
4
|
+
|
|
5
|
+
Welcome to the CDC - Cyprus Developer Community! Join us for our monthly Larnaka
|
|
6
|
+
meet & greet event. Meet likeminded people, discuss topics we would like to hear
|
|
7
|
+
about in upcoming talks, welcome potential speakers, discuss all things tech and
|
|
8
|
+
have fun!
|
|
9
|
+
|
|
10
|
+
#### Notice with regards to COVID:
|
|
11
|
+
|
|
12
|
+
All attendees must follow measures in accordance with Ministry of Health
|
|
13
|
+
directives. https://www.pio.gov.cy/coronavirus/eng
|
|
5
14
|
|
|
6
15
|
### Location
|
|
7
16
|
|
|
@@ -37,15 +46,6 @@ CDC (Cyprus Developer Community).
|
|
|
37
46
|
- [x] checked 3
|
|
38
47
|
- [ ] unchecked 2
|
|
39
48
|
|
|
40
|
-
### Complex List
|
|
41
|
-
|
|
42
|
-
- one
|
|
43
|
-
- two
|
|
44
|
-
- three
|
|
45
|
-
- four
|
|
46
|
-
1. five
|
|
47
|
-
2. six
|
|
48
|
-
|
|
49
49
|
### Repositories
|
|
50
50
|
|
|
51
51
|
```csv
|