flycal-cli 0.1.0 → 0.2.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: 8efca78d1d82f9e3310cbd90a29a95ad52fbf9fff714d3aada8321c999bfaa71
4
- data.tar.gz: 749f5417dfd3cc780c78e55524aa0acabea9b8fc28f07614d8faa25e7920253b
3
+ metadata.gz: c8d00cb30384c0dedb985b343cbb058db6c93b6e77469d5df0d22a6ef9c62846
4
+ data.tar.gz: ec29bf46d64995422a4bbf8a470035de10473bc9ceeaf2646b566347e3551d06
5
5
  SHA512:
6
- metadata.gz: be1d24bae7776ca94842c9edd5116544b750846ccc169bd57ba6720dc1b63904eaad92fb2f33ace8f668a8580352ea2ff530532c5262bbc83d44f4f92521b324
7
- data.tar.gz: 2d89f80c2b486f102b8ad6edf178dd71126fd78549a49caecc17a5bc0254827f2d0ed07cb727e2cbc18c0b3bf19cc64bbca04846f8a72a00b82d7cb77495273c
6
+ metadata.gz: 0ed2b407b9b51840497fc68f2842703f03b99dfa283598d2dfe9b6f324484b4fe513ec5e3a66a99c672996afba1bfcfb3c362f6514eccff6db9c5fdf06773725
7
+ data.tar.gz: a8b666a01f5b52d59e7e63ed1bb2b8e894a860d27e16c7c63f66785fdc1da6f8e8fe3cf9ca4cfe0401c7f01124062ae8596ced033ce4d5380631a952c90c3ecd
@@ -43,9 +43,9 @@ module FlycalCli
43
43
  def login
44
44
  unless Config.credentials_exist?
45
45
  raise FlycalCli::Error,
46
- "File credentials non trovato. Crea #{Config.credentials_path} con le credenziali OAuth dal Google Cloud Console.\n" \
47
- "Vai su: https://console.cloud.google.com/apis/credentials\n" \
48
- "Crea credenziali 'Desktop app' e scarica il JSON come credentials.json"
46
+ "Credentials file not found. Create #{Config.credentials_path} with OAuth credentials from Google Cloud Console.\n" \
47
+ "Go to: https://console.cloud.google.com/apis/credentials\n" \
48
+ "Create 'Desktop app' credentials and download the JSON as credentials.json"
49
49
  end
50
50
 
51
51
  token_store = Google::Auth::Stores::FileTokenStore.new(file: Config.tokens_path)
@@ -63,9 +63,9 @@ module FlycalCli
63
63
  return creds
64
64
  end
65
65
 
66
- # Avvia server locale per ricevere il codice
66
+ # Start local server to receive the auth code
67
67
  code = start_redirect_server(authorizer)
68
- raise FlycalCli::Error, "Autenticazione annullata o fallita" if code.nil? || code.empty?
68
+ raise FlycalCli::Error, "Authentication cancelled or failed" if code.nil? || code.empty?
69
69
 
70
70
  authorizer.get_and_store_credentials_from_code(
71
71
  user_id: "flycal_user",
@@ -104,12 +104,12 @@ module FlycalCli
104
104
 
105
105
  if params["error"]
106
106
  res.status = 400
107
- res.body = "<h1>Errore</h1><p>#{params['error']}: #{params['error_description']}</p>"
107
+ res.body = "<h1>Error</h1><p>#{params['error']}: #{params['error_description']}</p>"
108
108
  auth_code = nil
109
109
  elsif params["code"]
110
110
  auth_code = params["code"]
111
111
  res.status = 200
112
- res.body = "<h1>Autenticazione completata!</h1><p>Puoi chiudere questa finestra e tornare al terminale.</p>"
112
+ res.body = "<h1>Authentication complete!</h1><p>You can close this window and return to the terminal.</p>"
113
113
  end
114
114
 
115
115
  Thread.new { server.shutdown }
@@ -120,9 +120,9 @@ module FlycalCli
120
120
  state: state
121
121
  )
122
122
 
123
- puts "\nApri questo link nel browser per autenticarti:\n\n"
123
+ puts "\nOpen this link in your browser to authenticate:\n\n"
124
124
  puts " #{url}\n\n"
125
- puts "Dopo l'autenticazione, tornerai qui automaticamente.\n\n"
125
+ puts "After authentication, you will be redirected back here automatically.\n\n"
126
126
 
127
127
  server.start
128
128
  auth_code
@@ -45,12 +45,26 @@ module FlycalCli
45
45
  def list_all_events(calendar_ids, time_min:, time_max:, query: nil)
46
46
  all_events = []
47
47
 
