@zentered/issue-forms-body-parser 1.1.4 → 1.3.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 +119 -45
- package/dist/licenses.txt +13 -0
- package/package.json +2 -1
- package/src/index.js +2 -0
- package/src/parse.js +26 -82
- package/src/parsers/date.js +30 -0
- package/src/parsers/duration.js +13 -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 +37 -32
|
@@ -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,61 +53226,48 @@ 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
|
|
|
53187
|
-
|
|
53188
|
-
|
|
53189
|
-
|
|
53190
|
-
// if the system time is not UTC, we need to convert it to UTC
|
|
53191
|
-
|
|
53192
|
-
const loc = 'UTC'
|
|
53193
|
-
|
|
53194
|
-
const commonDateFormats = [
|
|
53195
|
-
'yyyy-MM-dd',
|
|
53196
|
-
'dd/MM/yyyy',
|
|
53197
|
-
'dd/MM/yy',
|
|
53198
|
-
'dd-MM-yyyy',
|
|
53199
|
-
'dd-MM-yy',
|
|
53200
|
-
'dd.MM.yyyy',
|
|
53201
|
-
'dd.MM.yy'
|
|
53202
|
-
]
|
|
53203
|
-
|
|
53235
|
+
const time_loc = 'UTC'
|
|
53204
53236
|
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
53205
53237
|
|
|
53206
|
-
function
|
|
53207
|
-
const match = commonDateFormats.map((format) => {
|
|
53208
|
-
return (0,date_fns.isMatch)(text, format)
|
|
53209
|
-
})
|
|
53210
|
-
if (match.indexOf(true) > -1) {
|
|
53211
|
-
const date = zonedTimeToUtc(
|
|
53212
|
-
(0,date_fns.parse)(text, commonDateFormats[match.indexOf(true)], new Date()),
|
|
53213
|
-
loc
|
|
53214
|
-
).toJSON()
|
|
53215
|
-
return date.split('T')[0]
|
|
53216
|
-
} else {
|
|
53217
|
-
return null
|
|
53218
|
-
}
|
|
53219
|
-
}
|
|
53220
|
-
|
|
53221
|
-
function parse_parseTime(text) {
|
|
53238
|
+
function time_parseTime(text) {
|
|
53222
53239
|
const match = commonTimeFormats.map((format) => {
|
|
53223
53240
|
return (0,date_fns.isMatch)(text, format)
|
|
53224
53241
|
})
|
|
53225
53242
|
if (match.indexOf(true) > -1) {
|
|
53226
53243
|
const time = zonedTimeToUtc(
|
|
53227
53244
|
(0,date_fns.parse)(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
53228
|
-
|
|
53245
|
+
time_loc
|
|
53229
53246
|
)
|
|
53230
|
-
return formatInTimeZone(time,
|
|
53247
|
+
return formatInTimeZone(time, time_loc, 'HH:mm')
|
|
53231
53248
|
} else {
|
|
53232
53249
|
return null
|
|
53233
53250
|
}
|
|
53234
53251
|
}
|
|
53235
53252
|
|
|
53253
|
+
;// CONCATENATED MODULE: ./src/parsers/duration.js
|
|
53254
|
+
|
|
53255
|
+
|
|
53256
|
+
function parseDuration(text) {
|
|
53257
|
+
const duration = {
|
|
53258
|
+
hours: 0,
|
|
53259
|
+
minutes: 0
|
|
53260
|
+
}
|
|
53261
|
+
|
|
53262
|
+
const pieces = text.replace('m', '').split('h')
|
|
53263
|
+
duration.hours = parseInt(pieces[0]) ? parseInt(pieces[0]) : 0
|
|
53264
|
+
duration.minutes = parseInt(pieces[1]) ? parseInt(pieces[1]) : 0
|
|
53265
|
+
return duration
|
|
53266
|
+
}
|
|
53267
|
+
|
|
53268
|
+
;// CONCATENATED MODULE: ./src/parsers/list.js
|
|
53269
|
+
|
|
53270
|
+
|
|
53236
53271
|
function parseList(list) {
|
|
53237
53272
|
return list.children
|
|
53238
53273
|
.map((item) => {
|
|
@@ -53263,42 +53298,79 @@ function parseList(list) {
|
|
|
53263
53298
|
.filter((x) => !!x)
|
|
53264
53299
|
}
|
|
53265
53300
|
|
|
53301
|
+
;// CONCATENATED MODULE: ./src/parsers/index.js
|
|
53302
|
+
|
|
53303
|
+
|
|
53304
|
+
;
|
|
53305
|
+
|
|
53306
|
+
|
|
53307
|
+
|
|
53308
|
+
|
|
53309
|
+
const parsers_parseDate = date_parseDate
|
|
53310
|
+
const parsers_parseTime = time_parseTime
|
|
53311
|
+
const parsers_parseDuration = parseDuration
|
|
53312
|
+
const parsers_parseList = parseList
|
|
53313
|
+
|
|
53314
|
+
;// CONCATENATED MODULE: ./src/parse.js
|
|
53315
|
+
|
|
53316
|
+
|
|
53317
|
+
;
|
|
53318
|
+
|
|
53319
|
+
|
|
53320
|
+
|
|
53321
|
+
|
|
53322
|
+
|
|
53323
|
+
|
|
53324
|
+
|
|
53266
53325
|
async function parseMD(body) {
|
|
53267
53326
|
const tokens = await unified().use(remark_parse).use(remarkGfm).parse(body)
|
|
53268
53327
|
if (!tokens) {
|
|
53269
53328
|
return []
|
|
53270
53329
|
}
|
|
53271
53330
|
|
|
53272
|
-
const r =
|
|
53331
|
+
const r = {}
|
|
53332
|
+
let counter = 0
|
|
53273
53333
|
for (let idx = 0; idx < tokens.children.length; idx = idx + 2) {
|
|
53274
53334
|
const current = tokens.children[idx]
|
|
53275
53335
|
const hasNext = idx + 1 < tokens.children.length
|
|
53276
53336
|
|
|
53277
53337
|
if (current.type === 'heading') {
|
|
53278
|
-
|
|
53338
|
+
const key = slugify(current.children[0].value)
|
|
53339
|
+
|
|
53340
|
+
// issue-form answers start with a h3 heading, ignore everything else for now
|
|
53279
53341
|
const obj = {
|
|
53280
|
-
|
|
53281
|
-
|
|
53342
|
+
title: current.children[0].value,
|
|
53343
|
+
order: counter++
|
|
53282
53344
|
}
|
|
53345
|
+
|
|
53283
53346
|
if (hasNext) {
|
|
53284
53347
|
const next = tokens.children[idx + 1]
|
|
53285
53348
|
if (next.type === 'list') {
|
|
53286
|
-
obj.list =
|
|
53349
|
+
obj.list = parsers_parseList(next).flat()
|
|
53287
53350
|
}
|
|
53288
|
-
|
|
53351
|
+
const text = await unified()
|
|
53289
53352
|
.use(remarkGfm)
|
|
53290
53353
|
.use(remark_stringify)
|
|
53291
53354
|
.stringify(next)
|
|
53292
|
-
|
|
53293
|
-
|
|
53355
|
+
obj.text = stripFinalNewline(text)
|
|
53356
|
+
|
|
53357
|
+
const date = parsers_parseDate(obj.text)
|
|
53358
|
+
const time = parsers_parseTime(obj.text)
|
|
53359
|
+
|
|
53294
53360
|
if (date) {
|
|
53295
53361
|
obj.date = date
|
|
53296
53362
|
}
|
|
53363
|
+
|
|
53297
53364
|
if (time) {
|
|
53298
53365
|
obj.time = time
|
|
53299
53366
|
}
|
|
53367
|
+
|
|
53368
|
+
if (key === 'duration') {
|
|
53369
|
+
obj.duration = parsers_parseDuration(obj.text)
|
|
53370
|
+
}
|
|
53371
|
+
|
|
53372
|
+
r[key] = obj
|
|
53300
53373
|
}
|
|
53301
|
-
r.push(obj)
|
|
53302
53374
|
}
|
|
53303
53375
|
}
|
|
53304
53376
|
|
|
@@ -53308,6 +53380,8 @@ async function parseMD(body) {
|
|
|
53308
53380
|
;// CONCATENATED MODULE: ./src/index.js
|
|
53309
53381
|
|
|
53310
53382
|
|
|
53383
|
+
;
|
|
53384
|
+
|
|
53311
53385
|
|
|
53312
53386
|
|
|
53313
53387
|
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.3.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,82 +5,13 @@ 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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'dd/MM/yyyy',
|
|
16
|
-
'dd/MM/yy',
|
|
17
|
-
'dd-MM-yyyy',
|
|
18
|
-
'dd-MM-yy',
|
|
19
|
-
'dd.MM.yyyy',
|
|
20
|
-
'dd.MM.yy'
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
24
|
-
|
|
25
|
-
function parseDate(text) {
|
|
26
|
-
const match = commonDateFormats.map((format) => {
|
|
27
|
-
return isMatch(text, format)
|
|
28
|
-
})
|
|
29
|
-
if (match.indexOf(true) > -1) {
|
|
30
|
-
const date = zonedTimeToUtc(
|
|
31
|
-
parse(text, commonDateFormats[match.indexOf(true)], new Date()),
|
|
32
|
-
loc
|
|
33
|
-
).toJSON()
|
|
34
|
-
return date.split('T')[0]
|
|
35
|
-
} else {
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function parseTime(text) {
|
|
41
|
-
const match = commonTimeFormats.map((format) => {
|
|
42
|
-
return isMatch(text, format)
|
|
43
|
-
})
|
|
44
|
-
if (match.indexOf(true) > -1) {
|
|
45
|
-
const time = zonedTimeToUtc(
|
|
46
|
-
parse(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
47
|
-
loc
|
|
48
|
-
)
|
|
49
|
-
return formatInTimeZone(time, loc, 'HH:mm')
|
|
50
|
-
} else {
|
|
51
|
-
return null
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function parseList(list) {
|
|
56
|
-
return list.children
|
|
57
|
-
.map((item) => {
|
|
58
|
-
const listItem = {}
|
|
59
|
-
if (item.type === 'list') {
|
|
60
|
-
return parseList(list)
|
|
61
|
-
} else if (item.type === 'listItem') {
|
|
62
|
-
listItem.checked = item.checked
|
|
63
|
-
return item.children
|
|
64
|
-
.map((child) => {
|
|
65
|
-
if (child.type === 'paragraph') {
|
|
66
|
-
listItem.text = child.children
|
|
67
|
-
.map((c) => {
|
|
68
|
-
if (c.type === 'link') {
|
|
69
|
-
return c.children[0].value
|
|
70
|
-
} else {
|
|
71
|
-
return c.value
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
.filter((x) => !!x)
|
|
75
|
-
.join('')
|
|
76
|
-
return listItem
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
|
-
.filter((x) => !!x)
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
.filter((x) => !!x)
|
|
83
|
-
}
|
|
8
|
+
import stripFinalNewline from 'strip-final-newline'
|
|
9
|
+
import {
|
|
10
|
+
parseDate,
|
|
11
|
+
parseTime,
|
|
12
|
+
parseDuration,
|
|
13
|
+
parseList
|
|
14
|
+
} from './parsers/index.js'
|
|
84
15
|
|
|
85
16
|
export default async function parseMD(body) {
|
|
86
17
|
const tokens = await unified().use(remarkParse).use(remarkGfm).parse(body)
|
|
@@ -88,36 +19,49 @@ export default async function parseMD(body) {
|
|
|
88
19
|
return []
|
|
89
20
|
}
|
|
90
21
|
|
|
91
|
-
const r =
|
|
22
|
+
const r = {}
|
|
23
|
+
let counter = 0
|
|
92
24
|
for (let idx = 0; idx < tokens.children.length; idx = idx + 2) {
|
|
93
25
|
const current = tokens.children[idx]
|
|
94
26
|
const hasNext = idx + 1 < tokens.children.length
|
|
95
27
|
|
|
96
28
|
if (current.type === 'heading') {
|
|
97
|
-
|
|
29
|
+
const key = slugify(current.children[0].value)
|
|
30
|
+
|
|
31
|
+
// issue-form answers start with a h3 heading, ignore everything else for now
|
|
98
32
|
const obj = {
|
|
99
|
-
|
|
100
|
-
|
|
33
|
+
title: current.children[0].value,
|
|
34
|
+
order: counter++
|
|
101
35
|
}
|
|
36
|
+
|
|
102
37
|
if (hasNext) {
|
|
103
38
|
const next = tokens.children[idx + 1]
|
|
104
39
|
if (next.type === 'list') {
|
|
105
40
|
obj.list = parseList(next).flat()
|
|
106
41
|
}
|
|
107
|
-
|
|
42
|
+
const text = await unified()
|
|
108
43
|
.use(remarkGfm)
|
|
109
44
|
.use(remarkStringify)
|
|
110
45
|
.stringify(next)
|
|
46
|
+
obj.text = stripFinalNewline(text)
|
|
47
|
+
|
|
111
48
|
const date = parseDate(obj.text)
|
|
112
49
|
const time = parseTime(obj.text)
|
|
50
|
+
|
|
113
51
|
if (date) {
|
|
114
52
|
obj.date = date
|
|
115
53
|
}
|
|
54
|
+
|
|
116
55
|
if (time) {
|
|
117
56
|
obj.time = time
|
|
118
57
|
}
|
|
58
|
+
|
|
59
|
+
if (key === 'duration') {
|
|
60
|
+
obj.duration = parseDuration(obj.text)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
r[key] = obj
|
|
119
64
|
}
|
|
120
|
-
r.push(obj)
|
|
121
65
|
}
|
|
122
66
|
}
|
|
123
67
|
|
|
@@ -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,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
export default function parseDuration(text) {
|
|
4
|
+
const duration = {
|
|
5
|
+
hours: 0,
|
|
6
|
+
minutes: 0
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const pieces = text.replace('m', '').split('h')
|
|
10
|
+
duration.hours = parseInt(pieces[0]) ? parseInt(pieces[0]) : 0
|
|
11
|
+
duration.minutes = parseInt(pieces[1]) ? parseInt(pieces[1]) : 0
|
|
12
|
+
return duration
|
|
13
|
+
}
|
|
@@ -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,27 +8,32 @@ 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
|
-
|
|
11
|
+
const expected = {
|
|
12
|
+
'event-description': {
|
|
13
|
+
order: 0,
|
|
14
14
|
title: 'Event Description',
|
|
15
|
-
text: "Let's meet for coffee and chat about tech, coding, Cyprus and the newly formed\nCDC (Cyprus Developer Community)
|
|
15
|
+
text: "Let's meet for coffee and chat about tech, coding, Cyprus and the newly formed\nCDC (Cyprus Developer Community)."
|
|
16
16
|
},
|
|
17
|
-
{
|
|
18
|
-
|
|
17
|
+
location: {
|
|
18
|
+
order: 1,
|
|
19
19
|
title: 'Location',
|
|
20
|
-
text: '[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)
|
|
20
|
+
text: '[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)'
|
|
21
21
|
},
|
|
22
|
-
{
|
|
23
|
-
|
|
22
|
+
date: {
|
|
23
|
+
order: 2,
|
|
24
24
|
title: 'Date',
|
|
25
|
-
text: '11.03.2022
|
|
25
|
+
text: '11.03.2022',
|
|
26
26
|
date: '2022-03-11'
|
|
27
27
|
},
|
|
28
|
-
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
time: { order: 3, title: 'Time', text: '16:00', time: '16:00' },
|
|
29
|
+
duration: {
|
|
30
|
+
order: 4,
|
|
31
|
+
title: 'Duration',
|
|
32
|
+
text: '2h',
|
|
33
|
+
duration: { hours: 2, minutes: 0 }
|
|
34
|
+
},
|
|
35
|
+
'list-item-checked': {
|
|
36
|
+
order: 5,
|
|
32
37
|
title: 'List Item Checked',
|
|
33
38
|
list: [
|
|
34
39
|
{
|
|
@@ -36,10 +41,10 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
36
41
|
text: "I agree to follow this project's\nCode of Conduct"
|
|
37
42
|
}
|
|
38
43
|
],
|
|
39
|
-
text: "* [x] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)
|
|
44
|
+
text: "* [x] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
|
|
40
45
|
},
|
|
41
|
-
{
|
|
42
|
-
|
|
46
|
+
'list-item-unchecked': {
|
|
47
|
+
order: 6,
|
|
43
48
|
title: 'List Item Unchecked',
|
|
44
49
|
list: [
|
|
45
50
|
{
|
|
@@ -47,10 +52,10 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
47
52
|
text: "I agree to follow this project's\nCode of Conduct"
|
|
48
53
|
}
|
|
49
54
|
],
|
|
50
|
-
text: "* [ ] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)
|
|
55
|
+
text: "* [ ] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
|
|
51
56
|
},
|
|
52
|
-
{
|
|
53
|
-
|
|
57
|
+
'mixed-task-list': {
|
|
58
|
+
order: 7,
|
|
54
59
|
title: 'Mixed Task List',
|
|
55
60
|
list: [
|
|
56
61
|
{ checked: true, text: 'checked' },
|
|
@@ -59,28 +64,28 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
59
64
|
{ checked: true, text: 'checked 3' },
|
|
60
65
|
{ checked: false, text: 'unchecked 2' }
|
|
61
66
|
],
|
|
62
|
-
text: '* [x] checked\n* [ ] unchecked\n* [x] checked 2\n* [x] checked 3\n* [ ] unchecked 2
|
|
67
|
+
text: '* [x] checked\n* [ ] unchecked\n* [x] checked 2\n* [x] checked 3\n* [ ] unchecked 2'
|
|
63
68
|
},
|
|
64
|
-
{
|
|
65
|
-
|
|
69
|
+
'complex-list': {
|
|
70
|
+
order: 8,
|
|
66
71
|
title: 'Complex List',
|
|
67
72
|
list: [
|
|
68
73
|
{ checked: null, text: 'one' },
|
|
69
74
|
{ checked: null, text: 'two' }
|
|
70
75
|
],
|
|
71
|
-
text: '* one\n* two\n * three\n * four\n 1. five\n 2. six
|
|
76
|
+
text: '* one\n* two\n * three\n * four\n 1. five\n 2. six'
|
|
72
77
|
},
|
|
73
|
-
{
|
|
74
|
-
|
|
78
|
+
repositories: {
|
|
79
|
+
order: 9,
|
|
75
80
|
title: 'Repositories',
|
|
76
|
-
text: '```csv\nhttps://example.com/repository-1\nhttps://example.com/repository-2\n
|
|
81
|
+
text: '```csv\nhttps://example.com/repository-1\nhttps://example.com/repository-2\n```'
|
|
77
82
|
},
|
|
78
|
-
{
|
|
79
|
-
|
|
83
|
+
visibility: {
|
|
84
|
+
order: 10,
|
|
80
85
|
title: 'Visibility',
|
|
81
|
-
text: 'Internal
|
|
86
|
+
text: 'Internal'
|
|
82
87
|
}
|
|
83
|
-
|
|
88
|
+
}
|
|
84
89
|
|
|
85
90
|
const md = await readFile(
|
|
86
91
|
join(process.cwd(), 'test', 'test-issue-1.md'),
|
|
@@ -92,7 +97,7 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
92
97
|
})
|
|
93
98
|
|
|
94
99
|
test('parse(md) return nothing', async (t) => {
|
|
95
|
-
const expected =
|
|
100
|
+
const expected = {}
|
|
96
101
|
|
|
97
102
|
const md = await readFile(
|
|
98
103
|
join(process.cwd(), 'test', 'test-issue-2.md'),
|