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/component.ts CHANGED
@@ -1,28 +1,44 @@
1
+ import { CalendarDateOrTime } from './date'
2
+ import {
3
+ AllowedPropertyName,
4
+ KnownPropertyName,
5
+ MissingPropertyError,
6
+ Property,
7
+ PropertyValidationError,
8
+ validateProperty,
9
+ } from './property'
10
+
11
+ /** Represents an error which occurs while validating a calendar component. */
12
+ export class ComponentValidationError extends Error {
13
+ constructor(message: string) {
14
+ super(message)
15
+ this.name = 'ComponentValidationError'
16
+ }
17
+ }
18
+
1
19
  // Max line length as defined by RFC 5545 3.1.
2
20
  const MAX_LINE_LENGTH = 75
3
21
 
4
- export interface Property {
5
- name: string
6
- params: string[]
7
- value: string
8
- }
9
-
10
22
  export class Component {
11
23
  name: string
12
24
  properties: Property[]
13
25
  components: Component[]
14
26
 
15
- constructor(name: string, properties?: Property[], components?: Component[]) {
27
+ constructor(
28
+ name: string,
29
+ properties?: Property[],
30
+ components?: Component[]
31
+ ) {
16
32
  this.name = name
17
33
  if (properties) {
18
34
  this.properties = properties
19
35
  } else {
20
- this.properties = new Array()
36
+ this.properties = []
21
37
  }
22
38
  if (components) {
23
39
  this.components = components
24
40
  } else {
25
- this.components = new Array()
41
+ this.components = []
26
42
  }
27
43
  }
28
44
 
@@ -30,7 +46,7 @@ export class Component {
30
46
  // Create lines
31
47
  const lines = [`BEGIN:${this.name}`]
32
48
 
33
- for (let property of this.properties) {
49
+ for (const property of this.properties) {
34
50
  let line =
35
51
  property['name'] + //
36
52
  property.params.map(p => ';' + p).join('') +
@@ -45,7 +61,7 @@ export class Component {
45
61
  lines.push(line)
46
62
  }
47
63
 
48
- for (let component of this.components) {
64
+ for (const component of this.components) {
49
65
  lines.push(component.serialize())
50
66
  }
51
67
 
@@ -66,24 +82,46 @@ export class Component {
66
82
  return null
67
83
  }
68
84
 
69
- setProperty(name: string, value: string): this {
85
+ setProperty(name: string, value: string | CalendarDateOrTime): this {
70
86
  for (const property of this.properties) {
71
87
  if (property.name === name) {
72
- property.value = value
88
+ if (typeof value === 'string') {
89
+ property.value = value
90
+ } else {
91
+ const prop = value.toProperty(name)
92
+ property.params = prop.params
93
+ property.value = prop.value
94
+ }
73
95
  return this
74
96
  }
75
97
  }
76
- this.properties.push({
77
- name: name,
78
- params: [],
79
- value: value,
80
- })
98
+ this.properties.push(
99
+ typeof value === 'string'
100
+ ? {
101
+ name: name,
102
+ params: [],
103
+ value: value,
104
+ }
105
+ : value.toProperty(name)
106
+ )
81
107
  return this
82
108
  }
83
109
 
110
+ hasProperty(name: string, value?: string): boolean {
111
+ for (const property of this.properties) {
112
+ if (
113
+ property.name === name &&
114
+ (value === undefined || property.value === value)
115
+ ) {
116
+ return true
117
+ }
118
+ }
119
+ return false
120
+ }
121
+
84
122
  removePropertiesWithName(name: string) {
85
123
  const index = this.properties.findIndex(p => p.name === name)
86
- if (index === -1) return this
124
+ if (index === -1) return
87
125
  // Remove property at index
88
126
  this.properties.splice(index, 1)
89
127
  }
@@ -111,9 +149,15 @@ export class Component {
111
149
  return this
112
150
  }
113
151
 
152
+ addComponents(components: Component[]): this {
153
+ this.components.push(...components)
154
+ return this
155
+ }
156
+
114
157
  /**
115
- * Remove a component from this component
116
- * @returns `true` if the component was removed. `false` if the component was not present
158
+ * Remove a component from this component.
159
+ * @param component The component to remove.
160
+ * @returns `true` if the component was removed. `false` if the component was not present.
117
161
  */
118
162
  removeComponent(component: Component): boolean {
119
163
  const index = this.components.indexOf(component)
@@ -125,10 +169,10 @@ export class Component {
125
169
  return true
126
170
  }
127
171
 
128
- getComponents(name: string): Component[] {
172
+ getComponentsWithName(name: string): Component[] {
129
173
  const components: Component[] = []
130
174
 
131
- for (let component of this.components) {
175
+ for (const component of this.components) {
132
176
  if (component.name === name) {
133
177
  components.push(component)
134
178
  }
@@ -136,4 +180,69 @@ export class Component {
136
180
 
137
181
  return components
138
182
  }
183
+
184
+ /**
185
+ * Validate the component according to the rules of the iCalendar
186
+ * specification.
187
+ *
188
+ * This method should be overridden by subclasses to
189
+ * implement specific validation logic. The methods
190
+ * {@link validateRequiredProperty} and {@link validateOptionalProperty} may
191
+ * help with this.
192
+ * @throws {ComponentValidationError} If the component is invalid.
193
+ */
194
+ validate(): void {}
195
+
196
+ /**
197
+ * Validate that a property exists and is valid using {@link validateProperty}.
198
+ * @param propertyName The name of the property to validate.
199
+ * @throws {MissingPropertyError} If the property doesn't exist on this component.
200
+ * @throws {PropertyValidationError} If the property exists but is invalid.
201
+ */
202
+ validateRequiredProperty(propertyName: AllowedPropertyName) {
203
+ const property = this.getProperty(propertyName)
204
+ if (property == null)
205
+ throw new MissingPropertyError(
206
+ `Missing required property ${propertyName}`
207
+ )
208
+ validateProperty(property)
209
+ }
210
+
211
+ /**
212
+ * Validate that a property is valid using {@link validateProperty} if it exists.
213
+ * @param propertyName The name of the property to validate.
214
+ * @throws {PropertyValidationError} If the property exists and is invalid.
215
+ */
216
+ validateOptionalProperty(propertyName: AllowedPropertyName) {
217
+ const property = this.getProperty(propertyName)
218
+ if (property != null) validateProperty(property)
219
+ }
220
+
221
+ /**
222
+ * Validate all properties on this component.
223
+ *
224
+ * If a property is in `requiredProperties` it is
225
+ * validated with {@link validateRequiredProperty}, otherwise it is
226
+ * validated with {@link validateOptionalProperty}.
227
+ * @param requiredProperties A list of property names which are required on this component.
228
+ */
229
+ validateAllProperties(requiredProperties?: AllowedPropertyName[]) {
230
+ requiredProperties?.forEach(propertyName => {
231
+ if (!this.hasProperty(propertyName)) {
232
+ throw new MissingPropertyError(
233
+ `Missing required property ${propertyName}`
234
+ )
235
+ }
236
+ })
237
+
238
+ this.properties.forEach(property => {
239
+ if (
240
+ requiredProperties?.includes(property.name as KnownPropertyName)
241
+ ) {
242
+ this.validateRequiredProperty(property.name)
243
+ } else {
244
+ this.validateOptionalProperty(property.name)
245
+ }
246
+ })
247
+ }
139
248
  }
@@ -1,33 +1,37 @@
1
- import { Component } from '../component'
1
+ import { KnownPropertyName } from '../property'
2
+ import { Component, ComponentValidationError } from '../component'
2
3
  import { CalendarEvent } from './CalendarEvent'
3
4
 
4
5
  /**
5
6
  * Represents a VCALENDAR component, the root component of an iCalendar file.
6
7
  */
7
8
  export class Calendar extends Component {
8
- name = 'VCALENDAR';
9
+ name = 'VCALENDAR'
9
10
 
10
11
  /**
11
12
  * @param prodid A unique identifier of the program creating the calendar.
12
- *
13
- * Example: `-//Google Inc//Google Calendar 70.9054//EN`
13
+ * @example
14
+ * new Calendar('-//Google Inc//Google Calendar 70.9054//EN')
14
15
  */
15
16
  constructor(prodid: string)
17
+
16
18
  /**
17
19
  * @param prodid A unique identifier of the program creating the calendar.
18
- *
19
- * Example: `-//Google Inc//Google Calendar 70.9054//EN`
20
20
  * @param version The version of the iCalendar specification that this calendar uses.
21
+ * @example
22
+ * new Calendar('-//Google Inc//Google Calendar 70.9054//EN')
21
23
  */
22
24
  constructor(prodid: string, version: string)
25
+
23
26
  /**
24
27
  * @param component A VCALENDAR component.
25
28
  */
26
29
  constructor(component: Component)
27
30
  constructor(a?: string | Component, b?: string) {
28
- var component: Component
31
+ let component: Component
29
32
  if (a instanceof Component) {
30
33
  component = a as Component
34
+ Calendar.prototype.validate.call(component)
31
35
  } else {
32
36
  const prodid = a as string
33
37
  const version = (b as string) ?? '2.0'
@@ -38,8 +42,19 @@ export class Calendar extends Component {
38
42
  super(component.name, component.properties, component.components)
39
43
  }
40
44
 
41
- events(): CalendarEvent[] {
42
- return this.getComponents('VEVENT').map(c => new CalendarEvent(c))
45
+ validate() {
46
+ if (this.name !== 'VCALENDAR')
47
+ throw new ComponentValidationError(
48
+ 'Component name must be VCALENDAR'
49
+ )
50
+ const requiredProperties: KnownPropertyName[] = ['PRODID', 'VERSION']
51
+ this.validateAllProperties(requiredProperties)
52
+ }
53
+
54
+ getEvents(): CalendarEvent[] {
55
+ return this.getComponentsWithName('VEVENT').map(
56
+ c => new CalendarEvent(c)
57
+ )
43
58
  }
44
59
 
45
60
  removeEvent(event: CalendarEvent): boolean
@@ -50,23 +65,23 @@ export class Calendar extends Component {
50
65
  return this.removeComponent(event)
51
66
  } else {
52
67
  const uid = a as string
53
- for (const event of this.events()) {
54
- if (event.uid() !== uid) continue
68
+ for (const event of this.getEvents()) {
69
+ if (event.getUid() !== uid) continue
55
70
  return this.removeComponent(event)
56
71
  }
57
72
  }
58
73
  return false
59
74
  }
60
75
 
61
- prodId(): string {
76
+ getProductId(): string {
62
77
  return this.getProperty('PRODID')!.value
63
78
  }
64
79
 
65
- setProdId(value: string): this {
80
+ setProductId(value: string): this {
66
81
  return this.setProperty('PRODID', value)
67
82
  }
68
83
 
69
- version(): string {
84
+ getVersion(): string {
70
85
  return this.getProperty('VERSION')!.value
71
86
  }
72
87
 
@@ -74,19 +89,19 @@ export class Calendar extends Component {
74
89
  return this.setProperty('VERSION', value)
75
90
  }
76
91
 
77
- calScale(): string | undefined {
92
+ getCalendarScale(): string | undefined {
78
93
  return this.getProperty('CALSCALE')?.value
79
94
  }
80
95
 
81
- setCalScale(value: string): this {
96
+ setCalendarScale(value: string): this {
82
97
  return this.setProperty('CALSCALE', value)
83
98
  }
84
99
 
85
- removeCalScale() {
100
+ removeCalendarScale() {
86
101
  this.removePropertiesWithName('CALSCALE')
87
102
  }
88
103
 
89
- method(): string | undefined {
104
+ getMethod(): string | undefined {
90
105
  return this.getProperty('METHOD')?.value
91
106
  }
92
107
 
@@ -98,7 +113,7 @@ export class Calendar extends Component {
98
113
  this.removePropertiesWithName('METHOD')
99
114
  }
100
115
 
101
- calendarName(): string | undefined {
116
+ getCalendarName(): string | undefined {
102
117
  return this.getProperty('X-WR-CALNAME')?.value
103
118
  }
104
119
 
@@ -110,7 +125,7 @@ export class Calendar extends Component {
110
125
  this.removePropertiesWithName('X-WR-CALNAME')
111
126
  }
112
127
 
113
- calendarDescription(): string | undefined {
128
+ getCalendarDescription(): string | undefined {
114
129
  return this.getProperty('X-WR-CALDESC')?.value
115
130
  }
116
131
 
@@ -121,4 +136,27 @@ export class Calendar extends Component {
121
136
  removeCalendarDescription() {
122
137
  this.removePropertiesWithName('X-WR-CALDESC')
123
138
  }
139
+
140
+ /* eslint-disable */
141
+
142
+ /** @deprecated use {@link getEvents} instead */
143
+ events = this.getEvents
144
+ /** @deprecated use {@link getProductId} instead */
145
+ prodId = this.getProductId
146
+ /** @deprecated use {@link setProductId} instead */
147
+ setProdId = this.setProductId
148
+ /** @deprecated use {@link getVersion} instead */
149
+ version = this.getVersion
150
+ /** @deprecated use {@link getCalendarScale} instead */
151
+ calScale = this.getCalendarScale
152
+ /** @deprecated use {@link setCalendarScale} instead */
153
+ setCalScale = this.setCalendarScale
154
+ /** @deprecated use {@link removeCalendarScale} instead */
155
+ removeCalScale = this.removeCalendarScale
156
+ /** @deprecated use {@link getMethod} instead */
157
+ method = this.getMethod
158
+ /** @deprecated use {@link getCalendarName} instead */
159
+ calendarName = this.getCalendarName
160
+ /** @deprecated use {@link getCalendarDescription} instead */
161
+ calendarDescription = this.getCalendarDescription
124
162
  }