Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/remove timex #5

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ import Config
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
import_config "#{config_env()}.exs"
Shakadak marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Config
3 changes: 3 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Config

config :elixir, :time_zone_database, Tz.TimeZoneDatabase
62 changes: 44 additions & 18 deletions lib/calibex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,41 @@ defmodule Calibex do
@moduledoc """
Calibex allows you to handle ICal file format.

In the same way as the `mailibex` library, Calibex allows bijective coding/decoding :
making it possible to modify an ical and to keep all fields and struct of the initial ical.
In the same way as the [`mailibex`](https://github.com/kbrw/mailibex) library, Calibex
allows bijective coding/decoding : making it possible to modify an ical and to keep all
fields and struct of the initial ical.

The ICal elixir term is exactly a representation of the ICal file format : for instance :
The ICal elixir term is exactly a representation of the ICal file format.

[vcalendar: [[
for instance :

```
[
vcalendar: [
[
prodid: "-//Google Inc//Google Calendar 70.9054//EN",
version: "2.0",
calscale: "GREGORIAN",
vevent: [[
dtstart: %DateTime{},
dtend: %DateTime{},
organizer: [cn: "My Name",value: "mailto:[email protected]"],
attendee: [cutype: "INDIVIDUAL",role: "REQ-PARTICIPANT",partstat: "NEEDS-ACTION",rsvp: true, cn: "Moi",
"x-num-guests": 0, value: "mailto:[email protected]"],
]]]]]
calscale: "GREGORIAN",
vevent: [
[
dtstart: %DateTime{},
dtend: %DateTime{},
organizer: [cn: "My Name", value: "mailto:[email protected]"],
attendee: [
cutype: "INDIVIDUAL",
role: "REQ-PARTICIPANT",
partstat: "NEEDS-ACTION",
rsvp: true,
cn: "Moi",
"x-num-guests": 0,
value: "mailto:[email protected]"
]
]
]
]
]
]
```

`encode/1` and `decode/1` parse and format an ICal from this terms : see
functions doc to find encoding rules.
Expand All @@ -38,9 +57,15 @@ defmodule Calibex do
## Example usage : email event request generation

```
Calibex.request(dtstart: Timex.now, dtend: Timex.shift(Timex.now,hours: 1), summary: "Mon évènement",
organizer: "[email protected]", attendee: "[email protected]", attendee: "[email protected]")
|> Calibex.encode
Calibex.request(
dtstart: Timex.now(),
dtend: Timex.shift(Timex.now(), hours: 1),
Shakadak marked this conversation as resolved.
Show resolved Hide resolved
summary: "Mon évènement",
organizer: "[email protected]",
attendee: "[email protected]",
attendee: "[email protected]"
)
|> Calibex.encode()
```
"""

Expand Down Expand Up @@ -74,8 +99,10 @@ defmodule Calibex do
"""
defdelegate new(event,fill_attrs), to: Calibex.Helper

@doc "see `new/2`, default fill_attrs are
`[:prodid, :version, :calscale, :organizer, :attendee, :cutype, :role, :partstat, :rsvp, :x_num_guests]`"
@doc """
see `new/2`, default fill_attrs are
`[:prodid, :version, :calscale, :organizer, :attendee, :cutype, :role, :partstat, :rsvp, :x_num_guests]`
"""
defdelegate new(event), to: Calibex.Helper

@doc """
Expand Down Expand Up @@ -116,7 +143,6 @@ defmodule Calibex do
standard rsvp enabled attendee, waiting for event acceptance
- `:organizer` TRANSFORM an email string into a `[cn: email,value: "mailto:"<>email]` props value.
- `:attendee` TRANSFORM an email string into a `[cn: email,value: "mailto:"<>email]` props value.

