@zentered/issue-forms-body-parser 1.2.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/.github/workflows/codeql-analysis.yml +59 -0
- package/README.md +33 -17
- package/dist/index.js +174 -76
- package/dist/licenses.txt +13 -0
- package/package.json +2 -1
- package/src/index.js +2 -0
- package/src/parse.js +68 -117
- package/src/parsers/date.js +30 -0
- package/src/parsers/duration.js +30 -0
- package/src/parsers/index.js +11 -0
- package/src/parsers/list.js +31 -0
- package/src/parsers/time.js +22 -0
- package/test/parse-issue.test.js +44 -49
- package/test/test-issue-1.md +11 -11
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: "CodeQL"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
# The branches below must be a subset of the branches above
|
|
8
|
+
branches: [ main ]
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: '29 21 * * 5'
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
analyze:
|
|
14
|
+
name: Analyze
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
actions: read
|
|
18
|
+
contents: read
|
|
19
|
+
security-events: write
|
|
20
|
+
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
language: [ 'javascript' ]
|
|
25
|
+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
26
|
+
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout repository
|
|
30
|
+
uses: actions/checkout@v3
|
|
31
|
+
|
|
32
|
+
# Initializes the CodeQL tools for scanning.
|
|
33
|
+
- name: Initialize CodeQL
|
|
34
|
+
uses: github/codeql-action/init@v2
|
|
35
|
+
with:
|
|
36
|
+
languages: ${{ matrix.language }}
|
|
37
|
+
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
38
|
+
# By default, queries listed here will override any specified in a config file.
|
|
39
|
+
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
40
|
+
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
41
|
+
|
|
42
|
+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
43
|
+
# If this step fails, then you should remove it and run the build manually (see below)
|
|
44
|
+
- name: Autobuild
|
|
45
|
+
uses: github/codeql-action/autobuild@v2
|
|
46
|
+
|
|
47
|
+
# ℹ️ Command-line programs to run using the OS shell.
|
|
48
|
+
# 📚 https://git.io/JvXDl
|
|
49
|
+
|
|
50
|
+
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
51
|
+
# and modify them (or add more) to build your code if your project
|
|
52
|
+
# uses a compiled language
|
|
53
|
+
|
|
54
|
+
#- run: |
|
|
55
|
+
# make bootstrap
|
|
56
|
+
# make release
|
|
57
|
+
|
|
58
|
+
- name: Perform CodeQL Analysis
|
|
59
|
+
uses: github/codeql-action/analyze@v2
|
package/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
-

