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 +4 -4
- data/README.md +87 -68
- data/lib/flycal_cli/cli.rb +110 -25
- data/lib/flycal_cli/version.rb +1 -1
- metadata +17 -3
- /data/{exe → bin}/flycal +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6e56787081596ebef227a579b0667514fa493998875f613167acdade53a4899
|
|
4
|
+
data.tar.gz: a8776aded68f58ba58759adc27b3aeb2ca63eacf9797eb8bc161aeb2f96589ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e0af4be4d8505aa21a63e4e6ed70f886419eddf971745154174ea55ec6348030cc0fdad3e40af926df128fa295a5741da84fe236930b2ec797259c2ba300219
|
|
7
|
+
data.tar.gz: d2021d42bc26dd9b82c4b7b672c923b137b2e83e1f983af96310b9c9d14e948c33cce752f99ba1f99e175d9071067484d2eb5b0763b5d8d63bf1ccb0d56085f4
|
data/README.md
CHANGED
|
@@ -1,139 +1,158 @@
|
|
|
1
1
|
# flycal-cli
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
16
|
+
Or add to your Gemfile:
|
|
12
17
|
|
|
13
18
|
```ruby
|
|
14
19
|
gem "flycal-cli"
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
Then run `bundle install`.
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
## Setup
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
36
|
+
6. Download the JSON file and save it as `~/.flycal/credentials.json`
|
|
30
37
|
|
|
31
|
-
##
|
|
38
|
+
## Commands
|
|
32
39
|
|
|
33
|
-
###
|
|
40
|
+
### login
|
|
34
41
|
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
+
Disconnect from your Google account and remove stored tokens.
|
|
44
53
|
|
|
45
54
|
```bash
|
|
46
|
-
flycal
|
|
55
|
+
flycal logout
|
|
47
56
|
```
|
|
48
57
|
|
|
49
|
-
###
|
|
58
|
+
### calendars
|
|
50
59
|
|
|
51
|
-
|
|
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
|
|
63
|
+
flycal calendars
|
|
55
64
|
```
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
The default calendar is used by the search command when no calendar is specified.
|
|
67
|
+
|
|
68
|
+
### search
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
Search for events in your calendar(s). Supports flexible date ranges and text filtering.
|
|
60
71
|
|
|
61
72
|
```bash
|
|
62
|
-
flycal search
|
|
63
|
-
flycal search
|
|
64
|
-
flycal search -f 2025-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
**Time range behavior:**
|
|
76
90
|
|
|
77
|
-
- `
|
|
78
|
-
- `
|
|
79
|
-
-
|
|
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
|
-
|
|
95
|
+
**Output:**
|
|
82
96
|
|
|
83
|
-
|
|
97
|
+
Each event is printed as: `Calendar | Start | End | Title`
|
|
84
98
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
gem push flycal-cli-0.1.0.gem
|
|
95
|
-
```
|
|
106
|
+
## Configuration
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
Data is stored in `~/.flycal/`:
|
|
98
109
|
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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.
|
|
126
|
+
gem push flycal-cli-0.1.0.gem
|
|
115
127
|
```
|
|
116
128
|
|
|
117
|
-
###
|
|
129
|
+
### Subsequent releases
|
|
118
130
|
|
|
119
|
-
|
|
131
|
+
1. Update the version in `lib/flycal_cli/version.rb`
|
|
132
|
+
2. Build and push:
|
|
120
133
|
|
|
121
|
-
```
|
|
122
|
-
|
|
134
|
+
```bash
|
|
135
|
+
gem build flycal-cli.gemspec
|
|
136
|
+
gem push flycal-cli-X.Y.Z.gem
|
|
123
137
|
```
|
|
124
138
|
|
|
125
|
-
|
|
139
|
+
3. Optionally tag and push:
|
|
126
140
|
|
|
127
141
|
```bash
|
|
128
|
-
|
|
129
|
-
|
|
142
|
+
git tag vX.Y.Z
|
|
143
|
+
git push origin vX.Y.Z
|
|
144
|
+
```
|
|
130
145
|
|
|
131
|
-
|
|
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
|
-
|
|
154
|
+
This builds the gem, pushes to RubyGems, and can handle git tagging and pushing.
|
|
136
155
|
|
|
137
|
-
##
|
|
156
|
+
## License
|
|
138
157
|
|
|
139
158
|
MIT
|
data/lib/flycal_cli/cli.rb
CHANGED
|
@@ -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
|
|
116
|
-
flycal search -i 1months --description
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
|
263
|
-
|
|
271
|
+
def format_date_with_day(dt)
|
|
272
|
+
return "-" if dt.nil?
|
|
264
273
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
318
|
+
def format_hours_and_days(hours, working_days)
|
|
319
|
+
"#{bold(hours)}h (#{bold(working_days)} working days)"
|
|
320
|
+
end
|
|
283
321
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
data/lib/flycal_cli/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- flycal-cli
|
|
8
|
-
bindir:
|
|
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
|
-
-
|
|
121
|
+
- bin/flycal
|
|
108
122
|
- lib/flycal_cli.rb
|
|
109
123
|
- lib/flycal_cli/auth.rb
|
|
110
124
|
- lib/flycal_cli/calendar_service.rb
|
/data/{exe → bin}/flycal
RENAMED
|
File without changes
|