timex_datalink_caldav 0.3.0 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d31450cb602c3c36c121b4ffd2240664ed8f9e550388a246f1c5e169d12f7fa
4
- data.tar.gz: 351b4213184ca2652205c80e99b7997538385bd0fab7e6ee3f4a6a57f1977247
3
+ metadata.gz: 8301c0dee4e085d6ebbbebbcd41872211625f6798980f688cf66765550519622
4
+ data.tar.gz: 30171ce1fcdb85fa57b17121982c85ab5805cdbef1f12106d56561397bd82ee6
5
5
  SHA512:
6
- metadata.gz: '08bf1aa6454647ccc74a568cb0e54e5b9724394da3f0d8ff6a8bcb0205d63ddb35208b40d99cbdbdab0be8c6b6e5c0c6febdb9db3369c415bad5cfdba7be7cb5'
7
- data.tar.gz: c2334ebf8d3d5515f4bce340808b61cacdc39ccdeaad6d2b7e7e356340fb97cf049c0316d1587d37b74dad9f4a593dcf91be66399bbfc3075018ef023a0e267d
6
+ metadata.gz: 31a43cf2e995bcd29d51ebae9d3a30002239ac0bea6862f97b0f967daf25734409432e75fbced7727ecc2126e6ec509bdb0c09fd6dc7fcbffed87e9c124d73a5
7
+ data.tar.gz: 5b8b1f14b62a03c6354c2b555651ae6336b6248ddc633b4749b966929c2e8b6e39c8d6f8845cb57329a46a5dc18c35522bd8a193c115efa781922ead52853705
data/Gemfile CHANGED
@@ -20,3 +20,13 @@ gem "icalendar-recurrence", "~> 1.1"
20
20
  gem "activesupport", "~> 7.0"
21
21
 
22
22
  gem "tzinfo", "~> 2.0"
