Skip to content

Commit

Permalink
Add examples and functional tests (#5)
Browse files Browse the repository at this point in the history
* Fix coverage script

* Create ergonomic interface for accessing ical data and first example

* Check examples on CI

* Create round trip test, to be extended with more data

* Force line endings for ics

* Upgrade cache action to fix node16 warning

* Add data to the round trip test

* Add missing recur tests from #3

* Round trip for remaining components

* Round trip for IANA and X- components
  • Loading branch information
ThetaSinner authored Aug 10, 2024
1 parent 443401b commit 756610d
Show file tree
Hide file tree
Showing 41 changed files with 2,692 additions and 663 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sample.ics eol=crlf
5 changes: 4 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
Expand All @@ -31,3 +31,6 @@ jobs:
run: cargo clippy --all-targets -- --deny warnings
- name: Run tests
run: cargo test --verbose
- name: Run examples
run: |
cargo run --example load_sample
39 changes: 39 additions & 0 deletions examples/load_sample.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use aetolia::prelude::*;

fn main() {
let input = std::fs::File::open("sample.ics").unwrap();
let ical = load_ical(input).unwrap();
println!("Loaded iCal document with {} object", ical.len());

for component in &ical[0].components {
match component {
CalendarComponent::TimeZone(tz) => {
println!(
"Found timezone with name: {}",
tz.property_opt::<TimeZoneIdProperty>().unwrap().value().id
);
}
CalendarComponent::Event(e) => {
println!(
"Found event with description: {}",
e.property_opt::<DescriptionProperty>().unwrap().value()
);

let attendee = e.property_opt::<AttendeeProperty>().unwrap();
let role_param = attendee.param_opt::<RoleParam>().unwrap();
println!(
"Found attendee {} with role: {:?}",
attendee.value(),
role_param.role
);
}
_ => {}
}
}

let validation_errors = validate_model(&ical[0]).unwrap();
assert!(
validation_errors.is_empty(),
"Didn't expect any validation errors"
);
}
34 changes: 34 additions & 0 deletions sample.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
BEGIN:VCALENDAR
PRODID:-//RDU Software//NONSGML HandCal//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:STANDARD
DTSTART:19981025T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19990404T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:19980309T231000Z
UID:guid-1.example.com
ORGANIZER:mailto:[email protected]
ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP:
mailto:[email protected]
DESCRIPTION:Project XYZ Review Meeting
CATEGORIES:MEETING
CLASS:PUBLIC
CREATED:19980309T130000Z
SUMMARY:XYZ Project Review
DTSTART;TZID=America/New_York:19980312T083000
DTEND;TZID=America/New_York:19980312T093000
LOCATION:1CP Conference Room 4350
END:VEVENT
END:VCALENDAR
1 change: 1 addition & 0 deletions scripts/coverage.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ $env:LLVM_PROFILE_FILE="aetolia-%p-%m.profraw"

(Get-ChildItem -Path $Path).Fullname -match ".*.profraw" | Remove-Item
cargo test
cargo run --example load_sample
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage/
3 changes: 2 additions & 1 deletion scripts/coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ cargo build
export RUSTFLAGS="-Cinstrument-coverage"
export LLVM_PROFILE_FILE="aetolia-%p-%m.profraw"

find --print0 . -iname "*.profraw" | xargs rm
rm "*.profraw"
cargo test
cargo run --example load_sample
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage/
89 changes: 89 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,45 @@ impl Default for LanguageTag {
}
}