"""
defdelegate all_fill_attrs(), to: Calibex.Helper
end
24 changes: 16 additions & 8 deletions lib/codec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ defmodule Calibex.Codec do
"#{encode_key pk}=#{encode_value pv}"
end) |> Enum.join(";")}:#{encode_value v[:value]}"
end
def encode_value({k,v}), do: "#{encode_key k}:#{encode_value v}" # encode standard key value
def encode_value(%DateTime{}=dt), do: %{dt|microsecond: {0,0}} |> Timex.to_datetime("UTC") |> Timex.format!("{ISO:Basic:Z}")
def encode_value(atom) when is_atom(atom), do: atom |> to_string() |> String.upcase

# encode standard key value
def encode_value({k, v}), do: "#{encode_key(k)}:#{encode_value(v)}"

def encode_value(%DateTime{} = dt) do
dt
|> DateTime.shift_zone!("Etc/UTC")
|> Calendar.strftime("%Y%m%dT%H%M%SZ")
end

def encode_value(atom) when is_atom(atom), do: atom |> to_string() |> String.upcase()
def encode_value(other), do: other

def encode_key(k) do
Expand All @@ -33,10 +41,10 @@ defmodule Calibex.Codec do
def decode(bin), do: bin |> decode_lines |> decode_blocks

def decode_lines(bin) do # split by unfolded line
bin |> String.splitter(["\r\n","\n"]) |> Enum.flat_map_reduce(nil,fn
bin |> String.splitter(["\r\n","\n"]) |> Enum.flat_map_reduce(nil,fn
" "<>rest,acc-> {[],acc<>rest}
line,prevline-> {prevline && [String.replace(prevline,"\\n","\n")] || [],line}
end) |> elem(0)
end) |> elem(0)
end
def decode_blocks([]), do: []
def decode_blocks(["BEGIN:"<>binkey|rest]) do # decode each block as a list
Expand All @@ -53,14 +61,14 @@ defmodule Calibex.Codec do
[keyprops,val] = String.split(prop,":",parts: 2)
case String.split(keyprops,";") do
[key]-> {decode_key(key),val}
[key|props]->
[key|props]->
props = props |> Enum.map(fn prop->
[k,v] = String.split(prop,"=")
{decode_key(k),v}
end)
{decode_key(key),[{:value,val}|props]}
{decode_key(key),[{:value,val}|props]}
end
end
def decode_key(bin), do:
def decode_key(bin), do:
bin |> String.replace("-","_") |> String.downcase |> String.to_atom
end
6 changes: 3 additions & 3 deletions lib/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ defmodule Calibex.Helper do
def augment(_,val,_vals), do: val

def default(:uid,vals), do: :crypto.hash(:sha,:erlang.term_to_binary(vals)) |> Base.encode16(case: :lower)
def default(:last_modified,_vals), do: Timex.now
def default(:last_modified,_vals), do: DateTime.now!("Etc/UTC")
def default(:sequence,_vals), do: 0
def default(:dtstamp,_vals), do: Timex.now
def default(:created,_vals), do: Timex.now
def default(:dtstamp,_vals), do: DateTime.now!("Etc/UTC")
def default(:created,_vals), do: DateTime.now!("Etc/UTC")
Shakadak marked this conversation as resolved.
Show resolved Hide resolved
def default(:status,_vals), do: :confirmed
def default(:cutype,_vals), do: "INDIVIDUAL"
def default(:role,_vals), do: "REQ-PARTICIPANT"
Expand Down
9 changes: 5 additions & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ defmodule Calibex.Mixfile do
end

def application do
[applications: [:timex]]
[extra_applications: [:crypto]]
end

defp deps do
[{:timex, "~> 3.1"},
{:ex_doc, ">= 0.0.0", only: :dev}]
[
{:ex_doc, ">= 0.0.0", only: :dev},
{:tz, "~> 0.25.1", only: :test}
]
end

defp package do
Expand Down
17 changes: 5 additions & 12 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
%{"certifi": {:hex, :certifi, "1.1.0", "c9b71a547016c2528a590ccfc28de786c7edb74aafa17446b84f54e04efc00ee", [:rebar3], []},
"combine": {:hex, :combine, "0.9.6", "8d1034a127d4cbf6924c8a5010d3534d958085575fa4d9b878f200d79ac78335", [:mix], []},
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.15.1", "d5f9d588fd802152516fccfdb96d6073753f77314fcfee892b15b6724ca0d596", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []},
"hackney": {:hex, :hackney, "1.8.0", "8388a22f4e7eb04d171f2cf0285b217410f266d6c13a4c397a6c22ab823a486c", [:rebar3], [{:certifi, "1.1.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []},
"timex": {:hex, :timex, "3.1.13", "48b33162e3ec33e9a08fb5f98e3f3c19c3e328dded3156096c1969b77d33eef0", [:mix], [{:combine, "~> 0.7", [hex: :combine, optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, optional: false]}]},
"tzdata": {:hex, :tzdata, "0.5.12", "1c17b68692c6ba5b6ab15db3d64cc8baa0f182043d5ae9d4b6d35d70af76f67b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, optional: false]}]}}
%{
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm", "59514c4a207f9f25c5252e09974367718554b6a0f41fe39f7dc232168f9cb309"},
"ex_doc": {:hex, :ex_doc, "0.15.1", "d5f9d588fd802152516fccfdb96d6073753f77314fcfee892b15b6724ca0d596", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm", "00d41bec3590533279b90870d68ebc4fdfcc3a1c7b6629796167b49bfa199d54"},
"tz": {:hex, :tz, "0.25.1", "918eef2965c952919d9108d97b7e0c332bc707943170d9060352e945a0bd2c64", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.4", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "6f34779ae6d05de78fdfc99244b83455c140a0ac0ac18b44d8129a48cc587294"},
}
159 changes: 110 additions & 49 deletions test/calibex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,59 @@ defmodule CalibexTest do
use ExUnit.Case

test "ical encoding" do
res = Calibex.encode(vcalendar: [[
prodid: "-//Google Inc//Google Calendar 70.9054//EN",
version: "2.0",
calscale: "GREGORIAN",
method: "REQUEST",
vevent: [[
dtstart: Timex.to_datetime({{2017,5,14},{9,0,0}},"Europe/Paris"),
dtend: Timex.to_datetime({{2017,5,14},{10,0,0}},"Europe/Paris"),
dtstamp: Timex.to_datetime({{2017,5,13},{10,52,50}},"UTC"),
organizer: [cn: "Arnaud Wetzel",value: "mailto:[email protected]"],
uid: "[email protected]",
attendee: [cutype: "INDIVIDUAL",role: "REQ-PARTICIPANT",partstat: "NEEDS-ACTION",rsvp: true, cn: "[email protected]",
"x-num-guests": 0, value: "mailto:[email protected]"],
attendee: [cutype: "INDIVIDUAL",role: "REQ-PARTICIPANT",partstat: "ACCEPTED",rsvp: true, cn: "Arnaud Wetzel",
x_num_guests: 0, value: "mailto:[email protected]"],
created: Timex.to_datetime({{2017,5,13},{10,50,32}},"UTC"),
description: String.trim_trailing("""
Cet événement est associé à un appel vidéo Google Hangouts.
Participer : https://plus.google.com/hangouts/_/exampleexampl.com/arnaud-wetzel?hceid=YXJuYXVkLndldHplbEBrYnJ3YWR2ZW50dXJlLmNvbQ.r30al68kn0b2epd9af6kqjg8rg&hs=121

