calendar-assistant 0.9.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/README.md +44 -22
- data/Rakefile +37 -6
- data/bin/calendar-assistant +3 -1
- data/lib/calendar_assistant.rb +14 -16
- data/lib/calendar_assistant/available_block.rb +1 -1
- data/lib/calendar_assistant/calendar_assistant.rb +21 -25
- data/lib/calendar_assistant/cli.rb +10 -10
- data/lib/calendar_assistant/cli/authorizer.rb +14 -14
- data/lib/calendar_assistant/cli/command_service.rb +2 -2
- data/lib/calendar_assistant/cli/commands.rb +47 -40
- data/lib/calendar_assistant/cli/config.rb +16 -17
- data/lib/calendar_assistant/cli/event_presenter.rb +2 -2
- data/lib/calendar_assistant/cli/event_set_presenter.rb +3 -3
- data/lib/calendar_assistant/cli/helpers.rb +7 -8
- data/lib/calendar_assistant/cli/linter_event_presenter.rb +3 -3
- data/lib/calendar_assistant/cli/linter_event_set_presenter.rb +4 -4
- data/lib/calendar_assistant/cli/printer.rb +15 -15
- data/lib/calendar_assistant/config.rb +11 -11
- data/lib/calendar_assistant/config/token_store.rb +4 -4
- data/lib/calendar_assistant/date_helpers.rb +1 -2
- data/lib/calendar_assistant/event.rb +54 -50
- data/lib/calendar_assistant/event_repository.rb +14 -15
- data/lib/calendar_assistant/event_repository_factory.rb +1 -1
- data/lib/calendar_assistant/event_set.rb +14 -14
- data/lib/calendar_assistant/extensions/google_apis_extensions.rb +1 -1
- data/lib/calendar_assistant/extensions/launchy_extensions.rb +7 -4
- data/lib/calendar_assistant/has_duration.rb +4 -5
- data/lib/calendar_assistant/lint_event_repository.rb +3 -3
- data/lib/calendar_assistant/location_config_validator.rb +3 -3
- data/lib/calendar_assistant/location_event_repository.rb +7 -8
- data/lib/calendar_assistant/predicate_collection.rb +1 -2
- data/lib/calendar_assistant/scheduler.rb +4 -5
- data/lib/calendar_assistant/string_helpers.rb +1 -1
- data/lib/calendar_assistant/version.rb +1 -1
- metadata +38 -30
@@ -7,7 +7,7 @@ class CalendarAssistant
|
|
7
7
|
@options = options.dup
|
8
8
|
@options[CalendarAssistant::Config::Keys::Options::CONTEXT] ||= context.to_s
|
9
9
|
|
10
|
-
@config
|
10
|
+
@config = CalendarAssistant::CLI::Config.new(options: @options)
|
11
11
|
@authorizer = {}
|
12
12
|
@out = CalendarAssistant::CLI::Printer.new
|
13
13
|
end
|
@@ -34,4 +34,4 @@ class CalendarAssistant
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
|
-
end
|
37
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
module CLI
|
3
3
|
class Commands < Thor
|
4
|
+
HISTORY_FILE_PATH = File.join (ENV["CA_HOME"] || ENV["HOME"]), ".calendar-assistant.history"
|
5
|
+
|
4
6
|
def self.will_create_a_service
|
5
7
|
option CalendarAssistant::Config::Keys::Settings::PROFILE,
|
6
8
|
type: :string,
|
@@ -11,7 +13,7 @@ class CalendarAssistant
|
|
11
13
|
type: :string,
|
12
14
|
banner: "FILENAME",
|
13
15
|
desc: "Load events from a local file instead of Google Calendar",
|
14
|
-
aliases: [
|
16
|
+
aliases: ["-l"]
|
15
17
|
end
|
16
18
|
|
17
19
|
def self.has_events
|
@@ -19,12 +21,12 @@ class CalendarAssistant
|
|
19
21
|
type: :string,
|
20
22
|
desc: "Event properties that must be true (see README)",
|
21
23
|
banner: "PROPERTY1[,PROPERTY2[,...]]",
|
22
|
-
aliases: [
|
24
|
+
aliases: ["-b"]
|
23
25
|
option CalendarAssistant::Config::Keys::Options::MUST_NOT_BE,
|
24
26
|
type: :string,
|
25
27
|
desc: "Event properties that must be false (see README)",
|
26
28
|
banner: "PROPERTY1[,PROPERTY2[,...]]",
|
27
|
-
aliases: [
|
29
|
+
aliases: ["-n"]
|
28
30
|
end
|
29
31
|
|
30
32
|
def self.has_multiple_calendars
|
@@ -50,7 +52,6 @@ class CalendarAssistant
|
|
50
52
|
default: CalendarAssistant::Config::DEFAULT_SETTINGS[CalendarAssistant::Config::Keys::Options::FORMATTING],
|
51
53
|
aliases: "-f"
|
52
54
|
|
53
|
-
|
54
55
|
desc "version",
|
55
56
|
"Display the version of calendar-assistant"
|
56
57
|
|
@@ -59,28 +60,26 @@ class CalendarAssistant
|
|
59
60
|
command_service.out.puts CalendarAssistant::VERSION
|
60
61
|
end
|
61
62
|
|
62
|
-
|
63
63
|
desc "config",
|
64
64
|
"Dump your configuration parameters (merge of defaults and overrides from #{CalendarAssistant::CLI::Config::CONFIG_FILE_PATH})"
|
65
65
|
|
66
66
|
def config
|
67
67
|
return if handle_help_args
|
68
68
|
settings = CalendarAssistant::CLI::Config.new.settings
|
69
|
-
command_service.out.puts TOML::Generator.new({CalendarAssistant::Config::Keys::SETTINGS => settings}).body
|
69
|
+
command_service.out.puts TOML::Generator.new({ CalendarAssistant::Config::Keys::SETTINGS => settings }).body
|
70
70
|
end
|
71
71
|
|
72
|
-
|
73
72
|
desc "setup",
|
74
73
|
"Link your local calendar-assistant installation to a Google API Client"
|
75
74
|
long_desc <<~EOD
|
76
|
-
|
77
|
-
|
78
|
-
|
75
|
+
This command will walk you through setting up a Google Cloud
|
76
|
+
Project, enabling the Google Calendar API, and saving the
|
77
|
+
credentials necessary to access the API on behalf of users.
|
79
78
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
79
|
+
If you already have downloaded client credentials, you don't
|
80
|
+
need to run this command. Instead, rename the downloaded JSON
|
81
|
+
file to `#{CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH}`
|
82
|
+
EOD
|
84
83
|
|
85
84
|
def setup
|
86
85
|
# TODO ugh see #34 for advice on how to clean this up
|
@@ -94,14 +93,14 @@ class CalendarAssistant
|
|
94
93
|
command_service.out.launch "https://developers.google.com/calendar/quickstart/ruby"
|
95
94
|
sleep 1
|
96
95
|
command_service.out.puts <<~EOT
|
97
|
-
|
96
|
+
Please click on "ENABLE THE GOOGLE CALENDAR API" and either create a new project or select an existing project.
|
98
97
|
|
99
|
-
|
98
|
+
(If you create a new project, name it something like "yourname-calendar-assistant" so you remember why it exists.)
|
100
99
|
|
101
|
-
|
100
|
+
Then click "DOWNLOAD CLIENT CONFIGURATION" to download the credentials to local disk.
|
102
101
|
|
103
|
-
|
104
|
-
|
102
|
+
Finally, paste the contents of the downloaded file here (it should be a complete JSON object):
|
103
|
+
EOT
|
105
104
|
|
106
105
|
json = command_service.out.prompt "Paste JSON here"
|
107
106
|
File.open(CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH, "w") do |f|
|
@@ -112,22 +111,21 @@ class CalendarAssistant
|
|
112
111
|
command_service.out.puts "\nOK! Your next step is to run `calendar-assistant authorize`."
|
113
112
|
end
|
114
113
|
|
115
|
-
|
116
114
|
desc "authorize PROFILE_NAME",
|
117
115
|
"create (or validate) a profile named NAME with calendar access"
|
118
116
|
long_desc <<~EOD
|
119
|
-
|
120
|
-
|
117
|
+
Create and authorize a named profile (e.g., "work", "home",
|
118
|
+
"flastname@company.tld") to access your calendar.
|
121
119
|
|
122
|
-
|
123
|
-
|
124
|
-
|
120
|
+
When setting up a profile, you'll be asked to visit a URL to
|
121
|
+
authenticate, grant authorization, and generate and persist an
|
122
|
+
access token.
|
125
123
|
|
126
|
-
|
127
|
-
|
128
|
-
|
124
|
+
In order for this to work, you'll need to have set up your API client
|
125
|
+
credentials. Run `calendar-assistant help setup` for instructions.
|
126
|
+
EOD
|
129
127
|
|
130
|
-
def authorize
|
128
|
+
def authorize(profile_name = nil)
|
131
129
|
return if handle_help_args
|
132
130
|
return help! if profile_name.nil?
|
133
131
|
|
@@ -142,7 +140,8 @@ class CalendarAssistant
|
|
142
140
|
has_multiple_calendars
|
143
141
|
|
144
142
|
has_events
|
145
|
-
|
143
|
+
|
144
|
+
def lint(datespec = "today")
|
146
145
|
calendar_assistant(datespec) do |ca, date, out|
|
147
146
|
event_set = ca.lint_events date
|
148
147
|
out.print_events ca, event_set, presenter_class: CalendarAssistant::CLI::LinterEventSetPresenter
|
@@ -159,14 +158,14 @@ class CalendarAssistant
|
|
159
158
|
has_multiple_calendars
|
160
159
|
|
161
160
|
has_events
|
162
|
-
|
161
|
+
|
162
|
+
def show(datespec = "today")
|
163
163
|
calendar_assistant(datespec) do |ca, date, out|
|
164
164
|
event_set = ca.find_events date
|
165
165
|
out.print_events ca, event_set
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
-
|
170
169
|
desc "join [TIME]",
|
171
170
|
"Open the URL for a video call attached to your meeting at time TIME (default 'now')"
|
172
171
|
option CalendarAssistant::Config::Keys::Options::JOIN,
|
@@ -175,7 +174,8 @@ class CalendarAssistant
|
|
175
174
|
will_create_a_service
|
176
175
|
|
177
176
|
has_events
|
178
|
-
|
177
|
+
|
178
|
+
def join(timespec = "now")
|
179
179
|
return if handle_help_args
|
180
180
|
set_formatting
|
181
181
|
ca = CalendarAssistant.new command_service.config, service: command_service.service
|
@@ -191,20 +191,19 @@ class CalendarAssistant
|
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
194
|
-
|
195
194
|
desc "location [DATE | DATERANGE]",
|
196
195
|
"Show your location for a date or range of dates (default 'today')"
|
197
196
|
will_create_a_service
|
198
197
|
|
199
198
|
has_events
|
200
|
-
|
199
|
+
|
200
|
+
def location(datespec = "today")
|
201
201
|
calendar_assistant(datespec) do |ca, date, out|
|
202
202
|
event_set = ca.find_location_events date
|
203
203
|
out.print_events ca, event_set
|
204
204
|
end
|
205
205
|
end
|
206
206
|
|
207
|
-
|
208
207
|
desc "location-set LOCATION [DATE | DATERANGE]",
|
209
208
|
"Set your location to LOCATION for a date or range of dates (default 'today')"
|
210
209
|
option CalendarAssistant::Config::Keys::Options::FORCE,
|
@@ -217,7 +216,8 @@ class CalendarAssistant
|
|
217
216
|
will_create_a_service
|
218
217
|
has_events
|
219
218
|
has_multiple_calendars
|
220
|
-
|
219
|
+
|
220
|
+
def location_set(location = nil, datespec = "today")
|
221
221
|
return help! if location.nil?
|
222
222
|
|
223
223
|
calendar_assistant(datespec) do |ca, date, out|
|
@@ -226,7 +226,6 @@ class CalendarAssistant
|
|
226
226
|
end
|
227
227
|
end
|
228
228
|
|
229
|
-
|
230
229
|
desc "availability [DATE | DATERANGE | TIMERANGE]",
|
231
230
|
"Show your availability for a date or range of dates (default 'today')"
|
232
231
|
option CalendarAssistant::Config::Keys::Settings::MEETING_LENGTH,
|
@@ -250,13 +249,21 @@ class CalendarAssistant
|
|
250
249
|
has_multiple_calendars
|
251
250
|
will_create_a_service
|
252
251
|
has_events
|
253
|
-
|
252
|
+
|
253
|
+
def availability(datespec = "today")
|
254
254
|
calendar_assistant(datespec) do |ca, date, out|
|
255
255
|
event_set = ca.availability date
|
256
256
|
out.print_available_blocks ca, event_set
|
257
257
|
end
|
258
258
|
end
|
259
259
|
|
260
|
+
desc "interactive", "interactive console for calendar assistant"
|
261
|
+
def interactive
|
262
|
+
return if handle_help_args
|
263
|
+
require "thor_repl"
|
264
|
+
ThorRepl.start(CalendarAssistant::CLI::Commands, history_file_path: HISTORY_FILE_PATH, prompt: Rainbow("calendar-asssistant> ").bright)
|
265
|
+
end
|
266
|
+
|
260
267
|
private
|
261
268
|
|
262
269
|
def set_formatting
|
@@ -267,7 +274,7 @@ class CalendarAssistant
|
|
267
274
|
@command_service ||= CommandService.new(context: current_command_chain.first, options: options)
|
268
275
|
end
|
269
276
|
|
270
|
-
def calendar_assistant
|
277
|
+
def calendar_assistant(datespec = "today", &block)
|
271
278
|
return if handle_help_args
|
272
279
|
set_formatting
|
273
280
|
command_service.calendar_assistant(datespec, &block)
|
@@ -1,31 +1,30 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
module CLI
|
3
3
|
class Config < CalendarAssistant::Config
|
4
|
-
class TomlParseFailure < CalendarAssistant::BaseException
|
4
|
+
class TomlParseFailure < CalendarAssistant::BaseException
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
|
+
class NoConfigFileToPersist < CalendarAssistant::BaseException
|
7
8
|
end
|
8
9
|
|
9
|
-
CONFIG_FILE_PATH = File.join (ENV[
|
10
|
+
CONFIG_FILE_PATH = File.join (ENV["CA_HOME"] || ENV["HOME"]), ".calendar-assistant"
|
10
11
|
attr_reader :config_file_path
|
11
12
|
|
12
|
-
def initialize
|
13
|
+
def initialize(options: {},
|
13
14
|
config_file_path: CONFIG_FILE_PATH,
|
14
|
-
defaults: DEFAULT_SETTINGS
|
15
|
-
|
16
|
-
|
15
|
+
defaults: DEFAULT_SETTINGS)
|
17
16
|
@config_file_path = config_file_path
|
18
17
|
|
19
18
|
user_config = if File.exist? config_file_path
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
19
|
+
begin
|
20
|
+
FileUtils.chmod 0600, config_file_path
|
21
|
+
TOML.load_file config_file_path
|
22
|
+
rescue Exception => e
|
23
|
+
raise TomlParseFailure, "could not parse TOML file '#{config_file_path}': #{e}"
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Hash.new
|
27
|
+
end
|
29
28
|
super(options: options, defaults: defaults, user_config: user_config)
|
30
29
|
end
|
31
30
|
|
@@ -48,4 +47,4 @@ class CalendarAssistant
|
|
48
47
|
end
|
49
48
|
end
|
50
49
|
end
|
51
|
-
end
|
50
|
+
end
|
@@ -46,7 +46,7 @@ class CalendarAssistant
|
|
46
46
|
date_ansi_codes << :bright if current?
|
47
47
|
date_ansi_codes << :faint if past?
|
48
48
|
|
49
|
-
date_ansi_codes.inject(rainbow.wrap(date)) {|text, ansi| text.send ansi}
|
49
|
+
date_ansi_codes.inject(rainbow.wrap(date)) { |text, ansi| text.send ansi }
|
50
50
|
end
|
51
51
|
|
52
52
|
def event_date
|
@@ -60,7 +60,7 @@ class CalendarAssistant
|
|
60
60
|
end
|
61
61
|
else
|
62
62
|
if start_date == end_date
|
63
|
-
sprintf("%s - %s", start.date_time.strftime("%Y-%m-%d %H:%M"),
|
63
|
+
sprintf("%s - %s", start.date_time.strftime("%Y-%m-%d %H:%M"), __getobj__.end.date_time.strftime("%H:%M"))
|
64
64
|
else
|
65
65
|
sprintf("%s - %s", start.date_time.strftime("%Y-%m-%d %H:%M"), __getobj__.end.date_time.strftime("%Y-%m-%d %H:%M"))
|
66
66
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
class CalendarAssistant
|
3
3
|
module CLI
|
4
4
|
module Helpers
|
5
|
-
class ChronicParseException < CalendarAssistant::BaseException
|
5
|
+
class ChronicParseException < CalendarAssistant::BaseException
|
6
6
|
end
|
7
7
|
|
8
|
-
def self.parse_datespec
|
8
|
+
def self.parse_datespec(userspec)
|
9
9
|
start_userspec, end_userspec = userspec.split(/ ?\.\.\.? ?/)
|
10
10
|
|
11
11
|
if end_userspec.nil?
|
@@ -25,21 +25,20 @@ class CalendarAssistant
|
|
25
25
|
|
26
26
|
def self.now
|
27
27
|
CalendarAssistant::Event.new(
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
Google::Apis::CalendarV3::Event.new(start: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
|
29
|
+
end: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
|
30
|
+
summary: Rainbow(" now ").inverse.faint)
|
31
31
|
)
|
32
32
|
end
|
33
33
|
|
34
|
-
def self.find_av_uri
|
34
|
+
def self.find_av_uri(ca, timespec)
|
35
35
|
time = Chronic.parse timespec
|
36
36
|
range = time..(time + 5.minutes)
|
37
37
|
event_set = ca.find_events range
|
38
38
|
|
39
39
|
[CalendarAssistant::Event::Response::ACCEPTED,
|
40
40
|
CalendarAssistant::Event::Response::TENTATIVE,
|
41
|
-
CalendarAssistant::Event::Response::NEEDS_ACTION
|
42
|
-
].each do |response|
|
41
|
+
CalendarAssistant::Event::Response::NEEDS_ACTION].each do |response|
|
43
42
|
event_set.events.reverse.select do |event|
|
44
43
|
event.response_status == response
|
45
44
|
end.each do |event|
|
@@ -12,12 +12,12 @@ class CalendarAssistant
|
|
12
12
|
s += rainbow.wrap(sprintf(" | %s", view_summary)).bold
|
13
13
|
s += event_attributes unless private?
|
14
14
|
s = rainbow.wrap(Rainbow.uncolor(s)).faint.strike if declined?
|
15
|
-
s += "\n #{
|
15
|
+
s += "\n #{" " * (date_length + 2)}attendees: #{attendees}"
|
16
16
|
s
|
17
17
|
end
|
18
18
|
|
19
19
|
def attendees
|
20
|
-
if required_other_attendees
|
20
|
+
if required_other_attendees.length > SUMMARY_THRESHOLD
|
21
21
|
summary_attendee_list
|
22
22
|
else
|
23
23
|
detailed_attendee_list
|
@@ -43,7 +43,7 @@ class CalendarAssistant
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def required_other_attendees
|
46
|
-
@required_other_attendees ||= (other_human_attendees || []).select {|a| !a.optional }
|
46
|
+
@required_other_attendees ||= (other_human_attendees || []).select { |a| !a.optional }
|
47
47
|
end
|
48
48
|
|
49
49
|
def response_emoji(response_status)
|
@@ -7,9 +7,9 @@ class CalendarAssistant
|
|
7
7
|
|
8
8
|
def title
|
9
9
|
rainbow.wrap(<<~OUT)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
#{event_repository.calendar.id}
|
11
|
+
- looking for events that need attention
|
12
|
+
- all times in #{event_repository.calendar.time_zone}
|
13
13
|
OUT
|
14
14
|
end
|
15
15
|
|
@@ -20,4 +20,4 @@ class CalendarAssistant
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
|
-
end
|
23
|
+
end
|
@@ -2,16 +2,15 @@
|
|
2
2
|
class CalendarAssistant
|
3
3
|
module CLI
|
4
4
|
class Printer
|
5
|
-
class LaunchUrlException < CalendarAssistant::BaseException
|
6
|
-
|
5
|
+
class LaunchUrlException < CalendarAssistant::BaseException; end
|
7
6
|
|
8
7
|
attr_reader :io
|
9
8
|
|
10
|
-
def initialize
|
9
|
+
def initialize(io = STDOUT)
|
11
10
|
@io = io
|
12
11
|
end
|
13
12
|
|
14
|
-
def launch
|
13
|
+
def launch(url)
|
15
14
|
begin
|
16
15
|
Launchy.open(url)
|
17
16
|
rescue Exception => e
|
@@ -19,11 +18,11 @@ class CalendarAssistant
|
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
22
|
-
def puts
|
21
|
+
def puts(*args)
|
23
22
|
io.puts(*args)
|
24
23
|
end
|
25
24
|
|
26
|
-
def prompt
|
25
|
+
def prompt(query, default = nil)
|
27
26
|
loop do
|
28
27
|
message = query
|
29
28
|
message += " [#{default}]" if default
|
@@ -39,27 +38,28 @@ class CalendarAssistant
|
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
42
|
-
def print_events
|
41
|
+
def print_events(ca, event_set, presenter_class: CLI::EventSetPresenter)
|
43
42
|
puts presenter_class.new(event_set, config: ca.config).to_s
|
44
43
|
puts
|
45
44
|
end
|
46
45
|
|
47
|
-
def print_available_blocks
|
48
|
-
ers = ca.config.calendar_ids.map {|calendar_id| ca.event_repository calendar_id}
|
49
|
-
time_zones = ers.map {|er| er.calendar.time_zone}.uniq
|
46
|
+
def print_available_blocks(ca, event_set, omit_title: false)
|
47
|
+
ers = ca.config.calendar_ids.map { |calendar_id| ca.event_repository calendar_id }
|
48
|
+
time_zones = ers.map { |er| er.calendar.time_zone }.uniq
|
50
49
|
|
51
50
|
unless omit_title
|
52
|
-
puts Rainbow(ers.map {|er| er.calendar.id}.join(", ")).italic
|
51
|
+
puts Rainbow(ers.map { |er| er.calendar.id }.join(", ")).italic
|
53
52
|
puts Rainbow(sprintf("- looking for blocks at least %s long",
|
54
53
|
ChronicDuration.output(
|
55
|
-
|
56
|
-
|
54
|
+
ChronicDuration.parse(
|
55
|
+
ca.config.setting(Config::Keys::Settings::MEETING_LENGTH)
|
56
|
+
)
|
57
|
+
))).italic
|
57
58
|
time_zones.each do |time_zone|
|
58
59
|
puts Rainbow(sprintf("- between %s and %s in %s",
|
59
60
|
ca.config.setting(Config::Keys::Settings::START_OF_DAY),
|
60
61
|
ca.config.setting(Config::Keys::Settings::END_OF_DAY),
|
61
|
-
time_zone
|
62
|
-
)).italic
|
62
|
+
time_zone)).italic
|
63
63
|
end
|
64
64
|
puts
|
65
65
|
end
|