Skip to content

Commit

Permalink
Merge pull request #108 from arran4/issue89
Browse files Browse the repository at this point in the history
Addition of some multiple Component Properties functionality and basis for later sanity checking
  • Loading branch information
arran4 authored Oct 15, 2024
2 parents 7fb626c + bdf47c9 commit 2467de0
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 3 deletions.
103 changes: 103 additions & 0 deletions calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,111 @@ const (
ComponentPropertyTzid = ComponentProperty(PropertyTzid)
ComponentPropertyComment = ComponentProperty(PropertyComment)
ComponentPropertyRelatedTo = ComponentProperty(PropertyRelatedTo)
ComponentPropertyMethod = ComponentProperty(PropertyMethod)
ComponentPropertyRecurrenceId = ComponentProperty(PropertyRecurrenceId)
ComponentPropertyDuration = ComponentProperty(PropertyDuration)
ComponentPropertyContact = ComponentProperty(PropertyContact)
ComponentPropertyRequestStatus = ComponentProperty(PropertyRequestStatus)
ComponentPropertyRDate = ComponentProperty(PropertyRdate)
)

// Required returns the rules from the RFC as to if they are required or not for any particular component type
// If unspecified or incomplete, it returns false. -- This list is incomplete verify source. Happy to take PRs with reference
// iana-prop and x-props are not covered as it would always be true and require an exhaustive list.
func (cp ComponentProperty) Required(c Component) bool {
// https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1
switch cp {
case ComponentPropertyDtstamp, ComponentPropertyUniqueId:
switch c.(type) {
case *VEvent:
return true
}
case ComponentPropertyDtStart:
switch c := c.(type) {
case *VEvent:
return !c.HasProperty(ComponentPropertyMethod)
}
}
return false
}

// Exclusive returns the ComponentProperty's using the rules from the RFC as to if one or more existing properties are prohibiting this one
// If unspecified or incomplete, it returns false. -- This list is incomplete verify source. Happy to take PRs with reference
// iana-prop and x-props are not covered as it would always be true and require an exhaustive list.
func (cp ComponentProperty) Exclusive(c Component) []ComponentProperty {
// https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1
switch cp {
case ComponentPropertyDtEnd:
switch c := c.(type) {
case *VEvent:
if c.HasProperty(ComponentPropertyDuration) {
return []ComponentProperty{ComponentPropertyDuration}
}
}
case ComponentPropertyDuration:
switch c := c.(type) {
case *VEvent:
if c.HasProperty(ComponentPropertyDtEnd) {
return []ComponentProperty{ComponentPropertyDtEnd}
}
}
}
return nil
}

// Singular returns the rules from the RFC as to if the spec states that if "Must not occur more than once"
// iana-prop and x-props are not covered as it would always be true and require an exhaustive list.
func (cp ComponentProperty) Singular(c Component) bool {
// https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1
switch cp {
case ComponentPropertyClass, ComponentPropertyCreated, ComponentPropertyDescription, ComponentPropertyGeo,
ComponentPropertyLastModified, ComponentPropertyLocation, ComponentPropertyOrganizer, ComponentPropertyPriority,
ComponentPropertySequence, ComponentPropertyStatus, ComponentPropertySummary, ComponentPropertyTransp,
ComponentPropertyUrl, ComponentPropertyRecurrenceId:
switch c.(type) {
case *VEvent:
return true
}
}
return false
}

// Optional returns the rules from the RFC as to if the spec states that if these are optional
// iana-prop and x-props are not covered as it would always be true and require an exhaustive list.
func (cp ComponentProperty) Optional(c Component) bool {
// https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1
switch cp {
case ComponentPropertyClass, ComponentPropertyCreated, ComponentPropertyDescription, ComponentPropertyGeo,
ComponentPropertyLastModified, ComponentPropertyLocation, ComponentPropertyOrganizer, ComponentPropertyPriority,
ComponentPropertySequence, ComponentPropertyStatus, ComponentPropertySummary, ComponentPropertyTransp,
ComponentPropertyUrl, ComponentPropertyRecurrenceId, ComponentPropertyRrule, ComponentPropertyAttach,
ComponentPropertyAttendee, ComponentPropertyCategories, ComponentPropertyComment,
ComponentPropertyContact, ComponentPropertyExdate, ComponentPropertyRequestStatus, ComponentPropertyRelatedTo,
ComponentPropertyResources, ComponentPropertyRDate:
switch c.(type) {
case *VEvent:
return true
}
}
return false
}

// Multiple returns the rules from the RFC as to if the spec states explicitly if multiple are allowed
// iana-prop and x-props are not covered as it would always be true and require an exhaustive list.
func (cp ComponentProperty) Multiple(c Component) bool {
// https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1
switch cp {
case ComponentPropertyAttach, ComponentPropertyAttendee, ComponentPropertyCategories, ComponentPropertyComment,
ComponentPropertyContact, ComponentPropertyExdate, ComponentPropertyRequestStatus, ComponentPropertyRelatedTo,
ComponentPropertyResources, ComponentPropertyRDate:
switch c.(type) {
case *VEvent:
return true
}
}
return false
}