23
+
24
+ gem "yaml", "~> 0.2.1"
25
+
26
+ gem "text", "~> 1.3"
27
+
28
+ gem "humanize", "~> 2.5"
29
+
30
+ gem "down", "~> 5.4"
31
+
32
+ gem "ruby-progressbar", "~> 1.13"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- timex_datalink_caldav (0.2.0)
4
+ timex_datalink_caldav (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -24,6 +24,8 @@ GEM
24
24
  diff-lcs (1.5.0)
25
25
  domain_name (0.5.20190701)
26
26
  unf (>= 0.0.5, < 1.0.0)
27
+ down (5.4.1)
28
+ addressable (~> 2.8)
27
29
  ffi (1.15.5)
28
30
  ffi-compiler (1.0.1)
29
31
  ffi (>= 1.0.0)
@@ -36,6 +38,7 @@ GEM
36
38
  http-cookie (1.0.5)
37
39
  domain_name (~> 0.5)
38
40
  http-form_data (2.3.0)
41
+ humanize (2.5.1)
39
42
  i18n (1.13.0)
40
43
  concurrent-ruby (~> 1.0)
41
44
  icalendar (2.8.0)
@@ -67,8 +70,10 @@ GEM
67
70
  diff-lcs (>= 1.2.0, < 2.0)
68
71
  rspec-support (~> 3.12.0)
69
72
  rspec-support (3.12.0)
73
+ ruby-progressbar (1.13.0)
70
74
  rubyserial (0.6.0)
71
75
  ffi (~> 1.9, >= 1.9.3)
76
+ text (1.3.1)
72
77
  timex_datalink_client (0.11.0)
73
78
  activemodel (~> 7.0.4)
74
79
  crc (~> 0.4.2)
@@ -79,6 +84,7 @@ GEM
79
84
  unf (0.1.4)
80
85
  unf_ext
81
86
  unf_ext (0.0.8.2)
87
+ yaml (0.2.1)
82
88
 
83
89
  PLATFORMS
84
90
  arm64-darwin-22
@@ -86,13 +92,18 @@ PLATFORMS
86
92
  DEPENDENCIES
87
93
  activesupport (~> 7.0)
88
94
  calendav (~> 0.4.0)
95
+ down (~> 5.4)
96
+ humanize (~> 2.5)
89
97
  icalendar (~> 2.8)
90
98
  icalendar-recurrence (~> 1.1)
91
99
  rake (~> 13.0)
92
100
  rspec (~> 3.12)
101
+ ruby-progressbar (~> 1.13)
102
+ text (~> 1.3)
93
103
  timex_datalink_caldav!
94
104
  timex_datalink_client (~> 0.11.0)
95
105
  tzinfo (~> 2.0)
106
+ yaml (~> 0.2.1)
96
107
 
97
108
  BUNDLED WITH
98
109
  2.4.10
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Willy Hardy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # TimexDatalinkCaldav
2
2
 
3
- TimexDatalinkCaldav is a simple Ruby gem designed to sync events from a CalDAV server to a Timex Datalink watch. It can also be used as a standalone command-line interface (CLI) tool.
3
+ TimexDatalinkCaldav is a simple Ruby gem designed to sync events from a CalDAV server or an ical formatted ics file to a Timex Datalink watch. It can also be used as a standalone command-line interface (CLI) tool.
4
+
5
+ ## Pre-requisites
6
+
7
+ If you need to install Ruby, follow the Ruby installation instructions [here](https://www.ruby-lang.org/en/documentation/installation/).
4
8
 
5
9
  ## Installation
6
10
 
@@ -13,9 +17,7 @@ gem install timex_datalink_caldav
13
17
  Or add this line to your application's Gemfile:
14
18
 
15
19
  ```ruby
16
- source "https://rubygems.pkg.github.com/wjhrdy" do
17
- gem "timex_datalink_caldav"
18
- end
20
+ gem "timex_datalink_caldav"
19
21
  ```
20
22
 
21
23
  And then execute:
@@ -33,26 +35,47 @@ Here's an example of how to use the tool in your Ruby code:
33
35
  ```ruby
34
36
  require 'timex_datalink_caldav'
35
37
 
36
- client = TimexDatalinkCaldav::Client.new(your_username, your_password, your_server_uri, your_device)
37
- client.sync_to_watch
38
+ client = TimexDatalinkCaldav::Client.new(your_username, your_password, your_server_uri, your_device, your_protocol_version, days_forward)
39
+
40
+ client.parse_events
41
+ client.write_to_watch
38
42
  ```
39
43
 
40
44
  ### As a CLI Tool
41
45
 
42
- After installing the gem, you can use it as a CLI tool:
46
+ After installing the gem, you can use it as a CLI tool. You can specify the CalDAV server details directly on the command line:
43
47
 
44
48
  ```sh
45
- timex_datalink_caldav -u https://caldavendpoint.com -n your_username -p your_password -d your_device
49
+ timex_datalink_caldav -u https://caldavendpoint.com -n your_username -p your_password -d your_device -a your_protocol_version -f days_forward
46
50
  ```
47
51
 
48
- Please replace `caldavendpoint.com` `your_username`, `your_password`, and `your_device` with your actual CalDAV server, username, password, and serial device respectively.
52
+ Please replace `https://caldavendpoint.com`, `your_username`, `your_password`, `your_device`, `your_protocol_version`, and `days_forward` with your actual CalDAV server URI, username, password, serial device, protocol version, and number of days to look forward for events, respectively.
53
+
54
+ Or you can provide these details in a configuration file:
55
+
56
+ ```sh
57
+ timex_datalink_caldav -c config.yml -a 1 -d /dev/tty.usbmodem0000000000001 -f 7
58
+ ```
59
+
60
+ The configuration file should be a YAML file in the following format:
61
+
62
+ ```yaml
63
+ endpoints:
64
+ - uri: https://www.google.com/calendar/dav/email@gmail.com/events
65
+ user: email@gmail.com
66
+ password: app_password
67
+ - uri: https://caldavendpoint2.com
68
+ user: your_username2
69
+ password: your_password2
70
+ - uri: https://icalendpoint.com/example.ics
71
+ ```
49
72
 
50
- The device is a serial device that flashes an led when it receives data. On Linux, this is usually `/dev/ttyUSB0`. On macOS, this is usually `/dev/cu.usbserial-0001`. On Windows, this is usually `COM1`.
73
+ The device is a serial device that flashes an led when it receives data. On Linux, this is usually `/dev/tty*`. On macOS, this is usually `$(ls /dev/tty.usbmodem* | head -n 1)`. On Windows, this is usually `COM1`.
51
74
 
52
- If you want to use this I highly recommend pairing it with the Raspberry Pi Pico and [this project](https://github.com/famiclone6502/DIY_Datalink_Adapter). It is the cheapest and easiest way to get a serial device that works with the Timex Datalink watch.
75
+ If you want to use this, I highly recommend pairing it with the Raspberry Pi Pico and [this project](https://github.com/famiclone6502/DIY_Datalink_Adapter). It is the cheapest and easiest way to get a serial device that works with the Timex Datalink watch.
53
76
 
54
- ## Note
77
+ ## Notes
55
78
 
56
- Ensure you have the necessary dependencies installed on your system and you have the correct permissions to access the specified device.
79
+ - This gem is not affiliated with Timex, nor is it affiliated with any CalDAV server. It is simply a tool that I wrote to sync my events from my CalDAV server to my Timex Datalink watch.
57
80
 
58
- The tool currently filters down to events that have attendees and converts event times to Eastern Standard Time (EST). Events are sorted by time before syncing to the watch.
81
+ - This gem uses the anniversary feature for full day events, and the appointments feature for events with a start and end time.
data/bin/get_ebrain_db ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'down'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+ require 'ruby-progressbar'
7
+
8
+ # Define URL and file names
9
+ url = "https://archive.org/download/ebrain-1.1.6/ebrain-1.1.6.iso"
10
+ iso_file = File.join(Dir.tmpdir, "ebrain-1.1.6.iso")
11
+ msi_file = File.join(Dir.tmpdir, "eBrain.MSI")
12
+ cab_file = File.join(Dir.tmpdir, "Cabs.w4.cab")
13
+ mdb_file1 = File.join(Dir.tmpdir, "pcvocab.mdb1")
14
+ mdb_file = "pcvocab.mdb" # This is in the current directory
15
+
16
+ # Check if 7zip is installed
17
+ unless system('which 7z > /dev/null 2>&1')
18
+ puts "7zip is not installed. Please download and install 7zip from https://www.7-zip.org/download.html"
19
+ exit 1
20
+ end
21
+
22
+ # Initialize the progress bar
23
+ progressbar = nil
24
+
25
+ # Download the ISO file
26
+ puts "Downloading ISO file..."
27
+ Down.download(url,
28
+ destination: iso_file,
29
+ content_length_proc: ->(content_length) {
30
+ progressbar = ProgressBar.create(
31
+ title: 'Download Progress',
32
+ total: content_length,
33
+ format: '%a %bᗧ%i %p%% %t, Estimated time: %e'
34
+ )
35
+ },
36
+ progress_proc: ->(progress) {
37
+ progressbar.progress = progress
38
+ }
39
+ )
40
+
41
+ # Extract the MSI file from the ISO
42
+ puts "Extracting MSI file..."
43
+ system("7z e #{iso_file} -o#{File.dirname(msi_file)} #{File.basename(msi_file)}")
44
+
45
+ # Extract the CAB file from the MSI
46
+ puts "Extracting CAB file..."
47
+ system("7z e #{msi_file} -o#{File.dirname(cab_file)} #{File.basename(cab_file)}")
48
+
49
+ # Extract the MDB file from the CAB
50
+ puts "Extracting MDB file..."
51
+ system("7z e #{cab_file} -o#{File.dirname(mdb_file1)} #{File.basename(mdb_file1)}")
52
+
53
+ # Rename and move the MDB file to the current directory
54
+ puts "Renaming and moving MDB file..."
55
+ FileUtils.mv mdb_file1, mdb_file
56
+
57
+ puts "Done."
@@ -0,0 +1,7 @@
1
+ # Examples
2
+
3
+ This directory contains sample configurations and code for the timex_datalink_caldav gem.
4
+
5
+ ## Configuration
6
+
7
+ `caldav.yaml.sample` is a sample configuration file for CalDAV endpoints. You should rename it to `caldav.yaml` and adjust the URI, username, and password for your endpoints.
@@ -0,0 +1,8 @@
1
+ endpoints:
2
+ - uri: https://example.com/caldav1
3
+ user: user1
4
+ password: password1
5
+ - uri: https://example.com/caldav2
6
+ user: user2
7
+ password: password2
8
+ - uri: https://example.com/example.ical
@@ -1,25 +1,30 @@
1
1
  require 'calendav'
2
+ require 'icalendar'
2
3
  require 'icalendar/recurrence'
3
4
  require 'timex_datalink_client'
4
5
  require 'active_support/time'
5
6
  require 'tzinfo'
7
+ require 'humanize'
8
+ require_relative 'similar_word'
6
9
 
7
10
  module TimexDatalinkCaldav
8
11
  class Client
9
- def initialize(user, password, server_url, serial_device, protocol_version)
12
+ def initialize(user, password, server_url, serial_device, protocol_version, days_forward = 1)
10
13
  @user = user
11
14
  @password = password
12
15
  @server_url = server_url
13
16
  @serial_device = serial_device
17
+ @days_forward = days_forward
14
18
  @protocol_version = protocol_version.to_i
15
19
  @protocol_class = case @protocol_version
16
- when 1 then TimexDatalinkClient::Protocol1
17
- when 3 then TimexDatalinkClient::Protocol3
18
- when 4 then TimexDatalinkClient::Protocol4
19
- else
20
- raise ArgumentError, "Invalid protocol version: #{@protocol_version}"
21
- end
22
- end
20
+ when 1 then TimexDatalinkClient::Protocol1
21
+ when 3 then TimexDatalinkClient::Protocol3
22
+ when 4 then TimexDatalinkClient::Protocol4
23
+ when 7 then TimexDatalinkClient::Protocol7
24
+ else
25
+ raise ArgumentError, "Invalid protocol version: #{@protocol_version}"
26
+ end
27
+ end
23
28
 
24
29
  def get_localzone
25
30
  TZInfo::Timezone.get(TZInfo::Timezone.all_country_zones.detect {|z| z.period_for_local(Time.now).utc_total_offset == Time.now.utc_offset}.identifier)
@@ -33,49 +38,140 @@ module TimexDatalinkCaldav
33
38
  authentication: :basic_auth
34
39
  )
35
40
 
36
- # Create a new client with the credentials
37
- client = Calendav.client(credentials)
41
+ if @user && @password
42
+ # Create a new client with the credentials
43
+ client = Calendav.client(credentials)
38
44
 
39
- # Get events from the calendar for the next day
40
- client.events.list(@server_url, from: Time.now, to: Time.now + 24*60*60)
45
+ # Get events from the calendar for the next day
46
+ caldav_events = client.events.list(@server_url, from: Time.now, to: Time.now + @days_forward*24*60*60)
47
+ events = caldav_events.map { |event| Icalendar::Event.parse(event.calendar_data).first }
48
+ else
49
+ cal_file = URI.open(@server_url)
50
+ cals = Icalendar::Calendar.parse(cal_file)
51
+ cal = cals.first
52
+ events = cal.events
53
+ end
54
+
55
+ events
41
56
  end
42
57
 
43
- def sync_to_watch
58
+ def parse_events
44
59
  events = get_events
45
-
60
+
46
61
  appointments = []
62
+ anniversaries = []
47
63
  appointment_map = {} # Used to avoid duplicate appointments
48
-
64
+ anniversary_map = {} # Used to avoid duplicate anniversaries
65
+
66
+ phrase_builder = SimlarWord.new(database: "pcvocab.mdb") if @protocol_version == 7
67
+
49
68
  events.each do |event|
50
- ical_events = Icalendar::Event.parse(event.calendar_data)
51
- if ical_events.any?
52
- ical_event = ical_events.first
53
- if ical_event.attendee&.any? # Exclude events without attendees
54
- next_occurrence = ical_event.occurrences_between(Time.now, Time.now + 24*60*60).first
55
- if next_occurrence
56
- puts get_localzone
57
- est_time = next_occurrence.start_time.in_time_zone(get_localzone)
58
- key = "#{est_time}_#{ical_event.summary.to_s}"
59
- unless appointment_map[key] # Check if the event is already in the map
60
- puts "Adding appointment: #{ical_event.summary.to_s} at time #{est_time}"
61
- appointment = @protocol_class::Eeprom::Appointment.new(
62
- time: est_time,
63
- message: ical_event.summary.to_s
64
- )
65
- appointments << appointment
66
- appointment_map[key] = true
67
- end
68
- end
69
+ next unless event && event.dtstart && event.dtend
70
+
71
+ summary_words = parse_summary(event)
72
+
73
+ occurrences = event.occurrences_between(Time.now, Time.now + @days_forward*24*60*60)
74
+
75
+ if @protocol_version == 7
76
+ if all_day_event?(event)
77
+ add_anniversary_event(anniversary_map, anniversaries, phrase_builder, summary_words, occurrences)
78
+ else
79
+ add_appointment_event(appointment_map, appointments, phrase_builder, summary_words, occurrences)
80
+ end
81
+ else
82
+ if all_day_event?(event)
83
+ add_anniversary(event, anniversary_map, anniversaries, summary_words, occurrences)
84
+ else
85
+ add_appointment(event, appointment_map, appointments, summary_words, occurrences)
69
86
  end
70
87
  end
71
88
  end
72
-
73
- # Sort the appointments by time
74
- appointments.sort_by! { |appointment| appointment.time }
75
- write_to_watch(appointments)
89
+
90
+ [appointments, anniversaries]
91
+ end
92
+
93
+ def all_day_event?(event)
94
+ (event.dtend.to_date - event.dtstart.to_date == 1) && event.dtstart.to_datetime.hour == 0 && event.dtstart.to_datetime.min == 0 && event.dtend.to_datetime.hour == 0 && event.dtend.to_datetime.min == 0
95
+ end
96
+
97
+ def parse_summary(event)
98
+ event.summary.to_s.split.map do |word|
99
+ if word =~ /\A[a-zA-Z]+\z/
100
+ word
101
+ elsif word =~ /\A\d+\z/
102
+ word.to_i.humanize
103
+ end
104
+ end.compact
105
+ end
106
+
107
+ def add_appointment_event(appointment_map, appointments, phrase_builder, summary_words, occurrences)
108
+ occurrences.each do |occurrence|
109
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
110
+ key = "#{est_time}_#{summary_words}"
111
+ unless appointment_map[key]
112
+ puts "Adding appointment event: #{summary_words.join(' ')} at time #{est_time}"
113
+ event_phrase = phrase_builder.vocab_ids_for(*summary_words)
114
+ appointment = @protocol_class::Eeprom::Calendar::Event.new(
115
+ time: est_time,
116
+ phrase: event_phrase
117
+ )
118
+ appointments << appointment
119
+ appointment_map[key] = true
120
+ end
121
+ end
122
+ end
123
+
124
+ def add_anniversary_event(anniversary_map, anniversaries, phrase_builder, summary_words, occurrences)
125
+ occurrences.each do |occurrence|
126
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
127
+ key = "#{est_time}_#{summary_words.join(' ')}"
128
+ unless anniversary_map[key]
129
+ puts "Adding anniversary event: #{summary_words.join(' ')} at date #{event.dtstart.to_s}"
130
+ event_phrase = phrase_builder.vocab_ids_for(*summary_words)
131
+ anniversary = @protocol_class::Eeprom::Calendar::Event.new(
132
+ time: Time.new(est_time.year, est_time.month, est_time.day, 9, 30, 0),
133
+ phrase: event_phrase
134
+ )
135
+ anniversaries << anniversary
136
+ anniversary_map[key] = true
137
+ end
138
+ end
139
+ end
140
+
141
+ def add_appointment(event, appointment_map, appointments, summary_words, occurrences)
142
+ occurrences.each do |occurrence|
143
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
144
+ key = "#{est_time}_#{summary_words}"
145
+ unless appointment_map[key]
146
+ puts "Adding appointment: #{summary_words} at time #{est_time}"
147
+ appointment = @protocol_class::Eeprom::Appointment.new(
148
+ time: est_time,
149
+ message: summary_words
150
+ )
151
+ appointments << appointment
152
+ appointment_map[key] = true
153
+ end
154
+ end
155
+ end
156
+
157
+ def add_anniversary(event, anniversary_map, anniversaries, summary_words, occurrences)
158
+ occurrences.each do |occurrence|
159
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
160
+ key = "#{est_time}_#{summary_words}"
161
+ unless anniversary_map[key]
162
+ puts "Adding anniversary: #{summary_words} at date #{event.dtstart.to_s}"
163
+ anniversary = @protocol_class::Eeprom::Anniversary.new(
164
+ time: event.dtstart.to_time,
165
+ anniversary: summary_words
166
+ )
167
+ anniversaries << anniversary
168
+ anniversary_map[key] = true
169
+ end
170
+ end
76
171
  end
77
172
 
78
- def write_to_watch(appointments)
173
+ def write_to_watch(appointments, anniversaries)
174
+ appointments.sort_by! { |appointment| appointment.time }
79
175
  # add 3 because it always seems to be about 3 seconds behind.
80
176
  time1 = Time.now + 3
81
177
  time2 = time1.dup.utc
@@ -99,7 +195,7 @@ module TimexDatalinkCaldav
99
195
  zone: 2,
100
196
  name: time2.zone
101
197
  )
102
- else
198
+ elsif @protocol_version == 3 || @protocol_version == 4
103
199
  time_model = @protocol_class::Time.new(
104
200
  zone: 1,
105
201
  name: time1.zone,
@@ -117,21 +213,41 @@ module TimexDatalinkCaldav
117
213
  time_name_model = nil # Not needed for protocol version 3 and 4
118
214
  utc_time_name_model = nil # Not needed for protocol version 3 and 4
119
215
 
216
+ else
217
+ time_model = nil
218
+ utc_time_model = nil
219
+ time_name_model = nil
220
+ utc_time_name_model = nil
221
+ end
222
+
223
+ if @protocol_version == 7
224
+ calendar = @protocol_class::Eeprom::Calendar.new(
225
+ time: time1,
226
+ events: appointments
227
+ )
228
+ models = [
229
+ @protocol_class::Sync.new,
230
+ @protocol_class::Start.new,
231
+ @protocol_class::Eeprom.new(
232
+ calendar: calendar
233
+ )
234
+ ]
235
+ else
236
+ models = [
237
+ @protocol_class::Sync.new,
238
+ @protocol_class::Start.new,
239
+ time_model,
240
+ time_name_model,
241
+ utc_time_model,
242
+ utc_time_name_model,
243
+ @protocol_class::Eeprom.new(
244
+ appointments: appointments,
245
+ anniversaries: anniversaries,
246
+ appointment_notification_minutes: 5
247
+ ),
248
+ @protocol_class::End.new
249
+ ].compact # Remove any nil entries
120
250
  end
121
-
122
- models = [
123
- @protocol_class::Sync.new,
124
- @protocol_class::Start.new,
125
- time_model,
126
- time_name_model,
127
- utc_time_model,
128
- utc_time_name_model,
129
- @protocol_class::Eeprom.new(
130
- appointments: appointments,
131
- appointment_notification_minutes: 5
132
- ),
133
- @protocol_class::End.new
134
- ].compact # Remove any nil entries
135
251
 
136
252
  timex_datalink_client = TimexDatalinkClient.new(
137
253
  serial_device: @serial_device,
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mdb"
4
+ require "text" # import the text gem
5
+
6
+
7
+ class SimlarWord
8
+ class WordNotFound < StandardError; end
9
+
10
+ attr_accessor :database
11
+
12
+ def initialize(database:)
13
+ @database = database
14
+ end
15
+
16
+ def vocab_ids_for(*words)
17
+ words.flat_map do |word|
18
+ vocab = vocab_for_word(word)
19
+
20
+ # If the word is not found, look for a similar sounding word
21
+ if vocab.nil?
22
+ word = find_similar_word(word)
23
+ vocab = vocab_for_word(word)
24
+ end
25
+
26
+ raise(WordNotFound, "#{word} is not a valid word!") unless vocab
27
+
28
+ vocab_links = vocab_links_for_vocab(vocab)
29
+
30
+ vocab_links.map do |vocab_link|
31
+ linked_vocab = vocab_for_vocab_link(vocab_link)
32
+
33
+ linked_vocab[:"PC Index"].to_i
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def find_similar_word(word)
41
+ word_metaphone = Text::Metaphone.double_metaphone(word)
42
+
43
+ # Filter out words that start with '<' before finding the minimum distance
44
+ filtered_vocab_table = vocab_table.reject { |vocab_word| vocab_word[:Label].start_with?('<') }
45
+
46
+ similar_word = filtered_vocab_table.min_by do |vocab_word|
47
+ distance = Text::Levenshtein.distance(
48
+ Text::Metaphone.double_metaphone(vocab_word[:Label]).first,
49
+ word_metaphone.first
50
+ )
51
+ distance
52
+ end
53
+
54
+ if similar_word
55
+ puts "Similar word for '#{word}' is '#{similar_word[:Label]}'"
56
+ similar_word[:Label]
57
+ else
58
+ puts "No similar word found for '#{word}', using original word"
59
+ word
60
+ end
61
+ end
62
+
63
+ def mdb
64
+ @mdb ||= Mdb.open(database)
65
+ end
66
+
67
+ def vocab_table
68
+ @vocab_table ||= mdb["Vocab"]
69
+ end
70
+
71
+ def vocab_links_table
72
+ @vocab_links_table ||= mdb["Vocab Links"]
73
+ end
74
+
75
+ def vocab_for_word(word)
76
+ vocab_table.detect { |vocab| vocab[:Label].casecmp?(word) }
77
+ end
78
+
79
+ def vocab_links_for_vocab(vocab)
80
+ links = vocab_links_table.select { |vocab_link| vocab_link[:"PC Index"] == vocab[:"PC Index"] }
81
+
82
+ links.sort_by { |link| link[:Sequence].to_i }
83
+ end
84
+
85
+ def vocab_for_vocab_link(vocab_link)
86
+ vocab_table.detect { |vocab| vocab[:"PC Index"] == vocab_link[:"eBrain Index"] }
87
+ end
88
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TimexDatalinkCaldav
4
- VERSION = "0.3.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -1,5 +1,6 @@
1
- # frozen_string_literal: true
2
1
  require 'optparse'
2
+ require 'yaml'
3
+ require 'open-uri'
3
4
  require_relative "timex_datalink_caldav/client"
4
5
 
5
6
  module TimexDatalinkCaldav
@@ -9,27 +10,55 @@ module TimexDatalinkCaldav
9
10
  end
10
11
 
11
12
  def execute
12
- client = TimexDatalinkCaldav::Client.new(@options.fetch(:user), @options.fetch(:password), @options.fetch(:uri), @options.fetch(:device), @options.fetch(:api,1))
13
- client.sync_to_watch
13
+ all_appointments = []
14
+ all_anniversaries = []
15
+
16
+ @options[:endpoints].each do |endpoint|
17
+ client = TimexDatalinkCaldav::Client.new(endpoint[:user], endpoint[:password], endpoint[:uri], @options[:device], @options[:api], @options[:days_forward])
18
+ appointments, anniversaries = client.parse_events
19
+ all_appointments.concat(appointments) if appointments.any?
20
+ all_anniversaries.concat(anniversaries) if anniversaries.any?
21
+ end
22
+
23
+ if all_appointments.any? || all_anniversaries.any?
24
+ client = TimexDatalinkCaldav::Client.new(
25
+ @options[:endpoints][0][:user],
26
+ @options[:endpoints][0][:password],
27
+ @options[:endpoints][0][:uri],
28
+ @options[:device],
29
+ @options[:api],
30
+ @options[:days_forward]
31
+ )
32
+ client.write_to_watch(all_appointments, all_anniversaries)
33
+ end
14
34
  end
15
35
 
16
36
  private
17
37
 
18
38
  def parse_options(arguments)
19
- options = {}
39
+ options = { days_forward: 1 }
40
+ cli_endpoint = {}
20
41
  OptionParser.new do |opts|
21
42
  opts.banner = "Usage: timex_datalink_caldav [options]"
22
43
 
44
+ opts.on("-c", "--config FILE", "Configuration file") do |v|
45
+ raise ArgumentError, "Both CLI options and configuration file provided. Please provide only one." if cli_endpoint.any?
46
+ parsed_config = YAML.load_file(v)
47
+ options[:endpoints] = parsed_config['endpoints'].map do |endpoint|
48
+ endpoint.each_with_object({}) { |(k, v), result| result[k.to_sym] = v }
49
+ end
50
+ end
51
+
23
52
  opts.on("-u", "--uri URI", "CalDAV server URI") do |v|
24
- options[:uri] = v
53
+ cli_endpoint[:uri] = v
25
54
  end
26
55
 
27
56
  opts.on("-n", "--user USERNAME", "Username for CalDAV server") do |v|
28
- options[:user] = v
57
+ cli_endpoint[:user] = v
29
58
  end
30
59
 
31
60
  opts.on("-p", "--password PASSWORD", "Password for CalDAV server") do |v|
32
- options[:password] = v
61
+ cli_endpoint[:password] = v
33
62
  end
34
63
 
35
64
  opts.on("-d", "--device DEVICE", "Serial device for Timex Datalink watch") do |v|
@@ -39,8 +68,14 @@ module TimexDatalinkCaldav
39
68
  opts.on("-a", "--api PROTOCOL_VERSION", "Protocol Version") do |v|
40
69
  options[:api] = v
41
70
  end
71
+
72
+ opts.on("-f", "--forward DAYS", Integer, "Number of days to look forward for events") do |v|
73
+ options[:days_forward] = v
74
+ end
42
75
  end.parse!(arguments)
76
+
77
+ options[:endpoints] = [cli_endpoint] if cli_endpoint.any? and options[:endpoints].nil?
43
78
  options
44
79
  end
45
80
  end
46
- end
81
+ end
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  end
29
29
  spec.require_paths = ["lib"]
30
30
  spec.executables << 'timex_datalink_caldav'
31
-
31
+ spec.executables << 'get_ebrain_db'
32
32
  # Uncomment to register a new dependency of your gem
33
33
  # spec.add_dependency "example-gem", "~> 1.0"
34
34
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timex_datalink_caldav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willy Hardy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-17 00:00:00.000000000 Z
11
+ date: 2023-05-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'Adds a CLI and a feature to pull your next day of calendar events into
14
14
  the Timex Datalink watch. Note: Hardcoded protocol1 and EST timezone. At the moment.'
@@ -16,17 +16,23 @@ email:
16
16
  - zpga8gbp@mailer.me
17
17
  executables:
18
18
  - timex_datalink_caldav
19
+ - get_ebrain_db
19
20
  extensions: []
20
21
  extra_rdoc_files: []
21
22
  files:
22
23
  - CHANGELOG.md
23
24
  - Gemfile
24
25
  - Gemfile.lock
26
+ - LICENSE
25
27
  - README.md
26
28
  - Rakefile
29
+ - bin/get_ebrain_db
27
30
  - bin/timex_datalink_caldav
31
+ - examples/README.md
32
+ - examples/caldav.yaml.sample
28
33
  - lib/timex_datalink_caldav.rb
29
34
  - lib/timex_datalink_caldav/client.rb
35
+ - lib/timex_datalink_caldav/similar_word.rb
30
36
  - lib/timex_datalink_caldav/version.rb
31
37
  - sig/timex_datalink_caldav.rbs
32
38
  - timex_datalink_caldav.gemspec
@@ -53,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
59
  - !ruby/object:Gem::Version
54
60
  version: '0'
55
61
  requirements: []
56
- rubygems_version: 3.4.10
62
+ rubygems_version: 3.4.13
57
63
  signing_key:
58
64
  specification_version: 4
59
65
  summary: Allows the Timex Datalink watch to sync with a CalDAV server.