impl LanguageTag {
pub fn new(language: &str) -> Self {
Self {
language: language.to_string(),
..Default::default()
}
}

pub fn with_ext_lang(mut self, ext_lang: &str) -> Self {
self.ext_lang = Some(ext_lang.to_string());
self
}

pub fn with_script(mut self, script: &str) -> Self {
self.script = Some(script.to_string());
self
}

pub fn with_region(mut self, region: &str) -> Self {
self.region = Some(region.to_string());
self
}

pub fn add_variant(mut self, variant: &str) -> Self {
self.variants.push(variant.to_string());
self
}

pub fn add_extension(mut self, extension: &str) -> Self {
self.extensions.push(extension.to_string());
self
}

pub fn with_private_use(mut self, private_use: &str) -> Self {
self.private_use = Some(private_use.to_string());
self
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Range {
ThisAndFuture,
Expand Down Expand Up @@ -390,6 +429,56 @@ impl Ord for CalendarDateTime {
}
}

#[derive(Debug, PartialEq)]
pub enum PropertyKind {
Attach,
Version,
DateTimeStart,
Description,
Organizer,
TimeZoneId,
Attendee,
Categories,
Comment,
GeographicPosition,
Location,
PercentComplete,
Priority,
Resources,
Status,
Summary,
DateTimeCompleted,
DateTimeEnd,
DateTimeDue,
Duration,
FreeBusyTime,
TimeTransparency,
TimeZoneName,
TimeZoneOffsetTo,
TimeZoneOffsetFrom,
TimeZoneUrl,
Contact,
RecurrenceId,
Related,
ExceptionDateTimes,
RecurrenceDateTimes,
RecurrenceRule,
Action,
Repeat,
Trigger,
DateTimeCreated,
DateTimeStamp,
LastModified,
Sequence,
RequestStatus,
#[allow(dead_code)]
Other,
UniqueIdentifier,
Classification,
Url,
RelatedTo,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
110 changes: 65 additions & 45 deletions src/convert/param.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,94 @@
use crate::convert::{convert_string, ToModel};

use crate::model::Param as ModelParam;
use crate::model::{
AlternateRepresentationParam, CalendarUserTypeParam, CommonNameParam, DelegatedToParam,
EncodingParam, FormatTypeParam, FreeBusyTimeTypeParam, Param as ModelParam,
ParticipationStatusParam, RelatedParam, RoleParam, RsvpParam, SentByParam, TimeZoneIdParam,
};
use crate::parser::ParamValue as ParserParam;
use crate::prelude::{
DelegatedFromParam, DirectoryEntryReferenceParam, LanguageParam, MembersParam, RangeParam,
RelationshipTypeParam, ValueTypeParam,
};

impl ToModel for ParserParam<'_> {
type Model = ModelParam;

fn to_model(&self) -> anyhow::Result<Self::Model> {
Ok(match self {
ParserParam::AltRep { uri } => ModelParam::AltRep {
ParserParam::AltRep { uri } => ModelParam::AltRep(AlternateRepresentationParam {
uri: convert_string(uri),
},
ParserParam::CommonName { name } => ModelParam::CommonName {
}),
ParserParam::CommonName { name } => ModelParam::CommonName(CommonNameParam {
name: name.to_string(),
},
ParserParam::CalendarUserType { cu_type } => ModelParam::CalendarUserType {
cu_type: cu_type.clone(),
},
ParserParam::DelegatedFrom { delegators } => ModelParam::DelegatedFrom {
delegators: delegators.iter().map(|d| convert_string(d)).collect(),
},
ParserParam::DelegatedTo { delegates } => ModelParam::DelegatedTo {
}),
ParserParam::CalendarUserType { cu_type } => {
ModelParam::CalendarUserType(CalendarUserTypeParam {
cu_type: cu_type.clone(),
})
}
ParserParam::DelegatedFrom { delegators } => {
ModelParam::DelegatedFrom(DelegatedFromParam {
delegators: delegators.iter().map(|d| convert_string(d)).collect(),
})
}
ParserParam::DelegatedTo { delegates } => ModelParam::DelegatedTo(DelegatedToParam {
delegates: delegates.iter().map(|d| convert_string(d)).collect(),
},
ParserParam::DirectoryEntryReference { uri } => ModelParam::DirectoryEntryReference {
uri: String::from_utf8_lossy(uri).to_string(),
},
ParserParam::Encoding { encoding } => ModelParam::Encoding {
}),
ParserParam::DirectoryEntryReference { uri } => {
ModelParam::DirectoryEntryReference(DirectoryEntryReferenceParam {
uri: String::from_utf8_lossy(uri).to_string(),
})
}
ParserParam::Encoding { encoding } => ModelParam::Encoding(EncodingParam {
encoding: encoding.clone(),
},
}),
ParserParam::FormatType {
type_name,
sub_type_name,
} => ModelParam::FormatType {
} => ModelParam::FormatType(FormatTypeParam {
type_name: type_name.to_string(),
sub_type_name: sub_type_name.to_string(),
},
ParserParam::FreeBusyTimeType { fb_type } => ModelParam::FreeBusyTimeType {
fb_type: fb_type.clone(),
},
ParserParam::Language { language } => ModelParam::Language {
}),
ParserParam::FreeBusyTimeType { fb_type } => {
ModelParam::FreeBusyTimeType(FreeBusyTimeTypeParam {
fb_type: fb_type.clone(),
})
}
ParserParam::Language { language } => ModelParam::Language(LanguageParam {
language: language.clone(),
},
ParserParam::Members { members } => ModelParam::Members {
}),
ParserParam::Members { members } => ModelParam::Members(MembersParam {
members: members.iter().map(|m| convert_string(m)).collect(),
},
ParserParam::ParticipationStatus { status } => ModelParam::ParticipationStatus {
status: status.clone(),
},
ParserParam::Range { range } => ModelParam::Range {
}),
ParserParam::ParticipationStatus { status } => {
ModelParam::ParticipationStatus(ParticipationStatusParam {
status: status.clone(),
})
}
ParserParam::Range { range } => ModelParam::Range(RangeParam {
range: range.clone(),
},
ParserParam::Related { related } => ModelParam::Related {
}),
ParserParam::Related { related } => ModelParam::Related(RelatedParam {
related: related.clone(),
},
ParserParam::RelationshipType { relationship } => ModelParam::RelationshipType {
relationship: relationship.clone(),
},
ParserParam::Role { role } => ModelParam::Role { role: role.clone() },
ParserParam::Rsvp { rsvp } => ModelParam::Rsvp { rsvp: *rsvp },
ParserParam::SentBy { address } => ModelParam::SentBy {
}),
ParserParam::RelationshipType { relationship } => {
ModelParam::RelationshipType(RelationshipTypeParam {
relationship: relationship.clone(),
})
}
ParserParam::Role { role } => ModelParam::Role(RoleParam { role: role.clone() }),
ParserParam::Rsvp { rsvp } => ModelParam::Rsvp(RsvpParam { rsvp: *rsvp }),
ParserParam::SentBy { address } => ModelParam::SentBy(SentByParam {
address: convert_string(address),
},
ParserParam::TimeZoneId { tz_id, unique } => ModelParam::TimeZoneId {
}),
ParserParam::TimeZoneId { tz_id, unique } => ModelParam::TimeZoneId(TimeZoneIdParam {
tz_id: tz_id.to_string(),
unique: *unique,
},
ParserParam::ValueType { value } => ModelParam::ValueType {
}),
ParserParam::ValueType { value } => ModelParam::ValueType(ValueTypeParam {
value: value.clone(),
},
}),
ParserParam::Other { name, value } => ModelParam::Other {
name: convert_string(name),
value: convert_string(value),
Expand Down
Loading

0 comments on commit 756610d

Please sign in to comment.