flycal-cli 0.1.0 → 0.2.1
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 +85 -68
- data/lib/flycal_cli/auth.rb +9 -9
- data/lib/flycal_cli/calendar_service.rb +16 -2
- data/lib/flycal_cli/cli.rb +97 -43
- data/lib/flycal_cli/version.rb +1 -1
- metadata +3 -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: c7e777c9b4698b273770ea0d9a70edf232ffe70f2e6bd9d32ab65818ca0f5cb5
|
|
4
|
+
data.tar.gz: 5d62d5fede48b31930a6a652c8b4f165a0f82315eb85e3fb5be885e56ffc2ae7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1d95fcb97e10fa0c1144e15c7e205cb693d2c48847dc96569cb718c8899c5ecb3479504b4c19b3315dd8c74ecbcbc9510e99f2070a4c392ae206c51dba395dd
|
|
7
|
+
data.tar.gz: 6d9d08601b3d78d1aa8d3520b2f0102517b80be6ac708d0d036e3e5558d2548c1a57613f9b3121ebb12329f564a682b71843ac96b5c56a07cdf578f4dcab8f71
|
data/README.md
CHANGED
|
@@ -1,139 +1,156 @@
|
|
|
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`.
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
Before using flycal, you need OAuth credentials from Google Cloud Console:
|
|
20
27
|
|
|
21
|
-
1.
|
|
22
|
-
2.
|
|
23
|
-
3.
|
|
24
|
-
4.
|
|
25
|
-
5.
|
|
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.
|
|
42
49
|
|
|
43
|
-
|
|
50
|
+
### logout
|
|
51
|
+
|
|
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
|
+
## Configuration
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
gem push flycal-cli-0.1.0.gem
|
|
95
|
-
```
|
|
106
|
+
Data is stored in `~/.flycal/`:
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
| File | Purpose |
|
|
109
|
+
|------|---------|
|
|
110
|
+
| `config.yml` | Default calendar ID and other settings |
|
|
111
|
+
| `credentials.json` | OAuth credentials (created manually from Google Cloud Console) |
|
|
112
|
+
| `tokens.yml` | Access tokens (managed automatically) |
|
|
98
113
|
|
|
99
|
-
|
|
100
|
-
2. Commit e tag:
|
|
114
|
+
## Publishing to RubyGems
|
|
101
115
|
|
|
102
|
-
|
|
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
|
+
### First release
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
1. Create an account at [rubygems.org](https://rubygems.org) if needed
|
|
119
|
+
2. Update `flycal-cli.gemspec` with your author, email, and homepage
|
|
120
|
+
3. Build and push:
|
|
111
121
|
|
|
112
122
|
```bash
|
|
113
123
|
gem build flycal-cli.gemspec
|
|
114
|
-
gem push flycal-cli-0.
|
|
124
|
+
gem push flycal-cli-0.1.0.gem
|
|
115
125
|
```
|
|
116
126
|
|
|
117
|
-
###
|
|
127
|
+
### Subsequent releases
|
|
118
128
|
|
|
119
|
-
|
|
129
|
+
1. Update the version in `lib/flycal_cli/version.rb`
|
|
130
|
+
2. Build and push:
|
|
120
131
|
|
|
121
|
-
```
|
|
122
|
-
|
|
132
|
+
```bash
|
|
133
|
+
gem build flycal-cli.gemspec
|
|
134
|
+
gem push flycal-cli-X.Y.Z.gem
|
|
123
135
|
```
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
3. Optionally tag and push:
|
|
126
138
|
|
|
127
139
|
```bash
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
git tag vX.Y.Z
|
|
141
|
+
git push origin vX.Y.Z
|
|
142
|
+
```
|
|
130
143
|
|
|
131
|
-
|
|
144
|
+
### Using rake release
|
|
145
|
+
|
|
146
|
+
With `bundler/gem_tasks` in your Rakefile:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
132
149
|
bundle exec rake release
|
|
133
150
|
```
|
|
134
151
|
|
|
135
|
-
|
|
152
|
+
This builds the gem, pushes to RubyGems, and can handle git tagging and pushing.
|
|
136
153
|
|
|
137
|
-
##
|
|
154
|
+
## License
|
|
138
155
|
|
|
139
156
|
MIT
|
data/lib/flycal_cli/auth.rb
CHANGED
|
@@ -43,9 +43,9 @@ module FlycalCli
|
|
|
43
43
|
def login
|
|
44
44
|
unless Config.credentials_exist?
|
|
45
45
|
raise FlycalCli::Error,
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
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
|
-
#
|
|
66
|
+
# Start local server to receive the auth code
|
|
67
67
|
code = start_redirect_server(authorizer)
|
|
68
|
-
raise FlycalCli::Error, "
|
|
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>
|
|
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>
|
|
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 "\
|
|
123
|
+
puts "\nOpen this link in your browser to authenticate:\n\n"
|
|
124
124
|
puts " #{url}\n\n"
|
|
125
|
-
puts "
|
|
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:
|
|
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 "
|
|
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
|
|
data/lib/flycal_cli/cli.rb
CHANGED
|
@@ -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", "
|
|
17
|
+
desc "login", "Connect to your Google account"
|
|
17
18
|
def login
|
|
18
19
|
if Auth.logged_in?
|
|
19
|
-
puts "✓
|
|
20
|
-
puts "\
|
|
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 "
|
|
26
|
-
puts "\
|
|
27
|
-
puts "1.
|
|
28
|
-
puts "2.
|
|
29
|
-
puts "3.
|
|
30
|
-
puts "\
|
|
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✓
|
|
38
|
-
puts "\
|
|
38
|
+
puts "\n✓ Authentication completed successfully!"
|
|
39
|
+
puts "\nRun 'flycal calendars' to set the default calendar."
|
|
39
40
|
rescue FlycalCli::Error => e
|
|
40
|
-
puts "
|
|
41
|
+
puts "Error: #{e.message}"
|
|
41
42
|
exit 1
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
desc "logout", "
|
|
46
|
+
desc "logout", "Disconnect from Google account"
|
|
46
47
|
def logout
|
|
47
48
|
unless Auth.logged_in?
|
|
48
|
-
puts "
|
|
49
|
+
puts "You are not connected to any Google account."
|
|
49
50
|
return
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
Auth.logout
|
|
53
|
-
puts "✓
|
|
54
|
+
puts "✓ Disconnected successfully."
|
|
54
55
|
end
|
|
55
56
|
|
|
56
|
-
desc "calendars", "
|
|
57
|
+
desc "calendars", "List available calendars and set the default one"
|
|
57
58
|
def calendars
|
|
58
59
|
unless Auth.logged_in?
|
|
59
|
-
puts "
|
|
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("
|
|
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 "
|
|
73
|
+
puts "No calendars found."
|
|
73
74
|
return
|
|
74
75
|
end
|
|
75
76
|
|
|
76
|
-
#
|
|
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 ? " (
|
|
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
|
-
"
|
|
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✓
|
|
98
|
+
puts "\n✓ Default calendar set to: #{calendar.summary}"
|
|
98
99
|
end
|
|
99
100
|
end
|
|
100
101
|
|
|
101
|
-
desc "search", "
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 placeholder
|
|
116
|
+
flycal search -i 1months --description placeholder
|
|
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 "
|
|
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
|
-
|
|
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 "
|
|
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 "
|
|
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
|
|
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
|
-
#
|
|
213
|
+
# Use primary calendar if available
|
|
165
214
|
primary = calendar_lists.find(&:primary)
|
|
166
215
|
return [primary.id] if primary
|
|
167
|
-
#
|
|
216
|
+
# Otherwise first calendar
|
|
168
217
|
return [calendar_lists.first.id] if calendar_lists.any?
|
|
169
218
|
return []
|
|
170
219
|
end
|
|
171
220
|
|
|
172
|
-
#
|
|
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
|
-
#
|
|
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 || "(
|
|
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 "
|
|
233
|
-
puts "
|
|
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
|
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.1
|
|
4
|
+
version: 0.2.1
|
|
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:
|
|
@@ -104,7 +104,7 @@ extra_rdoc_files: []
|
|
|
104
104
|
files:
|
|
105
105
|
- LICENSE
|
|
106
106
|
- README.md
|
|
107
|
-
-
|
|
107
|
+
- bin/flycal
|
|
108
108
|
- lib/flycal_cli.rb
|
|
109
109
|
- lib/flycal_cli/auth.rb
|
|
110
110
|
- lib/flycal_cli/calendar_service.rb
|
/data/{exe → bin}/flycal
RENAMED
|
File without changes
|