iamcal 3.0.3 → 4.0.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/src/parse.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import readline from 'readline'
2
- import { Readable } from 'stream'
3
2
  import { Component } from './component'
4
3
  import { Calendar, CalendarEvent } from './components'
5
4
  import {
@@ -23,10 +22,22 @@ export class DeserializationError extends Error {
23
22
  * @param lines The serialized component as a **readline** interface.
24
23
  * @returns The deserialized calendar component object.
25
24
  * @throws {DeserializationError} If the component is invalid.
25
+ * @deprecated Use the synchronous `deserializeComponentLines` instead.
26
26
  */
27
27
  export async function deserializeComponent(
28
28
  lines: readline.Interface
29
29
  ): Promise<Component> {
30
+ const realLines = await readLines(lines)
31
+ return deserializeComponentLines(realLines)
32
+ }
33
+
34
+ /**
35
+ * Deserialize a calendar component from a list of lines.
36
+ * @param lines The serialized component as a list of lines.
37
+ * @returns The deserialized calendar component object.
38
+ * @throws {DeserializationError} If the component is invalid.
39
+ */
40
+ export function deserializeComponentLines(lines: string[]): Component {
30
41
  const component = new Component('')
31
42
  let done = false
32
43
 
@@ -35,7 +46,7 @@ export async function deserializeComponent(
35
46
 
36
47
  const subcomponentLines = new Array<string>()
37
48
 
38
- const processLine = async (line: string) => {
49
+ const processLine = (line: string) => {
39
50
  if (line.trim() === '') return
40
51
 
41
52
  if (done) {
@@ -71,7 +82,7 @@ export async function deserializeComponent(
71
82
  // Check the length of the stack after being popped
72
83
  if (stack.length == 1) {
73
84
  subcomponentLines.push(line)
74
- const subcomponent = await deserializeComponentString(
85
+ const subcomponent = deserializeComponentString(
75
86
  subcomponentLines.join('\r\n')
76
87
  )
77
88
  subcomponentLines.length = 0
@@ -117,14 +128,14 @@ export async function deserializeComponent(
117
128
  that exists on a long line.
118
129
  */
119
130
  let unfoldedLine = ''
120
- for await (const line of lines) {
131
+ for (const line of lines) {
121
132
  if (line.startsWith(' ') || line.startsWith('\t')) {
122
133
  // Unfold continuation line (remove leading whitespace and append)
123
134
  unfoldedLine += line.replace(/[\r\n]/, '').slice(1)
124
135
  } else {
125
136
  if (unfoldedLine) {
126
137
  // Process the previous unfolded line
127
- await processLine(unfoldedLine)
138
+ processLine(unfoldedLine)
128
139
  }
129
140
  // Start a new unfolded line
130
141
  unfoldedLine = line
@@ -132,7 +143,7 @@ export async function deserializeComponent(
132
143
  }
133
144
 
134
145
  // Process the last unfolded line
135
- await processLine(unfoldedLine)
146
+ processLine(unfoldedLine)
136
147
 
137
148
  // Check that component has been closed
138
149
  if (!done) {
@@ -142,21 +153,34 @@ export async function deserializeComponent(
142
153
  return component
143
154
  }
144
155
 
156
+ /**
157
+ * Convert a **readline** interface to a list of lines.
158
+ * @param rl The interface.
159
+ * @returns The list of lines read from the interface.
160
+ * @throws {DeserializationError} If the component is invalid.
161
+ */
162
+ async function readLines(rl: readline.Interface): Promise<string[]> {
163
+ return new Promise(resolve => {
164
+ const lines: string[] = []
165
+
166
+ rl.on('line', line => {
167
+ lines.push(line)
168
+ })
169
+ rl.on('close', () => {
170
+ resolve(lines)
171
+ })
172
+ })
173
+ }
174
+
145
175
  /**
146
176
  * Deserialize a calendar component string.
147
177
  * @param text The serialized component.
148
178
  * @returns The deserialized component object.
149
179
  * @throws {DeserializationError} If the component is invalid.
150
180
  */
151
- export async function deserializeComponentString(
152
- text: string
153
- ): Promise<Component> {
154
- const stream = Readable.from(text)
155
- const lines = readline.createInterface({
156
- input: stream,
157
- crlfDelay: Infinity,
158
- })
159
- return deserializeComponent(lines)
181
+ export function deserializeComponentString(text: string): Component {
182
+ const lines = text.split(/\r?\n/g)
183
+ return deserializeComponentLines(lines)
160
184
  }
161
185
 
162
186
  /**
@@ -164,8 +188,8 @@ export async function deserializeComponentString(
164
188
  * @param text A serialized calendar as you would see in an iCalendar file.
165
189
  * @returns The parsed calendar object.
166
190
  */
167
- export async function parseCalendar(text: string): Promise<Calendar> {
168
- const component = await deserializeComponentString(text)
191
+ export function parseCalendar(text: string): Calendar {
192
+ const component = deserializeComponentString(text)
169
193
  return new Calendar(component)
170
194
  }
171
195
 
@@ -174,8 +198,8 @@ export async function parseCalendar(text: string): Promise<Calendar> {
174
198
  * @param text A serialized event as you would see in an iCalendar file.
175
199
  * @returns The parsed event object.
176
200
  */
177
- export async function parseEvent(text: string): Promise<CalendarEvent> {
178
- const component = await deserializeComponentString(text)
201
+ export function parseEvent(text: string): CalendarEvent {
202
+ const component = deserializeComponentString(text)
179
203
  return new CalendarEvent(component)
180
204
  }
181
205
 
@@ -1,4 +1,5 @@
1
- import { CalendarDateOrTime } from 'src/date'
1
+ import { CalendarDateOrTime, CalendarDate, CalendarDateTime, isCalendarDateOrTime } from '../date'
2
+ import { CalendarDuration } from '../duration'
2
3
  import {
3
4
  escapePropertyParameterValue,
4
5
  escapeTextPropertyValue,
@@ -16,7 +17,7 @@ import {
16
17
  RsvpExpectation,
17
18
  } from './parameter'
18
19
  import { validateCalendarUserAddress, validateContentType } from './validate'
19
- import { AllowedValueType, getDefaultValueType } from './valueType'
20
+ import { AllowedValueType, KnownValueType, getDefaultValueType } from './valueType'
20
21
 
21
22
  /**
22
23
  * Represents a property of a calendar component as described by RFC 5545 in
@@ -55,13 +56,72 @@ export class Property {
55
56
  }
56
57
 
57
58
  static fromDate(name: string, value: CalendarDateOrTime): Property {
59
+ const valueType = value.isFullDay() ? 'DATE' : 'DATE-TIME'
60
+ let properties: { [k: string]: string | string[] } | undefined = undefined
61
+ if (getDefaultValueType(name) !== valueType) {
62
+ properties = { VALUE: valueType }
63
+ }
64
+
65
+ return new Property(
66
+ name,
67
+ value.getValue(),
68
+ properties,
69
+ )
70
+ }
71
+
72
+ static fromDuration(name: string, value: CalendarDuration): Property {
58
73
  return new Property(
59
74
  name,
60
75
  value.getValue(),
61
- value.isFullDay() ? { VALUE: 'DATE' } : undefined
76
+ { VALUE: 'CALENDARDATE' },
62
77
  )
63
78
  }
64
79
 
80
+ /**
81
+ * Get the value of this property, converted into the appropriate class if possible.
82
+ * @returns The value as a string or appropriate class.
83
+ */
84
+ getValue(): string | CalendarDateOrTime | CalendarDuration {
85
+ const valueType = this.getValueType()
86
+ switch (valueType) {
87
+ case 'DATE':
88
+ return new CalendarDate(this.value)
89
+ case 'DATE-TIME':
90
+ return new CalendarDateTime(this.value)
91
+ case 'DURATION':
92
+ return new CalendarDuration(this.value)
93
+ default:
94
+ return this.value
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Set the value of this property and change the value type if a class is used.
100
+ * @param value The new value as a string or class.
101
+ */
102
+ setValue(value: string | CalendarDateOrTime | CalendarDuration) {
103
+ const valueTypeBefore = this.getExplicitValueType()
104
+ if (typeof value === 'string') {
105
+ // Set string
106
+ this.value = value
107
+ } else if (isCalendarDateOrTime(value)) {
108
+ // Set date
109
+ this.value = value.getValue()
110
+ this.setValueType(value.isFullDay() ? 'DATE' : 'DATE-TIME')
111
+ } else {
112
+ // Set duration
113
+ this.value = value.getValue()
114
+ this.setValueType('DURATION')
115
+ }
116
+
117
+ // Remove value type if set to default, unless it was already set
118
+ const valueType = this.getExplicitValueType()
119
+ const defaultValueType = this.getDefaultValueType()
120
+ if (valueType === defaultValueType && valueType !== valueTypeBefore) {
121
+ this.removeValueType()
122
+ }
123
+ }
124
+
65
125
  setParameter(name: string, value: string | string[]) {
66
126
  this._parameters.set(
67
127
  name.toUpperCase(),
@@ -148,8 +208,23 @@ export class Property {
148
208
  * @returns The value type of this property.
149
209
  */
150
210
  getValueType(): AllowedValueType {
151
- const assignedValueType = this.getParameter('VALUE')?.[0].toUpperCase()
152
- return assignedValueType ?? getDefaultValueType(this.name)
211
+ return this.getExplicitValueType() ?? this.getDefaultValueType()
212
+ }
213
+
214
+ /**
215
+ * Get the value type of this property.
216
+ * @returns The value type of this property, or `undefined` if unset.
217
+ */
218
+ getExplicitValueType(): AllowedValueType | undefined {
219
+ return this.getParameter('VALUE')?.[0].toUpperCase()
220
+ }
221
+
222
+ /**
223
+ * Get the default value type for this property.
224
+ * @returns The default value type based on the name of this property.
225
+ */
226
+ getDefaultValueType(): KnownValueType {
227
+ return getDefaultValueType(this.name)
153
228
  }
154
229
 
155
230
  /**
@@ -85,7 +85,7 @@ export function validateDateTime(value: string) {
85
85
  parseDateTimeString(value)
86
86
  } catch {
87
87
  throw new PropertyValidationError(
88
- `${value} does not match pattern for DATETIME`
88
+ `${value} does not match pattern for DATE-TIME`
89
89
  )
90
90
  }
91
91
  }