flycal-cli 0.2.0 → 0.3.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: c8d00cb30384c0dedb985b343cbb058db6c93b6e77469d5df0d22a6ef9c62846
4
- data.tar.gz: ec29bf46d64995422a4bbf8a470035de10473bc9ceeaf2646b566347e3551d06
3
+ metadata.gz: c6e56787081596ebef227a579b0667514fa493998875f613167acdade53a4899
4
+ data.tar.gz: a8776aded68f58ba58759adc27b3aeb2ca63eacf9797eb8bc161aeb2f96589ee
5
5
  SHA512:
6
- metadata.gz: 0ed2b407b9b51840497fc68f2842703f03b99dfa283598d2dfe9b6f324484b4fe513ec5e3a66a99c672996afba1bfcfb3c362f6514eccff6db9c5fdf06773725
7
- data.tar.gz: a8b666a01f5b52d59e7e63ed1bb2b8e894a860d27e16c7c63f66785fdc1da6f8e8fe3cf9ca4cfe0401c7f01124062ae8596ced033ce4d5380631a952c90c3ecd
6
+ metadata.gz: 0e0af4be4d8505aa21a63e4e6ed70f886419eddf971745154174ea55ec6348030cc0fdad3e40af926df128fa295a5741da84fe236930b2ec797259c2ba300219
7
+ data.tar.gz: d2021d42bc26dd9b82c4b7b672c923b137b2e83e1f983af96310b9c9d14e948c33cce752f99ba1f99e175d9071067484d2eb5b0763b5d8d63bf1ccb0d56085f4
data/README.md CHANGED
@@ -1,139 +1,158 @@
1
1
  # flycal-cli
2
2
 
3
- CLI per accedere ai calendari Google da riga di comando.
3
+ A command-line tool to access and search Google Calendar events. Connect your Google account, choose a default calendar, and search events with flexible date ranges and text filters.
4
4
 
5
- ## Installazione
5
+ ## Requirements
6
+
7
+ - Ruby 3.1 or later
8
+ - A Google Cloud project with Calendar API enabled
9
+
10
+ ## Installation
6
11
 
7
12
  ```bash
8
13
  gem install flycal-cli
9
14
  ```
10
15
 
11
- Oppure aggiungi al tuo Gemfile:
16
+ Or add to your Gemfile:
12
17
 
13
18
  ```ruby
14
19
  gem "flycal-cli"
15
20
  ```
16
21
 
17
- ## Configurazione iniziale
22
+ Then run `bundle install`.
18
23
 
19
- Prima di usare flycal, devi creare credenziali OAuth nel Google Cloud Console:
24
+ ## Setup
20
25
 