Affichez votre événement sur la page https://www.google.com/calendar/event?action=VIEW&eid=cjMwYWw2OGtuMGIyZXBkOWFmNmtxamc4cmcgYXJuYXVkd2V0emVsQHlhaG9vLmNvbQ&tok=MzEjYXJuYXVkLndldHplbEBrYnJ3YWR2ZW50dXJlLmNvbTdjMzQ0ZGFjN2FlYTM2OGI2NzEzNjAzZjVmNzk2NDVkMjA5ZDc0YjQ&ctz=Europe/Paris&hl=fr.
"""),
last_modified: Timex.to_datetime({{2017,5,13},{10,52,49}},"UTC"),
location: "",
sequence: 0,
status: "CONFIRMED",
summary: "c'est un évènement de test",
transp: "OPAQUE"
]]]])
res =
Calibex.encode(
vcalendar: [
[
prodid: "-//Google Inc//Google Calendar 70.9054//EN",
version: "2.0",
calscale: "GREGORIAN",
method: "REQUEST",
vevent: [
[
dtstart: DateTime.new!(~D[2017-05-14], ~T[09:00:00], "Europe/Paris"),
dtend: DateTime.new!(~D[2017-05-14], ~T[10:00:00], "Europe/Paris"),
dtstamp: DateTime.new!(~D[2017-05-13], ~T[10:52:50], "UTC"),
organizer: [cn: "Arnaud Wetzel", value: "mailto:[email protected]"],
uid: "[email protected]",
attendee: [
cutype: "INDIVIDUAL",
role: "REQ-PARTICIPANT",
partstat: "NEEDS-ACTION",
rsvp: true,
cn: "[email protected]",
"x-num-guests": 0,
value: "mailto:[email protected]"
],
attendee: [
cutype: "INDIVIDUAL",
role: "REQ-PARTICIPANT",
partstat: "ACCEPTED",
rsvp: true,
cn: "Arnaud Wetzel",
x_num_guests: 0,
value: "mailto:[email protected]"
],
created: DateTime.new!(~D[2017-05-13], ~T[10:50:32], "Etc/UTC"),
description:
String.trim_trailing("""
Cet événement est associé à un appel vidéo Google Hangouts.
Participer : https://plus.google.com/hangouts/_/exampleexampl.com/arnaud-wetzel?hceid=YXJuYXVkLndldHplbEBrYnJ3YWR2ZW50dXJlLmNvbQ.r30al68kn0b2epd9af6kqjg8rg&hs=121