48
+ # When --description is used: fetch all events and filter client-side to guarantee
49
+ # contains/like behavior. We do not use the API's q param so we can ensure we
50
+ # return all events where the string appears in summary or description (case-insensitive).
48
51
  calendar_ids.each do |cal_id|
49
52
  begin
50
- events = list_events(cal_id, time_min: time_min, time_max: time_max, query: query)
53
+ events = list_events(cal_id, time_min: time_min, time_max: time_max, query: nil)
51
54
  events.each { |e| all_events << { calendar_id: cal_id, event: e } }
52
55
  rescue Google::Apis::Errors::Error => e
53
- warn "Errore nel calendario #{cal_id}: #{e.message}"
56
+ warn "Error in calendar #{cal_id}: #{e.message}"
57
+ end
58
+ end
59
+
60
+ # Filter: only events where summary or description contains the search string (case-insensitive)
61
+ if query && !query.to_s.strip.empty?
62
+ q = query.strip.downcase
63
+ all_events.select! do |item|
64
+ event = item[:event]
65
+ summary = (event.summary || "").downcase
66
+ description = (event.description || "").downcase
67
+ summary.include?(q) || description.include?(q)
54
68
  end
55
69
  end
56
70
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "thor"
4
4
  require "time"
5
+ require "date"
5
6
  require "tty-prompt"
6
7
  require "tty-spinner"
7
8
 
@@ -13,70 +14,70 @@ module FlycalCli
13
14
  true
14
15
  end
15
16
 
16
- desc "login", "Connetti al tuo account Google"
17
+ desc "login", "Connect to your Google account"
17
18
  def login
18
19
  if Auth.logged_in?
19
- puts "✓ Sei già connesso al tuo account Google."
20
- puts "\nEsegui 'flycal calendars' per impostare il calendario di default."
20
+ puts "✓ You are already connected to your Google account."
21
+ puts "\nRun 'flycal calendars' to set the default calendar."
21
22
  return
22
23
  end
23
24
 
24
25
  unless Config.credentials_exist?
25
- puts "Errore: File credentials non trovato."
26
- puts "\nPer configurare flycal:"
27
- puts "1. Vai su https://console.cloud.google.com/apis/credentials"
28
- puts "2. Crea credenziali 'Applicazione desktop' (Desktop app)"
29
- puts "3. Scarica il JSON e salvalo come: #{Config.credentials_path}"
30
- puts "\nAggiungi anche questo URI come redirect autorizzato:"
26
+ puts "Error: Credentials file not found."
27
+ puts "\nTo configure flycal:"
28
+ puts "1. Go to https://console.cloud.google.com/apis/credentials"
29
+ puts "2. Create 'Desktop app' credentials"
30
+ puts "3. Download the JSON and save it as: #{Config.credentials_path}"
31
+ puts "\nAlso add this URI as an authorized redirect:"
31
32
  puts " http://127.0.0.1:9292/oauth2callback"
32
33
  return
33
34
  end
34
35
 
35
36
  begin
36
37
  Auth.login
37
- puts "\n✓ Autenticazione completata con successo!"
38
- puts "\nEsegui 'flycal calendars' per impostare il calendario di default."
38
+ puts "\n✓ Authentication completed successfully!"
39
+ puts "\nRun 'flycal calendars' to set the default calendar."
39
40
  rescue FlycalCli::Error => e
40
- puts "Errore: #{e.message}"
41
+ puts "Error: #{e.message}"
41
42
  exit 1
42
43
  end
43
44
  end
44
45
 
45
- desc "logout", "Disconnetti dall'account Google"
46
+ desc "logout", "Disconnect from Google account"
46
47
  def logout
47
48
  unless Auth.logged_in?
48
- puts "Non sei connesso a nessun account Google."
49
+ puts "You are not connected to any Google account."
49
50
  return
50
51
  end
51
52
 
52
53
  Auth.logout
53
- puts "✓ Disconnesso con successo."
54
+ puts "✓ Disconnected successfully."
54
55
  end
55
56
 
56
- desc "calendars", "Mostra i calendari disponibili e imposta quello di default"
57
+ desc "calendars", "List available calendars and set the default one"
57
58
  def calendars
58
59
  unless Auth.logged_in?
59
- puts "Non sei connesso. Esegui prima 'flycal login'."
60
+ puts "You are not connected. Run 'flycal login' first."
60
61
  exit 1
61
62
  end
62
63
 
63
64
  creds = Auth.credentials
64
65
  service = CalendarService.new(creds)
65
66
 
66
- spinner = TTY::Spinner.new("Caricamento calendari... ", format: :dots)
67
+ spinner = TTY::Spinner.new("Loading calendars... ", format: :dots)
67
68
  spinner.auto_spin
68
69
  calendars = service.list_calendars
69
70
  spinner.stop("✓")
70
71
 
71
72
  if calendars.empty?
