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.
Files changed (50) hide show
  1. package/README.md +47 -11
  2. package/lib/component.d.ts +46 -9
  3. package/lib/component.d.ts.map +1 -1
  4. package/lib/component.js +105 -17
  5. package/lib/components/Calendar.d.ts +35 -14
  6. package/lib/components/Calendar.d.ts.map +1 -1
  7. package/lib/components/Calendar.js +43 -15
  8. package/lib/components/CalendarEvent.d.ts +84 -22
  9. package/lib/components/CalendarEvent.d.ts.map +1 -1
  10. package/lib/components/CalendarEvent.js +142 -67
  11. package/lib/components/TimeZone.d.ts +62 -39
  12. package/lib/components/TimeZone.d.ts.map +1 -1
  13. package/lib/components/TimeZone.js +81 -86
  14. package/lib/components/TimeZoneOffset.d.ts +103 -0
  15. package/lib/components/TimeZoneOffset.d.ts.map +1 -0
  16. package/lib/components/TimeZoneOffset.js +148 -0
  17. package/lib/components/index.d.ts +1 -0
  18. package/lib/components/index.d.ts.map +1 -1
  19. package/lib/components/index.js +2 -1
  20. package/lib/date.d.ts +165 -0
  21. package/lib/date.d.ts.map +1 -0
  22. package/lib/date.js +373 -0
  23. package/lib/index.d.ts +3 -1
  24. package/lib/index.d.ts.map +1 -1
  25. package/lib/index.js +4 -2
  26. package/lib/io.d.ts +9 -7
  27. package/lib/io.d.ts.map +1 -1
  28. package/lib/io.js +16 -11
  29. package/lib/parse.d.ts +32 -15
  30. package/lib/parse.d.ts.map +1 -1
  31. package/lib/parse.js +55 -53
  32. package/lib/patterns.d.ts +36 -0
  33. package/lib/patterns.d.ts.map +1 -0
  34. package/lib/patterns.js +50 -0
  35. package/lib/property.d.ts +149 -0
  36. package/lib/property.d.ts.map +1 -0
  37. package/lib/property.js +450 -0
  38. package/package.json +50 -38
  39. package/src/component.ts +132 -23
  40. package/src/components/Calendar.ts +58 -20
  41. package/src/components/CalendarEvent.ts +170 -66
  42. package/src/components/TimeZone.ts +86 -96
  43. package/src/components/TimeZoneOffset.ts +187 -0
  44. package/src/components/index.ts +2 -1
  45. package/src/date.ts +395 -0
  46. package/src/index.ts +3 -1
  47. package/src/io.ts +16 -11
  48. package/src/parse.ts +71 -51
  49. package/src/patterns.ts +69 -0
  50. 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 the serialized component as a readline interface
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 deserialize(lines: readline.Interface): Promise<Component> {
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 function processLine(line: string) {
28
+ const processLine = async (line: string) => {
24
29
  if (line.trim() === '') return
25
30
 
26
31
  if (done) {
27
- throw new DeserializationError('Trailing data after component end')
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('Component end before begin')
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 deserializeString(subcomponentLines.join('\r\n'))
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) throw new DeserializationError('Property outside of components')
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(`Invalid content line: ${line}`)
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('No component end')
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 text the serialized component
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 deserializeString(text: string): Promise<Component> {
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({ input: stream, crlfDelay: Infinity })
141
- return deserialize(lines)
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 in ICalendar format
146
- * @param text the serialized calendar
147
- * @returns the parsed calendar
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 deserializeString(text)
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 an event in ICalendar format
158
- * @param text the serialized event
159
- * @returns the parsed event
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 deserializeString(text)
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
- }
@@ -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
+ }