Affichez votre événement sur la page https://www.google.com/calendar/event?action=VIEW&eid=cjMwYWw2OGtuMGIyZXBkOWFmNmtxamc4cmcgYXJuYXVkd2V0emVsQHlhaG9vLmNvbQ&tok=MzEjYXJuYXVkLndldHplbEBrYnJ3YWR2ZW50dXJlLmNvbTdjMzQ0ZGFjN2FlYTM2OGI2NzEzNjAzZjVmNzk2NDVkMjA5ZDc0YjQ&ctz=Europe/Paris&hl=fr.
"""),
last_modified: DateTime.new!(~D[2017-05-13], ~T[10:52:49], "Etc/UTC"),
location: "",
sequence: 0,
status: "CONFIRMED",
summary: "c'est un évènement de test",
transp: "OPAQUE"
]
]
]
]
)

assert res == File.read!("test/fixtures/invite.ics")
end

Expand All @@ -45,25 +69,62 @@ defmodule CalibexTest do
#end

test "ical helpers" do
req = Calibex.request(dtstart: Timex.now, dtend: Timex.shift(Timex.now,hours: 1), summary: "Mon évènement",
organizer: "[email protected]", attendee: "[email protected]", attendee: "[email protected]")
assert [vcalendar: [[prodid: "-//KBRW//Calibex 0.1.0//EN", version: "2.0",
calscale: "GREGORIAN", method: "REQUEST",
vevent: [[uid: _,
last_modified: %DateTime{},
sequence: 0, dtstamp: %DateTime{},
created: %DateTime{},
status: :confirmed,
dtstart: %DateTime{},
dtend: %DateTime{},
summary: "Mon évènement",
organizer: [cn: "[email protected]",
value: "mailto:[email protected]"],
attendee: [cutype: "INDIVIDUAL", role: "REQ-PARTICIPANT",
partstat: "NEEDS-ACTION", rsvp: true, x_num_guests: 0,
cn: "[email protected]", value: "mailto:[email protected]"],
attendee: [cutype: "INDIVIDUAL", role: "REQ-PARTICIPANT",
partstat: "NEEDS-ACTION", rsvp: true, x_num_guests: 0, cn: "[email protected]",
value: "mailto:[email protected]"]]]]]] = req
now = DateTime.utc_now()

req =
Calibex.request(
dtstart: now,
dtend: DateTime.add(now, 3_600, :second),
summary: "Mon évènement",
organizer: "[email protected]",
attendee: "[email protected]",
attendee: "[email protected]"
)

assert [
vcalendar: [
[
prodid: "-//KBRW//Calibex 0.1.0//EN",
version: "2.0",
calscale: "GREGORIAN",
method: "REQUEST",
vevent: [
[
uid: _,
last_modified: %DateTime{},
sequence: 0,
dtstamp: %DateTime{},
created: %DateTime{},
status: :confirmed,
dtstart: %DateTime{},
dtend: %DateTime{},
summary: "Mon évènement",
organizer: [
cn: "[email protected]",
value: "mailto:[email protected]"
],
attendee: [
cutype: "INDIVIDUAL",
role: "REQ-PARTICIPANT",
partstat: "NEEDS-ACTION",
rsvp: true,
x_num_guests: 0,
cn: "[email protected]",
value: "mailto:[email protected]"
],
attendee: [
cutype: "INDIVIDUAL",
role: "REQ-PARTICIPANT",
partstat: "NEEDS-ACTION",
rsvp: true,
x_num_guests: 0,
cn: "[email protected]",
value: "mailto:[email protected]"
]
]
]
]
]
] = req
end
end
Loading