72
- puts "Nessun calendario trovato."
73
+ puts "No calendars found."
73
74
  return
74
75
  end
75
76
 
76
- # Costruisci lista per selezione (display => calendar_id)
77
+ # Build selection list (display => calendar_id)
77
78
  default_id = Config.calendar_default
78
79
  choices = calendars.to_h do |cal|
79
- primary = cal.primary ? " (principale)" : ""
80
+ primary = cal.primary ? " (primary)" : ""
80
81
  default = (cal.id == default_id) ? " [default]" : ""
81
82
  summary = cal.summary || cal.id
82
83
  label = "#{summary}#{primary}#{default}"
@@ -85,7 +86,7 @@ module FlycalCli
85
86
 
86
87
  prompt = TTY::Prompt.new
87
88
  selected_id = prompt.select(
88
- "Scegli il calendario di default:",
89
+ "Choose the default calendar:",
89
90
  choices,
90
91
  per_page: 15,
91
92
  filter: true
@@ -94,26 +95,52 @@ module FlycalCli
94
95
  calendar = calendars.find { |c| c.id == selected_id }
95
96
  if calendar
96
97
  Config.calendar_default = calendar.id
97
- puts "\n✓ Calendario di default impostato: #{calendar.summary}"
98
+ puts "\n✓ Default calendar set to: #{calendar.summary}"
98
99
  end
99
100
  end
100
101
 
101
- desc "search", "Cerca eventi nei calendari"
102
- option :calendar, type: :string, aliases: "-c", desc: "Nome o ID del calendario (default: calendario default)"
103
- option :from, type: :string, aliases: "-f", required: true, desc: "Data/ora inizio (es. 2025-01-01 o 2025-01-01T09:00)"
104
- option :to, type: :string, aliases: "-t", required: true, desc: "Data/ora fine (es. 2025-01-31 o 2025-01-31T18:00)"
105
- option :description, type: :string, aliases: "-d", desc: "Filtra per descrizione (testo)"
102
+ desc "search", "Search for events in calendars"
103
+ long_desc <<-LONGDESC
104
+ Search for events in your Google calendar(s).
105
+
106
+ Time range:
107
+ -f, --from DATE Start (default: today midnight)
108
+ -t, --to DATE End (default: 23:59 of day 30)
109
+ -i, --in DURATION Duration from --from, overrides --to.
110
+ Format: 30days, 48hours, 2months, 1year (no space).
111
+ With space use quotes: --in "30 days"
112
+
113
+ Examples:
114
+ flycal search
115
+ flycal search --in 30days -d meeting
116
+ flycal search -i 1months --description rappydrive
117
+ flycal search -f 2025-03-01 --in 2months
118
+ LONGDESC
119
+ option :calendar, type: :string, aliases: "-c", desc: "Calendar name or ID"
120
+ option :from, type: :string, aliases: "-f", desc: "Start (default: today midnight)"
121
+ option :to, type: :string, aliases: "-t", desc: "End (default: 23:59 of day 30)"
122
+ option :in, type: :string, aliases: "-i", desc: "Duration: 30days, 48hours, 2months, 1year (overrides --to)"
123
+ option :description, type: :string, aliases: "-d", desc: "Filter by text in event"
106
124
  def search
107
125
  unless Auth.logged_in?
108
- puts "Non sei connesso. Esegui prima 'flycal login'."
126
+ puts "You are not connected. Run 'flycal login' first."
109
127
  exit 1
110
128
  end
111
129
 
112
- time_min = parse_datetime(options[:from])
113
- time_max = parse_datetime(options[:to], end_of_day: true)
130
+ time_min = options[:from].to_s.empty? ? Date.today.to_time : parse_datetime(options[:from])
131
+ begin
132
+ time_max = if options[:in].to_s.empty?
133
+ options[:to].to_s.empty? ? (Date.today + 30).to_time + 86400 - 1 : parse_datetime(options[:to], end_of_day: true)
134
+ else
135
+ parse_duration_in(options[:in], time_min)
136
+ end
137
+ rescue FlycalCli::Error => e
138
+ puts "Error: #{e.message}"
139
+ exit 1
140
+ end
114
141
 
115
142
  if time_min > time_max
116
- puts "Errore: la data 'from' deve essere prima della data 'to'."
143
+ puts "Error: 'from' date must be before 'to' date."
117
144
  exit 1
118
145
  end
119
146
 
@@ -122,7 +149,7 @@ module FlycalCli
122
149
 
123
150
  calendar_ids = resolve_calendar_ids(service, options[:calendar])
124
151
  if calendar_ids.empty?
125
- puts "Nessun calendario trovato."
152
+ puts "No calendars found."
126
153
  exit 1
127
154
  end
128
155
 
@@ -134,7 +161,7 @@ module FlycalCli
134
161
  )