|
|
6
5
|
[](https://conventionalcommits.org)
|
|
7
6
|
[](https://zentered.co)
|
|
8
7
|
|
|
@@ -21,9 +20,9 @@ and provides structured data to create calendar entries (ie `.ics` files for
|
|
|
21
20
|
[calendar subscriptions with GitEvents](https://github.com/gitevents/ics)),
|
|
22
21
|
calling 3rd party APIs, etc.
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
_Inspired by:
|
|
25
24
|
[Peter Murray's Issue Forms Body Parser](https://github.com/peter-murray/issue-forms-body-parser)
|
|
26
|
-
with valuable feedback from [Steffen](https://gist.github.com/steffen)
|
|
25
|
+
with valuable feedback from [Steffen](https://gist.github.com/steffen)_
|
|
27
26
|
|
|
28
27
|
## Features
|
|
29
28
|
|
|
@@ -62,30 +61,47 @@ Cafe Nero Finikoudes, Larnaka
|
|
|
62
61
|
to structured, usable data:
|
|
63
62
|
|
|
64
63
|
```json
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
"
|
|
64
|
+
{
|
|
65
|
+
"event-description": {
|
|
66
|
+
"order": 0,
|
|
68
67
|
"title": "Event Description",
|
|
69
|
-
"text": "Let's meet for coffee and chat about tech, coding, Cyprus and the newly formed\nCDC (Cyprus Developer Community)
|
|
68
|
+
"text": "Let's meet for coffee and chat about tech, coding, Cyprus and the newly formed\nCDC (Cyprus Developer Community)."
|
|
70
69
|
},
|
|
71
|
-
{
|
|
72
|
-
"
|
|
70
|
+
"location": {
|
|
71
|
+
"order": 1,
|
|
73
72
|
"title": "Location",
|
|
74
|
-
"text": "Cafe Nero Finikoudes, Larnaka
|
|
73
|
+
"text": "Cafe Nero Finikoudes, Larnaka"
|
|
75
74
|
},
|
|
76
|
-
{
|
|
77
|
-
"
|
|
75
|
+
"date": {
|
|
76
|
+
"order": 2,
|
|
78
77
|
"title": "Date",
|
|
79
|
-
"text": "11.03.2022
|
|
80
|
-
"date": "2022-03-
|
|
78
|
+
"text": "11.03.2022",
|
|
79
|
+
"date": "2022-03-11"
|
|
81
80
|
},
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
"time": {
|
|
82
|
+
"order": 3,
|
|
83
|
+
"title": "Time",
|
|
84
|
+
"text": "16:00",
|
|
85
|
+
"time": "16:00"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
84
88
|
```
|
|
85
89
|
|
|
86
90
|
See more examples in [md test cases](./test/test-issue-1.md) and
|
|
87
91
|
[test results](./test/parse-issue-test.md]).
|
|
88
92
|
|
|
93
|
+
### Parsers
|
|
94
|
+
|
|
95
|
+
- `date`: checks if the value matches a
|
|
96
|
+
[common date format](https://github.com/zentered/issue-forms-body-parser/blob/main/src/parsers/date.js#L7)
|
|
97
|
+
and returns a formatted `date` field (in UTC).
|
|
98
|
+
- `time`: checks if the value matches a
|
|
99
|
+
[common time format](https://github.com/zentered/issue-forms-body-parser/blob/main/src/parsers/time.js#L7)
|
|
100
|
+
and returns a formatted `time` field.
|
|
101
|
+
- `lists`: automatically returns lists as arrays
|
|
102
|
+
- `duration`: currently only the format `XXhYYm` is supported as duration, ie.
|
|
103
|
+
`1h30m` returns a `duration` object with `hours` and `minutes`.
|
|
104
|
+
|
|
89
105
|
## Installation & Usage
|
|
90
106
|
|
|
91
107
|
### GitHub Actions
|
|
@@ -101,7 +117,7 @@ jobs:
|
|
|
101
117
|
steps:
|
|
102
118
|
- name: Issue Forms Body Parser
|
|
103
119
|
id: parse
|
|
104
|
-
uses: zentered/issue-forms-body-parser@
|
|
120
|
+
uses: zentered/issue-forms-body-parser@v2.0.0
|
|
105
121
|
- run: echo "${{ JSON.stringify(steps.parse.outputs.data) }}"
|
|
106
122
|
```
|
|
107
123
|
|
package/dist/index.js
CHANGED
|
@@ -51787,6 +51787,22 @@ function remarkStringify(options) {
|
|
|
51787
51787
|
|
|
51788
51788
|
/* harmony default export */ const remark_stringify = (remarkStringify);
|
|
51789
51789
|
|
|
51790
|
+
;// CONCATENATED MODULE: ./node_modules/strip-final-newline/index.js
|
|
51791
|
+
function stripFinalNewline(input) {
|
|
51792
|
+
const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt();
|
|
51793
|
+
const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt();
|
|
51794
|
+
|
|
51795
|
+
if (input[input.length - 1] === LF) {
|
|
51796
|
+
input = input.slice(0, -1);
|
|
51797
|
+
}
|
|
51798
|
+
|
|
51799
|
+
if (input[input.length - 1] === CR) {
|
|
51800
|
+
input = input.slice(0, -1);
|
|
51801
|
+
}
|
|
51802
|
+
|
|
51803
|
+
return input;
|
|
51804
|
+
}
|
|
51805
|
+
|
|
51790
51806
|
// EXTERNAL MODULE: ./node_modules/date-fns/index.js
|
|
51791
51807
|
var date_fns = __nccwpck_require__(3314);
|
|
51792
51808
|
// EXTERNAL MODULE: ./node_modules/date-fns/_lib/cloneObject/index.js
|
|
@@ -52566,6 +52582,38 @@ function zonedTimeToUtc(date, timeZone, options) {
|
|
|
52566
52582
|
return new Date(utc + offsetMilliseconds)
|
|
52567
52583
|
}
|
|
52568
52584
|
|
|
52585
|
+
;// CONCATENATED MODULE: ./src/parsers/date.js
|
|
52586
|
+
|
|
52587
|
+
|
|
52588
|
+
;
|
|
52589
|
+
|
|
52590
|
+
|
|
52591
|
+
const loc = 'UTC'
|
|
52592
|
+
const commonDateFormats = [
|
|
52593
|
+
'yyyy-MM-dd',
|
|
52594
|
+
'dd/MM/yyyy',
|
|
52595
|
+
'dd/MM/yy',
|
|
52596
|
+
'dd-MM-yyyy',
|
|
52597
|
+
'dd-MM-yy',
|
|
52598
|
+
'dd.MM.yyyy',
|
|
52599
|
+
'dd.MM.yy'
|
|
52600
|
+
]
|
|
52601
|
+
|
|
52602
|
+
function date_parseDate(text) {
|
|
52603
|
+
const match = commonDateFormats.map((format) => {
|
|
52604
|
+
return (0,date_fns.isMatch)(text, format)
|
|
52605
|
+
})
|
|
52606
|
+
if (match.indexOf(true) > -1) {
|
|
52607
|
+
const date = zonedTimeToUtc(
|
|
52608
|
+
(0,date_fns.parse)(text, commonDateFormats[match.indexOf(true)], new Date()),
|
|
52609
|
+
loc
|
|
52610
|
+
).toJSON()
|
|
52611
|
+
return date.split('T')[0]
|
|
52612
|
+
} else {
|
|
52613
|
+
return null
|
|
52614
|
+
}
|
|
52615
|
+
}
|
|
52616
|
+
|
|
52569
52617
|
// EXTERNAL MODULE: ./node_modules/date-fns/format/index.js
|
|
52570
52618
|
var format = __nccwpck_require__(2168);
|
|
52571
52619
|
;// CONCATENATED MODULE: ./node_modules/date-fns-tz/esm/_lib/tzIntlTimeZoneName/index.js
|
|
@@ -53178,74 +53226,65 @@ function formatInTimeZone(date, timeZone, formatStr, options) {
|
|
|
53178
53226
|
return format_format(utcToZonedTime(date, timeZone), formatStr, extendedOptions)
|
|
53179
53227
|
}
|
|
53180
53228
|
|
|
53181
|
-
;// CONCATENATED MODULE: ./src/
|
|
53229
|
+
;// CONCATENATED MODULE: ./src/parsers/time.js
|
|
53182
53230
|
|
|
53183
53231
|
|
|
53184
53232
|
;
|
|
53185
53233
|
|
|
53186
53234
|
|
|
53235
|
+
const time_loc = 'UTC'
|
|
53236
|
+
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
53187
53237
|
|
|
53238
|
+
function time_parseTime(text) {
|
|
53239
|
+
const match = commonTimeFormats.map((format) => {
|
|
53240
|
+
return (0,date_fns.isMatch)(text, format)
|
|
53241
|
+
})
|
|
53242
|
+
if (match.indexOf(true) > -1) {
|
|
53243
|
+
const time = zonedTimeToUtc(
|
|
53244
|
+
(0,date_fns.parse)(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
53245
|
+
time_loc
|
|
53246
|
+
)
|
|
53247
|
+
return formatInTimeZone(time, time_loc, 'HH:mm')
|
|
53248
|
+
} else {
|
|
53249
|
+
return null
|
|
53250
|
+
}
|
|
53251
|
+
}
|
|
53188
53252
|
|
|
53253
|
+
;// CONCATENATED MODULE: ./src/parsers/duration.js
|
|
53189
53254
|
|
|
53190
53255
|
|
|
53191
|
-
// if the system time is not UTC, we need to convert it to UTC
|
|
53192
|
-
|
|
53193
|
-
const loc = 'UTC'
|
|
53194
|
-
|
|
53195
|
-
const commonDateFormats = [
|
|
53196
|
-
'yyyy-MM-dd',
|
|
53197
|
-
'dd/MM/yyyy',
|
|
53198
|
-
'dd/MM/yy',
|
|
53199
|
-
'dd-MM-yyyy',
|
|
53200
|
-
'dd-MM-yy',
|
|
53201
|
-
'dd.MM.yyyy',
|
|
53202
|
-
'dd.MM.yy'
|
|
53203
|
-
]
|
|
53204
|
-
|
|
53205
|
-
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
53206
|
-
|
|
53207
53256
|
function parseDuration(text) {
|
|
53257
|
+
let matched = false
|
|
53208
53258
|
const duration = {
|
|
53209
53259
|
hours: 0,
|
|
53210
53260
|
minutes: 0
|
|
53211
53261
|
}
|
|
53212
53262
|
|
|
53213
|
-
const
|
|
53214
|
-
|
|
53215
|
-
duration.minutes = parseInt(pieces[1]) ? parseInt(pieces[1]) : 0
|
|
53216
|
-
return duration
|
|
53217
|
-
}
|
|
53263
|
+
const hoursAndMinutes = new RegExp(/([0-9]+)h([0-9]+)m/)
|
|
53264
|
+
const hours = new RegExp(/([0-9]+)h/)
|
|
53218
53265
|
|
|
53219
|
-
|
|
53220
|
-
|
|
53221
|
-
|
|
53222
|
-
|
|
53223
|
-
|
|
53224
|
-
|
|
53225
|
-
|
|
53226
|
-
|
|
53227
|
-
|
|
53228
|
-
|
|
53229
|
-
} else {
|
|
53230
|
-
return null
|
|
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
|
|
53231
53276
|
}
|
|
53232
|
-
}
|
|
53233
53277
|
|
|
53234
|
-
|
|
53235
|
-
|
|
53236
|
-
return (0,date_fns.isMatch)(text, format)
|
|
53237
|
-
})
|
|
53238
|
-
if (match.indexOf(true) > -1) {
|
|
53239
|
-
const time = zonedTimeToUtc(
|
|
53240
|
-
(0,date_fns.parse)(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
53241
|
-
loc
|
|
53242
|
-
)
|
|
53243
|
-
return formatInTimeZone(time, loc, 'HH:mm')
|
|
53278
|
+
if (matched) {
|
|
53279
|
+
return duration
|
|
53244
53280
|
} else {
|
|
53245
53281
|
return null
|
|
53246
53282
|
}
|
|
53247
53283
|
}
|
|
53248
53284
|
|
|
53285
|
+
;// CONCATENATED MODULE: ./src/parsers/list.js
|
|
53286
|
+
|
|
53287
|
+
|
|
53249
53288
|
function parseList(list) {
|
|
53250
53289
|
return list.children
|
|
53251
53290
|
.map((item) => {
|
|
@@ -53276,54 +53315,113 @@ function parseList(list) {
|
|
|
53276
53315
|
.filter((x) => !!x)
|
|
53277
53316
|
}
|
|
53278
53317
|
|
|
53318
|
+
;// CONCATENATED MODULE: ./src/parsers/index.js
|
|
53319
|
+
|
|
53320
|
+
|
|
53321
|
+
;
|
|
53322
|
+
|
|
53323
|
+
|
|
53324
|
+
|
|
53325
|
+
|
|
53326
|
+
const parsers_parseDate = date_parseDate
|
|
53327
|
+
const parsers_parseTime = time_parseTime
|
|
53328
|
+
const parsers_parseDuration = parseDuration
|
|
53329
|
+
const parsers_parseList = parseList
|
|
53330
|
+
|
|
53331
|
+
;// CONCATENATED MODULE: ./src/parse.js
|
|
53332
|
+
|
|
53333
|
+
|
|
53334
|
+
;
|
|
53335
|
+
|
|
53336
|
+
|
|
53337
|
+
|
|
53338
|
+
|
|
53339
|
+
|
|
53340
|
+
|
|
53341
|
+
|
|
53342
|
+
|
|
53279
53343
|
async function parseMD(body) {
|
|
53280
53344
|
const tokens = await unified().use(remark_parse).use(remarkGfm).parse(body)
|
|
53281
53345
|
if (!tokens) {
|
|
53282
53346
|
return []
|
|
53283
53347
|
}
|
|
53284
53348
|
|
|
53285
|
-
const
|
|
53286
|
-
|
|
53287
|
-
|
|
53288
|
-
const
|
|
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: []
|
|
53364
|
+
}
|
|
53365
|
+
} else if (token.type === 'paragraph' && currentHeading) {
|
|
53366
|
+
const obj = structuredResponse[currentHeading]
|
|
53367
|
+
|
|
53368
|
+
const date = parsers_parseDate(cleanText)
|
|
53369
|
+
const time = parsers_parseTime(cleanText)
|
|
53370
|
+
const duration = parsers_parseDuration(cleanText)
|
|
53289
53371
|
|
|
53290
|
-
|
|
53291
|
-
|
|
53292
|
-
const obj = {
|
|
53293
|
-
id: slugify(current.children[0].value),
|
|
53294
|
-
title: current.children[0].value
|
|
53372
|
+
if (date) {
|
|
53373
|
+
obj.date = date
|
|
53295
53374
|
}
|
|
53296
|
-
|
|
53297
|
-
|
|
53298
|
-
|
|
53299
|
-
|
|
53300
|
-
|
|
53301
|
-
|
|
53302
|
-
|
|
53303
|
-
.use(remark_stringify)
|
|
53304
|
-
.stringify(next)
|
|
53305
|
-
const date = parse_parseDate(obj.text)
|
|
53306
|
-
const time = parse_parseTime(obj.text)
|
|
53307
|
-
if (date) {
|
|
53308
|
-
obj.date = date
|
|
53309
|
-
}
|
|
53310
|
-
if (time) {
|
|
53311
|
-
obj.time = time
|
|
53312
|
-
}
|
|
53313
|
-
if (obj.id === 'duration') {
|
|
53314
|
-
obj.duration = parseDuration(obj.text)
|
|
53315
|
-
}
|
|
53375
|
+
|
|
53376
|
+
if (time) {
|
|
53377
|
+
obj.time = time
|
|
53378
|
+
}
|
|
53379
|
+
|
|
53380
|
+
if (duration) {
|
|
53381
|
+
obj.duration = duration
|
|
53316
53382
|
}
|
|
53317
|
-
|
|
53383
|
+
|
|
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)
|
|
53318
53402
|
}
|
|
53319
53403
|
}
|
|
53320
53404
|
|
|
53321
|
-
|
|
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]
|
|
53411
|
+
}
|
|
53412
|
+
token.text = content.join('\n\n')
|
|
53413
|
+
}
|
|
53414
|
+
token.content = content
|
|
53415
|
+
}
|
|
53416
|
+
|
|
53417
|
+
return structuredResponse
|
|
53322
53418
|
}
|
|
53323
53419
|
|
|
53324
53420
|
;// CONCATENATED MODULE: ./src/index.js
|
|
53325
53421
|
|
|
53326
53422
|
|
|
53423
|
+
;
|
|
53424
|
+
|
|
53327
53425
|
|
|
53328
53426
|
|
|
53329
53427
|
async function run() {
|
package/dist/licenses.txt
CHANGED
|
@@ -1429,6 +1429,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
1429
1429
|
THE SOFTWARE.
|
|
1430
1430
|
|
|
1431
1431
|
|
|
1432
|
+
strip-final-newline
|
|
1433
|
+
MIT
|
|
1434
|
+
MIT License
|
|
1435
|
+
|
|
1436
|
+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
1437
|
+
|
|
1438
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
1439
|
+
|
|
1440
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
1441
|
+
|
|
1442
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1443
|
+
|
|
1444
|
+
|
|
1432
1445
|
tr46
|
|
1433
1446
|
MIT
|
|
1434
1447
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zentered/issue-forms-body-parser",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Parser for GitHub Issue Form body, also available as GitHub Action",
|
|
6
6
|
"keywords": [
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"remark-gfm": "^3.0.1",
|
|
63
63
|
"remark-parse": "^10.0.1",
|
|
64
64
|
"remark-stringify": "^10.0.2",
|
|
65
|
+
"strip-final-newline": "^3.0.0",
|
|
65
66
|
"unified": "^10.1.2"
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
package/src/index.js
CHANGED
package/src/parse.js
CHANGED
|
@@ -5,137 +5,88 @@ import remarkParse from 'remark-parse'
|
|
|
5
5
|
import remarkGfm from 'remark-gfm'
|
|
6
6
|
import slugify from '@sindresorhus/slugify'
|
|
7
7
|
import remarkStringify from 'remark-stringify'
|
|
8
|
-
import
|
|
8
|
+
import stripFinalNewline from 'strip-final-newline'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import {
|
|
11
|
+
parseDate,
|
|
12
|
+
parseTime,
|
|
13
|
+
parseDuration,
|
|
14
|
+
parseList
|
|
15
|
+
} from './parsers/index.js'
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'dd-MM-yy',
|
|
20
|
-
'dd.MM.yyyy',
|
|
21
|
-
'dd.MM.yy'
|
|
22
|
-
]
|
|
17
|
+
export default async function parseMD(body) {
|
|
18
|
+
const tokens = await unified().use(remarkParse).use(remarkGfm).parse(body)
|
|
19
|
+
if (!tokens) {
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
23
22
|
|
|
24
|
-
const
|
|
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)
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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: []
|
|
38
|
+
}
|
|
39
|
+
} else if (token.type === 'paragraph' && currentHeading) {
|
|
40
|
+
const obj = structuredResponse[currentHeading]
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return duration
|
|
36
|
-
}
|
|
42
|
+
const date = parseDate(cleanText)
|
|
43
|
+
const time = parseTime(cleanText)
|
|
44
|
+
const duration = parseDuration(cleanText)
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
42
|
-
if (match.indexOf(true) > -1) {
|
|
43
|
-
const date = zonedTimeToUtc(
|
|
44
|
-
parse(text, commonDateFormats[match.indexOf(true)], new Date()),
|
|
45
|
-
loc
|
|
46
|
-
).toJSON()
|
|
47
|
-
return date.split('T')[0]
|
|
48
|
-
} else {
|
|
49
|
-
return null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
46
|
+
if (date) {
|
|
47
|
+
obj.date = date
|
|
48
|
+
}
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
})
|
|
57
|
-
if (match.indexOf(true) > -1) {
|
|
58
|
-
const time = zonedTimeToUtc(
|
|
59
|
-
parse(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
60
|
-
loc
|
|
61
|
-
)
|
|
62
|
-
return formatInTimeZone(time, loc, 'HH:mm')
|
|
63
|
-
} else {
|
|
64
|
-
return null
|
|
65
|
-
}
|
|
66
|
-
}
|
|
50
|
+
if (time) {
|
|
51
|
+
obj.time = time
|
|
52
|
+
}
|
|
67
53
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.map((item) => {
|
|
71
|
-
const listItem = {}
|
|
72
|
-
if (item.type === 'list') {
|
|
73
|
-
return parseList(list)
|
|
74
|
-
} else if (item.type === 'listItem') {
|
|
75
|
-
listItem.checked = item.checked
|
|
76
|
-
return item.children
|
|
77
|
-
.map((child) => {
|
|
78
|
-
if (child.type === 'paragraph') {
|
|
79
|
-
listItem.text = child.children
|
|
80
|
-
.map((c) => {
|
|
81
|
-
if (c.type === 'link') {
|
|
82
|
-
return c.children[0].value
|
|
83
|
-
} else {
|
|
84
|
-
return c.value
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
.filter((x) => !!x)
|
|
88
|
-
.join('')
|
|
89
|
-
return listItem
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
.filter((x) => !!x)
|
|
54
|
+
if (duration) {
|
|
55
|
+
obj.duration = duration
|
|
93
56
|
}
|
|
94
|
-
})
|
|
95
|
-
.filter((x) => !!x)
|
|
96
|
-
}
|
|
97
57
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}
|
|
102
77
|
}
|
|
103
78
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// issue-form answers start with a h3 heading, ignore everything else
|
|
111
|
-
const obj = {
|
|
112
|
-
id: slugify(current.children[0].value),
|
|
113
|
-
title: current.children[0].value
|
|
114
|
-
}
|
|
115
|
-
if (hasNext) {
|
|
116
|
-
const next = tokens.children[idx + 1]
|
|
117
|
-
if (next.type === 'list') {
|
|
118
|
-
obj.list = parseList(next).flat()
|
|
119
|
-
}
|
|
120
|
-
obj.text = await unified()
|
|
121
|
-
.use(remarkGfm)
|
|
122
|
-
.use(remarkStringify)
|
|
123
|
-
.stringify(next)
|
|
124
|
-
const date = parseDate(obj.text)
|
|
125
|
-
const time = parseTime(obj.text)
|
|
126
|
-
if (date) {
|
|
127
|
-
obj.date = date
|
|
128
|
-
}
|
|
129
|
-
if (time) {
|
|
130
|
-
obj.time = time
|
|
131
|
-
}
|
|
132
|
-
if (obj.id === 'duration') {
|
|
133
|
-
obj.duration = parseDuration(obj.text)
|
|
134
|
-
}
|
|
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]
|
|
135
85
|
}
|
|
136
|
-
|
|
86
|
+
token.text = content.join('\n\n')
|
|
137
87
|
}
|
|
88
|
+
token.content = content
|
|
138
89
|
}
|
|
139
90
|
|
|
140
|
-
return
|
|
91
|
+
return structuredResponse
|
|
141
92
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { parse, isMatch } from 'date-fns'
|
|
4
|
+
import { zonedTimeToUtc } from 'date-fns-tz/esm'
|
|
5
|
+
|
|
6
|
+
const loc = 'UTC'
|
|
7
|
+
const commonDateFormats = [
|
|
8
|
+
'yyyy-MM-dd',
|
|
9
|
+
'dd/MM/yyyy',
|
|
10
|
+
'dd/MM/yy',
|
|
11
|
+
'dd-MM-yyyy',
|
|
12
|
+
'dd-MM-yy',
|
|
13
|
+
'dd.MM.yyyy',
|
|
14
|
+
'dd.MM.yy'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export default function parseDate(text) {
|
|
18
|
+
const match = commonDateFormats.map((format) => {
|
|
19
|
+
return isMatch(text, format)
|
|
20
|
+
})
|
|
21
|
+
if (match.indexOf(true) > -1) {
|
|
22
|
+
const date = zonedTimeToUtc(
|
|
23
|
+
parse(text, commonDateFormats[match.indexOf(true)], new Date()),
|
|
24
|
+
loc
|
|
25
|
+
).toJSON()
|
|
26
|
+
return date.split('T')[0]
|
|
27
|
+
} else {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
export default function parseDuration(text) {
|
|
4
|
+
let matched = false
|
|
5
|
+
const duration = {
|
|
6
|
+
hours: 0,
|
|
7
|
+
minutes: 0
|
|
8
|
+
}
|
|
9
|
+
|
|
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
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import date from './date.js'
|
|
4
|
+
import time from './time.js'
|
|
5
|
+
import duration from './duration.js'
|
|
6
|
+
import list from './list.js'
|
|
7
|
+
|
|
8
|
+
export const parseDate = date
|
|
9
|
+
export const parseTime = time
|
|
10
|
+
export const parseDuration = duration
|
|
11
|
+
export const parseList = list
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
export default function parseList(list) {
|
|
4
|
+
return list.children
|
|
5
|
+
.map((item) => {
|
|
6
|
+
const listItem = {}
|
|
7
|
+
if (item.type === 'list') {
|
|
8
|
+
return parseList(list)
|
|
9
|
+
} else if (item.type === 'listItem') {
|
|
10
|
+
listItem.checked = item.checked
|
|
11
|
+
return item.children
|
|
12
|
+
.map((child) => {
|
|
13
|
+
if (child.type === 'paragraph') {
|
|
14
|
+
listItem.text = child.children
|
|
15
|
+
.map((c) => {
|
|
16
|
+
if (c.type === 'link') {
|
|
17
|
+
return c.children[0].value
|
|
18
|
+
} else {
|
|
19
|
+
return c.value
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.filter((x) => !!x)
|
|
23
|
+
.join('')
|
|
24
|
+
return listItem
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.filter((x) => !!x)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
.filter((x) => !!x)
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { parse, isMatch } from 'date-fns'
|
|
4
|
+
import { zonedTimeToUtc, formatInTimeZone } from 'date-fns-tz/esm'
|
|
5
|
+
|
|
6
|
+
const loc = 'UTC'
|
|
7
|
+
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
8
|
+
|
|
9
|
+
export default function parseTime(text) {
|
|
10
|
+
const match = commonTimeFormats.map((format) => {
|
|
11
|
+
return isMatch(text, format)
|
|
12
|
+
})
|
|
13
|
+
if (match.indexOf(true) > -1) {
|
|
14
|
+
const time = zonedTimeToUtc(
|
|
15
|
+
parse(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
16
|
+
loc
|
|
17
|
+
)
|
|
18
|
+
return formatInTimeZone(time, loc, 'HH:mm')
|
|
19
|
+
} else {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}
|
package/test/parse-issue.test.js
CHANGED
|
@@ -8,84 +8,78 @@ import { join } from 'path'
|
|
|
8
8
|
const test = t.test
|
|
9
9
|
|
|
10
10
|
test('parse(md) should parse GitHub Issue Form data into useful, structured data', async (t) => {
|
|
11
|
-
const expected =
|
|
12
|
-
{
|
|
13
|
-
id: 'event-description',
|
|
11
|
+
const expected = {
|
|
12
|
+
'event-description': {
|
|
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
|
-
{
|
|
18
|
-
id: 'location',
|
|
21
|
+
location: {
|
|
19
22
|
title: 'Location',
|
|
20
|
-
|
|
23
|
+
content: [
|
|
24
|
+
'[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)'
|
|
25
|
+
],
|
|
26
|
+
text: '[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)'
|
|
21
27
|
},
|
|
22
|
-
{
|
|
23
|
-
id: 'date',
|
|
28
|
+
date: {
|
|
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
|
-
{
|
|
29
|
-
{
|
|
30
|
-
id: 'duration',
|
|
34
|
+
time: { title: 'Time', content: ['16:00'], time: '16:00', text: '16:00' },
|
|
35
|
+
duration: {
|
|
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
|
-
{
|
|
36
|
-
id: 'list-item-checked',
|
|
41
|
+
'list-item-checked': {
|
|
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)\n"
|
|
50
|
+
]
|
|
45
51
|
},
|
|
46
|
-
{
|
|
47
|
-
id: 'list-item-unchecked',
|
|
52
|
+
'list-item-unchecked': {
|
|
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)\n"
|
|
61
|
+
]
|
|
56
62
|
},
|
|
57
|
-
{
|
|
58
|
-
id: 'mixed-task-list',
|
|
63
|
+
'mixed-task-list': {
|
|
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\n'
|
|
73
|
+
]
|
|
68
74
|
},
|
|
69
|
-
{
|
|
70
|
-
id: 'complex-list',
|
|
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\n'
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: 'repositories',
|
|
75
|
+
repositories: {
|
|
80
76
|
title: 'Repositories',
|
|
81
|
-
|
|
77
|
+
content: [],
|
|
78
|
+
lang: 'csv',
|
|
79
|
+
text: '```csv\nhttps://example.com/repository-1\nhttps://example.com/repository-2\n```'
|
|
82
80
|
},
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
title: 'Visibility',
|
|
86
|
-
text: 'Internal\n'
|
|
87
|
-
}
|
|
88
|
-
]
|
|
81
|
+
visibility: { title: 'Visibility', content: ['Internal'], text: 'Internal' }
|
|
82
|
+
}
|
|
89
83
|
|
|
90
84
|
const md = await readFile(
|
|
91
85
|
join(process.cwd(), 'test', 'test-issue-1.md'),
|
|
@@ -93,11 +87,12 @@ 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) => {
|
|
100
|
-
const expected =
|
|
95
|
+
const expected = {}
|
|
101
96
|
|
|
102
97
|
const md = await readFile(
|
|
103
98
|
join(process.cwd(), 'test', 'test-issue-2.md'),
|
|
@@ -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
|