valerie 0.0.6 → 0.0.7
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.
- checksums.yaml +4 -4
- data/README.md +188 -0
- data/Rakefile +8 -0
- data/lib/valerie/address.rb +70 -0
- data/lib/valerie/birthday.rb +46 -0
- data/lib/valerie/card.rb +84 -0
- data/lib/valerie/collection/address_collection.rb +21 -0
- data/lib/valerie/collection/collection.rb +41 -0
- data/lib/valerie/collection/email_collection.rb +23 -0
- data/lib/valerie/collection/phone_collection.rb +21 -0
- data/lib/valerie/core/parser.rb +90 -0
- data/lib/valerie/email.rb +61 -0
- data/lib/valerie/gender.rb +42 -0
- data/lib/valerie/name.rb +44 -0
- data/lib/valerie/ordered.rb +19 -0
- data/lib/valerie/organization.rb +47 -0
- data/lib/valerie/phone.rb +61 -0
- data/lib/valerie.rb +1 -1
- data/test/birthday_test.rb +26 -0
- data/test/card_test.rb +73 -0
- data/test/collection/email_collection_test.rb +39 -0
- data/test/collection/phone_collection_test.rb +23 -0
- data/test/email_test.rb +26 -0
- data/test/gender_test.rb +36 -0
- data/test/name_test.rb +17 -0
- data/test/organization_test.rb +48 -0
- data/test/phone_test.rb +29 -0
- data/test/test_helper.rb +8 -0
- metadata +27 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea7e19804b751742d2949d006db9f884f3baa7a17d0a0a1f6a6db00196a6c194
|
4
|
+
data.tar.gz: 49741e0b54e5c6ee460654d4f7134aae2912e74539625fe8c3b37e8621a23fa6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2518a65329eb490ad21ce723bcab923c09db8b7b8f02afb678aac41469a6799203fa92cd03f530b6d11d451034d540fa3ce58f5eb7b80f214c2b9ddab75ec98
|
7
|
+
data.tar.gz: fa001df7282f3af9a1c1cc2aa3aaf6957b79158640808e45b11e1958e086bbe4ec52b5fb59cc2812b7cf25aa0ec0f8c010df0f81c9d9381d7a8e43572f42cdc6
|
data/README.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# VCard parser and generator
|
2
|
+
|
3
|
+
This is a simple VCard parser and generator extracted from Hellotext.
|
4
|
+
It implements the VCard 3.0 specification. It supports all of the cases we want to use in Hellotext.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'valerie'
|
12
|
+
```
|
13
|
+
|
14
|
+
Or install it yourself as:
|
15
|
+
|
16
|
+
```bash
|
17
|
+
gem install valerie
|
18
|
+
```
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Parsing a VCard is as simple as passing a VCard string, like
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
data = "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Hellotext www.hellotext.com//EN\r\nN:Rosenbaum;Shira;;;\r\nTEL;:+598 00 000 00\r\nEND:VCARD"
|
26
|
+
Valerie::Card.parse(data)
|
27
|
+
```
|
28
|
+
|
29
|
+
## Generating a VCard
|
30
|
+
|
31
|
+
You usually start by initializing a new Card object.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
card = Valerie::Card.new
|
35
|
+
```
|
36
|
+
|
37
|
+
### Types of Properties
|
38
|
+
|
39
|
+
According to the VCard 3.0 specification, some types of properties can have multiple values. From the specs, Valerie supports the following properties:
|
40
|
+
|
41
|
+
- `EMAIL`: for email addresses
|
42
|
+
- `TEL`: for telephone numbers
|
43
|
+
- `ADR`: for addresses
|
44
|
+
|
45
|
+
Aside from these, every other support property supports a single value.
|
46
|
+
|
47
|
+
|
48
|
+
### Single value properties
|
49
|
+
|
50
|
+
#### Name
|
51
|
+
|
52
|
+
To set the name property for the VCard, you can use the `Card#name=` setter. It accepts two types of arguments,
|
53
|
+
|
54
|
+
A hash that contains the following
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
{
|
58
|
+
first_name: 'Shira',
|
59
|
+
last_name: 'Rosenbaum',
|
60
|
+
middle_name: 'M',
|
61
|
+
prefix: 'Ms.',
|
62
|
+
suffix: 'PhD'
|
63
|
+
}
|
64
|
+
```
|
65
|
+
|
66
|
+
Or an array with the following order `[first_name, last_name, middle_name, prefix, suffix]`.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
card.name = %w[Shira Rosenbaum M Ms. PhD]
|
70
|
+
```
|
71
|
+
|
72
|
+
#### Gender
|
73
|
+
|
74
|
+
To set the Gender of the card you can, set it value `Card#gender=`, this accepts one of the following constants:
|
75
|
+
`male`, `female`, `other`, `none` and `unknown`. Passing another value raises an `ArgumentError`.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
card.gender = 'female'
|
79
|
+
```
|
80
|
+
|
81
|
+
#### Birthday
|
82
|
+
|
83
|
+
The birthday can be set via `Card#birthday=`. It accepts any object that responds to `strftime`, otherwise it stores the `to_s` version of the value.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
card.birthday = Date.new(1999, 11, 4)
|
87
|
+
```
|
88
|
+
|
89
|
+
#### Organization
|
90
|
+
|
91
|
+
The organization can be an tuple of `[organization_name, department]`, or a hash of `{organization: 'organization_name', department: 'department'}`.
|
92
|
+
When a string value is passed, only the `organization_name` is set.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
card.organization = %w[Hellotext Engineering]
|
96
|
+
card.organization = { organization: 'Hellotext', department: 'Engineering' }
|
97
|
+
card.organization = 'Hellotext'
|
98
|
+
```
|
99
|
+
|
100
|
+
### Collections
|
101
|
+
|
102
|
+
The card exposes methods to adding the respective collectable properties. Email, telephone and address.
|
103
|
+
|
104
|
+
Adding an email,
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
card.emails.add('user@domain.com')
|
108
|
+
```
|
109
|
+
|
110
|
+
Or a telephone number,
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
card.phones.add('+598 00 000 00')
|
114
|
+
```
|
115
|
+
|
116
|
+
Or an address,
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
card.addresses.add(
|
120
|
+
{
|
121
|
+
post_office_box: 'PO Box 123',
|
122
|
+
extended_address: 'Suite 123',
|
123
|
+
street_address: '123 Main St',
|
124
|
+
locality: 'Anytown',
|
125
|
+
region: 'CA',
|
126
|
+
postal_code: '12345',
|
127
|
+
country: 'United States'
|
128
|
+
}
|
129
|
+
)
|
130
|
+
```
|
131
|
+
|
132
|
+
#### Positions
|
133
|
+
|
134
|
+
Emails, phones and addresses are ordered. They can include an optional `position` argument
|
135
|
+
to specify their order when the profile has multiple values. Whenever you add a new value,
|
136
|
+
a default position argument is picked and the value is appended as the last element.
|
137
|
+
|
138
|
+
To specify the position, you can pass the `position` argument as a keyword argument. Unlike arrays
|
139
|
+
the position is 1-based and not 0-based.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
card.emails.add('user@domain.com')
|
143
|
+
card.emails.add('user@domain2.com', position: 1) # inserts the email as the first element
|
144
|
+
```
|
145
|
+
|
146
|
+
#### Types
|
147
|
+
|
148
|
+
Emails, phones and addresses can have types. The types are defined in the VCard 3.0 specification.
|
149
|
+
To specify the type of one of these properties, you can pass the `types` argument as a keyword argument.
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
card.emails.add('user@domain.com', type: 'work')
|
153
|
+
card.emails.add('user@domain.com', type: %w[work internet])
|
154
|
+
```
|
155
|
+
|
156
|
+
### Configuration
|
157
|
+
|
158
|
+
You can configure the following properties of the card.
|
159
|
+
|
160
|
+
- `prodid`: The product id of the card. This defaults to 'Valerie www.hellotext.com'.
|
161
|
+
- `version`: The version of the card. This defaults to '3.0'.
|
162
|
+
- `language`: The language of the card. This defaults to 'en'.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
Valerie.configure do |config|
|
166
|
+
config.product = 'Valerie www.hellotext.com'
|
167
|
+
config.version = '3.0'
|
168
|
+
config.language = 'en'
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
### Licence
|
173
|
+
|
174
|
+
This code is released under the MIT License. See the LICENSE file for more information.
|
175
|
+
|
176
|
+
### Acknowledgements
|
177
|
+
|
178
|
+
- [VCardigan](https://github.com/brewster/vcardigan)
|
179
|
+
|
180
|
+
### Contributing
|
181
|
+
|
182
|
+
Contributions are welcome. Please follow the steps below to contribute.
|
183
|
+
|
184
|
+
1. Fork it
|
185
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
186
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
187
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
188
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative 'ordered'
|
2
|
+
|
3
|
+
module Valerie
|
4
|
+
class Address
|
5
|
+
include Ordered
|
6
|
+
|
7
|
+
def self.from_s(data)
|
8
|
+
data = data[data.index("ADR;")..] unless data.start_with?("ADR;")
|
9
|
+
identifier = data.split(":").last.split(";")
|
10
|
+
options = data.gsub("ADR", "").split(":").first.split(";").compact.filter { _1.to_s.include?('=')}.map { _1.downcase.split("=") }.to_h
|
11
|
+
|
12
|
+
new(
|
13
|
+
post_office_box: identifier[0],
|
14
|
+
extended_address: identifier[1],
|
15
|
+
street_address: identifier[2],
|
16
|
+
locality: identifier[3],
|
17
|
+
region: identifier[4],
|
18
|
+
postal_code: identifier[5],
|
19
|
+
country: identifier[6],
|
20
|
+
**options
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(post_office_box:, extended_address:, street_address:, locality:, region:, postal_code:, country:, **options)
|
25
|
+
@post_office_box = post_office_box
|
26
|
+
@extended_address = extended_address
|
27
|
+
@street_address = street_address
|
28
|
+
@locality = locality
|
29
|
+
@region = region
|
30
|
+
@postal_code = postal_code
|
31
|
+
@country = country
|
32
|
+
@options = options
|
33
|
+
|
34
|
+
raise ArgumentError, 'Invalid Position' if invalid_position?
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](key)
|
38
|
+
@options[key]
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
parts = ['ADR']
|
43
|
+
|
44
|
+
parts << "PERF=#{position}" if position?
|
45
|
+
|
46
|
+
@options.map do |key, value|
|
47
|
+
next if key == :position
|
48
|
+
parts << "#{key}=#{value}"
|
49
|
+
end
|
50
|
+
|
51
|
+
parts.join(';') + ":#{identifier}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def identifier
|
55
|
+
[@post_office_box, @extended_address, @street_address, @locality, @region, @postal_code, @country].join(';')
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_h
|
59
|
+
{
|
60
|
+
post_office_box: @post_office_box,
|
61
|
+
extended_address: @extended_address,
|
62
|
+
street_address: @street_address,
|
63
|
+
locality: @locality,
|
64
|
+
region: @region,
|
65
|
+
postal_code: @postal_code,
|
66
|
+
country: @country
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Valerie
|
4
|
+
class Birthday
|
5
|
+
def self.from_s(data)
|
6
|
+
new data.gsub("BDAY:", "")
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(date)
|
10
|
+
if date.respond_to?(:strftime)
|
11
|
+
@date = date.strftime("%Y-%m-%d")
|
12
|
+
else
|
13
|
+
@date = date
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"BDAY:#{@date}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_date
|
22
|
+
Date.new(year, month, day)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
{
|
27
|
+
day:,
|
28
|
+
month:,
|
29
|
+
year:
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def year
|
35
|
+
@year ||= @date.split('-')[0].to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
def month
|
39
|
+
@month ||= @date.split('-')[1].to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def day
|
43
|
+
@day ||= @date.split('-')[-1].to_i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/valerie/card.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require_relative '../valerie'
|
2
|
+
require_relative 'core/parser'
|
3
|
+
|
4
|
+
module Valerie
|
5
|
+
class Card
|
6
|
+
extend Core::Parser
|
7
|
+
|
8
|
+
attr_reader :name, :formatted_name, :organization, :gender, :birthday
|
9
|
+
|
10
|
+
def name=(parts)
|
11
|
+
if parts.instance_of?(Name)
|
12
|
+
@name = parts
|
13
|
+
else
|
14
|
+
@name = Name.new parts
|
15
|
+
end
|
16
|
+
|
17
|
+
if (@name.first_name || @name.last_name) && @formatted_name.nil?
|
18
|
+
@formatted_name = "#{@name.first_name} #{@name.last_name}".strip
|
19
|
+
end
|
20
|
+
|
21
|
+
@name
|
22
|
+
end
|
23
|
+
|
24
|
+
def organization=(*args)
|
25
|
+
if args.first.instance_of?(Organization)
|
26
|
+
@organization = args.flatten.first
|
27
|
+
else
|
28
|
+
@organization = Organization.new(*args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def gender=(value)
|
33
|
+
@gender = value.is_a?(Gender) ? value : Gender.new(value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def birthday=(value)
|
37
|
+
@birthday = value.is_a?(Birthday) ? value : Birthday.new(value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def emails
|
41
|
+
@emails ||= Collection::EmailCollection.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def addresses
|
45
|
+
@addresses ||= Collection::AddressCollection.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def phones
|
49
|
+
@phones ||= Collection::PhoneCollection.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
to_a.join("\r\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_a
|
57
|
+
parts = [
|
58
|
+
'BEGIN:VCARD',
|
59
|
+
"VERSION:#{Valerie.configuration.version}",
|
60
|
+
"PRODID:-//#{Valerie.configuration.product}//#{Valerie.configuration.language}",
|
61
|
+
]
|
62
|
+
|
63
|
+
parts << @name.to_s if @name
|
64
|
+
parts << @organization.to_s if @organization
|
65
|
+
parts << @birthday.to_s if @birthday
|
66
|
+
parts << @gender.to_s if @gender
|
67
|
+
|
68
|
+
emails.each do |email|
|
69
|
+
parts << email.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
phones.each do |phone|
|
73
|
+
parts << phone.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
addresses.each do |address|
|
77
|
+
parts << address.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
parts << 'END:VCARD'
|
81
|
+
parts
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'collection'
|
2
|
+
require_relative '../address'
|
3
|
+
|
4
|
+
module Valerie
|
5
|
+
module Collection
|
6
|
+
class AddressCollection < Base
|
7
|
+
def add(address, **options)
|
8
|
+
@item = if address.is_a?(Address)
|
9
|
+
address.dup { _1.position = options[:position] || @items.size + 1 }
|
10
|
+
else
|
11
|
+
Address.new(**address, **options)
|
12
|
+
end
|
13
|
+
|
14
|
+
@items << @item
|
15
|
+
@items = @items.sort_by(&:position)
|
16
|
+
|
17
|
+
@item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Valerie
|
2
|
+
module Collection
|
3
|
+
class Base
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :items
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@items = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
items.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def blank?
|
21
|
+
@items.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def present?
|
25
|
+
!blank?
|
26
|
+
end
|
27
|
+
|
28
|
+
def size
|
29
|
+
@items.size
|
30
|
+
end
|
31
|
+
|
32
|
+
def count
|
33
|
+
@items.count
|
34
|
+
end
|
35
|
+
|
36
|
+
def length
|
37
|
+
@items.length
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'collection'
|
2
|
+
require_relative '../email'
|
3
|
+
|
4
|
+
module Valerie
|
5
|
+
module Collection
|
6
|
+
class EmailCollection < Base
|
7
|
+
def add(email, **options)
|
8
|
+
@item = if email.is_a?(Email)
|
9
|
+
email.dup.tap { _1.position = options[:position] || @items.size + 1 }
|
10
|
+
elsif email.is_a?(Hash)
|
11
|
+
Email.new(address: email[:address], **email, **options, position: options[:position] || @items.size + 1)
|
12
|
+
else
|
13
|
+
Email.new(address: email, **options, position: options[:position] || @items.size + 1)
|
14
|
+
end
|
15
|
+
|
16
|
+
@items << @item
|
17
|
+
@items = @items.sort_by(&:position)
|
18
|
+
|
19
|
+
@item
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'collection'
|
2
|
+
require_relative '../phone'
|
3
|
+
|
4
|
+
module Valerie
|
5
|
+
module Collection
|
6
|
+
class PhoneCollection < Base
|
7
|
+
def add(phone, **options)
|
8
|
+
@item = if phone.is_a?(Phone)
|
9
|
+
phone.dup.tap { _1.position = options[:position] || @items.size + 1 }
|
10
|
+
else
|
11
|
+
Phone.new(phone, **options, position: options[:position] || @items.size + 1)
|
12
|
+
end
|
13
|
+
|
14
|
+
@items << @item
|
15
|
+
@items = @items.sort_by(&:position)
|
16
|
+
|
17
|
+
@item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative '../../valerie'
|
2
|
+
|
3
|
+
module Valerie
|
4
|
+
module Core
|
5
|
+
module Parser
|
6
|
+
def parse(data)
|
7
|
+
if data.instance_of?(String)
|
8
|
+
from_s(data)
|
9
|
+
elsif data.instance_of?(Array)
|
10
|
+
from_a(data)
|
11
|
+
else
|
12
|
+
raise ArgumentError, "Expected String or Array, got #{data.class}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def from_a(data)
|
18
|
+
Card.new.tap do |vcard|
|
19
|
+
if (name = data.find { _1.start_with?("N:") })
|
20
|
+
vcard.name = Name.from_s name.split(":").last
|
21
|
+
end
|
22
|
+
|
23
|
+
if (organization = data.find { _1.start_with?("ORG:") })
|
24
|
+
vcard.organization = Organization.from_s(organization)
|
25
|
+
end
|
26
|
+
|
27
|
+
if (birthday = data.find { _1.start_with?("BDAY:") })
|
28
|
+
vcard.birthday = Birthday.from_s(birthday)
|
29
|
+
end
|
30
|
+
|
31
|
+
if (gender = data.find { _1.start_with?("GENDER:") })
|
32
|
+
vcard.gender = gender.split(":").last
|
33
|
+
end
|
34
|
+
|
35
|
+
data.select { _1.include?("TEL;") }.each do |phone|
|
36
|
+
vcard.phones.add(Phone.from_s(phone))
|
37
|
+
end
|
38
|
+
|
39
|
+
data.select { _1.start_with?("EMAIL;") }.each do |email|
|
40
|
+
vcard.emails.add(Email.from_s(email))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def from_s(str)
|
46
|
+
vcards = []
|
47
|
+
vcard = []
|
48
|
+
|
49
|
+
unfold(str).each do |entry|
|
50
|
+
if entry.include?("VERSION:")
|
51
|
+
vcards << from_a(vcard) unless vcard.empty?
|
52
|
+
vcard = []
|
53
|
+
else
|
54
|
+
vcard << entry
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
vcards << from_a(vcard)
|
59
|
+
|
60
|
+
vcards
|
61
|
+
end
|
62
|
+
|
63
|
+
UNTERMINATED_QUOTED_PRINTABLE = /ENCODING=QUOTED-PRINTABLE:.*=$/
|
64
|
+
|
65
|
+
def unfold(vcard)
|
66
|
+
unfolded = []
|
67
|
+
|
68
|
+
prior_line = nil
|
69
|
+
vcard.lines do |line|
|
70
|
+
line.chomp!
|
71
|
+
# If it's a continuation line, add it to the last.
|
72
|
+
# If it's an empty line, drop it from the input.
|
73
|
+
if line =~ /^[ \t]/
|
74
|
+
unfolded[-1] << line[1, line.size - 1]
|
75
|
+
elsif line =~ /(^BEGIN:VCARD$)|(^END:VCARD$)/
|
76
|
+
elsif prior_line && (prior_line =~ UNTERMINATED_QUOTED_PRINTABLE)
|
77
|
+
# Strip the trailing = off prior line, then append current line
|
78
|
+
unfolded[-1] = prior_line[0, prior_line.length - 1] + line
|
79
|
+
elsif line =~ /^$/
|
80
|
+
else
|
81
|
+
unfolded << line
|
82
|
+
end
|
83
|
+
prior_line = unfolded[-1]
|
84
|
+
end
|
85
|
+
|
86
|
+
unfolded
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'ordered'
|
2
|
+
|
3
|
+
module Valerie
|
4
|
+
class Email
|
5
|
+
include Ordered
|
6
|
+
|
7
|
+
def self.from_s(data)
|
8
|
+
data = data[data.index("EMAIL;")..] unless data.start_with?("EMAIL;")
|
9
|
+
identifier = data.split(":").last.split(";")
|
10
|
+
options = data.gsub("EMAIL", "").split(":").first.split(";").compact.filter { _1.to_s.include?('=')}.map { _1.downcase.split("=") }.to_h
|
11
|
+
|
12
|
+
new(
|
13
|
+
address: identifier[0],
|
14
|
+
**options
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :address, :options
|
19
|
+
|
20
|
+
def initialize(address:, **options)
|
21
|
+
@address = address
|
22
|
+
@options = options
|
23
|
+
|
24
|
+
raise ArgumentError, 'Invalid Position' if invalid_position?
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
@options[key]
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
parts = ['EMAIL']
|
33
|
+
|
34
|
+
parts << "PERF=#{@options[:position]}" if position?
|
35
|
+
parts << type_to_s if type?
|
36
|
+
|
37
|
+
parts.join(';') + ":#{@address}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
{
|
42
|
+
address: @address,
|
43
|
+
position: @options[:position],
|
44
|
+
type: @options[:type],
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def type?
|
50
|
+
@options[:type]
|
51
|
+
end
|
52
|
+
|
53
|
+
def type_to_s
|
54
|
+
if @options[:type].is_a?(Array)
|
55
|
+
"TYPE=\"#{@options[:type].join(',')}\""
|
56
|
+
else
|
57
|
+
"TYPE=#{@options[:type]}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Valerie
|
2
|
+
class Gender
|
3
|
+
SEX_COMPONENTS = {
|
4
|
+
male: 'M',
|
5
|
+
female: 'F',
|
6
|
+
other: 'O',
|
7
|
+
unknown: 'U',
|
8
|
+
none: 'N'
|
9
|
+
}
|
10
|
+
|
11
|
+
attr_reader :identifier
|
12
|
+
|
13
|
+
def self.from_s(data)
|
14
|
+
new(data.split(":").last)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(identifier)
|
18
|
+
if SEX_COMPONENTS.keys.include?(identifier.to_s.downcase.to_sym)
|
19
|
+
@identifier = SEX_COMPONENTS[identifier.to_s.downcase.to_sym]
|
20
|
+
else
|
21
|
+
@identifier = identifier
|
22
|
+
end
|
23
|
+
|
24
|
+
raise ArgumentError, 'Invalid Sex identifier' unless SEX_COMPONENTS.values.include?(@identifier)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"GENDER:#{@identifier}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
{
|
33
|
+
identifier: @identifier,
|
34
|
+
sex:,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def sex
|
39
|
+
@sex ||= SEX_COMPONENTS.key(@identifier)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/valerie/name.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Valerie
|
2
|
+
class Name
|
3
|
+
class << self
|
4
|
+
def new(parts)
|
5
|
+
if parts.is_a?(Hash)
|
6
|
+
super **parts
|
7
|
+
else
|
8
|
+
first_name, last_name = parts.split(" ")
|
9
|
+
|
10
|
+
super first_name:, last_name:
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_s(data)
|
15
|
+
parts = data.gsub("N:", "").split(";")
|
16
|
+
new first_name: parts[1], last_name: parts[0], middle_name: parts[2], prefix: parts[3], suffix: parts[4]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :first_name, :last_name
|
21
|
+
|
22
|
+
def initialize(first_name: nil, last_name: nil, middle_name: nil, prefix: nil, suffix: nil)
|
23
|
+
@first_name = first_name
|
24
|
+
@last_name = last_name
|
25
|
+
@middle_name = middle_name
|
26
|
+
@prefix = prefix
|
27
|
+
@suffix = suffix
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"N:#{[@last_name, @first_name, @middle_name, @prefix, @suffix].join(";")}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
{
|
36
|
+
first_name: @first_name,
|
37
|
+
last_name: @last_name,
|
38
|
+
middle_name: @middle_name,
|
39
|
+
prefix: @prefix,
|
40
|
+
suffix: @suffix,
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Valerie
|
2
|
+
module Ordered
|
3
|
+
def invalid_position?
|
4
|
+
position? && position.to_i < 1
|
5
|
+
end
|
6
|
+
|
7
|
+
def position
|
8
|
+
@options[:position]
|
9
|
+
end
|
10
|
+
|
11
|
+
def position=(value)
|
12
|
+
@options[:position] = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def position?
|
16
|
+
position
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Valerie
|
2
|
+
class Organization
|
3
|
+
attr_reader :name, :department
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def new(*organization)
|
7
|
+
if organization.first.is_a?(Hash)
|
8
|
+
super(name: organization.first[:name], department: organization.first[:department])
|
9
|
+
else
|
10
|
+
name, department = organization.flatten
|
11
|
+
super(name:, department:)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_s(data)
|
16
|
+
name, department = data.gsub("ORG:", "").split(";")
|
17
|
+
new(name:, department:)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(name:, department: nil, **options)
|
22
|
+
@name, @department = name, department
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
"ORG:#{@name};#{department_part}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def present?
|
31
|
+
@name.to_s.length > 0 || @department.to_s.length > 0
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def department_part
|
36
|
+
if @department.to_s.empty?
|
37
|
+
fallback_department
|
38
|
+
else
|
39
|
+
@department
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def fallback_department
|
44
|
+
" \n"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'ordered'
|
2
|
+
|
3
|
+
module Valerie
|
4
|
+
class Phone
|
5
|
+
VALID_TYPES = %w[text voice fax cell video pager textphone].freeze
|
6
|
+
|
7
|
+
include Ordered
|
8
|
+
|
9
|
+
def self.from_s(data)
|
10
|
+
data = data[data.index("TEL;")..] unless data.start_with?("TEL;")
|
11
|
+
identifier = data.split(":").last
|
12
|
+
options = data.gsub("TEL", "").split(":").first.split(";").compact.filter { _1.to_s.include?('=')}.map { _1.downcase.split("=") }.to_h
|
13
|
+
|
14
|
+
new(identifier, **options)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :number, :options
|
18
|
+
|
19
|
+
def initialize(number, **options)
|
20
|
+
@number = number
|
21
|
+
@options = options.transform_keys!(&:to_sym)
|
22
|
+
@type = @options[:type] || 'voice'
|
23
|
+
|
24
|
+
raise ArgumentError, 'Invalid Position' if invalid_position?
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
@options[key]
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
parts = ['TEL']
|
33
|
+
|
34
|
+
parts << "PERF=#{@options[:position]}" if position?
|
35
|
+
parts << type_to_s if type?
|
36
|
+
|
37
|
+
parts.join(';') + ":#{@number}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
{
|
42
|
+
number: @tel,
|
43
|
+
type: @type,
|
44
|
+
position: @options[:position],
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def type?
|
50
|
+
@options[:type]
|
51
|
+
end
|
52
|
+
|
53
|
+
def type_to_s
|
54
|
+
if @options[:type].is_a?(Array)
|
55
|
+
"TYPE=\"#{@options[:type].join(',')}\""
|
56
|
+
else
|
57
|
+
"TYPE=#{@options[:type]}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/valerie.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class BirthdayTest < Minitest::Test
|
4
|
+
def test_from_s
|
5
|
+
Valerie::Birthday.from_s('BDAY:1999-11-04').to_date.tap do |date|
|
6
|
+
assert_equal(date.year, 1999)
|
7
|
+
assert_equal(date.month, 11)
|
8
|
+
assert_equal(date.day, 4)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_to_s
|
13
|
+
assert_equal(
|
14
|
+
Valerie::Birthday.from_s('BDAY:1999-11-04').to_s,
|
15
|
+
'BDAY:1999-11-04'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_to_h
|
20
|
+
Valerie::Birthday.from_s('BDAY:1999-11-04').to_h.tap do |hash|
|
21
|
+
assert_equal(hash[:year], 1999)
|
22
|
+
assert_equal(hash[:month], 11)
|
23
|
+
assert_equal(hash[:day], 4)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/test/card_test.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class VCardTest < Minitest::Test
|
4
|
+
def initialize(name)
|
5
|
+
super
|
6
|
+
@card = Valerie::Card.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_setting_name_with_hash_parameter
|
10
|
+
@card.name = { first_name: 'John', last_name: 'Doe' }
|
11
|
+
|
12
|
+
assert_equal(@card.name.first_name, 'John')
|
13
|
+
assert_equal(@card.name.last_name, 'Doe')
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_setting_name_with_string_parameter
|
17
|
+
@card.name = 'Adam Hull'
|
18
|
+
|
19
|
+
assert_equal(@card.name.first_name, 'Adam')
|
20
|
+
assert_equal(@card.name.last_name, 'Hull')
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_setting_organization_with_hash
|
24
|
+
@card.organization = { name: 'Hellotext', department: 'Product Development' }
|
25
|
+
|
26
|
+
assert_equal(@card.organization.name, 'Hellotext')
|
27
|
+
assert_equal(@card.organization.department, 'Product Development')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_setting_organization_with_string
|
31
|
+
@card.organization = ['Hellotext', 'Product Development']
|
32
|
+
|
33
|
+
assert_equal(@card.organization.name, 'Hellotext')
|
34
|
+
assert_equal(@card.organization.department, 'Product Development')
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_setting_gender_with_valid_identifier
|
38
|
+
assert_runs_without_errors do
|
39
|
+
@card.gender = 'Male'
|
40
|
+
assert_equal(@card.gender.sex, :male)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_setting_gender_with_invalid_identifier
|
45
|
+
assert_raises(ArgumentError) do
|
46
|
+
@card.gender = 'invalid-identifier'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_parsing_array
|
51
|
+
data = ['PRODID:-//Hellotext', 'N:Doe;John;;;', "ORG:HelloText;", "TEL;type=CELL:+598 94 000 000"]
|
52
|
+
|
53
|
+
Valerie::Card.parse(data).tap do |vcard|
|
54
|
+
assert_equal(vcard.name.first_name, 'John')
|
55
|
+
assert_equal(vcard.name.last_name, 'Doe')
|
56
|
+
|
57
|
+
assert_equal(vcard.organization.name, 'HelloText')
|
58
|
+
|
59
|
+
assert_equal(vcard.phones.first.number, '+598 94 000 000')
|
60
|
+
assert_equal(vcard.phones.first.options[:type], 'cell')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_parsing_string
|
65
|
+
data = "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Hellotext www.hellotext.com//EN\r\nN:Rosenbaum;Shira;;;\r\nTEL;:+598 94 987 924\r\nEND:VCARD"
|
66
|
+
|
67
|
+
Valerie::Card.parse(data).first.tap do |vcard|
|
68
|
+
assert_equal(vcard.name.first_name, 'Shira')
|
69
|
+
assert_equal(vcard.name.last_name, 'Rosenbaum')
|
70
|
+
assert_equal(vcard.phones.first.number, '+598 94 987 924')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class EmailCollectionTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@collection = Valerie::Collection::EmailCollection.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_add_email_as_string
|
9
|
+
email = @collection.add('ahmed@hellotext.com', type: :work)
|
10
|
+
|
11
|
+
assert_instance_of(Valerie::Email, email)
|
12
|
+
|
13
|
+
assert_equal(email.address, 'ahmed@hellotext.com')
|
14
|
+
assert_equal(email[:type], :work)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_add_email_as_hash
|
18
|
+
email = @collection.add({ address: 'john@hellotext.com', type: :home })
|
19
|
+
|
20
|
+
assert_instance_of(Valerie::Email, email)
|
21
|
+
assert_equal(email.address, 'john@hellotext.com')
|
22
|
+
assert_equal(email[:type], :home)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_add_email_as_email_object
|
26
|
+
email = Valerie::Email.new(address: 'ahmed@hellotext.com', type: :work)
|
27
|
+
@collection.add({ address: 'john@hellotext.com', type: :home })
|
28
|
+
|
29
|
+
assert_equal(@collection.add(email).position, 2)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_setting_the_position_for_each_inserted_email
|
33
|
+
email = @collection.add('ahmed@hellotext.com')
|
34
|
+
email2 = @collection.add('john@hellotext.com')
|
35
|
+
|
36
|
+
assert_equal(email[:position], 1)
|
37
|
+
assert_equal(email2[:position], 2)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class PhoneCollectionTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@collection = Valerie::Collection::PhoneCollection.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_add_phone_as_string
|
9
|
+
phone = @collection.add('1234567890', type: :work)
|
10
|
+
|
11
|
+
assert_instance_of(Valerie::Phone, phone)
|
12
|
+
|
13
|
+
assert_equal(phone.number, '1234567890')
|
14
|
+
assert_equal(phone[:type], :work)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_add_phone_as_phone_object
|
18
|
+
phone = Valerie::Phone.new('1234567890', type: :work)
|
19
|
+
@collection.add('0987654321', type: :home)
|
20
|
+
|
21
|
+
assert_equal(@collection.add(phone).position, 2)
|
22
|
+
end
|
23
|
+
end
|
data/test/email_test.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class EmailTest < Minitest::Test
|
4
|
+
def test_to_s_with_single_type
|
5
|
+
Valerie::Email.new(address: 'ahmed@hellotext.com', type: :work).tap do |email|
|
6
|
+
assert_equal(email.to_s.start_with?('EMAIL;TYPE=work:'), true)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_with_invalid_position
|
11
|
+
error = assert_raises(ArgumentError) do
|
12
|
+
Valerie::Email.new(address: 'ahmed@hellotext.com', position: -1)
|
13
|
+
end
|
14
|
+
|
15
|
+
assert_equal(error.message, 'Invalid Position')
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_to_s_with_preferred
|
19
|
+
Valerie::Email.new(address: 'ahmed@hellotext.com', type: :work, position: 1).tap do |email|
|
20
|
+
assert_equal(
|
21
|
+
email.to_s.include?(';PERF=1;'),
|
22
|
+
true
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/test/gender_test.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class GenderTest < Minitest::Test
|
4
|
+
def test_from_s
|
5
|
+
identifier = 'GENDER:M'
|
6
|
+
|
7
|
+
Valerie::Gender.from_s(identifier).tap do |entry|
|
8
|
+
assert_equal(entry.identifier, 'M')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_initialize_with_valid_sex_component_key
|
13
|
+
assert_runs_without_errors do
|
14
|
+
Valerie::Gender.new('male').tap do |entry|
|
15
|
+
assert_equal(entry.identifier, 'M')
|
16
|
+
assert_equal(entry.sex, :male)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_initialize_with_invalid_identifier
|
22
|
+
assert_raises(ArgumentError) do
|
23
|
+
Valerie::Gender.new('invalid-identifier')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_to_s
|
28
|
+
assert_equal(Valerie::Gender.new('M').to_s, 'GENDER:M')
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_to_h
|
32
|
+
Valerie::Gender.new('M').to_h.tap do |hash|
|
33
|
+
assert_equal(hash[:identifier], 'M')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/test/name_test.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class NameTest < Minitest::Test
|
4
|
+
def test_initialize_with_hash
|
5
|
+
Valerie::Name.new(first_name: 'Ahmed', last_name: 'Khattab').tap do |name|
|
6
|
+
assert_equal(name.to_h[:first_name], 'Ahmed')
|
7
|
+
assert_equal(name.to_h[:last_name], 'Khattab')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_initialize_with_string
|
12
|
+
Valerie::Name.new('Ahmed Khattab').tap do |name|
|
13
|
+
assert_equal(name.to_h[:first_name], 'Ahmed')
|
14
|
+
assert_equal(name.to_h[:last_name], 'Khattab')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class OrganizationTest < Minitest::Test
|
4
|
+
def test_new_constructor_with_array
|
5
|
+
Valerie::Organization.new('Hellotext', 'Product Engineer').tap do |org|
|
6
|
+
assert_equal(org.name, 'Hellotext')
|
7
|
+
assert_equal(org.department, 'Product Engineer')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_new_constructor_with_hash
|
12
|
+
Valerie::Organization.new(name: 'Hellotext', department: 'Product Engineer').tap do |org|
|
13
|
+
assert_equal(org.name, 'Hellotext')
|
14
|
+
assert_equal(org.department, 'Product Engineer')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_from_s
|
19
|
+
Valerie::Organization.from_s('ORG:Hellotext;Product Engineer').tap do |org|
|
20
|
+
assert_equal(org.name, 'Hellotext')
|
21
|
+
assert_equal(org.department, 'Product Engineer')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_to_s
|
26
|
+
Valerie::Organization.new(name: 'Hellotext', department: 'Product Engineer').tap do |org|
|
27
|
+
assert_equal(org.to_s, 'ORG:Hellotext;Product Engineer')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_present_when_name_is_set
|
32
|
+
Valerie::Organization.new(name: 'Hellotext').tap do |org|
|
33
|
+
assert_equal(org.present?, true)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_present_when_department_is_set
|
38
|
+
Valerie::Organization.new(department: 'Product Engineer').tap do |org|
|
39
|
+
assert_equal(org.present?, true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_present_when_none_is_set
|
44
|
+
Valerie::Organization.new.tap do |org|
|
45
|
+
assert_equal(org.present?, false)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/test/phone_test.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class PhoneTest < Minitest::Test
|
4
|
+
def test_to_s_with_single_type
|
5
|
+
Valerie::Phone.new('01000000000', type: :voice).tap do |phone|
|
6
|
+
assert_equal(phone.to_s.start_with?('TEL;TYPE=voice:'), true)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_to_s_with_multiple_types
|
11
|
+
Valerie::Phone.new('01000000000', type: %i[voice work]).tap do |phone|
|
12
|
+
assert_equal(phone.to_s, 'TEL;TYPE="voice,work":01000000000')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_with_invalid_position
|
17
|
+
error = assert_raises(ArgumentError) do
|
18
|
+
Valerie::Phone.new('01000000000', position: -1)
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_equal(error.message, 'Invalid Position')
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_to_s_with_position
|
25
|
+
Valerie::Phone.new('01000000000', type: :voice, position: 1).tap do |phone|
|
26
|
+
assert_equal(phone.to_s, 'TEL;PERF=1;TYPE=voice:01000000000')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: valerie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hellotext
|
@@ -45,7 +45,33 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
48
50
|
- lib/valerie.rb
|
51
|
+
- lib/valerie/address.rb
|
52
|
+
- lib/valerie/birthday.rb
|
53
|
+
- lib/valerie/card.rb
|
54
|
+
- lib/valerie/collection/address_collection.rb
|
55
|
+
- lib/valerie/collection/collection.rb
|
56
|
+
- lib/valerie/collection/email_collection.rb
|
57
|
+
- lib/valerie/collection/phone_collection.rb
|
58
|
+
- lib/valerie/core/parser.rb
|
59
|
+
- lib/valerie/email.rb
|
60
|
+
- lib/valerie/gender.rb
|
61
|
+
- lib/valerie/name.rb
|
62
|
+
- lib/valerie/ordered.rb
|
63
|
+
- lib/valerie/organization.rb
|
64
|
+
- lib/valerie/phone.rb
|
65
|
+
- test/birthday_test.rb
|
66
|
+
- test/card_test.rb
|
67
|
+
- test/collection/email_collection_test.rb
|
68
|
+
- test/collection/phone_collection_test.rb
|
69
|
+
- test/email_test.rb
|
70
|
+
- test/gender_test.rb
|
71
|
+
- test/name_test.rb
|
72
|
+
- test/organization_test.rb
|
73
|
+
- test/phone_test.rb
|
74
|
+
- test/test_helper.rb
|
49
75
|
homepage: https://github.com/hellotext/valerie
|
50
76
|
licenses:
|
51
77
|
- MIT
|