135
162
 
136
163
  print_events(service, events)
137
- print_summary(events)
164
+ print_summary(events, time_min: time_min, time_max: time_max)
138
165
  end
139
166
 
140
167
  default_task :help
@@ -149,7 +176,29 @@ module FlycalCli
149
176
  else
150
177
  d = Date.parse(str)
151
178
  t = d.to_time
152
- end_of_day ? t + 86400 - 1 : t # 23:59:59 per --to quando è solo data
179
+ end_of_day ? t + 86400 - 1 : t # 23:59:59 for --to when date-only
180
+ end
181
+ end
182
+
183
+ def parse_duration_in(str, from_time)
184
+ # Support both "30days" and "30 days" (no space avoids shell splitting)
185
+ m = str.to_s.strip.match(/\A(\d+)\s*(day|days|d|hour|hours|h|month|months|m|year|years|y)\z/i)
186
+ raise FlycalCli::Error, "Invalid --in format. Use: 30days, 48hours, 2months, 1year (no space, or quote: --in \"30 days\")" unless m
187
+
188
+ n = m[1].to_i
189
+ unit = m[2].downcase
190
+
191
+ case unit
192
+ when "hour", "hours", "h"
193
+ from_time + n * 3600
194
+ when "day", "days", "d"
195
+ from_time + n * 86400
196
+ when "month", "months", "m"
197
+ (from_time.to_date >> n).to_time + (from_time.hour * 3600 + from_time.min * 60 + from_time.sec)
198
+ when "year", "years", "y"
199
+ (from_time.to_date >> (n * 12)).to_time + (from_time.hour * 3600 + from_time.min * 60 + from_time.sec)
200
+ else
201
+ raise FlycalCli::Error, "Invalid unit: #{unit}. Use: days, hours, months, years"
153
202
  end
154
203
  end
155
204
 
@@ -161,15 +210,15 @@ module FlycalCli
161
210
  if default_id
162
211
  return [default_id]
163
212
  end
164
- # Usa calendario principale se disponibile
213
+ # Use primary calendar if available
165
214
  primary = calendar_lists.find(&:primary)
166
215
  return [primary.id] if primary
167
- # Altrimenti primo calendario
216
+ # Otherwise first calendar
168
217
  return [calendar_lists.first.id] if calendar_lists.any?
169
218
  return []
170
219
  end
171
220
 
172
- # Cerca per nome o ID
221
+ # Search by name or ID
173
222
  matches = calendar_lists.select do |cal|
174
223
  cal.id == calendar_name ||
175
224
  (cal.summary && cal.summary.downcase.include?(calendar_name.downcase))
@@ -179,7 +228,7 @@ module FlycalCli
179
228
  end
180
229
 
181
230
  def print_events(service, events)
182
- # Usa lista calendari per i nomi (evita chiamate API extra)
231
+ # Use calendar list for names (avoids extra API calls)
183
232
  calendar_list = service.list_calendars
184
233
  calendar_names = calendar_list.to_h { |c| [c.id, c.summary || c.id] }
185
234
 
@@ -193,7 +242,7 @@ module FlycalCli
193
242
 
194
243
  start_str = format_datetime(start_time)
195
244
  end_str = format_datetime(end_time)
196
- desc = event.summary || "(senza titolo)"
245
+ desc = event.summary || "(no title)"
197
246
  desc = event.description&.slice(0, 80) if event.summary.nil? && event.description
198
247
 
199
248
  puts "#{cal_name} | #{start_str} | #{end_str} | #{desc}"
@@ -210,7 +259,7 @@ module FlycalCli
210
259
  end
211
260
  end
212
261
 
213
- def print_summary(events)
262
+ def print_summary(events, time_min:, time_max:)
214
263
  total_minutes = 0
215
264
 
216
265
  events.each do |item|
@@ -227,10 +276,15 @@ module FlycalCli
227
276
 
228
277
  hours = (total_minutes / 60).floor
229
278
  mins = (total_minutes % 60).round
279
+ total_working_days = (total_minutes / 60.0 / 8).round(1)
280
+
281
+ from_str = time_min.strftime("%Y-%m-%d %H:%M")
282
+ to_str = time_max.strftime("%Y-%m-%d %H:%M")
230
283
 
231
284
  puts "\n---"
232
- puts "Eventi trovati: #{events.size}"
233
- puts "Tempo totale occupato: #{hours}h #{mins}min"
285
+ puts "From: #{from_str} | To: #{to_str}"
286
+ puts "Events found: #{events.size}"
287
+ puts "Total time occupied: #{hours}h #{mins}min (#{total_working_days} working days)"
234
288
  end
235
289
  end
236
290
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlycalCli
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flycal-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - flycal-cli