twig 1.6 → 1.7

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.
data/lib/twig/branch.rb CHANGED
@@ -2,14 +2,14 @@ class Twig
2
2
 
3
3
  # Represents a Git branch.
4
4
  class Branch
5
-
6
- EMPTY_PROPERTY_NAME_ERROR = 'Branch property names cannot be empty strings.'
5
+ PARENT_PROPERTY = 'diff-branch'
7
6
  PROPERTY_NAME_FROM_GIT_CONFIG = /^branch\.[^.]+\.([^=]+)=.*$/
8
7
  RESERVED_BRANCH_PROPERTY_NAMES = %w[branch merge property rebase remote]
9
8
 
10
9
  class EmptyPropertyNameError < ArgumentError
10
+ DEFAULT_MESSAGE = 'Branch property names cannot be empty strings.'
11
11
  def initialize(message = nil)
12
- message ||= EMPTY_PROPERTY_NAME_ERROR
12
+ message ||= DEFAULT_MESSAGE
13
13
  super
14
14
  end
15
15
  end
@@ -24,9 +24,9 @@ class Twig
24
24
  split("\n")
25
25
 
26
26
  branch_tuples.inject([]) do |result, branch_tuple|
27
- name, time_string, time_ago = branch_tuple.split(REF_FORMAT_SEPARATOR)
27
+ name, time_string = branch_tuple.split(REF_FORMAT_SEPARATOR)
28
28
  time = Time.parse(time_string)
29
- commit_time = Twig::CommitTime.new(time, time_ago)
29
+ commit_time = Twig::CommitTime.new(time)
30
30
  branch = Branch.new(name, :last_commit_time => commit_time)
31
31
  result << branch
32
32
  end
@@ -34,7 +34,7 @@ class Twig
34
34
  end
35
35
 
36
36
  def self.all_branch_names
37
- @_all_branch_names ||= self.all_branches.map { |branch| branch.name }
37
+ @_all_branch_names ||= all_branches.map { |branch| branch.name }
38
38
  end
39
39
 
40
40
  def self.all_property_names
@@ -59,6 +59,10 @@ class Twig
59
59
  raise EmptyPropertyNameError if property_name.empty?
60
60
  end
61
61
 
62
+ def self.shellescape_property_value(property_value)
63
+ Shellwords.escape(property_value).gsub('\\ ', ' ')
64
+ end
65
+
62
66
  def initialize(name, attrs = {})
63
67
  self.name = name
64
68
  raise ArgumentError, '`name` is required' if name.empty?
@@ -76,6 +80,10 @@ class Twig
76
80
  }
77
81
  end
78
82
 
83
+ def parent_name
84
+ get_property(PARENT_PROPERTY)
85
+ end
86
+
79
87
  def sanitize_property(property_name)
80
88
  property_name.gsub(/[ _]+/, '')
81
89
  end
@@ -92,7 +100,7 @@ class Twig
92
100
  return {} if property_names.empty?
93
101
 
94
102
  property_names_regexp = escaped_property_names(property_names).join('|')
95
- git_config_regexp = "branch\.#{name}\.(#{ property_names_regexp })$"
103
+ git_config_regexp = "branch\.#{Shellwords.escape(name)}\.(#{ property_names_regexp })$"
96
104
  cmd = %{git config --get-regexp "#{git_config_regexp}"}
97
105
 
98
106
  git_result = Twig.run(cmd) || ''
@@ -114,7 +122,6 @@ class Twig
114
122
  properties.merge(property_name => property_value)
115
123
  end
116
124
  end
117
-
118
125
  end
119
126
 
120
127
  def get_property(property_name)
@@ -134,8 +141,9 @@ class Twig
134
141
  raise ArgumentError,