21
- 1. Vai su [Google Cloud Console - Credenziali](https://console.cloud.google.com/apis/credentials)
22
- 2. Crea un progetto (o usa uno esistente)
23
- 3. Abilita l'**API Google Calendar** (API e servizi → Libreria → Cerca "Google Calendar API")
24
- 4. Crea credenziali **Applicazione desktop** (OAuth 2.0 Client IDs)
25
- 5. Aggiungi questo URI come redirect autorizzato:
26
+ Before using flycal, you need OAuth credentials from Google Cloud Console:
27
+
28
+ 1. Go to [Google Cloud Console - Credentials](https://console.cloud.google.com/apis/credentials)
29
+ 2. Create a project or select an existing one
30
+ 3. Enable the **Google Calendar API** (APIs & Services → Library → search for "Google Calendar API")
31
+ 4. Create **Desktop app** credentials (OAuth 2.0 Client IDs)
32
+ 5. Add this URI as an authorized redirect:
26
33
  ```
27
34
  http://127.0.0.1:9292/oauth2callback
28
35
  ```
29
- 6. Scarica il file JSON e salvalo come `~/.flycal/credentials.json`
36
+ 6. Download the JSON file and save it as `~/.flycal/credentials.json`
30
37
 
31
- ## Comandi
38
+ ## Commands
32
39
 
33
- ### `flycal login`
40
+ ### login
34
41
 
35
- Connetti al tuo account Google. Se non sei connesso, verrà generato un link da aprire nel browser per completare l'autenticazione OAuth.
42
+ Connect to your Google account. Opens a browser for OAuth authentication when not yet connected.
36
43
 
37
44
  ```bash
38
45
  flycal login
39
46
  ```
40
47
 
41
- ### `flycal calendars`
48
+ If already connected, the command reports the current status and suggests running `flycal calendars` to set the default calendar.
49
+
50
+ ### logout
42
51
 
43
- Mostra la lista dei calendari disponibili e permette di selezionare il calendario di default. La selezione è scorrevole (usa le frecce per navigare).
52
+ Disconnect from your Google account and remove stored tokens.
44
53
 
45
54
  ```bash
46
- flycal calendars
55
+ flycal logout
47
56
  ```
48
57
 
49
- ### `flycal logout`
58
+ ### calendars
50
59
 
51
- Disconnetti dall'account Google.
60
+ List available calendars and set the default one. Uses an interactive scrollable menu (arrow keys to navigate, type to filter).
52
61
 
53
62
  ```bash
54
- flycal logout
63
+ flycal calendars
55
64
  ```
56
65
 
57
- ### `flycal search`
66
+ The default calendar is used by the search command when no calendar is specified.
67
+
68
+ ### search
58
69
 
59
- Cerca eventi nei calendari.
70
+ Search for events in your calendar(s). Supports flexible date ranges and text filtering.
60
71
 
61
72
  ```bash
62
- flycal search --from 2025-02-01 --to 2025-02-28
63
- flycal search -f 2025-02-01 -t 2025-02-28 --description "riunione"
64
- flycal search -f 2025-02-01T09:00 -t 2025-02-28T18:00 -c "Lavoro"
73
+ flycal search
74
+ flycal search --in 30days --description placeholder
75
+ flycal search -f 2025-03-01 -t 2025-03-31 -c "Work"
76
+ flycal search -i 2months -d placeholder
65
77
  ```
66
78
 
67
- Opzioni:
68
- - `-f, --from`: Data/ora inizio (es. `2025-01-01` o `2025-01-01T09:00`)
69
- - `-t, --to`: Data/ora fine
70
- - `-c, --calendar`: Nome o ID del calendario (default: calendario impostato con `flycal calendars`)
71
- - `-d, --description`: Filtra eventi per testo nella descrizione
79
+ **Options:**
72
80
 
73
- ## File di configurazione
81
+ | Option | Short | Description |
82
+ |--------|-------|-------------|
83
+ | `--from` | `-f` | Start date/time. Default: midnight of current day. Format: `2025-01-01` or `2025-01-01T09:00` |
84
+ | `--to` | `-t` | End date/time. Default: 23:59 of the 30th day from today. Format: same as `--from` |
85
+ | `--in` | `-i` | Duration from `--from`, overrides `--to`. Format: `30days`, `48hours`, `2months`, `1year` (no space between number and unit). With space, use quotes: `--in "30 days"` |
86
+ | `--calendar` | `-c` | Calendar name or ID. Default: calendar set via `flycal calendars` |
87
+ | `--description` | `-d` | Filter events by text. Matches events where the string appears in the title or description (case-insensitive, contains) |
74
88
 
75
- I dati vengono salvati in `~/.flycal/`:
89
+ **Time range behavior:**
76
90
 
77
- - `config.yml` - calendario di default (`calendar_default`)
78
- - `credentials.json` - credenziali OAuth (da creare manualmente)
79
- - `tokens.yml` - token di accesso (gestito automaticamente)
91
+ - If neither `--from` nor `--to` is given: searches from today at midnight to 23:59 of the 30th day from today
92
+ - If `--in` is given: `--to` is ignored; the end time is computed from `--from` plus the duration
93
+ - Examples: `--in 30days`, `--in 48hours`, `--in 1months`, `--in 1year`
80
94
 
81
- ## Pubblicazione su RubyGems
95
+ **Output:**
82
96
 
83
- ### Primo caricamento
97
+ Each event is printed as: `Calendar | Start | End | Title`
84
98
 
85
- 1. Crea un account su [rubygems.org](https://rubygems.org) se non ce l'hai
86
- 2. Aggiorna `flycal-cli.gemspec` con i tuoi dati (autori, email, homepage)
87
- 3. Build e push:
99
+ The summary shows:
100
+ - From and To dates used
101
+ - Number of events found
102
+ - Total time occupied (hours, minutes, and working days based on 8-hour days)
88
103
 
89
- ```bash
90
- # Build della gem
91
- gem build flycal-cli.gemspec
104
+ For time frames longer than 7 days, a weekly breakdown is added (week number, start/end dates, hours, working days per week). For time frames longer than 30 days, a monthly breakdown is shown instead (month number, month name, hours, working days per month).
92
105
 
93
- # Push (ti chiederà email e password RubyGems)
94
- gem push flycal-cli-0.1.0.gem
95
- ```
106
+ ## Configuration
96
107
 
97
- ### Versioni successive
108
+ Data is stored in `~/.flycal/`:
98
109
 
99
- 1. Aggiorna la versione in `lib/flycal_cli/version.rb`
100
- 2. Commit e tag:
110
+ | File | Purpose |
111
+ |------|---------|
112
+ | `config.yml` | Default calendar ID and other settings |
113
+ | `credentials.json` | OAuth credentials (created manually from Google Cloud Console) |
114
+ | `tokens.yml` | Access tokens (managed automatically) |
101
115
 
102
- ```bash
103
- git add .
104
- git commit -m "Release v0.2.0"
105
- git tag v0.2.0
106
- git push origin main
107
- git push origin v0.2.0
108
- ```
116
+ ## Publishing to RubyGems
117
+
118
+ ### First release
109
119
 
110
- 3. Build e push:
120
+ 1. Create an account at [rubygems.org](https://rubygems.org) if needed
121
+ 2. Update `flycal-cli.gemspec` with your author, email, and homepage
122
+ 3. Build and push:
111
123
 
112
124
  ```bash
113
125
  gem build flycal-cli.gemspec
114
- gem push flycal-cli-0.2.0.gem
126
+ gem push flycal-cli-0.1.0.gem
115
127
  ```
116
128
 
117
- ### Usando `rake release` (raccomandato)
129
+ ### Subsequent releases
118
130
 
119
- Aggiungi al Rakefile:
131
+ 1. Update the version in `lib/flycal_cli/version.rb`
132
+ 2. Build and push:
120
133
 
121
- ```ruby
122
- require "bundler/gem_tasks"
134
+ ```bash
135
+ gem build flycal-cli.gemspec
136
+ gem push flycal-cli-X.Y.Z.gem
123
137
  ```
124
138
 
125
- Poi:
139
+ 3. Optionally tag and push:
126
140
 
127
141
  ```bash
128
- # Per la prima release
129
- bundle exec rake release
142
+ git tag vX.Y.Z
143
+ git push origin vX.Y.Z
144
+ ```
130
145
 
131
- # Per release successive: aggiorna VERSION e poi
146
+ ### Using rake release
147
+
148
+ With `bundler/gem_tasks` in your Rakefile:
149
+
150
+ ```bash
132
151
  bundle exec rake release
133
152
  ```
134
153
 
135
- `rake release` esegue: build, push su RubyGems, commit, tag e push su git.
154
+ This builds the gem, pushes to RubyGems, and can handle git tagging and pushing.
136
155
 
137
- ## Licenza
156
+ ## License
138
157
 
139
158
  MIT
@@ -3,6 +3,9 @@
3
3
  require "thor"
4
4
  require "time"
5
5
  require "date"
6
+ require "active_support/core_ext/date"
7
+ require "active_support/core_ext/integer"
8
+ require "active_support/core_ext/time"
6
9
  require "tty-prompt"
7
10
  require "tty-spinner"
8
11
 
@@ -112,8 +115,8 @@ module FlycalCli
112
115
 
113
116
  Examples:
114
117
  flycal search
115
- flycal search --in 30days -d meeting
116
- flycal search -i 1months --description rappydrive
118
+ flycal search --in 30days -d placeholder
119
+ flycal search -i 1months --description placeholder
117
120
  flycal search -f 2025-03-01 --in 2months
118
121
  LONGDESC
119
122
  option :calendar, type: :string, aliases: "-c", desc: "Calendar name or ID"
@@ -130,10 +133,10 @@ module FlycalCli
130
133
  time_min = options[:from].to_s.empty? ? Date.today.to_time : parse_datetime(options[:from])
131
134
  begin
132
135
  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
136
+ options[:to].to_s.empty? ? (Date.today + 30).to_time + 86400 - 1 : parse_datetime(options[:to], end_of_day: true)
137
+ else
138
+ parse_duration_in(options[:in], time_min)
139
+ end
137
140
  rescue FlycalCli::Error => e
138
141
  puts "Error: #{e.message}"
139
142
  exit 1
@@ -161,13 +164,19 @@ module FlycalCli
161
164
  )
162
165
 
163
166
  print_events(service, events)
164
- print_summary(events, time_min: time_min, time_max: time_max)
167
+ print_search_summary(events, time_min: time_min, time_max: time_max)
165
168
  end
166
169
 
167
170
  default_task :help
168
171
 
169
172
  private
170
173
 
174
+ HOURS_PER_WORKING_DAY = 8
175
+
176
+ def bold(str)
177
+ "\e[1m#{str}\e[0m"
178
+ end
179
+
171
180
  def parse_datetime(str, end_of_day: false)
172
181
  return nil if str.nil? || str.empty?
173
182
 
@@ -255,36 +264,112 @@ module FlycalCli
255
264
  if dt.is_a?(String)
256
265
  dt
257
266
  else
258
- dt.strftime("%Y-%m-%d %H:%M")
267
+ dt.strftime("%a %Y-%m-%d %H:%M")
259
268
  end
260
269
  end
261
270
 
262
- def print_summary(events, time_min:, time_max:)
263
- total_minutes = 0
271
+ def format_date_with_day(dt)
272
+ return "-" if dt.nil?
264
273
 
265
- events.each do |item|
266
- event = item[:event]
267
- start = event.start&.date_time || event.start&.date
268
- end_dt = event.end&.date_time || event.end&.date
274
+ t = dt.respond_to?(:to_time) ? dt.to_time : dt
275
+ t.strftime("%a %Y-%m-%d")
276
+ end
269
277
 
270
- next if start.nil? || end_dt.nil?
278
+ def print_search_summary(events, time_min:, time_max:)
279
+ event_ranges = extract_event_ranges(events)
280
+ total_minutes = event_ranges.sum { |s, e| (e - s) / 60 }
271
281
 
272
- start = start.to_time if start.respond_to?(:to_time)
273
- end_dt = end_dt.to_time if end_dt.respond_to?(:to_time)
274
- total_minutes += (end_dt - start) / 60
282
+ from_str = time_min.strftime("%a %Y-%m-%d %H:%M")
283
+ to_str = time_max.strftime("%a %Y-%m-%d %H:%M")
284
+
285
+ puts "\n---"
286
+ puts "From: #{from_str} | To: #{to_str}"
287
+ puts "Events found: #{events.size}"
288
+ puts "Total time occupied: #{format_duration(total_minutes)}"
289
+
290
+ frame_days = (time_max - time_min) / 86400.0
291
+ if frame_days > 30
292
+ print_monthly_breakdown(event_ranges, time_min, time_max)
293
+ elsif frame_days > 7
294
+ print_weekly_breakdown(event_ranges, time_min, time_max)
275
295
  end
296
+ end
297
+
298
+ def extract_event_ranges(events)
299
+ events.filter_map do |item|
300
+ event = item[:event]
301
+ start_t = event.start&.date_time || event.start&.date
302
+ end_t = event.end&.date_time || event.end&.date
303
+ next if start_t.nil? || end_t.nil?
276
304
 
305
+ start_t = start_t.to_time if start_t.respond_to?(:to_time)
306
+ end_t = end_t.to_time if end_t.respond_to?(:to_time)
307
+ [start_t, end_t]
308
+ end
309
+ end
310
+
311
+ def format_duration(total_minutes)
277
312
  hours = (total_minutes / 60).floor
278
313
  mins = (total_minutes % 60).round
279
- total_working_days = (total_minutes / 60.0 / 8).round(1)
314
+ working_days = (total_minutes / 60.0 / HOURS_PER_WORKING_DAY).round(1)
315
+ "#{bold(hours)}h #{bold(mins)}min (#{bold(working_days)} working days)"
316
+ end
280
317
 
281
- from_str = time_min.strftime("%Y-%m-%d %H:%M")
282
- to_str = time_max.strftime("%Y-%m-%d %H:%M")
318
+ def format_hours_and_days(hours, working_days)
319
+ "#{bold(hours)}h (#{bold(working_days)} working days)"
320
+ end
283
321
 
284
- puts "\n---"
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)"
322
+ def minutes_in_period(event_ranges, period_start, period_end)
323
+ event_ranges.sum do |ev_start, ev_end|
324
+ overlap_start = [ev_start, period_start].max
325
+ overlap_end = [ev_end, period_end].min
326
+ overlap_sec = overlap_end - overlap_start
327
+ overlap_sec > 0 ? overlap_sec / 60.0 : 0
328
+ end
329
+ end
330
+
331
+ def print_weekly_breakdown(event_ranges, time_min, time_max)
332
+ puts "\nBy week:"
333
+ week_num = 1
334
+ current = time_min.to_date.beginning_of_week(:monday)
335
+ while current.to_time < time_max
336
+ week_start = [current.to_time, time_min].max
337
+ week_end_date = current.end_of_week(:monday)
338
+ week_end = [week_end_date.to_time + 1.day, time_max].min
339
+ mins = minutes_in_period(event_ranges, week_start, week_end)
340
+ hours = (mins / 60).round(1)
341
+ working_days = (mins / 60.0 / HOURS_PER_WORKING_DAY).round(1)
342
+ start_str = format_date_with_day(week_start)
343
+ end_str = format_date_with_day(week_end_date)
344
+ puts " #{bold(week_num)}. #{start_str} - #{end_str}: #{format_hours_and_days(hours, working_days)}"
345
+ week_num += 1
346
+ current = current + 7.days
347
+ end
348
+ end
349
+
350
+ def print_monthly_breakdown(event_ranges, time_min, time_max)
351
+ puts "\nBy month:"
352
+ month_num = 1
353
+ current = time_min.to_date.beginning_of_month
354
+ end_date = time_max.to_date
355
+
356
+ while current <= end_date
357
+ month_start = current.beginning_of_month.to_time
358
+ month_end = (current.end_of_month + 1.day).to_time
359
+ period_start = [month_start, time_min].max
360
+ period_end = [month_end, time_max].min
361
+
362
+ mins = minutes_in_period(event_ranges, period_start, period_end)
363
+ hours = (mins / 60).round(1)
364
+ working_days = (mins / 60.0 / HOURS_PER_WORKING_DAY).round(1)
365
+ month_name = current.strftime("%B %Y")
366
+ start_str = format_date_with_day(period_start)
367
+ last_day = (period_end.to_date - 1.day)
368
+ end_str = format_date_with_day(last_day)
369
+ puts " #{month_num}. #{month_name} (#{start_str} - #{end_str}): #{format_hours_and_days(hours, working_days)}"
370
+ month_num += 1
371
+ current = current.next_month.beginning_of_month
372
+ end
288
373
  end
289
374
  end
290
375
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlycalCli
4
- VERSION = "0.2.0"
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flycal-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - flycal-cli
8
- bindir: exe
8
+ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
@@ -79,6 +79,20 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0.9'
82
+ - !ruby/object:Gem::Dependency
83
+ name: activesupport
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '6.0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '6.0'
82
96
  - !ruby/object:Gem::Dependency
83
97
  name: webrick
84
98
  requirement: !ruby/object:Gem::Requirement
@@ -104,7 +118,7 @@ extra_rdoc_files: []
104
118
  files:
105
119
  - LICENSE
106
120
  - README.md
107
- - exe/flycal
121
+ - bin/flycal
108
122
  - lib/flycal_cli.rb
109
123
  - lib/flycal_cli/auth.rb
110
124
  - lib/flycal_cli/calendar_service.rb
File without changes