flycal-cli 0.7.1 → 0.7.3
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 +7 -5
- data/lib/flycal_cli/cli.rb +4 -10
- data/lib/flycal_cli/clipboard.rb +54 -0
- data/lib/flycal_cli/date_time_parser.rb +95 -7
- data/lib/flycal_cli/version.rb +1 -1
- data/lib/flycal_cli.rb +1 -0
- data/locales/en.json +1 -1
- data/locales/it.json +1 -1
- data/mcp/tools.json +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9b15a5f08efcc922a2088d9a4c47fb852794932b4261406312f262905664c09
|
|
4
|
+
data.tar.gz: 114f71e6f2ca779315908015cf0b772f374b0e9de04e5775b220f8c3278aa5e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1545a6259e5a710d4db52267909e7bbbff350aba2e75d617c9e6e2cbb722bb77f9663b5ca8e08483a5606f8030642952c7b1f5c170c4550de706503d15db3fd8
|
|
7
|
+
data.tar.gz: 6a63614ed3c9cd16ad5d1cf01b358e49edd5fc46ef285b986e181973ef0c660e9353f40e38ae130265bc470073730314d31fce48639ba3942338989a371042c5
|
data/README.md
CHANGED
|
@@ -157,16 +157,18 @@ Find free time slots in your calendar. Output is a simple list for copy/paste in
|
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
159
|
flycal slots
|
|
160
|
-
flycal slots --in "5 days"
|
|
160
|
+
flycal slots --from monday --in "5 days"
|
|
161
|
+
flycal slots --from "next monday" --in "5 days" --locale it
|
|
162
|
+
flycal slots --from lunedi --in "5 days" --locale it
|
|
161
163
|
flycal slots --in "12 days" --template dinner
|
|
162
|
-
flycal slots --duration 1h
|
|
163
|
-
flycal slots --from 2026-08-01 --in "2 weeks" --duration 1h
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
**Options:**
|
|
167
167
|
|
|
168
168
|
- `--duration` — Slot length. Default: `slots.defaults.default_duration` in config (`45min`). Examples: `1h`, `30 minutes`
|
|
169
|
-
- `--from` / `-f` — Start date/time. Default: `slots.defaults.from` in config (`now`).
|
|
169
|
+
- `--from` / `-f` — Start date/time. Default: `slots.defaults.from` in config (`now`). Accepts:
|
|
170
|
+
- absolute: `YYYY-MM-DD`, locale dates (`DD-MM-YYYY` / `MM-DD-YYYY`), optional time
|
|
171
|
+
- relative: `now`, `today`/`oggi`, `tomorrow`/`domani`, `monday`/`lunedi`, `next monday`/`prossimo lunedi`, `last friday`/`scorso venerdi`
|
|
170
172
|
- `--in` / `-i` — Search window from `--from`. Default: `1 week`.
|
|
171
173
|
- `--template` / `-T` — Template from `slots.templates` in config. Default: first template (`work`)
|
|
172
174
|
- `--calendar` / `-c` — Calendar name or ID used as fallback when `exclude_calendars` is not configured
|
|
@@ -190,7 +192,7 @@ monday 3/8
|
|
|
190
192
|
19 - 23
|
|
191
193
|
```
|
|
192
194
|
|
|
193
|
-
If
|
|
195
|
+
If a clipboard tool is available (`pbcopy` on macOS, `wl-copy`/`xclip` on Linux, `clip` on Windows), the slot list without the header is also copied to the clipboard.
|
|
194
196
|
|
|
195
197
|
Slot search windows are configured in `~/.flycal/config.yml`:
|
|
196
198
|
|
data/lib/flycal_cli/cli.rb
CHANGED
|
@@ -195,7 +195,7 @@ module FlycalCli
|
|
|
195
195
|
option :in, type: :string, aliases: "-i", default: "1 week",
|
|
196
196
|
desc: "Search window from --from (default: 1 week)"
|
|
197
197
|
option :from, type: :string, aliases: "-f",
|
|
198
|
-
desc: "Start
|
|
198
|
+
desc: "Start (default: now). Dates, or relative: monday, next monday, tomorrow, lunedi..."
|
|
199
199
|
option :template, type: :string, aliases: "-T",
|
|
200
200
|
desc: "Slot template name from config (default: first template, usually work)"
|
|
201
201
|
option :calendar, type: :string, aliases: "-c", desc: "Calendar name or ID"
|
|
@@ -505,17 +505,11 @@ module FlycalCli
|
|
|
505
505
|
end
|
|
506
506
|
|
|
507
507
|
def copy_slots_to_clipboard(text)
|
|
508
|
-
|
|
508
|
+
tool = Clipboard.copy(SlotFormatter.strip_ansi(text))
|
|
509
|
+
return unless tool
|
|
509
510
|
|
|
510
|
-
IO.popen("pbcopy", "w") { |io| io.write(SlotFormatter.strip_ansi(text)) }
|
|
511
511
|
puts ""
|
|
512
|
-
puts Locale.t("slots.copied_to_clipboard")
|
|
513
|
-
rescue StandardError
|
|
514
|
-
nil
|
|
515
|
-
end
|
|
516
|
-
|
|
517
|
-
def pbcopy_available?
|
|
518
|
-
system("which", "pbcopy", out: File::NULL, err: File::NULL)
|
|
512
|
+
puts Locale.t("slots.copied_to_clipboard", tool: tool)
|
|
519
513
|
end
|
|
520
514
|
|
|
521
515
|
def parse_hour_minute(value)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlycalCli
|
|
4
|
+
# Cross-platform clipboard helper.
|
|
5
|
+
# Prefers: pbcopy (macOS) → wl-copy (Wayland) → xclip (X11) → clip (Windows).
|
|
6
|
+
module Clipboard
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
TOOLS = [
|
|
10
|
+
{ name: :pbcopy, command: %w[pbcopy] },
|
|
11
|
+
{ name: :wl_copy, command: %w[wl-copy] },
|
|
12
|
+
{ name: :xclip, command: %w[xclip -selection clipboard] },
|
|
13
|
+
{ name: :clip, command: %w[clip] }
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def copy(text)
|
|
17
|
+
tool = available_tool
|
|
18
|
+
return nil unless tool
|
|
19
|
+
|
|
20
|
+
IO.popen(tool[:command], "w") { |io| io.write(text.to_s) }
|
|
21
|
+
return nil unless $?.success?
|
|
22
|
+
|
|
23
|
+
tool[:name].to_s
|
|
24
|
+
rescue StandardError
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def available?
|
|
29
|
+
!available_tool.nil?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def available_tool
|
|
33
|
+
TOOLS.find { |tool| command_exists?(tool[:command].first) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def command_exists?(name)
|
|
37
|
+
exts =
|
|
38
|
+
if Gem.win_platform?
|
|
39
|
+
ENV.fetch("PATHEXT", ".EXE;.BAT;.CMD").split(";").reject(&:empty?)
|
|
40
|
+
else
|
|
41
|
+
[""]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |dir|
|
|
45
|
+
next false if dir.to_s.empty?
|
|
46
|
+
|
|
47
|
+
exts.any? do |ext|
|
|
48
|
+
path = File.join(dir, "#{name}#{ext}")
|
|
49
|
+
File.file?(path) && File.executable?(path)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -12,16 +12,38 @@ module FlycalCli
|
|
|
12
12
|
# YYYY-MM-DD, YYYY/MM/DD
|
|
13
13
|
# YYYY-MM-DDTHH:MM, YYYY-MM-DD HH:MM(:SS)
|
|
14
14
|
#
|
|
15
|
+
# Relative forms (en + it):
|
|
16
|
+
# now, today/oggi, tomorrow/domani, yesterday/ieri
|
|
17
|
+
# monday / lunedi, next monday / prossimo lunedi, last friday / scorso venerdi
|
|
18
|
+
#
|
|
15
19
|
# With locale +it+: DD-MM-YYYY / DD/MM/YYYY (+ optional time)
|
|
16
20
|
# With locale +en+ (default): MM-DD-YYYY / MM/DD/YYYY (+ optional time)
|
|
17
21
|
class DateTimeParser
|
|
18
22
|
TIME_SUFFIX = '(?:\s+|T)(\d{1,2}):(\d{2})(?::(\d{2}))?'.freeze
|
|
19
23
|
|
|
24
|
+
# Ruby Date#wday: 0=Sunday ... 6=Saturday
|
|
25
|
+
WEEKDAYS = {
|
|
26
|
+
"sunday" => 0, "sun" => 0, "domenica" => 0, "dom" => 0,
|
|
27
|
+
"monday" => 1, "mon" => 1, "lunedi" => 1, "lunedì" => 1, "lun" => 1,
|
|
28
|
+
"tuesday" => 2, "tue" => 2, "martedi" => 2, "martedì" => 2, "mar" => 2,
|
|
29
|
+
"wednesday" => 3, "wed" => 3, "mercoledi" => 3, "mercoledì" => 3, "mer" => 3,
|
|
30
|
+
"thursday" => 4, "thu" => 4, "giovedi" => 4, "giovedì" => 4, "gio" => 4,
|
|
31
|
+
"friday" => 5, "fri" => 5, "venerdi" => 5, "venerdì" => 5, "ven" => 5,
|
|
32
|
+
"saturday" => 6, "sat" => 6, "sabato" => 6, "sab" => 6
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
NEXT_WORDS = %w[next prossimo prossima].freeze
|
|
36
|
+
LAST_WORDS = %w[last scorso scorsa].freeze
|
|
37
|
+
THIS_WORDS = %w[this questo questa].freeze
|
|
38
|
+
|
|
20
39
|
class << self
|
|
21
40
|
def parse(str, end_of_day: false, locale: nil)
|
|
22
41
|
value = str.to_s.strip
|
|
23
42
|
raise FlycalCli::Error, invalid_message(str) if value.empty?
|
|
24
43
|
|
|
44
|
+
relative = parse_relative(value, end_of_day: end_of_day)
|
|
45
|
+
return relative if relative
|
|
46
|
+
|
|
25
47
|
loc = (locale || Locale.current_locale).to_s
|
|
26
48
|
formats = formats_for(loc)
|
|
27
49
|
|
|
@@ -33,7 +55,6 @@ module FlycalCli
|
|
|
33
55
|
return build_time(year, month, day, hour, min, sec, end_of_day: end_of_day)
|
|
34
56
|
end
|
|
35
57
|
|
|
36
|
-
# Last resort: ISO8601 / Time.parse for full timestamps
|
|
37
58
|
begin
|
|
38
59
|
return Time.iso8601(value)
|
|
39
60
|
rescue ArgumentError
|
|
@@ -56,6 +77,76 @@ module FlycalCli
|
|
|
56
77
|
|
|
57
78
|
private
|
|
58
79
|
|
|
80
|
+
def parse_relative(value, end_of_day:)
|
|
81
|
+
normalized = value.downcase.strip
|
|
82
|
+
normalized = normalized.tr("àèéìòù", "aeeiou")
|
|
83
|
+
|
|
84
|
+
case normalized
|
|
85
|
+
when "now", "adesso", "ora"
|
|
86
|
+
return Time.now
|
|
87
|
+
when "today", "oggi"
|
|
88
|
+
return time_for_date(Date.today, end_of_day: end_of_day)
|
|
89
|
+
when "tomorrow", "domani"
|
|
90
|
+
return time_for_date(Date.today + 1, end_of_day: end_of_day)
|
|
91
|
+
when "yesterday", "ieri"
|
|
92
|
+
return time_for_date(Date.today - 1, end_of_day: end_of_day)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
weekday = parse_weekday_phrase(normalized)
|
|
96
|
+
return nil unless weekday
|
|
97
|
+
|
|
98
|
+
time_for_date(weekday, end_of_day: end_of_day)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_weekday_phrase(normalized)
|
|
102
|
+
tokens = normalized.split(/\s+/)
|
|
103
|
+
return nil if tokens.empty?
|
|
104
|
+
|
|
105
|
+
qualifier = nil
|
|
106
|
+
day_token = nil
|
|
107
|
+
|
|
108
|
+
if tokens.length == 1
|
|
109
|
+
day_token = tokens[0]
|
|
110
|
+
elsif tokens.length == 2
|
|
111
|
+
qualifier = tokens[0]
|
|
112
|
+
day_token = tokens[1]
|
|
113
|
+
else
|
|
114
|
+
return nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
wday = WEEKDAYS[day_token]
|
|
118
|
+
return nil unless wday
|
|
119
|
+
|
|
120
|
+
today = Date.today
|
|
121
|
+
|
|
122
|
+
if qualifier.nil? || THIS_WORDS.include?(qualifier)
|
|
123
|
+
next_weekday(today, wday, inclusive: true)
|
|
124
|
+
elsif NEXT_WORDS.include?(qualifier)
|
|
125
|
+
next_weekday(today, wday, inclusive: false)
|
|
126
|
+
elsif LAST_WORDS.include?(qualifier)
|
|
127
|
+
previous_weekday(today, wday, inclusive: false)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def next_weekday(from, wday, inclusive:)
|
|
132
|
+
delta = (wday - from.wday) % 7
|
|
133
|
+
delta = 7 if delta.zero? && !inclusive
|
|
134
|
+
from + delta
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def previous_weekday(from, wday, inclusive:)
|
|
138
|
+
delta = (from.wday - wday) % 7
|
|
139
|
+
delta = 7 if delta.zero? && !inclusive
|
|
140
|
+
from - delta
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def time_for_date(date, end_of_day:)
|
|
144
|
+
return end_of_day_for(date) if end_of_day
|
|
145
|
+
return [date.to_time, Time.now].max if date == Date.today
|
|
146
|
+
|
|
147
|
+
date.to_time
|
|
148
|
+
end
|
|
149
|
+
|
|
59
150
|
def formats_for(locale)
|
|
60
151
|
iso = [
|
|
61
152
|
[/\A(\d{4})[-\/](\d{1,2})[-\/](\d{1,2})#{TIME_SUFFIX}\z/i, %i[year month day hour min sec]],
|
|
@@ -98,10 +189,7 @@ module FlycalCli
|
|
|
98
189
|
date = Date.new(year, month, day)
|
|
99
190
|
|
|
100
191
|
if hour.nil?
|
|
101
|
-
return
|
|
102
|
-
return [date.to_time, Time.now].max if date == Date.today
|
|
103
|
-
|
|
104
|
-
return date.to_time
|
|
192
|
+
return time_for_date(date, end_of_day: end_of_day)
|
|
105
193
|
end
|
|
106
194
|
|
|
107
195
|
Time.local(year, month, day, hour, min || 0, sec || 0)
|
|
@@ -119,9 +207,9 @@ module FlycalCli
|
|
|
119
207
|
|
|
120
208
|
def invalid_message(str)
|
|
121
209
|
if Locale.current_locale.to_s == "it"
|
|
122
|
-
"Data non valida: #{str.inspect}. Usa YYYY-MM-DD, DD-MM-YYYY
|
|
210
|
+
"Data non valida: #{str.inspect}. Usa YYYY-MM-DD, DD-MM-YYYY, oggi/domani, lunedi, prossimo lunedi, ecc."
|
|
123
211
|
else
|
|
124
|
-
"Invalid date: #{str.inspect}. Use YYYY-MM-DD, MM-DD-YYYY
|
|
212
|
+
"Invalid date: #{str.inspect}. Use YYYY-MM-DD, MM-DD-YYYY, today/tomorrow, monday, next monday, etc."
|
|
125
213
|
end
|
|
126
214
|
end
|
|
127
215
|
end
|
data/lib/flycal_cli/version.rb
CHANGED
data/lib/flycal_cli.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "flycal_cli/locale"
|
|
|
6
6
|
require "flycal_cli/auth"
|
|
7
7
|
require "flycal_cli/duration_parser"
|
|
8
8
|
require "flycal_cli/date_time_parser"
|
|
9
|
+
require "flycal_cli/clipboard"
|
|
9
10
|
require "flycal_cli/calendar_service"
|
|
10
11
|
require "flycal_cli/slot_finder"
|
|
11
12
|
require "flycal_cli/slot_formatter"
|
data/locales/en.json
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"no_available": "No available slots found.",
|
|
33
33
|
"no_calendar": "No calendar found. Run 'flycal config' to set a default.",
|
|
34
34
|
"header": "found %{count} slots\nfrom %{from} to %{to}\nwith duration %{duration}, template %{template}\nconsidering calendars",
|
|
35
|
-
"copied_to_clipboard": "✓ Slot list copied to clipboard (
|
|
35
|
+
"copied_to_clipboard": "✓ Slot list copied to clipboard (%{tool})"
|
|
36
36
|
},
|
|
37
37
|
"errors": {
|
|
38
38
|
"not_connected": "You are not connected. Run 'flycal login' first.",
|
data/locales/it.json
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"no_available": "Nessuno slot disponibile.",
|
|
33
33
|
"no_calendar": "Nessun calendario trovato. Esegui 'flycal config' per impostare il predefinito.",
|
|
34
34
|
"header": "trovati %{count} slot\nda %{from} a %{to}\ncon durata %{duration}, template %{template}\nconsiderando i calendari",
|
|
35
|
-
"copied_to_clipboard": "✓ Lista slot copiata negli appunti (
|
|
35
|
+
"copied_to_clipboard": "✓ Lista slot copiata negli appunti (%{tool})"
|
|
36
36
|
},
|
|
37
37
|
"errors": {
|
|
38
38
|
"not_connected": "Non sei connesso. Esegui prima 'flycal login'.",
|
data/mcp/tools.json
CHANGED
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
},
|
|
288
288
|
"from": {
|
|
289
289
|
"type": "string",
|
|
290
|
-
"description": "Start date/time. Default: now.
|
|
290
|
+
"description": "Start date/time. Default: now. Absolute dates, or relative: monday, next monday, tomorrow, lunedi, prossimo lunedi, etc."
|
|
291
291
|
},
|
|
292
292
|
"in": {
|
|
293
293
|
"type": "string",
|
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.7.
|
|
4
|
+
version: 0.7.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- flycal-cli
|
|
@@ -138,6 +138,7 @@ files:
|
|
|
138
138
|
- lib/flycal_cli/auth.rb
|
|
139
139
|
- lib/flycal_cli/calendar_service.rb
|
|
140
140
|
- lib/flycal_cli/cli.rb
|
|
141
|
+
- lib/flycal_cli/clipboard.rb
|
|
141
142
|
- lib/flycal_cli/config.rb
|
|
142
143
|
- lib/flycal_cli/date_time_parser.rb
|
|
143
144
|
- lib/flycal_cli/duration_parser.rb
|