flycal-cli 0.7.7 → 1.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 +6 -181
- data/bin/flycal +2 -2
- data/config/defaults.yml +2 -28
- data/lib/flycal/cache_annotator.rb +83 -0
- data/lib/flycal/cache_key.rb +82 -0
- data/lib/flycal/calendar_query.rb +60 -0
- data/lib/{flycal_cli → flycal}/calendar_service.rb +48 -4
- data/lib/{flycal_cli/cli.rb → flycal/cli/app.rb} +246 -171
- data/lib/{flycal_cli → flycal/cli}/auth.rb +5 -3
- data/lib/{flycal_cli → flycal/cli}/clipboard.rb +3 -1
- data/lib/{flycal_cli → flycal/cli}/config.rb +6 -3
- data/lib/flycal/cli/file_cache.rb +44 -0
- data/lib/flycal/cli/locale.rb +58 -0
- data/lib/flycal/cli.rb +14 -0
- data/lib/flycal/client.rb +141 -0
- data/lib/flycal/credentials.rb +34 -0
- data/lib/{flycal_cli → flycal}/date_time_parser.rb +4 -4
- data/lib/flycal/default_slots.rb +77 -0
- data/lib/{flycal_cli → flycal}/description_query.rb +3 -3
- data/lib/{flycal_cli → flycal}/duration_parser.rb +3 -3
- data/lib/flycal/error.rb +5 -0
- data/lib/flycal/event_mapper.rb +58 -0
- data/lib/{flycal_cli → flycal}/locale.rb +35 -3
- data/lib/{flycal_cli → flycal}/mock/calendar_service.rb +1 -1
- data/lib/{flycal_cli → flycal}/mock/config.rb +14 -14
- data/lib/{flycal_cli → flycal}/mock/event_generator.rb +6 -4
- data/lib/flycal/mock/slot_scenario.rb +75 -0
- data/lib/flycal/mock.rb +6 -0
- data/lib/{flycal_cli → flycal}/pipeline/aggregator.rb +21 -24
- data/lib/{flycal_cli → flycal}/pipeline/json_renderer.rb +16 -17
- data/lib/{flycal_cli → flycal}/pipeline/params.rb +1 -1
- data/lib/{flycal_cli → flycal}/pipeline/renderer.rb +4 -4
- data/lib/{flycal_cli → flycal}/pipeline/retriever.rb +1 -1
- data/lib/{flycal_cli → flycal}/pipeline/search_pipeline.rb +1 -1
- data/lib/{flycal_cli → flycal}/pipeline/text_renderer.rb +14 -12
- data/lib/flycal/pipeline.rb +6 -0
- data/lib/{flycal_cli → flycal}/slot_finder.rb +12 -12
- data/lib/flycal/slot_formatter.rb +196 -0
- data/lib/flycal/slot_query.rb +74 -0
- data/lib/flycal/slot_templates.rb +129 -0
- data/lib/flycal/time_format.rb +36 -0
- data/lib/flycal/version.rb +11 -0
- data/lib/flycal.rb +12 -0
- data/lib/flycal_cli.rb +42 -17
- data/mcp/tools.json +21 -8
- metadata +41 -25
- data/lib/flycal_cli/mock.rb +0 -10
- data/lib/flycal_cli/pipeline.rb +0 -14
- data/lib/flycal_cli/slot_formatter.rb +0 -68
- data/lib/flycal_cli/version.rb +0 -5
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/object/blank"
|
|
4
|
+
require "active_support/core_ext/hash/indifferent_access"
|
|
5
|
+
|
|
6
|
+
module Flycal
|
|
7
|
+
# Resolves slots search params (template, window, duration) without calling Google.
|
|
8
|
+
class SlotQuery
|
|
9
|
+
class << self
|
|
10
|
+
def resolve(params = {})
|
|
11
|
+
p = params.respond_to?(:with_indifferent_access) ? params.with_indifferent_access : params
|
|
12
|
+
calendar_ids = Array(p[:calendar_ids]).map(&:to_s).reject(&:empty?)
|
|
13
|
+
raise Error, "calendar_ids are required" if calendar_ids.empty?
|
|
14
|
+
|
|
15
|
+
duration_value = p[:duration].presence || "45min"
|
|
16
|
+
from_value = p[:from].presence || "now"
|
|
17
|
+
free_before_value = p[:free_before].presence || "0m"
|
|
18
|
+
free_after_value = p[:free_after].presence || "0m"
|
|
19
|
+
locale = p[:locale].presence || Locale.current_locale
|
|
20
|
+
|
|
21
|
+
template = SlotTemplates.resolve(
|
|
22
|
+
name: p[:template],
|
|
23
|
+
custom: p[:template_custom],
|
|
24
|
+
catalog: p[:template_catalog]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
slot_duration = DurationParser.to_seconds(duration_value)
|
|
28
|
+
free_before = DurationParser.to_seconds(free_before_value)
|
|
29
|
+
free_after = DurationParser.to_seconds(free_after_value)
|
|
30
|
+
time_min = DateTimeParser.parse(from_value.to_s, locale: locale)
|
|
31
|
+
time_max = resolve_time_max(p, time_min, locale)
|
|
32
|
+
raise Error, "'from' must be before end of window" if time_min >= time_max
|
|
33
|
+
|
|
34
|
+
hours = p[:hours].present? ? SlotTemplates.parse_hours(p[:hours]) : template[:hours]
|
|
35
|
+
days = p[:days].present? ? SlotTemplates.parse_days(p[:days]) : template[:days]
|
|
36
|
+
in_value = p[:in].presence
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
command: "slots",
|
|
40
|
+
calendar_ids: calendar_ids,
|
|
41
|
+
duration: duration_value,
|
|
42
|
+
from: from_value,
|
|
43
|
+
to: p[:to],
|
|
44
|
+
in: in_value,
|
|
45
|
+
free_before: free_before_value,
|
|
46
|
+
free_after: free_after_value,
|
|
47
|
+
template: template[:name],
|
|
48
|
+
locale: locale,
|
|
49
|
+
calendar: p[:calendar],
|
|
50
|
+
hours: hours,
|
|
51
|
+
days: days,
|
|
52
|
+
time_min: time_min,
|
|
53
|
+
time_max: time_max,
|
|
54
|
+
slot_duration_seconds: slot_duration,
|
|
55
|
+
free_before_seconds: free_before,
|
|
56
|
+
free_after_seconds: free_after,
|
|
57
|
+
format: p[:format].presence || "json"
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def resolve_time_max(params, time_min, locale)
|
|
64
|
+
if params[:in].present?
|
|
65
|
+
DurationParser.add_to_time(params[:in], time_min)
|
|
66
|
+
elsif params[:to].present?
|
|
67
|
+
DateTimeParser.parse(params[:to].to_s, end_of_day: true, locale: locale)
|
|
68
|
+
else
|
|
69
|
+
DurationParser.add_to_time("1 week", time_min)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flycal
|
|
4
|
+
# Named and custom slot windows. YAML/JSON shape matches ~/.flycal/config.yml:
|
|
5
|
+
#
|
|
6
|
+
# days: [1, 2, 3, 4, 5]
|
|
7
|
+
# hours:
|
|
8
|
+
# - 9:30-13:00
|
|
9
|
+
# - 14:00-18:30
|
|
10
|
+
module SlotTemplates
|
|
11
|
+
BUILTIN = {
|
|
12
|
+
"work" => {
|
|
13
|
+
"days" => [ 1, 2, 3, 4, 5 ],
|
|
14
|
+
"hours" => [ "9:30-13:00", "14:00-18:30" ]
|
|
15
|
+
},
|
|
16
|
+
"dinner" => {
|
|
17
|
+
"days" => [ 1, 2, 3, 4, 5, 6 ],
|
|
18
|
+
"hours" => [ "19-23" ]
|
|
19
|
+
}
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def names(catalog = nil)
|
|
25
|
+
normalize_catalog(catalog || DefaultSlots.templates).keys
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Resolve a named template, or a custom YAML-shaped hash.
|
|
29
|
+
# +custom+ wins over +name+ when present.
|
|
30
|
+
def resolve(name: nil, custom: nil, catalog: nil)
|
|
31
|
+
if custom_present?(custom)
|
|
32
|
+
parsed = parse(custom)
|
|
33
|
+
return parsed.merge(name: "custom")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
source = normalize_catalog(catalog || DefaultSlots.templates)
|
|
37
|
+
raise Error, "slots.templates is missing." if source.empty?
|
|
38
|
+
key = name.to_s.strip
|
|
39
|
+
key = source.keys.first.to_s if key.empty?
|
|
40
|
+
definition = source[key]
|
|
41
|
+
unless definition
|
|
42
|
+
raise Error, "Unknown slots template #{key.inspect}. Available: #{source.keys.join(", ")}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
parse(definition).merge(name: key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse(definition)
|
|
49
|
+
data = indifferent(definition)
|
|
50
|
+
days = parse_days(data[:days])
|
|
51
|
+
hours = parse_hours(data[:hours])
|
|
52
|
+
{ days: days, hours: hours }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parse_days(values)
|
|
56
|
+
days = Array(values).map(&:to_i)
|
|
57
|
+
raise Error, "slots template days cannot be empty." if days.empty?
|
|
58
|
+
|
|
59
|
+
invalid = days.reject { |d| d.between?(1, 7) }
|
|
60
|
+
raise Error, "Invalid template days #{invalid.inspect}. Use 1 (Mon) .. 7 (Sun)." unless invalid.empty?
|
|
61
|
+
|
|
62
|
+
days.uniq
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def parse_hours(values)
|
|
66
|
+
return values if values.is_a?(Array) && values.first.is_a?(Array)
|
|
67
|
+
|
|
68
|
+
ranges = Array(values).map { |item| parse_hour_range(item) }
|
|
69
|
+
raise Error, "template hours cannot be empty." if ranges.empty?
|
|
70
|
+
|
|
71
|
+
ranges.sort_by { |sh, sm, _eh, _em| (sh * 60) + sm }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse_hour_range(item)
|
|
75
|
+
start_str, end_str = item.to_s.strip.split("-", 2)
|
|
76
|
+
if start_str.nil? || end_str.nil? || start_str.empty? || end_str.empty?
|
|
77
|
+
raise Error, "Invalid hours item #{item.inspect}. Use format like '9-13' or '14:30-18:00'."
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
sh, sm = parse_clock(start_str)
|
|
81
|
+
eh, em = parse_clock(end_str)
|
|
82
|
+
start_minutes = (sh * 60) + sm
|
|
83
|
+
end_minutes = (eh * 60) + em
|
|
84
|
+
raise Error, "Invalid hours range #{item.inspect}: end must be after start." if end_minutes <= start_minutes
|
|
85
|
+
|
|
86
|
+
[ sh, sm, eh, em ]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_clock(value)
|
|
90
|
+
str = value.to_s.strip
|
|
91
|
+
if str.match?(/\A\d{1,2}\z/)
|
|
92
|
+
hour = str.to_i
|
|
93
|
+
raise Error, "Invalid hour #{value.inspect}. Must be 0-23." unless hour.between?(0, 23)
|
|
94
|
+
|
|
95
|
+
return [ hour, 0 ]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
hour_s, minute_s = str.split(":", 2)
|
|
99
|
+
hour = hour_s.to_i
|
|
100
|
+
minute = minute_s.to_i
|
|
101
|
+
unless hour.between?(0, 23) && minute.between?(0, 59)
|
|
102
|
+
raise Error, "Invalid time #{value.inspect}. Use HH:MM (e.g. 9:00, 18:30)."
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
[ hour, minute ]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def custom_present?(custom)
|
|
109
|
+
return false if custom.nil?
|
|
110
|
+
return false if custom.respond_to?(:blank?) && custom.blank?
|
|
111
|
+
return false if custom.respond_to?(:empty?) && custom.empty?
|
|
112
|
+
|
|
113
|
+
true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def normalize_catalog(catalog)
|
|
117
|
+
indifferent(catalog).each_with_object({}) do |(key, value), out|
|
|
118
|
+
out[key.to_s] = value
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def indifferent(value)
|
|
123
|
+
return {} if value.nil?
|
|
124
|
+
return value.with_indifferent_access if value.respond_to?(:with_indifferent_access)
|
|
125
|
+
|
|
126
|
+
value
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
require "date"
|
|
5
|
+
|
|
6
|
+
module Flycal
|
|
7
|
+
# Single clock for every datetime Flycal emits (JSON + text).
|
|
8
|
+
#
|
|
9
|
+
# Instants are converted to process-local Time so start/end/from/to never mix
|
|
10
|
+
# offsets (e.g. +02:00 vs +00:00) in the same payload.
|
|
11
|
+
module TimeFormat
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def iso8601(value)
|
|
15
|
+
time = unified(value)
|
|
16
|
+
time&.iso8601
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def unified(value)
|
|
20
|
+
time = coerce(value)
|
|
21
|
+
return nil unless time
|
|
22
|
+
|
|
23
|
+
Time.at(time.to_f)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def coerce(value)
|
|
27
|
+
return nil if value.nil?
|
|
28
|
+
return value if value.is_a?(Time)
|
|
29
|
+
return value.to_time if value.respond_to?(:to_time)
|
|
30
|
+
|
|
31
|
+
Time.parse(value.to_s)
|
|
32
|
+
rescue ArgumentError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flycal
|
|
4
|
+
# Kept for Zeitwerk (`lib/flycal/version.rb` → Flycal::Version).
|
|
5
|
+
# Canonical version string lives on Flycal::VERSION (see lib/flycal.rb).
|
|
6
|
+
module Version
|
|
7
|
+
def self.to_s
|
|
8
|
+
Flycal::VERSION
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
data/lib/flycal.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Flycal core namespace.
|
|
4
|
+
# Under Rails, Zeitwerk autoloads lib/flycal/*.
|
|
5
|
+
# The CLI gem entrypoint (flycal_cli/lib/flycal_cli.rb) eager-requires what it needs.
|
|
6
|
+
module Flycal
|
|
7
|
+
VERSION = "1.1"
|
|
8
|
+
|
|
9
|
+
def self.connect(credentials:)
|
|
10
|
+
Client.new(credentials: credentials)
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/flycal_cli.rb
CHANGED
|
@@ -1,20 +1,45 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
require "flycal_cli/date_time_parser"
|
|
9
|
-
require "flycal_cli/clipboard"
|
|
10
|
-
require "flycal_cli/description_query"
|
|
11
|
-
require "flycal_cli/calendar_service"
|
|
12
|
-
require "flycal_cli/slot_finder"
|
|
13
|
-
require "flycal_cli/slot_formatter"
|
|
14
|
-
require "flycal_cli/mock"
|
|
15
|
-
require "flycal_cli/pipeline"
|
|
16
|
-
require "flycal_cli/cli"
|
|
3
|
+
# Gem entrypoint for the flycal-cli package.
|
|
4
|
+
core_lib = File.expand_path("../../lib", __dir__)
|
|
5
|
+
$LOAD_PATH.unshift(core_lib) unless $LOAD_PATH.include?(core_lib)
|
|
6
|
+
cli_lib = File.expand_path(".", __dir__)
|
|
7
|
+
$LOAD_PATH.unshift(cli_lib) unless $LOAD_PATH.include?(cli_lib)
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
# Eager-load Flycal core (outside Rails/Zeitwerk).
|
|
10
|
+
require "flycal/version"
|
|
11
|
+
require "flycal/error"
|
|
12
|
+
require "flycal/locale"
|
|
13
|
+
require "flycal/time_format"
|
|
14
|
+
require "flycal/credentials"
|
|
15
|
+
require "flycal/event_mapper"
|
|
16
|
+
require "flycal/cache_key"
|
|
17
|
+
require "flycal/cache_annotator"
|
|
18
|
+
require "flycal/client"
|
|
19
|
+
require "flycal/duration_parser"
|
|
20
|
+
require "flycal/date_time_parser"
|
|
21
|
+
require "flycal/description_query"
|
|
22
|
+
require "flycal/calendar_query"
|
|
23
|
+
require "flycal/calendar_service"
|
|
24
|
+
require "flycal/slot_finder"
|
|
25
|
+
require "flycal/slot_templates"
|
|
26
|
+
require "flycal/default_slots"
|
|
27
|
+
require "flycal/slot_query"
|
|
28
|
+
require "flycal/slot_formatter"
|
|
29
|
+
require "flycal/mock"
|
|
30
|
+
require "flycal/mock/config"
|
|
31
|
+
require "flycal/mock/event_generator"
|
|
32
|
+
require "flycal/mock/calendar_service"
|
|
33
|
+
require "flycal/mock/slot_scenario"
|
|
34
|
+
require "flycal/pipeline"
|
|
35
|
+
require "flycal/pipeline/params"
|
|
36
|
+
require "flycal/pipeline/retriever"
|
|
37
|
+
require "flycal/pipeline/aggregator"
|
|
38
|
+
require "flycal/pipeline/renderer"
|
|
39
|
+
require "flycal/pipeline/text_renderer"
|
|
40
|
+
require "flycal/pipeline/json_renderer"
|
|
41
|
+
require "flycal/pipeline/search_pipeline"
|
|
42
|
+
|
|
43
|
+
require "flycal/cli"
|
|
44
|
+
|
|
45
|
+
FlycalCli = Flycal::Cli unless defined?(FlycalCli)
|
data/mcp/tools.json
CHANGED
|
@@ -461,7 +461,7 @@
|
|
|
461
461
|
{
|
|
462
462
|
"name": "flycal_slots",
|
|
463
463
|
"title": "Find free time slots",
|
|
464
|
-
"description": "Find continuous free ranges using a slots.templates schedule (days + hours). Default template = first key (usually work). Busy events come from slots.exclude_calendars (or calendar_default if empty). Respects free_before/free_after.
|
|
464
|
+
"description": "Find continuous free ranges using a slots.templates schedule (days + hours). Default template = first key (usually work). Busy events come from slots.exclude_calendars (or calendar_default if empty). Respects free_before/free_after. Output: text (header + availability, clipboard) or JSON via --format json.",
|
|
465
465
|
"interactive": false,
|
|
466
466
|
"requires_auth": true,
|
|
467
467
|
"command": {
|
|
@@ -473,14 +473,16 @@
|
|
|
473
473
|
"in": ["--in", "{value}"],
|
|
474
474
|
"template": ["--template", "{value}"],
|
|
475
475
|
"calendar": ["--calendar", "{value}"],
|
|
476
|
-
"locale": ["--locale", "{value}"]
|
|
476
|
+
"locale": ["--locale", "{value}"],
|
|
477
|
+
"format": ["--format", "{value}"]
|
|
477
478
|
},
|
|
478
479
|
"examples": [
|
|
479
480
|
["flycal", "slots"],
|
|
480
481
|
["flycal", "slots", "--in", "5 days"],
|
|
481
482
|
["flycal", "slots", "--from", "monday", "--in", "5 days", "--locale", "it"],
|
|
482
483
|
["flycal", "slots", "--in", "12 days", "--template", "dinner"],
|
|
483
|
-
["flycal", "slots", "--from", "next monday", "--duration", "1h", "--template", "work"]
|
|
484
|
+
["flycal", "slots", "--from", "next monday", "--duration", "1h", "--template", "work"],
|
|
485
|
+
["flycal", "slots", "--format", "json", "--in", "5 days"]
|
|
484
486
|
]
|
|
485
487
|
},
|
|
486
488
|
"parameters": {
|
|
@@ -532,15 +534,21 @@
|
|
|
532
534
|
"enum": ["en", "it"],
|
|
533
535
|
"required": false,
|
|
534
536
|
"description": "Optional locale override."
|
|
537
|
+
},
|
|
538
|
+
"format": {
|
|
539
|
+
"cli": ["--format"],
|
|
540
|
+
"type": "string",
|
|
541
|
+
"enum": ["text", "json"],
|
|
542
|
+
"required": false,
|
|
543
|
+
"default": "text",
|
|
544
|
+
"description": "Output format: text (shell + clipboard) or json (params/info/items/groups)."
|
|
535
545
|
}
|
|
536
546
|
},
|
|
537
547
|
"output": {
|
|
538
|
-
"format": "text/plain",
|
|
548
|
+
"format": "text/plain or application/json",
|
|
539
549
|
"includes": [
|
|
540
|
-
"count + from/to + duration + template",
|
|
541
|
-
"
|
|
542
|
-
"link: Google Calendar day URL at --from",
|
|
543
|
-
"availability ranges grouped by day (copied to clipboard without header)"
|
|
550
|
+
"text: count + from/to + duration + template, calendars, link, ranges by day (clipboard)",
|
|
551
|
+
"json: params, info (slots_found, calendars, link), flat items, groups by day"
|
|
544
552
|
],
|
|
545
553
|
"example": "found 6 slots\nfrom sat 1 August 2026 to sat 8 August 2026\nwith duration 45min, template dinner\nconsidering calendars\n- Work\n- Personal\nlink: https://calendar.google.com/calendar/r/day/2026/8/1\n\nsaturday 1/8\n19 - 23"
|
|
546
554
|
},
|
|
@@ -573,6 +581,11 @@
|
|
|
573
581
|
"type": "string",
|
|
574
582
|
"enum": ["en", "it"],
|
|
575
583
|
"description": "--locale. Optional locale override (en, it)."
|
|
584
|
+
},
|
|
585
|
+
"format": {
|
|
586
|
+
"type": "string",
|
|
587
|
+
"enum": ["text", "json"],
|
|
588
|
+
"description": "--format. text (default) or json."
|
|
576
589
|
}
|
|
577
590
|
}
|
|
578
591
|
}
|
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:
|
|
4
|
+
version: '1.1'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- flycal-cli
|
|
@@ -134,31 +134,47 @@ files:
|
|
|
134
134
|
- README.md
|
|
135
135
|
- bin/flycal
|
|
136
136
|
- config/defaults.yml
|
|
137
|
+
- lib/flycal.rb
|
|
138
|
+
- lib/flycal/cache_annotator.rb
|
|
139
|
+
- lib/flycal/cache_key.rb
|
|
140
|
+
- lib/flycal/calendar_query.rb
|
|
141
|
+
- lib/flycal/calendar_service.rb
|
|
142
|
+
- lib/flycal/cli.rb
|
|
143
|
+
- lib/flycal/cli/app.rb
|
|
144
|
+
- lib/flycal/cli/auth.rb
|
|
145
|
+
- lib/flycal/cli/clipboard.rb
|
|
146
|
+
- lib/flycal/cli/config.rb
|
|
147
|
+
- lib/flycal/cli/file_cache.rb
|
|
148
|
+
- lib/flycal/cli/locale.rb
|
|
149
|
+
- lib/flycal/client.rb
|
|
150
|
+
- lib/flycal/credentials.rb
|
|
151
|
+
- lib/flycal/date_time_parser.rb
|
|
152
|
+
- lib/flycal/default_slots.rb
|
|
153
|
+
- lib/flycal/description_query.rb
|
|
154
|
+
- lib/flycal/duration_parser.rb
|
|
155
|
+
- lib/flycal/error.rb
|
|
156
|
+
- lib/flycal/event_mapper.rb
|
|
157
|
+
- lib/flycal/locale.rb
|
|
158
|
+
- lib/flycal/mock.rb
|
|
159
|
+
- lib/flycal/mock/calendar_service.rb
|
|
160
|
+
- lib/flycal/mock/config.rb
|
|
161
|
+
- lib/flycal/mock/event_generator.rb
|
|
162
|
+
- lib/flycal/mock/slot_scenario.rb
|
|
163
|
+
- lib/flycal/pipeline.rb
|
|
164
|
+
- lib/flycal/pipeline/aggregator.rb
|
|
165
|
+
- lib/flycal/pipeline/json_renderer.rb
|
|
166
|
+
- lib/flycal/pipeline/params.rb
|
|
167
|
+
- lib/flycal/pipeline/renderer.rb
|
|
168
|
+
- lib/flycal/pipeline/retriever.rb
|
|
169
|
+
- lib/flycal/pipeline/search_pipeline.rb
|
|
170
|
+
- lib/flycal/pipeline/text_renderer.rb
|
|
171
|
+
- lib/flycal/slot_finder.rb
|
|
172
|
+
- lib/flycal/slot_formatter.rb
|
|
173
|
+
- lib/flycal/slot_query.rb
|
|
174
|
+
- lib/flycal/slot_templates.rb
|
|
175
|
+
- lib/flycal/time_format.rb
|
|
176
|
+
- lib/flycal/version.rb
|
|
137
177
|
- lib/flycal_cli.rb
|
|
138
|
-
- lib/flycal_cli/auth.rb
|
|
139
|
-
- lib/flycal_cli/calendar_service.rb
|
|
140
|
-
- lib/flycal_cli/cli.rb
|
|
141
|
-
- lib/flycal_cli/clipboard.rb
|
|
142
|
-
- lib/flycal_cli/config.rb
|
|
143
|
-
- lib/flycal_cli/date_time_parser.rb
|
|
144
|
-
- lib/flycal_cli/description_query.rb
|
|
145
|
-
- lib/flycal_cli/duration_parser.rb
|
|
146
|
-
- lib/flycal_cli/locale.rb
|
|
147
|
-
- lib/flycal_cli/mock.rb
|
|
148
|
-
- lib/flycal_cli/mock/calendar_service.rb
|
|
149
|
-
- lib/flycal_cli/mock/config.rb
|
|
150
|
-
- lib/flycal_cli/mock/event_generator.rb
|
|
151
|
-
- lib/flycal_cli/pipeline.rb
|
|
152
|
-
- lib/flycal_cli/pipeline/aggregator.rb
|
|
153
|
-
- lib/flycal_cli/pipeline/json_renderer.rb
|
|
154
|
-
- lib/flycal_cli/pipeline/params.rb
|
|
155
|
-
- lib/flycal_cli/pipeline/renderer.rb
|
|
156
|
-
- lib/flycal_cli/pipeline/retriever.rb
|
|
157
|
-
- lib/flycal_cli/pipeline/search_pipeline.rb
|
|
158
|
-
- lib/flycal_cli/pipeline/text_renderer.rb
|
|
159
|
-
- lib/flycal_cli/slot_finder.rb
|
|
160
|
-
- lib/flycal_cli/slot_formatter.rb
|
|
161
|
-
- lib/flycal_cli/version.rb
|
|
162
178
|
- locales/en.json
|
|
163
179
|
- locales/it.json
|
|
164
180
|
- mcp/README.md
|
data/lib/flycal_cli/mock.rb
DELETED
data/lib/flycal_cli/pipeline.rb
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "flycal_cli/pipeline/params"
|
|
4
|
-
require "flycal_cli/pipeline/retriever"
|
|
5
|
-
require "flycal_cli/pipeline/aggregator"
|
|
6
|
-
require "flycal_cli/pipeline/renderer"
|
|
7
|
-
require "flycal_cli/pipeline/text_renderer"
|
|
8
|
-
require "flycal_cli/pipeline/json_renderer"
|
|
9
|
-
require "flycal_cli/pipeline/search_pipeline"
|
|
10
|
-
|
|
11
|
-
module FlycalCli
|
|
12
|
-
module Pipeline
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FlycalCli
|
|
4
|
-
class SlotFormatter
|
|
5
|
-
class << self
|
|
6
|
-
def format_header(from:, to:, duration:, calendars:, count:, template: nil)
|
|
7
|
-
lines = []
|
|
8
|
-
lines << Locale.t(
|
|
9
|
-
"slots.header",
|
|
10
|
-
count: underline(count),
|
|
11
|
-
from: underline(Locale.format_long_date(from)),
|
|
12
|
-
to: underline(Locale.format_long_date(to)),
|
|
13
|
-
duration: underline(duration),
|
|
14
|
-
template: underline(template.to_s)
|
|
15
|
-
)
|
|
16
|
-
calendars.each do |cal|
|
|
17
|
-
lines << "- #{cal[:name]}"
|
|
18
|
-
end
|
|
19
|
-
lines << "link: #{google_calendar_day_url(from)}"
|
|
20
|
-
lines.join("\n")
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def format_output(slots_by_day)
|
|
24
|
-
lines = []
|
|
25
|
-
|
|
26
|
-
slots_by_day.sort_by(&:first).each do |date, slots|
|
|
27
|
-
lines << "" unless lines.empty?
|
|
28
|
-
lines << underline(day_header(date))
|
|
29
|
-
slots.each do |start_at, end_at|
|
|
30
|
-
lines << slot_range(start_at, end_at)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
lines.join("\n")
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def strip_ansi(text)
|
|
38
|
-
text.to_s.gsub(/\e\[[0-9;]*m/, "")
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def underline(str)
|
|
44
|
-
"\e[4m#{str}\e[0m"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def google_calendar_day_url(from)
|
|
48
|
-
t = from.respond_to?(:to_time) ? from.to_time : from
|
|
49
|
-
"https://calendar.google.com/calendar/r/day/#{t.year}/#{t.month}/#{t.day}"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def day_header(date)
|
|
53
|
-
"#{Locale.day_name(date)} #{date.day}/#{date.month}"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def slot_range(start_at, end_at)
|
|
57
|
-
"#{format_time(start_at)} - #{format_time(end_at)}"
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def format_time(time)
|
|
61
|
-
return time.strftime("%-H") if time.min.zero?
|
|
62
|
-
|
|
63
|
-
minutes = sprintf("%02d", time.min)
|
|
64
|
-
"#{time.hour}.#{minutes}"
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|