@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.
@@ -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
  ![Test](https://github.com/zentered/issue-forms-body-parser/workflows/Test/badge.svg)
4
4
  ![Release](https://github.com/zentered/issue-forms-body-parser/workflows/Publish/badge.svg)
5
- ![Semantic Release](https://github.com/zentered/issue-forms-body-parser/workflows/Semantic%20Release/badge.svg)
6
5
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
7
6
  [![zentered.co](https://img.shields.io/badge/%3E-zentered.co-blue.svg?style=flat)](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
- \_Inspired by:
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
- "id": "event-description",
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).\n"
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
- "id": "location",
70
+ "location": {
71
+ "order": 1,
73
72
  "title": "Location",
74
- "text": "Cafe Nero Finikoudes, Larnaka\n"
73
+ "text": "Cafe Nero Finikoudes, Larnaka"
75
74
  },
76
- {
77
- "id": "date",
75
+ "date": {
76
+ "order": 2,
78
77
  "title": "Date",
79
- "text": "11.03.2022\n",
80
- "date": "2022-03-11T00:00:00.000Z"
78
+ "text": "11.03.2022",
79
+ "date": "2022-03-11"
81
80
  },
82
- { "id": "time", "title": "Time", "text": "16:00\n", "time": "16:00" }
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@1.0.0
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/parse.js
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 parse_parseDate(text) {
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
- loc
53245
+ time_loc
53229
53246
  )
53230
- return formatInTimeZone(time, loc, 'HH:mm')
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
- // issue-form answers start with a h3 heading, ignore everything else
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
- id: slugify(current.children[0].value),
53281
- title: current.children[0].value
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 = parseList(next).flat()
53349
+ obj.list = parsers_parseList(next).flat()
53287
53350
  }
53288
- obj.text = await unified()
53351
+ const text = await unified()
53289
53352
  .use(remarkGfm)
53290
53353
  .use(remark_stringify)
53291
53354
  .stringify(next)
53292
- const date = parse_parseDate(obj.text)
53293
- const time = parse_parseTime(obj.text)
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.1.4",
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
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  import github from '@actions/github'
2
4
  import core from '@actions/core'
3
5
  import parse from './parse.js'
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 { parse, isMatch } from 'date-fns'
9
- // if the system time is not UTC, we need to convert it to UTC
10
- import { zonedTimeToUtc, formatInTimeZone } from 'date-fns-tz/esm'
11
- const loc = 'UTC'
12
-
13
- const commonDateFormats = [
14
- 'yyyy-MM-dd',
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
- // issue-form answers start with a h3 heading, ignore everything else
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
- id: slugify(current.children[0].value),
100
- title: current.children[0].value
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
- obj.text = await unified()
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
+ }
@@ -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
- id: 'event-description',
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).\n"
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
- id: 'location',
17
+ location: {
18
+ order: 1,
19
19
  title: 'Location',
20
- text: '[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)\n'
20
+ text: '[Cafe Nero Finikoudes, Larnaka](https://goo.gl/maps/Bzjxdeat3BSdsUSVA)'
21
21
  },
22
- {
23
- id: 'date',
22
+ date: {
23
+ order: 2,
24
24
  title: 'Date',
25
- text: '11.03.2022\n',
25
+ text: '11.03.2022',
26
26
  date: '2022-03-11'
27
27
  },
28
- { id: 'time', title: 'Time', text: '16:00\n', time: '16:00' },
29
- { id: 'duration', title: 'Duration', text: '2h\n' },
30
- {
31
- id: 'list-item-checked',
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)\n"
44
+ text: "* [x] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
40
45
  },
41
- {
42
- id: 'list-item-unchecked',
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)\n"
55
+ text: "* [ ] I agree to follow this project's\n [Code of Conduct](https://berlincodeofconduct.org)"
51
56
  },
52
- {
53
- id: 'mixed-task-list',
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\n'
67
+ text: '* [x] checked\n* [ ] unchecked\n* [x] checked 2\n* [x] checked 3\n* [ ] unchecked 2'
63
68
  },
64
- {
65
- id: 'complex-list',
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\n'
76
+ text: '* one\n* two\n * three\n * four\n 1. five\n 2. six'
72
77
  },
73
- {
74
- id: 'repositories',
78
+ repositories: {
79
+ order: 9,
75
80
  title: 'Repositories',
76
- text: '```csv\nhttps://example.com/repository-1\nhttps://example.com/repository-2\n```\n'
81
+ text: '```csv\nhttps://example.com/repository-1\nhttps://example.com/repository-2\n```'
77
82
  },
78
- {
79
- id: 'visibility',
83
+ visibility: {
84
+ order: 10,
80
85
  title: 'Visibility',
81
- text: 'Internal\n'
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'),