///|
pub fn all_property_specs() -> Array[PropertySpec] {
[
spec(
"VERSION",
Text,
RequiredOnce,
true,
false,
false,
false,
false,
false,
false,
"calendar version",
),
spec(
"PRODID",
Text,
RequiredOnce,
true,
false,
false,
false,
false,
false,
false,
"product identifier",
),
spec(
"CALSCALE",
Text,
OptionalOnce,
true,
false,
false,
false,
false,
false,
false,
"calendar scale",
),
spec(
"METHOD",
Text,
OptionalOnce,
true,
false,
false,
false,
false,
false,
false,
"scheduling method",
),
spec(
"UID",
Text,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"persistent component id",
),
spec(
"DTSTAMP",
DateTime,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"creation timestamp",
),
spec(
"DTSTART",
DateTime,
OptionalOnce,
false,
true,
true,
true,
false,
true,
false,
"start date or time",
),
spec(
"CREATED",
DateTime,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"creation time",
),
spec(
"LAST-MODIFIED",
DateTime,
OptionalOnce,
false,
true,
true,
true,
true,
false,
false,
"last modification time",
),
spec(
"SEQUENCE",
Integer,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"revision sequence",
),
spec(
"SUMMARY",
Text,
OptionalOnce,
false,
true,
true,
true,
false,
false,
true,
"short display title",
),
spec(
"DESCRIPTION",
Text,
OptionalOnce,
false,
true,
true,
true,
false,
false,
true,
"long display text",
),
spec(
"LOCATION",
Text,
OptionalOnce,
false,
true,
true,
false,
false,
false,
false,
"event location",
),
spec(
"URL",
Uri,
OptionalOnce,
false,
true,
true,
true,
true,
false,
false,
"external resource",
),
spec(
"CLASS",
Text,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"visibility class",
),
spec(
"STATUS",
Text,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"workflow state",
),
spec(
"PRIORITY",
Integer,
OptionalOnce,
false,
true,
true,
false,
false,
false,
false,
"priority from 0 to 9",
),
spec(
"TRANSP",
Text,
OptionalOnce,
false,
true,
false,
false,
false,
false,
false,
"free busy transparency",
),
spec(
"DTEND",
DateTime,
OptionalOnce,
false,
true,
false,
false,
false,
false,
false,
"exclusive event end",
),
spec(
"DUE",
DateTime,
OptionalOnce,
false,
false,
true,
false,
false,
false,
false,
"todo due time",
),
spec(
"COMPLETED",
DateTime,
OptionalOnce,
false,
false,
true,
false,
false,
false,
false,
"todo completed time",
),
spec(
"PERCENT-COMPLETE",
Integer,
OptionalOnce,
false,
false,
true,
false,
false,
false,
false,
"todo completion percent",
),
spec(
"DURATION",
Duration,
OptionalOnce,
false,
true,
true,
false,
false,
false,
true,
"relative duration",
),
spec(
"RRULE",
Recurrence,
OptionalOnce,
false,
true,
true,
true,
false,
true,
false,
"recurrence rule",
),
spec(
"RDATE",
DateTime,
OptionalMany,
false,
true,
true,
true,
false,
true,
false,
"additional recurrence dates",
),
spec(
"EXDATE",
DateTime,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"excluded recurrence dates",
),
spec(
"EXRULE",
Recurrence,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"deprecated exclusion rule",
),
spec(
"RECURRENCE-ID",
DateTime,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"overridden instance id",
),
spec(
"ATTENDEE",
CalAddress,
OptionalMany,
false,
true,
true,
false,
false,
false,
true,
"participant address",
),
spec(
"ORGANIZER",
CalAddress,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"organizer address",
),
spec(
"CONTACT",
Text,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"human contact",
),
spec(
"COMMENT",
Text,
OptionalMany,
false,
true,
true,
true,
true,
true,
false,
"comment text",
),
spec(
"CATEGORIES",
Text,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"classification tags",
),
spec(
"RESOURCES",
Text,
OptionalMany,
false,
true,
true,
false,
false,
false,
false,
"event resources",
),
spec(
"RELATED-TO",
Text,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"related component id",
),
spec(
"GEO",
Float,
OptionalOnce,
false,
true,
true,
false,
false,
false,
false,
"latitude and longitude",
),
spec(
"REQUEST-STATUS",
Text,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"scheduling status",
),
spec(
"FREEBUSY",
Period,
OptionalMany,
false,
false,
false,
false,
false,
false,
false,
"free busy periods",
),
spec(
"TZID",
Text,
RequiredOnce,
false,
false,
false,
false,
true,
false,
false,
"timezone id",
),
spec(
"TZNAME",
Text,
OptionalMany,
false,
false,
false,
false,
false,
true,
false,
"timezone display name",
),
spec(
"TZOFFSETFROM",
UtcOffset,
RequiredOnce,
false,
false,
false,
false,
false,
true,
false,
"offset before transition",
),
spec(
"TZOFFSETTO",
UtcOffset,
RequiredOnce,
false,
false,
false,
false,
false,
true,
false,
"offset after transition",
),
spec(
"TZURL",
Uri,
OptionalOnce,
false,
false,
false,
false,
true,
false,
false,
"timezone definition url",
),
spec(
"ACTION",
Text,
RequiredOnce,
false,
false,
false,
false,
false,
false,
true,
"alarm action",
),
spec(
"TRIGGER",
Duration,
RequiredOnce,
false,
false,
false,
false,
false,
false,
true,
"alarm trigger",
),
spec(
"REPEAT",
Integer,
OptionalOnce,
false,
false,
false,
false,
false,
false,
true,
"alarm repeat count",
),
spec(
"ATTACH",
Uri,
OptionalMany,
false,
true,
true,
true,
false,
false,
true,
"attached resource",
),
spec(
"IMAGE",
Uri,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"display image extension",
),
spec(
"COLOR",
Text,
OptionalOnce,
false,
true,
true,
true,
false,
false,
false,
"calendar color extension",
),
spec(
"CONFERENCE",
Uri,
OptionalMany,
false,
true,
false,
false,
false,
false,
false,
"conference url extension",
),
spec(
"NAME",
Text,
OptionalOnce,
true,
false,
false,
false,
false,
false,
false,
"calendar name extension",
),
spec(
"REFRESH-INTERVAL",
Duration,
OptionalOnce,
true,
false,
false,
false,
false,
false,
false,
"feed refresh extension",
),
spec(
"SOURCE",
Uri,
OptionalOnce,
true,
false,
false,
false,
false,
false,
false,
"feed source extension",
),
spec(
"BUSYTYPE",
Text,
OptionalOnce,
false,
true,
false,
false,
false,
false,
false,
"busy type extension",
),
spec(
"STRUCTURED-DATA",
Uri,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"structured data extension",
),
spec(
"PARTICIPANT-TYPE",
Text,
OptionalOnce,
false,
true,
true,
false,
false,
false,
false,
"participant extension",
),
spec(
"CALENDAR-ADDRESS",
CalAddress,
OptionalOnce,
false,
true,
true,
false,
false,
false,
false,
"participant calendar address",
),
spec(
"PROXIMITY",
Text,
OptionalOnce,
false,
false,
false,
false,
false,
false,
true,
"location based alarm extension",
),
spec(
"ACKNOWLEDGED",
DateTime,
OptionalOnce,
false,
false,
false,
false,
false,
false,
true,
"alarm acknowledged time",
),
spec(
"TZID-ALIAS-OF",
Text,
OptionalMany,
false,
false,
false,
false,
true,
false,
false,
"timezone alias extension",
),
spec(
"TZUNTIL",
DateTime,
OptionalOnce,
false,
false,
false,
false,
false,
true,
false,
"timezone observance end extension",
),
spec(
"TZID-ALIAS-TO",
Text,
OptionalMany,
false,
false,
false,
false,
true,
false,
false,
"timezone alias target extension",
),
spec(
"LOCATION-TYPE",
Text,
OptionalMany,
false,
true,
true,
false,
false,
false,
false,
"location taxonomy extension",
),
spec(
"STYLED-DESCRIPTION",
Text,
OptionalMany,
false,
true,
true,
true,
false,
false,
false,
"html description extension",
),
]
}
///|
fn spec(
name : String,
kind : ValueKind,
cardinality : PropertyCardinality,
in_calendar : Bool,
in_event : Bool,
in_todo : Bool,
in_journal : Bool,
in_timezone : Bool,
in_observance : Bool,
in_alarm : Bool,
note : String,
) -> PropertySpec {
{
name,
kind,
cardinality,
in_calendar,
in_event,
in_todo,
in_journal,
in_timezone,
in_observance,
in_alarm,
note,
}
}
///|
pub fn find_property_spec(name : String) -> PropertySpec? {
let wanted = ascii_upper(trim_ascii(name))
for item in all_property_specs() {
if item.name == wanted {
return Some(item)
}
}
None
}
///|
pub fn is_known_property(name : String) -> Bool {
find_property_spec(name) is Some(_)
}
///|
pub fn property_value_kind(name : String) -> ValueKind {
match find_property_spec(name) {
Some(item) => item.kind
None => Unknown("UNKNOWN")
}
}
///|
pub fn property_cardinality(name : String) -> PropertyCardinality {
match find_property_spec(name) {
Some(item) => item.cardinality
None => Many
}
}
///|
pub fn role_for_component(name : String) -> ComponentRole {
let normalized = ascii_upper(trim_ascii(name))
if normalized == "VCALENDAR" {
CalendarRole
} else if normalized == "VEVENT" {
EventRole
} else if normalized == "VTODO" {
TodoRole
} else if normalized == "VJOURNAL" {
JournalRole
} else if normalized == "VTIMEZONE" {
TimezoneRole
} else if normalized == "STANDARD" || normalized == "DAYLIGHT" {
ObservanceRole
} else if normalized == "VALARM" {
AlarmRole
} else {
UnknownRole(normalized)
}
}
///|
pub fn spec_allows_role(spec : PropertySpec, role : ComponentRole) -> Bool {
match role {
CalendarRole => spec.in_calendar
EventRole => spec.in_event
TodoRole => spec.in_todo
JournalRole => spec.in_journal
TimezoneRole => spec.in_timezone
ObservanceRole => spec.in_observance
AlarmRole => spec.in_alarm
UnknownRole(_) => false
}
}
///|
pub fn property_allowed_in_role(name : String, role : ComponentRole) -> Bool {
match find_property_spec(name) {
Some(item) => spec_allows_role(item, role)
None => false
}
}
///|
pub fn property_allowed_in_component(
name : String,
component_name : String,
) -> Bool {
property_allowed_in_role(name, role_for_component(component_name))
}
///|
pub fn cardinality_requires_once(cardinality : PropertyCardinality) -> Bool {
match cardinality {
RequiredOnce => true
_ => false
}
}
///|
pub fn cardinality_allows_many(cardinality : PropertyCardinality) -> Bool {
match cardinality {
OptionalMany => true
Many => true
_ => false
}
}
///|
pub fn component_known_property_count(component : Component) -> Int {
let mut count = 0
for prop in component.properties {
if is_known_property(prop.name) {
count += 1
}
}
count
}
///|
pub fn component_unknown_property_count(component : Component) -> Int {
let mut count = 0
for prop in component.properties {
if !is_known_property(prop.name) {
count += 1
}
}
count
}
///|
pub fn component_supported_property_count(component : Component) -> Int {
let mut count = 0
for prop in component.properties {
if property_allowed_in_component(prop.name, component.name) {
count += 1
}
}
count
}
///|
pub fn component_unsupported_property_count(component : Component) -> Int {
let mut count = 0
for prop in component.properties {
if !property_allowed_in_component(prop.name, component.name) {
count += 1
}
}
count
}
///|
pub fn known_property_names() -> Array[String] {
let names : Array[String] = []
for item in all_property_specs() {
names.push(item.name)
}
names
}
///|
pub fn known_properties_for_role(role : ComponentRole) -> Array[String] {
let names : Array[String] = []
for item in all_property_specs() {
if spec_allows_role(item, role) {
names.push(item.name)
}
}
names
}
///|
pub fn render_property_spec(spec : PropertySpec) -> String {
let b = StringBuilder::new()
b.write_string(spec.name)
b.write_string(" ")
b.write_string(spec.kind.name())
b.write_string(" ")
b.write_string(spec.cardinality.name())
if spec.note.length() > 0 {
b.write_string(" - ")
b.write_string(spec.note)
}
b.to_string()
}
///|
pub fn render_property_catalog() -> String {
let b = StringBuilder::new()
b.write_string("# MoonCalGuard property catalog\n\n")
for item in all_property_specs() {
b.write_string("- ")
b.write_string(render_property_spec(item))
b.write_string("\n")
}
b.to_string()
}
///|
pub fn render_role_catalog(role : ComponentRole) -> String {
let b = StringBuilder::new()
b.write_string("# ")
b.write_string(role.name())
b.write_string(" properties\n\n")
for item in all_property_specs() {
if spec_allows_role(item, role) {
b.write_string("- ")
b.write_string(render_property_spec(item))
b.write_string("\n")
}
}
b.to_string()
}
///|
pub fn catalog_support_summary() -> String {
let b = StringBuilder::new()
b.write_string("properties=")
b.write_string(all_property_specs().length().to_string())
b.write_string(" calendar=")
b.write_string(known_properties_for_role(CalendarRole).length().to_string())
b.write_string(" event=")
b.write_string(known_properties_for_role(EventRole).length().to_string())
b.write_string(" todo=")
b.write_string(known_properties_for_role(TodoRole).length().to_string())
b.write_string(" journal=")
b.write_string(known_properties_for_role(JournalRole).length().to_string())
b.write_string(" timezone=")
b.write_string(known_properties_for_role(TimezoneRole).length().to_string())
b.write_string(" alarm=")
b.write_string(known_properties_for_role(AlarmRole).length().to_string())
b.to_string()
}