@zentered/issue-forms-body-parser 1.2.1 → 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 +28 -20
- package/dist/index.js +98 -58
- package/package.json +1 -1
- package/src/index.js +2 -0
- package/src/parse.js +21 -95
- 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 +24 -24
|
@@ -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,25 +61,30 @@ 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
|
|
78
|
+
"text": "11.03.2022",
|
|
80
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
|
|
@@ -88,11 +92,15 @@ See more examples in [md test cases](./test/test-issue-1.md) and
|
|
|
88
92
|
|
|
89
93
|
### Parsers
|
|
90
94
|
|
|
91
|
-
- `date`: checks if the value matches a
|
|
92
|
-
|
|
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.
|
|
93
101
|
- `lists`: automatically returns lists as arrays
|
|
94
|
-
- `duration`: currently only the format `XXhYYm` is supported as duration, ie.
|
|
95
|
-
|
|
102
|
+
- `duration`: currently only the format `XXhYYm` is supported as duration, ie.
|
|
103
|
+
`1h30m` returns a `duration` object with `hours` and `minutes`.
|
|
96
104
|
|
|
97
105
|
## Installation & Usage
|
|
98
106
|
|
|
@@ -109,7 +117,7 @@ jobs:
|
|
|
109
117
|
steps:
|
|
110
118
|
- name: Issue Forms Body Parser
|
|
111
119
|
id: parse
|
|
112
|
-
uses: zentered/issue-forms-body-parser@
|
|
120
|
+
uses: zentered/issue-forms-body-parser@v2.0.0
|
|
113
121
|
- run: echo "${{ JSON.stringify(steps.parse.outputs.data) }}"
|
|
114
122
|
```
|
|
115
123
|
|
package/dist/index.js
CHANGED
|
@@ -51787,8 +51787,6 @@ function remarkStringify(options) {
|
|
|
51787
51787
|
|
|
51788
51788
|
/* harmony default export */ const remark_stringify = (remarkStringify);
|
|
51789
51789
|
|
|
51790
|
-
// EXTERNAL MODULE: ./node_modules/date-fns/index.js
|
|
51791
|
-
var date_fns = __nccwpck_require__(3314);
|
|
51792
51790
|
;// CONCATENATED MODULE: ./node_modules/strip-final-newline/index.js
|
|
51793
51791
|
function stripFinalNewline(input) {
|
|
51794
51792
|
const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt();
|
|
@@ -51805,6 +51803,8 @@ function stripFinalNewline(input) {
|
|
|
51805
51803
|
return input;
|
|
51806
51804
|
}
|
|
51807
51805
|
|
|
51806
|
+
// EXTERNAL MODULE: ./node_modules/date-fns/index.js
|
|
51807
|
+
var date_fns = __nccwpck_require__(3314);
|
|
51808
51808
|
// EXTERNAL MODULE: ./node_modules/date-fns/_lib/cloneObject/index.js
|
|
51809
51809
|
var cloneObject = __nccwpck_require__(7934);
|
|
51810
51810
|
// EXTERNAL MODULE: ./node_modules/date-fns/_lib/toInteger/index.js
|
|
@@ -52582,6 +52582,38 @@ function zonedTimeToUtc(date, timeZone, options) {
|
|
|
52582
52582
|
return new Date(utc + offsetMilliseconds)
|
|
52583
52583
|
}
|
|
52584
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
|
+
|
|
52585
52617
|
// EXTERNAL MODULE: ./node_modules/date-fns/format/index.js
|
|
52586
52618
|
var format = __nccwpck_require__(2168);
|
|
52587
52619
|
;// CONCATENATED MODULE: ./node_modules/date-fns-tz/esm/_lib/tzIntlTimeZoneName/index.js
|
|
@@ -53194,33 +53226,33 @@ function formatInTimeZone(date, timeZone, formatStr, options) {
|
|
|
53194
53226
|
return format_format(utcToZonedTime(date, timeZone), formatStr, extendedOptions)
|
|
53195
53227
|
}
|
|
53196
53228
|
|
|
53197
|
-
;// CONCATENATED MODULE: ./src/
|
|
53229
|
+
;// CONCATENATED MODULE: ./src/parsers/time.js
|
|
53198
53230
|
|
|
53199
53231
|
|
|
53200
53232
|
;
|
|
53201
53233
|
|
|
53202
53234
|
|
|
53235
|
+
const time_loc = 'UTC'
|
|
53236
|
+
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
53203
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
|
+
}
|
|
53204
53252
|
|
|
53253
|
+
;// CONCATENATED MODULE: ./src/parsers/duration.js
|
|
53205
53254
|
|
|
53206
53255
|
|
|
53207
|
-
|
|
53208
|
-
// if the system time is not UTC, we need to convert it to UTC
|
|
53209
|
-
|
|
53210
|
-
const loc = 'UTC'
|
|
53211
|
-
|
|
53212
|
-
const commonDateFormats = [
|
|
53213
|
-
'yyyy-MM-dd',
|
|
53214
|
-
'dd/MM/yyyy',
|
|
53215
|
-
'dd/MM/yy',
|
|
53216
|
-
'dd-MM-yyyy',
|
|
53217
|
-
'dd-MM-yy',
|
|
53218
|
-
'dd.MM.yyyy',
|
|
53219
|
-
'dd.MM.yy'
|
|
53220
|
-
]
|
|
53221
|
-
|
|
53222
|
-
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
53223
|
-
|
|
53224
53256
|
function parseDuration(text) {
|
|
53225
53257
|
const duration = {
|
|
53226
53258
|
hours: 0,
|
|
@@ -53233,35 +53265,8 @@ function parseDuration(text) {
|
|
|
53233
53265
|
return duration
|
|
53234
53266
|
}
|
|
53235
53267
|
|
|
53236
|
-
|
|
53237
|
-
const match = commonDateFormats.map((format) => {
|
|
53238
|
-
return (0,date_fns.isMatch)(text, format)
|
|
53239
|
-
})
|
|
53240
|
-
if (match.indexOf(true) > -1) {
|
|
53241
|
-
const date = zonedTimeToUtc(
|
|
53242
|
-
(0,date_fns.parse)(text, commonDateFormats[match.indexOf(true)], new Date()),
|
|
53243
|
-
loc
|
|
53244
|
-
).toJSON()
|
|
53245
|
-
return date.split('T')[0]
|
|
53246
|
-
} else {
|
|
53247
|
-
return null
|
|
53248
|
-
}
|
|
53249
|
-
}
|
|
53268
|
+
;// CONCATENATED MODULE: ./src/parsers/list.js
|
|
53250
53269
|
|
|
53251
|
-
function parse_parseTime(text) {
|
|
53252
|
-
const match = commonTimeFormats.map((format) => {
|
|
53253
|
-
return (0,date_fns.isMatch)(text, format)
|
|
53254
|
-
})
|
|
53255
|
-
if (match.indexOf(true) > -1) {
|
|
53256
|
-
const time = zonedTimeToUtc(
|
|
53257
|
-
(0,date_fns.parse)(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
53258
|
-
loc
|
|
53259
|
-
)
|
|
53260
|
-
return formatInTimeZone(time, loc, 'HH:mm')
|
|
53261
|
-
} else {
|
|
53262
|
-
return null
|
|
53263
|
-
}
|
|
53264
|
-
}
|
|
53265
53270
|
|
|
53266
53271
|
function parseList(list) {
|
|
53267
53272
|
return list.children
|
|
@@ -53293,46 +53298,79 @@ function parseList(list) {
|
|
|
53293
53298
|
.filter((x) => !!x)
|
|
53294
53299
|
}
|
|
53295
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
|
+
|
|
53296
53325
|
async function parseMD(body) {
|
|
53297
53326
|
const tokens = await unified().use(remark_parse).use(remarkGfm).parse(body)
|
|
53298
53327
|
if (!tokens) {
|
|
53299
53328
|
return []
|
|
53300
53329
|
}
|
|
53301
53330
|
|
|
53302
|
-
const r =
|
|
53331
|
+
const r = {}
|
|
53332
|
+
let counter = 0
|
|
53303
53333
|
for (let idx = 0; idx < tokens.children.length; idx = idx + 2) {
|
|
53304
53334
|
const current = tokens.children[idx]
|
|
53305
53335
|
const hasNext = idx + 1 < tokens.children.length
|
|
53306
53336
|
|
|
53307
53337
|
if (current.type === 'heading') {
|
|
53308
|
-
|
|
53338
|
+
const key = slugify(current.children[0].value)
|
|
53339
|
+
|
|
53340
|
+
// issue-form answers start with a h3 heading, ignore everything else for now
|
|
53309
53341
|
const obj = {
|
|
53310
|
-
|
|
53311
|
-
|
|
53342
|
+
title: current.children[0].value,
|
|
53343
|
+
order: counter++
|
|
53312
53344
|
}
|
|
53345
|
+
|
|
53313
53346
|
if (hasNext) {
|
|
53314
53347
|
const next = tokens.children[idx + 1]
|
|
53315
53348
|
if (next.type === 'list') {
|
|
53316
|
-
obj.list =
|
|
53349
|
+
obj.list = parsers_parseList(next).flat()
|
|
53317
53350
|
}
|
|
53318
53351
|
const text = await unified()
|
|
53319
53352
|
.use(remarkGfm)
|
|
53320
53353
|
.use(remark_stringify)
|
|
53321
53354
|
.stringify(next)
|
|
53322
53355
|
obj.text = stripFinalNewline(text)
|
|
53323
|
-
|
|
53324
|
-
const
|
|
53356
|
+
|
|
53357
|
+
const date = parsers_parseDate(obj.text)
|
|
53358
|
+
const time = parsers_parseTime(obj.text)
|
|
53359
|
+
|
|
53325
53360
|
if (date) {
|
|
53326
53361
|
obj.date = date
|
|
53327
53362
|
}
|
|
53363
|
+
|
|
53328
53364
|
if (time) {
|
|
53329
53365
|
obj.time = time
|
|
53330
53366
|
}
|
|
53331
|
-
|
|
53332
|
-
|
|
53367
|
+
|
|
53368
|
+
if (key === 'duration') {
|
|
53369
|
+
obj.duration = parsers_parseDuration(obj.text)
|
|
53333
53370
|
}
|
|
53371
|
+
|
|
53372
|
+
r[key] = obj
|
|
53334
53373
|
}
|
|
53335
|
-
r.push(obj)
|
|
53336
53374
|
}
|
|
53337
53375
|
}
|
|
53338
53376
|
|
|
@@ -53342,6 +53380,8 @@ async function parseMD(body) {
|
|
|
53342
53380
|
;// CONCATENATED MODULE: ./src/index.js
|
|
53343
53381
|
|
|
53344
53382
|
|
|
53383
|
+
;
|
|
53384
|
+
|
|
53345
53385
|
|
|
53346
53386
|
|
|
53347
53387
|
async function run() {
|
package/package.json
CHANGED
package/src/index.js
CHANGED
package/src/parse.js
CHANGED
|
@@ -5,96 +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 { parse, isMatch } from 'date-fns'
|
|
9
8
|
import stripFinalNewline from 'strip-final-newline'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'yyyy-MM-dd',
|
|
17
|
-
'dd/MM/yyyy',
|
|
18
|
-
'dd/MM/yy',
|
|
19
|
-
'dd-MM-yyyy',
|
|
20
|
-
'dd-MM-yy',
|
|
21
|
-
'dd.MM.yyyy',
|
|
22
|
-
'dd.MM.yy'
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
const commonTimeFormats = ['HH:mm', 'HH.mm', 'hh:mm a', 'hh:mm A']
|
|
26
|
-
|
|
27
|
-
function parseDuration(text) {
|
|
28
|
-
const duration = {
|
|
29
|
-
hours: 0,
|
|
30
|
-
minutes: 0
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const pieces = text.replace('m', '').split('h')
|
|
34
|
-
duration.hours = parseInt(pieces[0]) ? parseInt(pieces[0]) : 0
|
|
35
|
-
duration.minutes = parseInt(pieces[1]) ? parseInt(pieces[1]) : 0
|
|
36
|
-
return duration
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function parseDate(text) {
|
|
40
|
-
const match = commonDateFormats.map((format) => {
|
|
41
|
-
return isMatch(text, format)
|
|
42
|
-
})
|
|
43
|
-
if (match.indexOf(true) > -1) {
|
|
44
|
-
const date = zonedTimeToUtc(
|
|
45
|
-
parse(text, commonDateFormats[match.indexOf(true)], new Date()),
|
|
46
|
-
loc
|
|
47
|
-
).toJSON()
|
|
48
|
-
return date.split('T')[0]
|
|
49
|
-
} else {
|
|
50
|
-
return null
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function parseTime(text) {
|
|
55
|
-
const match = commonTimeFormats.map((format) => {
|
|
56
|
-
return isMatch(text, format)
|
|
57
|
-
})
|
|
58
|
-
if (match.indexOf(true) > -1) {
|
|
59
|
-
const time = zonedTimeToUtc(
|
|
60
|
-
parse(text, commonTimeFormats[match.indexOf(true)], new Date()),
|
|
61
|
-
loc
|
|
62
|
-
)
|
|
63
|
-
return formatInTimeZone(time, loc, 'HH:mm')
|
|
64
|
-
} else {
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function parseList(list) {
|
|
70
|
-
return list.children
|
|
71
|
-
.map((item) => {
|
|
72
|
-
const listItem = {}
|
|
73
|
-
if (item.type === 'list') {
|
|
74
|
-
return parseList(list)
|
|
75
|
-
} else if (item.type === 'listItem') {
|
|
76
|
-
listItem.checked = item.checked
|
|
77
|
-
return item.children
|
|
78
|
-
.map((child) => {
|
|
79
|
-
if (child.type === 'paragraph') {
|
|
80
|
-
listItem.text = child.children
|
|
81
|
-
.map((c) => {
|
|
82
|
-
if (c.type === 'link') {
|
|
83
|
-
return c.children[0].value
|
|
84
|
-
} else {
|
|
85
|
-
return c.value
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
.filter((x) => !!x)
|
|
89
|
-
.join('')
|
|
90
|
-
return listItem
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
.filter((x) => !!x)
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
.filter((x) => !!x)
|
|
97
|
-
}
|
|
9
|
+
import {
|
|
10
|
+
parseDate,
|
|
11
|
+
parseTime,
|
|
12
|
+
parseDuration,
|
|
13
|
+
parseList
|
|
14
|
+
} from './parsers/index.js'
|
|
98
15
|
|
|
99
16
|
export default async function parseMD(body) {
|
|
100
17
|
const tokens = await unified().use(remarkParse).use(remarkGfm).parse(body)
|
|
@@ -102,17 +19,21 @@ export default async function parseMD(body) {
|
|
|
102
19
|
return []
|
|
103
20
|
}
|
|
104
21
|
|
|
105
|
-
const r =
|
|
22
|
+
const r = {}
|
|
23
|
+
let counter = 0
|
|
106
24
|
for (let idx = 0; idx < tokens.children.length; idx = idx + 2) {
|
|
107
25
|
const current = tokens.children[idx]
|
|
108
26
|
const hasNext = idx + 1 < tokens.children.length
|
|
109
27
|
|
|
110
28
|
if (current.type === 'heading') {
|
|
111
|
-
|
|
29
|
+
const key = slugify(current.children[0].value)
|
|
30
|
+
|
|
31
|
+
// issue-form answers start with a h3 heading, ignore everything else for now
|
|
112
32
|
const obj = {
|
|
113
|
-
|
|
114
|
-
|
|
33
|
+
title: current.children[0].value,
|
|
34
|
+
order: counter++
|
|
115
35
|
}
|
|
36
|
+
|
|
116
37
|
if (hasNext) {
|
|
117
38
|
const next = tokens.children[idx + 1]
|
|
118
39
|
if (next.type === 'list') {
|
|
@@ -123,19 +44,24 @@ export default async function parseMD(body) {
|
|
|
123
44
|
.use(remarkStringify)
|
|
124
45
|
.stringify(next)
|
|
125
46
|
obj.text = stripFinalNewline(text)
|
|
47
|
+
|
|
126
48
|
const date = parseDate(obj.text)
|
|
127
49
|
const time = parseTime(obj.text)
|
|
50
|
+
|
|
128
51
|
if (date) {
|
|
129
52
|
obj.date = date
|
|
130
53
|
}
|
|
54
|
+
|
|
131
55
|
if (time) {
|
|
132
56
|
obj.time = time
|
|
133
57
|
}
|
|
134
|
-
|
|
58
|
+
|
|
59
|
+
if (key === 'duration') {
|
|
135
60
|
obj.duration = parseDuration(obj.text)
|
|
136
61
|
}
|
|
62
|
+
|
|
63
|
+
r[key] = obj
|
|
137
64
|
}
|
|
138
|
-
r.push(obj)
|
|
139
65
|
}
|
|
140
66
|
}
|
|
141
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,32 +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
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
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
25
|
text: '11.03.2022',
|
|
26
26
|
date: '2022-03-11'
|
|
27
27
|
},
|
|
28
|
-
{
|
|
29
|
-
{
|
|
30
|
-
|
|
28
|
+
time: { order: 3, title: 'Time', text: '16:00', time: '16:00' },
|
|
29
|
+
duration: {
|
|
30
|
+
order: 4,
|
|
31
31
|
title: 'Duration',
|
|
32
32
|
text: '2h',
|
|
33
33
|
duration: { hours: 2, minutes: 0 }
|
|
34
34
|
},
|
|
35
|
-
{
|
|
36
|
-
|
|
35
|
+
'list-item-checked': {
|
|
36
|
+
order: 5,
|
|
37
37
|
title: 'List Item Checked',
|
|
38
38
|
list: [
|
|
39
39
|
{
|
|
@@ -43,8 +43,8 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
43
43
|
],
|
|
44
44
|
text: "* [x] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
|
|
45
45
|
},
|
|
46
|
-
{
|
|
47
|
-
|
|
46
|
+
'list-item-unchecked': {
|
|
47
|
+
order: 6,
|
|
48
48
|
title: 'List Item Unchecked',
|
|
49
49
|
list: [
|
|
50
50
|
{
|
|
@@ -54,8 +54,8 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
54
54
|
],
|
|
55
55
|
text: "* [ ] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
|
|
56
56
|
},
|
|
57
|
-
{
|
|
58
|
-
|
|
57
|
+
'mixed-task-list': {
|
|
58
|
+
order: 7,
|
|
59
59
|
title: 'Mixed Task List',
|
|
60
60
|
list: [
|
|
61
61
|
{ checked: true, text: 'checked' },
|
|
@@ -66,8 +66,8 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
66
66
|
],
|
|
67
67
|
text: '* [x] checked\n* [ ] unchecked\n* [x] checked 2\n* [x] checked 3\n* [ ] unchecked 2'
|
|
68
68
|
},
|
|
69
|
-
{
|
|
70
|
-
|
|
69
|
+
'complex-list': {
|
|
70
|
+
order: 8,
|
|
71
71
|
title: 'Complex List',
|
|
72
72
|
list: [
|
|
73
73
|
{ checked: null, text: 'one' },
|
|
@@ -75,17 +75,17 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
75
75
|
],
|
|
76
76
|
text: '* one\n* two\n * three\n * four\n 1. five\n 2. six'
|
|
77
77
|
},
|
|
78
|
-
{
|
|
79
|
-
|
|
78
|
+
repositories: {
|
|
79
|
+
order: 9,
|
|
80
80
|
title: 'Repositories',
|
|
81
81
|
text: '```csv\nhttps://example.com/repository-1\nhttps://example.com/repository-2\n```'
|
|
82
82
|
},
|
|
83
|
-
{
|
|
84
|
-
|
|
83
|
+
visibility: {
|
|
84
|
+
order: 10,
|
|
85
85
|
title: 'Visibility',
|
|
86
86
|
text: 'Internal'
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
}
|
|
89
89
|
|
|
90
90
|
const md = await readFile(
|
|
91
91
|
join(process.cwd(), 'test', 'test-issue-1.md'),
|
|
@@ -97,7 +97,7 @@ test('parse(md) should parse GitHub Issue Form data into useful, structured data
|
|
|
97
97
|
})
|
|
98
98
|
|
|
99
99
|
test('parse(md) return nothing', async (t) => {
|
|
100
|
-
const expected =
|
|
100
|
+
const expected = {}
|
|
101
101
|
|
|
102
102
|
const md = await readFile(
|
|
103
103
|
join(process.cwd(), 'test', 'test-issue-2.md'),
|