135
142
  %{Can't set a branch property to an empty string.}
136
143
  else
137
- git_config = "branch.#{name}.#{property_name}"
138
- Twig.run(%{git config #{git_config} "#{value}"})
144
+ git_config = "branch.#{name.shellescape}.#{property_name}"
145
+ escaped_value = Branch.shellescape_property_value(value)
146
+ Twig.run(%{git config #{git_config} "#{escaped_value}"})
139
147
  result_body = %{property "#{property_name}" as "#{value}" for branch "#{name}".}
140
148
  if $?.success?
141
149
  "Saved #{result_body}"
@@ -152,7 +160,7 @@ class Twig
152
160
  value = get_property(property_name)
153
161
 
154
162
  if value
155
- git_config = "branch.#{name}.#{property_name}"
163
+ git_config = "branch.#{name.shellescape}.#{property_name}"
156
164
  Twig.run(%{git config --unset #{git_config}})
157
165
  %{Removed property "#{property_name}" for branch "#{name}".}
158
166
  else
@@ -160,6 +168,5 @@ class Twig
160
168
  %{The branch "#{name}" does not have the property "#{property_name}".}
161
169
  end
162
170
  end
163
-
164
171
  end
165
172
  end
data/lib/twig/cli.rb CHANGED
@@ -1,17 +1,16 @@
1
1
  require 'optparse'
2
2
  require 'rbconfig'
3
+ require File.expand_path(File.join(File.dirname(__FILE__), 'cli', 'help'))
3
4
 
4
5
  class Twig
6
+ # Handles raw input from the command-line interface.
5
7
  module Cli
6
-
7
8
  def self.prompt_with_choices(prompt, choices)
8
9
  # Prints the given string `prompt` and the array `choices` numbered, and
9
10
  # prompts the user to enter a number. Returns the matching value, or nil
10
11
  # if the user input is invalid.
11
12
 
12
- if choices.size < 2
13
- raise ArgumentError, 'At least two choices required'
14
- end
13
+ raise ArgumentError, 'At least two choices required' if choices.size < 2
15
14
 
16
15
  puts prompt
17
16
  choices.each_with_index do |choice, index|
@@ -23,106 +22,11 @@ class Twig
23
22
  choices[input - 1]
24
23
  end
25
24
 
26
- def help_intro
27
- version_string = "Twig v#{Twig::VERSION}"
28
-
29
- intro = help_paragraph(%{
30
- Twig is your personal Git branch assistant. It's a command-line tool for
31
- listing your most recent branches, and for remembering branch details
32
- for you, like issue tracker ids and todos. It also supports subcommands,
33
- like automatically fetching statuses from your issue tracking system.
34
- })
35
-
36
- intro = <<-BANNER.gsub(/^[ ]+/, '')
37
-
38
- #{version_string}
39
- #{'-' * version_string.size}
40
-
41
- #{intro}
42
-
43
- #{Twig::HOMEPAGE}
44
- BANNER
45
-
46
- intro + ' ' # Force extra blank line
47
- end
48
-
49
- def help_separator(option_parser, text, options = {})
50
- options[:trailing] ||= "\n\n"
51
- option_parser.separator "\n#{text}#{options[:trailing]}"
52
- end
53
-
54
- def help_description(text, options = {})
55
- width = options[:width] || 40
56
- words = text.gsub(/\n?\s+/, ' ').strip.split(' ')
57
- lines = []
58
-
59
- # Split words into lines
60
- while words.any?
61
- current_word = words.shift
62
- current_word_size = unformat_string(current_word).size
63
- last_line = lines.last
64
- last_line_size = last_line && unformat_string(last_line).size
65
-
66
- if last_line_size && (last_line_size + current_word_size + 1 <= width)
67
- last_line << ' ' << current_word
68
- elsif current_word_size >= width
69
- lines << current_word[0...width]
70
- words.unshift(current_word[width..-1])
71
- else
72
- lines << current_word
73
- end
74
- end
75
-
76
- lines << ' ' if options[:add_separator]
77
- lines
78
- end
79
-
80
- def help_description_for_custom_property(option_parser, desc_lines, options = {})
81
- options[:trailing] ||= "\n"
82
- indent = ' '
83
- left_column_width = 29
84
-
85
- help_desc = desc_lines.inject('') do |desc, (left_column, right_column)|
86
- desc + indent +
87
- sprintf("%-#{left_column_width}s", left_column) + right_column + "\n"
88
- end
89
-
90
- help_separator(option_parser, help_desc, :trailing => options[:trailing])
91
- end
92
-
93
- def help_paragraph(text)
94
- help_description(text, :width => 80).join("\n")
95
- end
96
-
97
- def help_line_for_custom_property?(line)
98
- is_custom_property_except = (
99
- line.include?('--except-') &&
100
- !line.include?('--except-branch') &&
101
- !line.include?('--except-property') &&
102
- !line.include?('--except-PROPERTY')
103
- )
104
- is_custom_property_only = (
105
- line.include?('--only-') &&
106
- !line.include?('--only-branch') &&
107
- !line.include?('--only-property') &&
108
- !line.include?('--only-PROPERTY')
109
- )
110
- is_custom_property_width = (
111
- line =~ /--.+-width/ &&
112
- !line.include?('--branch-width') &&
113
- !line.include?('--PROPERTY-width')
114
- )
115
-
116
- is_custom_property_except ||
117
- is_custom_property_only ||
118
- is_custom_property_width
119
- end
120
-
121
25
  def run_pager
122
26
  # Starts a pager so that all following STDOUT output is paginated.
123
27
  # Based on: http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
124
28
 
125
- return if Twig::System.windows? || !$stdout.tty?
29
+ return if Twig::System.windows? || !$stdout.tty? || !Kernel.respond_to?(:fork)
126
30
 
127
31
  read_io, write_io = IO.pipe
128
32
 
@@ -152,28 +56,28 @@ class Twig
152
56
  custom_properties = Twig::Branch.all_property_names
153
57
 
154
58
  option_parser = OptionParser.new do |opts|
155
- opts.banner = help_intro
59
+ opts.banner = Help.intro
156
60
  opts.summary_indent = ' ' * 2
157
61
  opts.summary_width = 32
158
62
 
63
+ ###
159
64
 
160
-
161
- help_separator(opts, 'Common options:')
65
+ Help.header(opts, 'Common options')
162
66
 
163
67
  desc = 'Use a specific branch.'
164
68
  opts.on(
165
- '-b BRANCH', '--branch BRANCH', *help_description(desc)
69
+ '-b BRANCH', '--branch BRANCH', *Help.description(desc)
166
70
  ) do |branch|
167
71
  set_option(:branch, branch)
168
72
  end
169
73
 
170
74
  desc = 'Unset a branch property.'
171
- opts.on('--unset PROPERTY', *help_description(desc)) do |property_name|
75
+ opts.on('--unset PROPERTY', *Help.description(desc)) do |property_name|
172
76
  set_option(:unset_property, property_name)
173
77
  end
174
78
 
175
79
  desc = 'Show this help content.'
176
- opts.on('--help', *help_description(desc)) do
80
+ opts.on('--help', *Help.description(desc)) do
177
81
  summary_lines = opts.to_s.split("\n")
178
82
  run_pager
179
83
 
@@ -183,7 +87,7 @@ class Twig
183
87
  # Squash successive blank lines
184
88
  next if line == "\n" && prev_line == "\n"
185
89
 
186
- unless help_line_for_custom_property?(line)
90
+ unless Help.line_for_custom_property?(line)
187
91
  puts line
188
92
  prev_line = line
189
93
  end
@@ -193,17 +97,18 @@ class Twig
193
97
  end
194
98
 
195
99
  desc = 'Show Twig version.'
196
- opts.on('--version', *help_description(desc)) do
197
- puts Twig::VERSION; exit
100
+ opts.on('--version', *Help.description(desc)) do
101
+ puts Twig::VERSION
102
+ exit
198
103
  end
199
104
 
105
+ ###
200
106
 
201
-
202
- help_separator(opts, 'Filtering branches:')
107
+ Help.header(opts, 'Filtering branches')
203
108
 
204
109
  desc = 'Only list branches below a given age.'
205
110
  opts.on(
206
- '--max-days-old AGE', *help_description(desc, :add_separator => true)
111
+ '--max-days-old AGE', *Help.description(desc, :add_blank_line => true)
207
112
  ) do |age|
208
113
  set_option(:max_days_old, age)
209
114
  end
@@ -211,13 +116,13 @@ class Twig
211
116
  desc = 'Only list branches whose name matches a given pattern.'
212
117
  opts.on(
213
118
  '--only-branch PATTERN',
214
- *help_description(desc, :add_separator => true)
119
+ *Help.description(desc, :add_blank_line => true)
215
120
  ) do |pattern|
216
121
  set_option(:property_only, :branch => pattern)
217
122
  end
218
123
 
219
124
  desc = 'Do not list branches whose name matches a given pattern.'
220
- opts.on('--except-branch PATTERN', *help_description(desc)) do |pattern|
125
+ opts.on('--except-branch PATTERN', *Help.description(desc)) do |pattern|
221
126
  set_option(:property_except, :branch => pattern)
222
127
  end
223
128
 
@@ -232,43 +137,43 @@ class Twig
232
137
  set_option(:property_except, property_name_sym => pattern)
233
138
  end
234
139
  end
235
- help_description_for_custom_property(opts, [
140
+ Help.description_for_custom_property(opts, [
236
141
  ['--only-PROPERTY PATTERN', 'Only list branches with a given property'],
237
142
  ['', 'that matches a given pattern.']
238
143
  ], :trailing => '')
239
- help_description_for_custom_property(opts, [
144
+ Help.description_for_custom_property(opts, [
240
145
  ['--except-PROPERTY PATTERN', 'Do not list branches with a given property'],
241
146
  ['', 'that matches a given pattern.']
242
147
  ])
243
148
 
244
149
  desc =
245
- 'Print branch properties in a format that can be used by other ' +
150
+ 'Print branch properties in a format that can be used by other ' \
246
151
  'tools. Currently, the only supported value is `json`.'
247
152
  opts.on(
248
- '--format FORMAT', *help_description(desc, :add_separator => true)
153
+ '--format FORMAT', *Help.description(desc, :add_blank_line => true)
249
154
  ) do |format|
250
155
  set_option(:format, format)
251
156
  end
252
157
 
253
158
  desc =
254
- 'Lists all branches regardless of other filtering options. ' +
159
+ 'Lists all branches regardless of other filtering options. ' \
255
160
  'Useful for overriding options in ' +
256
161
  File.basename(Twig::Options::CONFIG_PATH) + '.'
257
- opts.on('--all', *help_description(desc)) do |pattern|
162
+ opts.on('--all', *Help.description(desc)) do |pattern|
258
163
  unset_option(:max_days_old)
259
164
  unset_option(:property_except)
260
165
  unset_option(:property_only)
261
166
  end
262
167
 
168
+ ###
263
169
 
264
-
265
- help_separator(opts, 'Listing branches:')
170
+ Help.header(opts, 'Listing branches')
266
171
 
267
172
  desc = <<-DESC
268
173
  Set the width for the `branch` column.
269
174
  (Default: #{Twig::DEFAULT_BRANCH_COLUMN_WIDTH})
270
175
  DESC
271
- opts.on('--branch-width NUMBER', *help_description(desc)) do |width|
176
+ opts.on('--branch-width NUMBER', *Help.description(desc)) do |width|
272
177
  set_option(:property_width, :branch => width)
273
178
  end
274
179
 
@@ -277,7 +182,7 @@ class Twig
277
182
  set_option(:property_width, property_name.to_sym => width)
278
183
  end
279
184
  end
280
- help_description_for_custom_property(opts, [
185
+ Help.description_for_custom_property(opts, [
281
186
  ['--PROPERTY-width NUMBER', "Set the width for a given property's column."],
282
187
  ['', "(Default: #{Twig::DEFAULT_PROPERTY_COLUMN_WIDTH})"]
283
188
  ])
@@ -288,7 +193,7 @@ class Twig
288
193
  DESC
289
194
  opts.on(
290
195
  '--only-property PATTERN',
291
- *help_description(desc, :add_separator => true)
196
+ *Help.description(desc, :add_blank_line => true)
292
197
  ) do |pattern|
293
198
  set_option(:property_only_name, pattern)
294
199
  end
@@ -299,17 +204,17 @@ class Twig
299
204
  DESC
300
205
  opts.on(
301
206
  '--except-property PATTERN',
302
- *help_description(desc, :add_separator => true)
207
+ *Help.description(desc, :add_blank_line => true)
303
208
  ) do |pattern|
304
209
  set_option(:property_except_name, pattern)
305
210
  end
306
211
 
307
- colors = Twig::Display::COLORS.keys.map do |value|
308
- format_string(value, :color => value)
309
- end.join(', ')
310
- weights = Twig::Display::WEIGHTS.keys.map do |value|
311
- format_string(value, :weight => value)
312
- end.join(' and ')
212
+ colors = Twig::Display::COLORS.keys.
213
+ map { |value| format_string(value, :color => value) }.
214
+ join(', ')
215
+ weights = Twig::Display::WEIGHTS.keys.
216
+ map { |value| format_string(value, :weight => value) }.
217
+ join(' and ')
313
218
  default_header_style = format_string(
314
219
  Twig::DEFAULT_HEADER_COLOR.to_s,
315
220
  :color => Twig::DEFAULT_HEADER_COLOR
@@ -321,19 +226,19 @@ class Twig
321
226
  DESC
322
227
  opts.on(
323
228
  '--header-style "STYLE"',
324
- *help_description(desc, :add_separator => true)
229
+ *Help.description(desc, :add_blank_line => true)
325
230
  ) do |style|
326
231
  set_option(:header_style, style)
327
232
  end
328
233
 
329
234
  desc = 'Show oldest branches first. (Default: false)'
330
- opts.on('--reverse', *help_description(desc)) do
235
+ opts.on('--reverse', *Help.description(desc)) do
331
236
  set_option(:reverse, true)
332
237
  end
333
238
 
239
+ ###
334
240
 
335
-
336
- help_separator(opts, 'GitHub integration:')
241
+ Help.header(opts, 'GitHub integration')
337
242
 
338
243
  desc = <<-DESC
339
244
  Set a custom GitHub API URI prefix, e.g.,
@@ -342,7 +247,7 @@ class Twig
342
247
  DESC
343
248
  opts.on(
344
249
  '--github-api-uri-prefix PREFIX',
345
- *help_description(desc, :add_separator => true)
250
+ *Help.description(desc, :add_blank_line => true)
346
251
  ) do |prefix|
347
252
  set_option(:github_api_uri_prefix, prefix)
348
253
  end
@@ -352,31 +257,78 @@ class Twig
352
257
  https://github-enterprise.example.com.
353
258
  (Default: "#{Twig::DEFAULT_GITHUB_URI_PREFIX}")
354
259
  DESC
355
- opts.on(
356
- '--github-uri-prefix PREFIX',
357
- *help_description(desc, :add_separator => true)
358
- ) do |prefix|
260
+ opts.on('--github-uri-prefix PREFIX', *Help.description(desc)) do |prefix|
359
261
  set_option(:github_uri_prefix, prefix)
360
262
  end
361
263
 
264
+ ###
265
+
266
+ Help.header(opts, 'Config files and tab completion', :trailing => '')
362
267
 
268
+ Help.print_paragraph(opts, %{
269
+ Twig can automatically set up a config file for you, where you can put
270
+ your most frequently used options for filtering and listing branches.
271
+ To get started, run `twig init` and follow the instructions. This does
272
+ two things:
273
+ })
363
274
 
364
- help_separator(opts, help_paragraph(%{
365
- You can put your most frequently used options for filtering and
366
- listing branches into #{Twig::Options::CONFIG_PATH}. For example:
367
- }), :trailing => '')
275
+ Help.print_paragraph(opts, %{
276
+ * Creates #{Twig::Options::CONFIG_PATH}, where you can put your
277
+ favorite options, e.g.:
278
+ })
368
279
 
369
- help_separator(opts, [
280
+ Help.print_section(opts, [
370
281
  ' except-branch: staging',
371
282
  ' header-style: green bold',
372
283
  ' max-days-old: 30',
373
284
  ' reverse: true'
285
+ ].join("\n"))
286
+
287
+ Help.print_paragraph(opts, %{
288
+ * Enables tab completion for Twig subcommands and branch names, e.g.:
289
+ })
290
+
291
+ Help.print_section(opts, [
292
+ ' `twig cre<tab>` -> `twig create-branch`',
293
+ ' `twig status -b my-br<tab>` -> `twig status -b my-branch`'
374
294
  ].join("\n"), :trailing => '')
375
295
 
376
- help_separator(opts, help_paragraph(%{
377
- To enable tab completion for Twig, run `twig init-completion` and
378
- follow the instructions.
379
- }))
296
+ ###
297
+
298
+ Help.header(opts, 'Subcommands', :trailing => '')
299
+
300
+ Help.print_paragraph(opts, "Twig comes with these subcommands:", :trailing => "\n\n")
301
+
302
+ Help.subcommand_descriptions.each do |desc|
303
+ Help.print_line(opts, desc)
304
+ end
305
+
306
+ Help.subheader(opts, 'Writing a subcommand', :trailing => '')
307
+
308
+ Help.print_paragraph(opts, %{
309
+ You can write any Twig subcommand that fits your own Git workflow. To
310
+ write a Twig subcommand:
311
+ })
312
+
313
+ Help.print_section(opts, [
314
+ '1. Write a script; any language will do. (If you want to take',
315
+ ' advantage of Twig\'s option parsing and branch processing, you\'ll',
316
+ ' need Ruby. See `twig-checkout-parent` for an example.)'
317
+ ].join("\n"))
318
+
319
+ Help.print_section(opts, [
320
+ '2. Save it with the `twig-` prefix in your `$PATH`,',
321
+ ' e.g., `~/bin/twig-my-subcommand`.'
322
+ ].join("\n"))
323
+
324
+ Help.print_section(opts, [
325
+ '3. Make it executable: `chmod ugo+x ~/bin/twig-my-subcommand`'
326
+ ].join("\n"))
327
+
328
+ Help.print_section(opts, [
329
+ '4. Run your subcommand: `twig my-subcommand` (with a *space* after',
330
+ ' `twig`'
331
+ ].join("\n"))
380
332
  end
381
333
 
382
334
  option_parser.parse!(args)
@@ -387,22 +339,12 @@ class Twig
387
339
  end
388
340
 
389
341
  def abort_for_option_exception(exception)
390
- puts exception.message + "\nFor a list of options, run `twig --help`."
342
+ puts exception.message + "\nFor a list of options, run `twig help`."
391
343
  exit 1
392
344
  end
393
345
 
394
- def exec_subcommand_if_any(args)
395
- # Run subcommand binary, if any, and exit here
396
- possible_subcommand_name = Twig::Subcommands::BIN_PREFIX + args[0]
397
- command_path = Twig.run("which #{possible_subcommand_name} 2>/dev/null")
398
- unless command_path.empty?
399
- command = ([command_path] + args[1..-1]).join(' ')
400
- exec(command)
401
- end
402
- end
403
-
404
346
  def read_cli_args!(args)
405
- exec_subcommand_if_any(args) if args.any?
347
+ Twig::Subcommands.exec_subcommand_if_any(args) if args.any?
406
348
 
407
349
  args = read_cli_options!(args)
408
350
  branch_name = target_branch_name
@@ -436,34 +378,27 @@ class Twig
436
378
  end
437
379
 
438
380
  def get_branch_property_for_cli(branch_name, property_name)
439
- begin
440
- value = get_branch_property(branch_name, property_name)
441
- if value
442
- puts value
443
- else
444
- raise Twig::Branch::MissingPropertyError,
445
- %{The branch "#{branch_name}" does not have the property "#{property_name}".}
446
- end
447
- rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
448
- abort exception.message
381
+ value = get_branch_property(branch_name, property_name)
382
+ if value
383
+ puts value
384
+ else
385
+ raise Twig::Branch::MissingPropertyError,
386
+ %{The branch "#{branch_name}" does not have the property "#{property_name}".}
449
387
  end
388
+ rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
389
+ abort exception.message
450
390
  end
451
391
 
452
392
  def set_branch_property_for_cli(branch_name, property_name, property_value)
453
- begin
454
- puts set_branch_property(branch_name, property_name, property_value)
455
- rescue ArgumentError, RuntimeError => exception
456
- abort exception.message
457
- end
393
+ puts set_branch_property(branch_name, property_name, property_value)
394
+ rescue ArgumentError, RuntimeError => exception
395
+ abort exception.message
458
396
  end
459
397
 
460
398
  def unset_branch_property_for_cli(branch_name, property_name)
461
- begin
462
- puts unset_branch_property(branch_name, property_name)
463
- rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
464
- abort exception.message
465
- end
399
+ puts unset_branch_property(branch_name, property_name)
400
+ rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
401
+ abort exception.message
466
402
  end
467
-
468
403
  end
469
404
  end