flycal-cli 0.7.1 → 0.7.2
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 +6 -4
- data/lib/flycal_cli/cli.rb +1 -1
- data/lib/flycal_cli/date_time_parser.rb +95 -7
- data/lib/flycal_cli/version.rb +1 -1
- data/mcp/tools.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 371b44577d9ce6befe5b01bc54768999844615943b982a6e9cd9cd8c9737d007
|
|
4
|
+
data.tar.gz: e3c6c00e03e38eccaea97ca045582a35d737916f1203963cc2c989ebf75de46b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35d47be3955556a98d3301782c2fc7deb7064134cb38792f128ade6c78807d5f28131696d14b3f2a72c7d7ae5acf9764b880d634ac52665accc6aa239c1026fe
|
|
7
|
+
data.tar.gz: da10a2548c8bdd03ebc94e990b58e7a3d30de98e2f759a927e777e08d8617006713abeafd9e220fac4fbbb6d9c1e0835a8e0cb8650f4ae30f4a00a355bb1f478
|
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
|
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"
|
|
@@ -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/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",
|