type Property string

const (
Expand Down
72 changes: 69 additions & 3 deletions components.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ func NewComponent(uniqueId string) ComponentBase {
}
}

// GetProperty returns the first match for the particular property you're after. Please consider using:
// ComponentProperty.Required to determine if GetProperty or GetProperties is more appropriate.
func (cb *ComponentBase) GetProperty(componentProperty ComponentProperty) *IANAProperty {
for i := range cb.Properties {
if cb.Properties[i].IANAToken == string(componentProperty) {
Expand All @@ -71,6 +73,31 @@ func (cb *ComponentBase) GetProperty(componentProperty ComponentProperty) *IANAP
return nil
}

// GetProperties returns all matches for the particular property you're after. Please consider using:
// ComponentProperty.Singular/ComponentProperty.Multiple to determine if GetProperty or GetProperties is more appropriate.
func (cb *ComponentBase) GetProperties(componentProperty ComponentProperty) []*IANAProperty {
var result []*IANAProperty
for i := range cb.Properties {
if cb.Properties[i].IANAToken == string(componentProperty) {
result = append(result, &cb.Properties[i])
}
}
return result
}

// HasProperty returns true if a component property is in the component.
func (cb *ComponentBase) HasProperty(componentProperty ComponentProperty) bool {
for i := range cb.Properties {
if cb.Properties[i].IANAToken == string(componentProperty) {
return true
}
}
return false
}

// SetProperty replaces the first match for the particular property you're setting, otherwise adds it. Please consider using:
// ComponentProperty.Singular/ComponentProperty.Multiple to determine if AddProperty, SetProperty or ReplaceProperty is
// more appropriate.
func (cb *ComponentBase) SetProperty(property ComponentProperty, value string, params ...PropertyParameter) {
for i := range cb.Properties {
if cb.Properties[i].IANAToken == string(property) {
Expand All @@ -86,6 +113,17 @@ func (cb *ComponentBase) SetProperty(property ComponentProperty, value string, p
cb.AddProperty(property, value, params...)
}

// ReplaceProperty replaces all matches of the particular property you're setting, otherwise adds it. Returns a slice
// of removed properties. Please consider using:
// ComponentProperty.Singular/ComponentProperty.Multiple to determine if AddProperty, SetProperty or ReplaceProperty is
// more appropriate.
func (cb *ComponentBase) ReplaceProperty(property ComponentProperty, value string, params ...PropertyParameter) []IANAProperty {
removed := cb.RemoveProperty(property)
cb.AddProperty(property, value, params...)
return removed
}

// AddProperty appends a property
func (cb *ComponentBase) AddProperty(property ComponentProperty, value string, params ...PropertyParameter) {
r := IANAProperty{
BaseProperty{
Expand All @@ -101,16 +139,44 @@ func (cb *ComponentBase) AddProperty(property ComponentProperty, value string, p
cb.Properties = append(cb.Properties, r)
}

// RemoveProperty removes from the component all properties that has
// the name passed in removeProp.
func (cb *ComponentBase) RemoveProperty(removeProp ComponentProperty) {
// RemoveProperty removes from the component all properties that is of a particular property type, returning an slice of
// removed entities
func (cb *ComponentBase) RemoveProperty(removeProp ComponentProperty) []IANAProperty {
var keptProperties []IANAProperty
var removedProperties []IANAProperty
for i := range cb.Properties {
if cb.Properties[i].IANAToken != string(removeProp) {
keptProperties = append(keptProperties, cb.Properties[i])
} else {
removedProperties = append(removedProperties, cb.Properties[i])
}
}
cb.Properties = keptProperties
return removedProperties
}

// RemovePropertyByValue removes from the component all properties that has a particular property type and value,
// return a count of removed properties
func (cb *ComponentBase) RemovePropertyByValue(removeProp ComponentProperty, value string) []IANAProperty {
return cb.RemovePropertyByFunc(removeProp, func(p IANAProperty) bool {
return p.Value == value
})
}

// RemovePropertyByFunc removes from the component all properties that has a particular property type and the function
// remove returns true for
func (cb *ComponentBase) RemovePropertyByFunc(removeProp ComponentProperty, remove func(p IANAProperty) bool) []IANAProperty {
var keptProperties []IANAProperty
var removedProperties []IANAProperty
for i := range cb.Properties {
if cb.Properties[i].IANAToken != string(removeProp) && remove(cb.Properties[i]) {
keptProperties = append(keptProperties, cb.Properties[i])
} else {
removedProperties = append(removedProperties, cb.Properties[i])
}
}
cb.Properties = keptProperties
return removedProperties
}

const (
Expand Down

0 comments on commit 2467de0

Please sign in to comment.