iamcal 2.0.0 → 2.1.1
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/README.md +47 -11
- package/lib/component.d.ts +46 -9
- package/lib/component.d.ts.map +1 -1
- package/lib/component.js +105 -17
- package/lib/components/Calendar.d.ts +35 -14
- package/lib/components/Calendar.d.ts.map +1 -1
- package/lib/components/Calendar.js +43 -15
- package/lib/components/CalendarEvent.d.ts +84 -22
- package/lib/components/CalendarEvent.d.ts.map +1 -1
- package/lib/components/CalendarEvent.js +142 -67
- package/lib/components/TimeZone.d.ts +62 -39
- package/lib/components/TimeZone.d.ts.map +1 -1
- package/lib/components/TimeZone.js +81 -86
- package/lib/components/TimeZoneOffset.d.ts +103 -0
- package/lib/components/TimeZoneOffset.d.ts.map +1 -0
- package/lib/components/TimeZoneOffset.js +148 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.d.ts.map +1 -1
- package/lib/components/index.js +2 -1
- package/lib/date.d.ts +165 -0
- package/lib/date.d.ts.map +1 -0
- package/lib/date.js +373 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -2
- package/lib/io.d.ts +9 -7
- package/lib/io.d.ts.map +1 -1
- package/lib/io.js +16 -11
- package/lib/parse.d.ts +32 -15
- package/lib/parse.d.ts.map +1 -1
- package/lib/parse.js +55 -53
- package/lib/patterns.d.ts +36 -0
- package/lib/patterns.d.ts.map +1 -0
- package/lib/patterns.js +50 -0
- package/lib/property.d.ts +149 -0
- package/lib/property.d.ts.map +1 -0
- package/lib/property.js +450 -0
- package/package.json +50 -38
- package/src/component.ts +132 -23
- package/src/components/Calendar.ts +58 -20
- package/src/components/CalendarEvent.ts +170 -66
- package/src/components/TimeZone.ts +86 -96
- package/src/components/TimeZoneOffset.ts +187 -0
- package/src/components/index.ts +2 -1
- package/src/date.ts +395 -0
- package/src/index.ts +3 -1
- package/src/io.ts +16 -11
- package/src/parse.ts +71 -51
- package/src/patterns.ts +69 -0
- package/src/property.ts +492 -0
package/src/parse.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { Component, Property } from './component'
|
|
2
1
|
import readline from 'readline'
|
|
3
2
|
import { Readable } from 'stream'
|
|
3
|
+
import { Component } from './component'
|
|
4
4
|
import { Calendar, CalendarEvent } from './components'
|
|
5
5
|
|
|
6
|
+
/** Represents an error that occurs when deserializing a calendar component. */
|
|
6
7
|
export class DeserializationError extends Error {
|
|
7
8
|
name = 'DeserializationError'
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* Deserialize a calendar component
|
|
12
|
-
* @param lines
|
|
12
|
+
* Deserialize a calendar component.
|
|
13
|
+
* @param lines The serialized component as a **readline** interface.
|
|
14
|
+
* @returns The deserialized calendar component object.
|
|
15
|
+
* @throws {DeserializationError} If the component is invalid.
|
|
13
16
|
*/
|
|
14
|
-
export async function
|
|
17
|
+
export async function deserializeComponent(
|
|
18
|
+
lines: readline.Interface
|
|
19
|
+
): Promise<Component> {
|
|
15
20
|
const component = new Component('')
|
|
16
21
|
let done = false
|
|
17
22
|
|
|
@@ -20,11 +25,13 @@ export async function deserialize(lines: readline.Interface): Promise<Component>
|
|
|
20
25
|
|
|
21
26
|
const subcomponentLines = new Array<string>()
|
|
22
27
|
|
|
23
|
-
async
|
|
28
|
+
const processLine = async (line: string) => {
|
|
24
29
|
if (line.trim() === '') return
|
|
25
30
|
|
|
26
31
|
if (done) {
|
|
27
|
-
throw new DeserializationError(
|
|
32
|
+
throw new DeserializationError(
|
|
33
|
+
'Found trailing data after component end'
|
|
34
|
+
)
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
if (line.startsWith('BEGIN:')) {
|
|
@@ -40,7 +47,9 @@ export async function deserialize(lines: readline.Interface): Promise<Component>
|
|
|
40
47
|
} else if (line.startsWith('END:')) {
|
|
41
48
|
// End component
|
|
42
49
|
if (stack.length == 0) {
|
|
43
|
-
throw new DeserializationError(
|
|
50
|
+
throw new DeserializationError(
|
|
51
|
+
'Unexpected component end outside of components'
|
|
52
|
+
)
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
const stackName = stack.pop()
|
|
@@ -52,7 +61,9 @@ export async function deserialize(lines: readline.Interface): Promise<Component>
|
|
|
52
61
|
// Check the length of the stack after being popped
|
|
53
62
|
if (stack.length == 1) {
|
|
54
63
|
subcomponentLines.push(line)
|
|
55
|
-
const subcomponent = await
|
|
64
|
+
const subcomponent = await deserializeComponentString(
|
|
65
|
+
subcomponentLines.join('\r\n')
|
|
66
|
+
)
|
|
56
67
|
subcomponentLines.length = 0
|
|
57
68
|
|
|
58
69
|
component.components.push(subcomponent)
|
|
@@ -62,7 +73,10 @@ export async function deserialize(lines: readline.Interface): Promise<Component>
|
|
|
62
73
|
done = true
|
|
63
74
|
}
|
|
64
75
|
} else {
|
|
65
|
-
if (stack.length == 0)
|
|
76
|
+
if (stack.length == 0)
|
|
77
|
+
throw new DeserializationError(
|
|
78
|
+
'Found stray property outside of components'
|
|
79
|
+
)
|
|
66
80
|
|
|
67
81
|
if (stack.length > 1) {
|
|
68
82
|
// Line of subcomponent
|
|
@@ -71,7 +85,9 @@ export async function deserialize(lines: readline.Interface): Promise<Component>
|
|
|
71
85
|
// Property
|
|
72
86
|
const colon = line.indexOf(':')
|
|
73
87
|
if (colon === -1) {
|
|
74
|
-
throw new DeserializationError(
|
|
88
|
+
throw new DeserializationError(
|
|
89
|
+
`Invalid content line: ${line}`
|
|
90
|
+
)
|
|
75
91
|
}
|
|
76
92
|
const name = line.slice(0, colon)
|
|
77
93
|
const value = line.slice(colon + 1)
|
|
@@ -125,65 +141,69 @@ export async function deserialize(lines: readline.Interface): Promise<Component>
|
|
|
125
141
|
|
|
126
142
|
// Check that component has been closed
|
|
127
143
|
if (!done) {
|
|
128
|
-
throw new DeserializationError('
|
|
144
|
+
throw new DeserializationError('Component has no end')
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
return component
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
/**
|
|
135
|
-
* Deserialize a calendar component
|
|
136
|
-
* @param
|
|
151
|
+
* Deserialize a calendar component.
|
|
152
|
+
* @param lines The serialized component as a **readline** interface.
|
|
153
|
+
* @returns The deserialized calendar component object.
|
|
154
|
+
* @throws {DeserializationError} If the component is invalid.
|
|
155
|
+
* @deprecated Use {@link deserializeComponent} instead.
|
|
137
156
|
*/
|
|
138
|
-
export async function
|
|
157
|
+
export async function deserialize(
|
|
158
|
+
lines: readline.Interface
|
|
159
|
+
): Promise<Component> {
|
|
160
|
+
return deserializeComponent(lines)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Deserialize a calendar component string.
|
|
165
|
+
* @param text The serialized component.
|
|
166
|
+
* @returns The deserialized component object.
|
|
167
|
+
* @throws {DeserializationError} If the component is invalid.
|
|
168
|
+
*/
|
|
169
|
+
export async function deserializeComponentString(
|
|
170
|
+
text: string
|
|
171
|
+
): Promise<Component> {
|
|
139
172
|
const stream = Readable.from(text)
|
|
140
|
-
const lines = readline.createInterface({
|
|
141
|
-
|
|
173
|
+
const lines = readline.createInterface({
|
|
174
|
+
input: stream,
|
|
175
|
+
crlfDelay: Infinity,
|
|
176
|
+
})
|
|
177
|
+
return deserializeComponent(lines)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Deserialize a calendar component string.
|
|
182
|
+
* @param text The serialized component.
|
|
183
|
+
* @returns The deserialized component object.
|
|
184
|
+
* @throws {DeserializationError} If the component is invalid.
|
|
185
|
+
* @deprecated Use {@link deserializeComponentString} instead.
|
|
186
|
+
*/
|
|
187
|
+
export async function deserializeString(text: string): Promise<Component> {
|
|
188
|
+
return deserializeComponentString(text)
|
|
142
189
|
}
|
|
143
190
|
|
|
144
191
|
/**
|
|
145
|
-
* Parse a calendar
|
|
146
|
-
* @param text
|
|
147
|
-
* @returns
|
|
192
|
+
* Parse a serialized calendar.
|
|
193
|
+
* @param text A serialized calendar as you would see in an iCalendar file.
|
|
194
|
+
* @returns The parsed calendar object.
|
|
148
195
|
*/
|
|
149
196
|
export async function parseCalendar(text: string): Promise<Calendar> {
|
|
150
|
-
const component = await
|
|
151
|
-
if (component.name !== "VCALENDAR")
|
|
152
|
-
throw new DeserializationError("Not a calendar")
|
|
197
|
+
const component = await deserializeComponentString(text)
|
|
153
198
|
return new Calendar(component)
|
|
154
199
|
}
|
|
155
200
|
|
|
156
201
|
/**
|
|
157
|
-
* Parse
|
|
158
|
-
* @param text
|
|
159
|
-
* @returns
|
|
202
|
+
* Parse a serialized calendar event.
|
|
203
|
+
* @param text A serialized event as you would see in an iCalendar file.
|
|
204
|
+
* @returns The parsed event object.
|
|
160
205
|
*/
|
|
161
206
|
export async function parseEvent(text: string): Promise<CalendarEvent> {
|
|
162
|
-
const component = await
|
|
163
|
-
if (component.name !== "VEVENT")
|
|
164
|
-
throw new DeserializationError("Not an event")
|
|
207
|
+
const component = await deserializeComponentString(text)
|
|
165
208
|
return new CalendarEvent(component)
|
|
166
209
|
}
|
|
167
|
-
|
|
168
|
-
export function parseDate(dateProperty: Property): Date {
|
|
169
|
-
const value = dateProperty.value.trim()
|
|
170
|
-
if (dateProperty.params.includes('VALUE=DATE')) {
|
|
171
|
-
// Parse date only
|
|
172
|
-
return new Date(`${value.substring(0, 4)}-${value.substring(4, 6)}-${value.substring(6, 8)}`)
|
|
173
|
-
} else {
|
|
174
|
-
// Parse date and time
|
|
175
|
-
return new Date(`${value.substring(0, 4)}-${value.substring(4, 6)}-${value.substring(6, 8)} ${value.substring(9, 11)}:${value.substring(11, 13)}:${value.substring(13, 15)}`)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function toTimeString(date: Date): string {
|
|
180
|
-
return `${date.getHours().toString().padStart(2, '0')}${date.getMinutes().toString().padStart(2, '0')}${date.getSeconds().toString().padStart(2, '0')}`
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function toDateString(date: Date): string {
|
|
184
|
-
return `${date.getFullYear().toString().padStart(4, '0')}${date.getMonth().toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function toDateTimeString(date: Date): string {
|
|
188
|
-
return `${toDateString(date)}T${toTimeString(date)}`
|
|
189
|
-
}
|
package/src/patterns.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* eslint-disable no-control-regex */
|
|
2
|
+
|
|
3
|
+
// ABNF patterns from RFC5545
|
|
4
|
+
export const CONTROL = /[\x00-\x08\x0A-\x1F\x7F]/g
|
|
5
|
+
export const NON_US_ASCII = /[^\x00-\x7F]/g
|
|
6
|
+
export const VALUE_CHAR = new RegExp(
|
|
7
|
+
/[\x09\x20\x21-\x7E]/.source + '|' + NON_US_ASCII.source
|
|
8
|
+
)
|
|
9
|
+
export const SAFE_CHAR = new RegExp(
|
|
10
|
+
/[\x09\x20\x21\x23-\x2B\x2D-\x39\x3C-\x7E]/.source +
|
|
11
|
+
'|' +
|
|
12
|
+
NON_US_ASCII.source
|
|
13
|
+
)
|
|
14
|
+
export const QSAFE_CHAR = new RegExp(
|
|
15
|
+
/[\x09\x20\x21\x23-\x7E]/.source + '|(' + NON_US_ASCII.source + ')'
|
|
16
|
+
)
|
|
17
|
+
export const quotedString = new RegExp('"(' + QSAFE_CHAR.source + ')*"')
|
|
18
|
+
export const value = new RegExp('(' + VALUE_CHAR.source + ')*')
|
|
19
|
+
export const paramtext = new RegExp('(' + SAFE_CHAR.source + ')*')
|
|
20
|
+
export const paramValue = new RegExp(
|
|
21
|
+
'(' + paramtext.source + ')|(' + quotedString.source + ')'
|
|
22
|
+
)
|
|
23
|
+
export const vendorId = /[0-9a-zA-Z]{3}/
|
|
24
|
+
export const xName = new RegExp('X-(' + vendorId.source + '-)?[0-9a-zA-Z-]')
|
|
25
|
+
export const ianaToken = /[0-9a-zA-Z-]/
|
|
26
|
+
export const paramName = new RegExp(ianaToken.source + '|' + xName.source)
|
|
27
|
+
export const param = new RegExp(
|
|
28
|
+
paramName.source + '=' + paramValue.source + '(,' + paramValue.source + ')*'
|
|
29
|
+
)
|
|
30
|
+
export const name = new RegExp(ianaToken.source + '|' + xName.source)
|
|
31
|
+
export const contentline = new RegExp(
|
|
32
|
+
name.source + '(;' + param.source + ')*:' + xName.source + '\r\n'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export const TSAFE_CHAR = new RegExp(
|
|
36
|
+
/[\x09\x20\x21\x23-\x2B\x2D-\x39\x3C-\x5B\x5D-\x7E]/.source +
|
|
37
|
+
'|' +
|
|
38
|
+
NON_US_ASCII.source
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// Property value type patterns
|
|
42
|
+
export const valueTypeBinary =
|
|
43
|
+
/([0-9a-zA-Z+/]{4})*([0-9a-zA-Z]{2}==|[0-9a-zA-Z]{3}=)?/
|
|
44
|
+
export const valueTypeBoolean = /(TRUE|FALSE)/
|
|
45
|
+
export const valueTypeDate = /[0-9]{8}/
|
|
46
|
+
export const valueTypeDateTime = /[0-9]{8}T[0-9]{6}Z?/
|
|
47
|
+
export const valueTypeDuration =
|
|
48
|
+
/(\+?|-)P([0-9]+D(T([0-9]+H([0-9]+M([0-9]+S)?)?|[0-9]+M([0-9]+S)?|[0-9]+S))?|T([0-9]+H([0-9]+M([0-9]+S)?)?|[0-9]+M([0-9]+S)?|[0-9]+S)|[0-9]+W)/
|
|
49
|
+
export const valueTypeFloat = /(\+?|-)?[0-9]+(\.[0-9]+)?/
|
|
50
|
+
export const valueTypeInteger = /(\+?|-)?[0-9]+/
|
|
51
|
+
export const valueTypePeriod = new RegExp(
|
|
52
|
+
`${valueTypeDateTime.source}/(${valueTypeDateTime}|${valueTypeDuration.source})`
|
|
53
|
+
)
|
|
54
|
+
export const valueTypeText = new RegExp(
|
|
55
|
+
'((' + TSAFE_CHAR.source + String.raw`)|:|"|\\[\\;,Nn])*`
|
|
56
|
+
)
|
|
57
|
+
export const valueTypeTime = /[0-9]{6}Z?/
|
|
58
|
+
export const valueTypeUtcOffset = /[+-]([0-9]{2}){2,3}/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if a string matches a pattern for the whole string.
|
|
62
|
+
* @param pattern The RegExp pattern to match against.
|
|
63
|
+
* @param text The text to check.
|
|
64
|
+
* @returns Whether or not the string matches.
|
|
65
|
+
*/
|
|
66
|
+
export function matchesWholeString(pattern: RegExp, text: string): boolean {
|
|
67
|
+
const match = text.match(pattern)
|
|
68
|
+
return match !== null && match[0] === text
|
|
69
|
+
}
|