escort 0.2.1 → 0.3.0

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.
Files changed (69) hide show
  1. checksums.yaml +8 -8
  2. data/.irbrc +2 -0
  3. data/.travis.yml +1 -1
  4. data/README.md +272 -3
  5. data/TODO.md +118 -71
  6. data/examples/.my_apprc +24 -0
  7. data/examples/basic_config_file +16 -0
  8. data/examples/basic_conflicts +1 -1
  9. data/examples/basic_with_everything +30 -0
  10. data/examples/suite_complex +65 -0
  11. data/examples/{command → suite_simple} +0 -0
  12. data/examples/suite_with_sub_commands +94 -0
  13. data/lib/escort.rb +6 -4
  14. data/lib/escort/action_command/base.rb +7 -5
  15. data/lib/escort/app.rb +2 -8
  16. data/lib/escort/auto_options.rb +5 -3
  17. data/lib/escort/formatter/cursor_position.rb +29 -0
  18. data/lib/escort/formatter/default_help_formatter.rb +37 -32
  19. data/lib/escort/formatter/option.rb +1 -1
  20. data/lib/escort/formatter/stream_output_formatter.rb +88 -0
  21. data/lib/escort/formatter/{borderless_table.rb → string_grid.rb} +21 -19
  22. data/lib/escort/formatter/string_splitter.rb +24 -4
  23. data/lib/escort/setup/configuration/loader.rb +8 -2
  24. data/lib/escort/setup/configuration/locator/chaining.rb +29 -0
  25. data/lib/escort/setup/configuration/locator/executing_script_directory.rb +15 -0
  26. data/lib/escort/setup/configuration/locator/specified_directory.rb +21 -0
  27. data/lib/escort/setup/configuration/reader.rb +4 -2
  28. data/lib/escort/setup/configuration/writer.rb +6 -2
  29. data/lib/escort/setup/dsl/command.rb +7 -8
  30. data/lib/escort/setup/dsl/global.rb +3 -51
  31. data/lib/escort/version.rb +1 -1
  32. data/spec/integration/basic_config_file_spec.rb +82 -0
  33. data/spec/integration/suite_simple_spec.rb +45 -0
  34. data/spec/integration/suite_sub_command_spec.rb +51 -0
  35. data/spec/lib/escort/action_command/base_spec.rb +200 -0
  36. data/spec/lib/escort/formatter/option_spec.rb +2 -2
  37. data/spec/lib/escort/formatter/stream_output_formatter_spec.rb +214 -0
  38. data/spec/lib/escort/formatter/string_grid_spec.rb +59 -0
  39. data/spec/lib/escort/setup/configuration/generator_spec.rb +101 -0
  40. data/spec/lib/escort/setup/configuration/loader_spec.rb +79 -0
  41. data/spec/lib/escort/setup/configuration/locator/chaining_spec.rb +81 -0
  42. data/spec/lib/escort/setup/configuration/locator/descending_to_home_spec.rb +57 -0
  43. data/spec/lib/escort/setup/configuration/locator/executing_script_directory_spec.rb +29 -0
  44. data/spec/lib/escort/setup/configuration/locator/specified_directory_spec.rb +33 -0
  45. data/spec/lib/escort/setup/configuration/merge_tool_spec.rb +41 -0
  46. data/spec/lib/escort/setup/configuration/reader_spec.rb +41 -0
  47. data/spec/lib/escort/setup/configuration/writer_spec.rb +75 -0
  48. data/spec/spec_helper.rb +2 -1
  49. metadata +44 -24
  50. data/examples/attic/1_1_basic.rb +0 -15
  51. data/examples/attic/1_2_basic_requires_arguments.rb +0 -15
  52. data/examples/attic/2_2_command.rb +0 -18
  53. data/examples/attic/2_2_command_requires_arguments.rb +0 -20
  54. data/examples/attic/2_3_nested_commands.rb +0 -26
  55. data/examples/attic/3_validations.rb +0 -31
  56. data/examples/attic/4_1_config_file.rb +0 -42
  57. data/examples/attic/argument_handling/basic.rb +0 -12
  58. data/examples/attic/argument_handling/basic_command.rb +0 -18
  59. data/examples/attic/argument_handling/no_arguments.rb +0 -14
  60. data/examples/attic/argument_handling/no_arguments_command.rb +0 -20
  61. data/examples/attic/command_aliases/app.rb +0 -31
  62. data/examples/attic/config_file/.apprc2 +0 -16
  63. data/examples/attic/config_file/app.rb +0 -78
  64. data/examples/attic/config_file/sub_commands.rb +0 -35
  65. data/examples/attic/default_command/app.rb +0 -20
  66. data/examples/attic/sub_commands/app.rb +0 -18
  67. data/examples/attic/validation_basic/app.rb +0 -31
  68. data/lib/escort/formatter/terminal_formatter.rb +0 -58
  69. data/lib/escort/setup/dsl/validations.rb +0 -25
@@ -0,0 +1,24 @@
1
+ {
2
+ "global": {
3
+ "options": {
4
+ "option1": "option from file",
5
+ "config": null,
6
+ "verbosity": "WARN",
7
+ "error_output_format": "basic"
8
+ },
9
+ "commands": {
10
+ "escort": {
11
+ "options": {
12
+ "create_config": null,
13
+ "create_default_config": null,
14
+ "update_config": null,
15
+ "update_default_config": null
16
+ },
17
+ "commands": {
18
+ }
19
+ }
20
+ }
21
+ },
22
+ "user": {
23
+ }
24
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.expand_path(__FILE__), "..", "..", "lib", "escort"))
4
+ require File.join(File.expand_path(__FILE__), "..", "commands", "example_command")
5
+
6
+ Escort::App.create do |app|
7
+ app.config_file ".my_apprc", :autocreate => true
8
+
9
+ app.options do |opts|
10
+ opts.opt :option1, "Option1", :short => '-o', :long => '--option1', :type => :string, :default => "option 1"
11
+ end
12
+
13
+ app.action do |options, arguments|
14
+ Escort::ExampleCommand.new(options, arguments).execute
15
+ end
16
+ end
@@ -8,7 +8,7 @@ Escort::App.create do |app|
8
8
  opts.opt :flag1, "Flag 1", :short => '-f', :long => '--flag1', :type => :boolean
9
9
  opts.opt :flag2, "Flag 2", :short => :none, :long => '--flag2', :type => :boolean
10
10
 
11
- opts.conflict :flag1, :flag2, :flag3
11
+ opts.conflict :flag1, :flag2
12
12
  end
13
13
 
14
14
  app.action do |options, arguments|
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.expand_path(__FILE__), "..", "..", "lib", "escort"))
4
+ require File.join(File.expand_path(__FILE__), "..", "commands", "example_command")
5
+
6
+ Escort::App.create do |app|
7
+ app.version "0.1.1"
8
+ app.summary "Summary 1"
9
+ app.description "Description 1"
10
+
11
+ app.config_file ".my_apprc", :autocreate => false
12
+
13
+ app.requires_arguments
14
+
15
+ app.options do |opts|
16
+ opts.opt :option1, "Option1", :short => '-o', :long => '--option1', :type => :string, :default => "option 1"
17
+ opts.opt :option2, "Option2", :short => :none, :long => '--option2', :type => :string, :multi => true
18
+ opts.opt :int_option, "Int option", :short => '-i', :long => '--int-option', :type => :int
19
+ opts.opt :flag1, "Flag 1", :short => '-f', :long => '--flag1', :type => :boolean, :default => true
20
+ opts.opt :flag2, "Flag 2", :short => :none, :long => '--flag2', :type => :boolean
21
+
22
+ opts.conflict :flag1, :flag2
23
+ opts.dependency :option1, :on => :flag1
24
+ opts.validate(:int_option, "must be greater than 10") { |option| option > 10 }
25
+ end
26
+
27
+ app.action do |options, arguments|
28
+ Escort::ExampleCommand.new(options, arguments).execute
29
+ end
30
+ end
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.expand_path(__FILE__), "..", "..", "lib", "escort"))
4
+ require File.join(File.expand_path(__FILE__), "..", "commands", "example_command")
5
+
6
+ Escort::App.create do |app|
7
+ app.version "0.1.1"
8
+ app.summary "Summary 1"
9
+ app.description "Description 1"
10
+
11
+ app.config_file ".my_apprc", :autocreate => false
12
+
13
+ app.requires_arguments
14
+
15
+ app.options do |opts|
16
+ opts.opt :option1, "Option1", :short => '-o', :long => '--option1', :type => :string, :default => "option 1"
17
+ opts.opt :option2, "Option2", :short => :none, :long => '--option2', :type => :string, :multi => true
18
+ opts.opt :int_option, "Int option", :short => '-i', :long => '--int-option', :type => :int
19
+ opts.opt :flag1, "Flag 1", :short => '-f', :long => '--flag1', :type => :boolean, :default => true
20
+ opts.opt :flag2, "Flag 2", :short => :none, :long => '--flag2', :type => :boolean
21
+
22
+ opts.conflict :flag1, :flag2
23
+ opts.dependency :option1, :on => :flag1
24
+ opts.validate(:int_option, "must be greater than 10") { |option| option > 10 }
25
+ end
26
+
27
+ app.command :command1 do |command|
28
+ command.summary "Command summary 1"
29
+ command.description "Command description 1"
30
+
31
+ command.options do |opts|
32
+ opts.opt :option1, "Option1 for command1", :short => '-o', :long => '--option1', :type => :string, :default => "option 1 command 1"
33
+ opts.opt :flag_for_command1, "Flag for command 1", :short => :none, :long => '--flag-for-command1', :type => :boolean
34
+
35
+ opts.dependency :option1, :on => :flag_for_command1
36
+ end
37
+
38
+ command.action do |options, arguments|
39
+ Escort::ExampleCommand.new(options, arguments).execute
40
+ end
41
+ end
42
+
43
+ app.command :command2, :aliases => :c2 do |command|
44
+ command.summary "Command summary 2"
45
+ command.description "Command description 2"
46
+
47
+ command.requires_arguments false
48
+
49
+ command.options do |opts|
50
+ opts.opt :optionb, "Optionb", :short => :none, :long => '--optionb', :type => :string, :multi => true
51
+ opts.opt :float_option, "Float option", :short => '-d', :long => '--float-option', :type => :float
52
+
53
+ opts.conflict :optionb, :float_option
54
+ opts.validate(:float_option, "must be less than 5") { |option| option < 5 }
55
+ end
56
+
57
+ command.action do |options, arguments|
58
+ Escort::ExampleCommand.new(options, arguments).execute
59
+ end
60
+ end
61
+
62
+ app.action do |options, arguments|
63
+ Escort::ExampleCommand.new(options, arguments).execute
64
+ end
65
+ end
File without changes
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.expand_path(__FILE__), "..", "..", "lib", "escort"))
4
+ require File.join(File.expand_path(__FILE__), "..", "commands", "example_command")
5
+
6
+ Escort::App.create do |app|
7
+ app.version "0.1.1"
8
+ app.summary "Summary 1"
9
+ app.description "Description 1"
10
+
11
+ app.config_file ".my_apprc", :autocreate => false
12
+
13
+ app.requires_arguments
14
+
15
+ app.options do |opts|
16
+ opts.opt :option1, "Option1", :short => '-o', :long => '--option1', :type => :string, :default => "option 1"
17
+ opts.opt :option2, "Option2", :short => :none, :long => '--option2', :type => :string, :multi => true
18
+ opts.opt :int_option, "Int option", :short => '-i', :long => '--int-option', :type => :int
19
+ opts.opt :flag1, "Flag 1", :short => '-f', :long => '--flag1', :type => :boolean, :default => true
20
+ opts.opt :flag2, "Flag 2", :short => :none, :long => '--flag2', :type => :boolean
21
+
22
+ opts.conflict :flag1, :flag2
23
+ opts.dependency :option1, :on => :flag1
24
+ opts.validate(:int_option, "must be greater than 10") { |option| option > 10 }
25
+ end
26
+
27
+ app.command :command1 do |command|
28
+ command.summary "Command summary 1"
29
+ command.description "Command description 1"
30
+
31
+ command.options do |opts|
32
+ opts.opt :option1, "Option1 for command1", :short => '-o', :long => '--option1', :type => :string, :default => "option 1 command 1"
33
+ opts.opt :flag_for_command1, "Flag for command 1", :short => :none, :long => '--flag-for-command1', :type => :boolean
34
+
35
+ opts.dependency :option1, :on => :flag_for_command1
36
+ end
37
+
38
+ command.action do |options, arguments|
39
+ Escort::ExampleCommand.new(options, arguments).execute
40
+ end
41
+ end
42
+
43
+ app.command :command2, :aliases => :c2 do |command|
44
+ command.summary "Command summary 2"
45
+ command.description "Command description 2"
46
+
47
+ command.requires_arguments false
48
+
49
+ command.options do |opts|
50
+ opts.opt :optionb, "Optionb", :short => :none, :long => '--optionb', :type => :string, :multi => true
51
+ opts.opt :float_option, "Float option", :short => '-d', :long => '--float-option', :type => :float
52
+
53
+ opts.conflict :optionb, :float_option
54
+ opts.validate(:float_option, "must be less than 5") { |option| option < 5 }
55
+ end
56
+
57
+ command.command :sub_command1 do |command|
58
+ command.summary "Sub command summary 1"
59
+ command.description "Sub command description 1"
60
+
61
+ command.requires_arguments
62
+
63
+ command.options do |opts|
64
+ opts.opt :flag2, "Flag 2", :short => :none, :long => '--flag2', :type => :boolean
65
+ end
66
+
67
+ command.action do |options, arguments|
68
+ Escort::ExampleCommand.new(options, arguments).execute
69
+ end
70
+ end
71
+
72
+ command.command :sub_command2 do |command|
73
+ command.summary "Sub command summary 2"
74
+ command.description "Sub command description 2"
75
+
76
+ command.options do |opts|
77
+ opts.opt :sub_option1, "Sub option1", :short => '-s', :long => '--sub-option1', :type => :string
78
+ opts.validate(:sub_option1, "must be 'x' or 'y'") { |option| ['x', 'y'].include? option }
79
+ end
80
+
81
+ command.action do |options, arguments|
82
+ Escort::ExampleCommand.new(options, arguments).execute
83
+ end
84
+ end
85
+
86
+ command.action do |options, arguments|
87
+ Escort::ExampleCommand.new(options, arguments).execute
88
+ end
89
+ end
90
+
91
+ app.action do |options, arguments|
92
+ Escort::ExampleCommand.new(options, arguments).execute
93
+ end
94
+ end
@@ -14,20 +14,22 @@ require 'escort/formatter/global_command'
14
14
  require 'escort/formatter/shell_command_executor'
15
15
  require 'escort/formatter/terminal'
16
16
  require 'escort/formatter/string_splitter'
17
- require 'escort/formatter/terminal_formatter'
18
- require 'escort/formatter/borderless_table'
19
- #require 'escort/formatter/common'
17
+ require 'escort/formatter/cursor_position'
18
+ require 'escort/formatter/stream_output_formatter'
19
+ require 'escort/formatter/string_grid'
20
20
  require 'escort/formatter/default_help_formatter'
21
21
 
22
22
  require 'escort/setup/dsl/options'
23
23
  require 'escort/setup/dsl/action'
24
- require 'escort/setup/dsl/validations'
25
24
  require 'escort/setup/dsl/command'
26
25
  require 'escort/setup/dsl/config_file'
27
26
  require 'escort/setup/dsl/global'
28
27
 
29
28
  require 'escort/setup/configuration/locator/base'
30
29
  require 'escort/setup/configuration/locator/descending_to_home'
30
+ require 'escort/setup/configuration/locator/executing_script_directory'
31
+ require 'escort/setup/configuration/locator/specified_directory'
32
+ require 'escort/setup/configuration/locator/chaining'
31
33
  require 'escort/setup/configuration/merge_tool'
32
34
  require 'escort/setup/configuration/instance'
33
35
  require 'escort/setup/configuration/reader'
@@ -19,17 +19,18 @@ module Escort
19
19
  def command_context
20
20
  return @command_context if @command_context
21
21
  @command_context = []
22
- current_command_hash = options[:global][:commands]
22
+ options[:global] ||= {}
23
+ current_command_hash = options[:global][:commands] || {}
23
24
  until current_command_hash.keys.empty?
24
25
  key = current_command_hash.keys.first
25
26
  @command_context << key
26
- current_command_hash = current_command_hash[key][:commands]
27
+ current_command_hash = current_command_hash[key][:commands] || {}
27
28
  end
28
29
  @command_context
29
30
  end
30
31
 
31
32
  def command_name
32
- command_context.first || :global
33
+ command_context.last || :global
33
34
  end
34
35
 
35
36
  def command_options
@@ -73,8 +74,9 @@ module Escort
73
74
  end
74
75
 
75
76
  def ensure_ancestor(generation_number, &block)
76
- return {} if generation_number < 1
77
- return {} unless command_context.size > generation_number
77
+ return {} if generation_number < 0
78
+ #return {} if generation_number == 0
79
+ return {} unless command_context.size >= generation_number
78
80
  ancestor_context = command_context.dup.slice(0, command_context.size - generation_number)
79
81
  block.call(ancestor_context)
80
82
  end
@@ -2,14 +2,6 @@ module Escort
2
2
  class App
3
3
  #TODO ensure that every command must have an action
4
4
  class << self
5
- #def create(option_string = '', &block)
6
- #cli_app_configuration = Escort::Setup::Dsl::Global.new(&block)
7
- #setup = Escort::SetupAccessor.new(cli_app_configuration)
8
- #app = self.new(option_string, setup)
9
- #app.execute
10
- #exit(0)
11
- #end
12
-
13
5
  def create(option_string = '', &block)
14
6
  self.new(option_string, &block).setup_application.execute
15
7
  exit(0)
@@ -54,6 +46,8 @@ module Escort
54
46
  invoked_options, arguments = Escort::OptionParser.new(configuration, setup).parse(cli_options)
55
47
  context = context_from_options(invoked_options[:global])
56
48
  action = setup.action_for(context)
49
+ current_command = context.empty? ? :global : context.last
50
+ raise Escort::ClientError.new("No action defined for command '#{current_command}'") unless action
57
51
  actual_arguments = Escort::Arguments.read(arguments, setup.arguments_required_for(context))
58
52
  rescue => e
59
53
  handle_escort_error(e)
@@ -5,7 +5,9 @@ module Escort
5
5
  if setup.has_config_file?
6
6
  setup.add_global_option :config, "Configuration file to use for this execution", :short => :none, :long => '--config', :type => :string
7
7
 
8
- setup.add_global_command :escort, :description => "Auto created utility command", :aliases => [] do |command|
8
+ setup.add_global_command :escort, :aliases => [] do |command|
9
+ command.summary "Auto created utility command"
10
+ command.description "Auto created utility command"
9
11
  command.requires_arguments false
10
12
 
11
13
  command.options do |opts|
@@ -13,9 +15,9 @@ module Escort
13
15
  opts.opt :create_default_config, "Create a default configuration file", :short => :none, :long => '--create-default-config', :type => :boolean, :default => false
14
16
  opts.opt :update_config, "Update configuration file at specified location", :short => :none, :long => '--update-config', :type => :string
15
17
  opts.opt :update_default_config, "Update the default configuration file", :short => :none, :long => '--update-default-config', :type => :boolean, :default => false
16
- end
17
18
 
18
- command.conflicting_options :create_config, :create_default_config, :update_config, :update_default_config
19
+ opts.conflict :create_config, :create_default_config, :update_config, :update_default_config
20
+ end
19
21
 
20
22
  command.action do |options, arguments|
21
23
  ActionCommand::EscortUtilityCommand.new(setup, options, arguments).execute
@@ -0,0 +1,29 @@
1
+ module Escort
2
+ module Formatter
3
+ class CursorPosition
4
+ attr_reader :max_line_width, :position
5
+
6
+ def initialize(max_line_width)
7
+ @max_line_width = max_line_width
8
+ reset
9
+ end
10
+
11
+ def update_for(string)
12
+ @position += string.length
13
+ raise Escort::InternalError.new("Cursor position for help output is out of bounds") if position > max_line_width
14
+ end
15
+
16
+ def newline?
17
+ @position == 0
18
+ end
19
+
20
+ def reset
21
+ @position = 0
22
+ end
23
+
24
+ def chars_to_end_of_line
25
+ max_line_width - position
26
+ end
27
+ end
28
+ end
29
+ end
@@ -13,48 +13,49 @@ module Escort
13
13
  commands = Commands.new(setup, context)
14
14
  current_command = Commands.command_for(setup, context)
15
15
 
16
- TerminalFormatter.display($stdout, Terminal.width) do |d|
17
- name_help(current_command, d)
18
- usage_help(current_command, d)
19
- version_help(current_command, d)
20
- options_help(options, d)
21
- commands_help(commands, d)
16
+ StreamOutputFormatter.new($stdout, :max_output_width => Terminal.width) do |f|
17
+ name_help(current_command, f)
18
+ usage_help(current_command, f)
19
+ version_help(current_command, f)
20
+ options_help(options, f)
21
+ commands_help(commands, f)
22
22
  end
23
23
  end
24
24
 
25
25
  private
26
26
 
27
- def name_help(current_command, d)
28
- d.puts "NAME"
29
- d.indent(4) do
30
- d.table(:columns => 3, :newlines => 1) do |t|
31
- t.row current_command.script_name, '-', current_command.summary
27
+ def name_help(current_command, f)
28
+ f.puts "NAME"
29
+ f.indent(4) do |f|
30
+ f.grid(:columns => 3) do |t|
31
+ t.row current_command.script_name, '-', setup.summary_for(context)
32
32
  end
33
- d.put(setup.description_for(context), :newlines => 2) if setup.description_for(context)
33
+ f.newline
34
+ f.puts(setup.description_for(context), :newlines => 2) if setup.description_for(context)
34
35
  end
35
36
  end
36
37
 
37
- def usage_help(current_command, d)
38
- d.puts "USAGE"
39
- d.indent(4) do
40
- d.put current_command.usage, :newlines => 2
38
+ def usage_help(current_command, f)
39
+ f.puts "USAGE"
40
+ f.indent(4) do |f|
41
+ f.puts current_command.usage, :newlines => 2
41
42
  end
42
43
  end
43
44
 
44
- def version_help(current_command, d)
45
+ def version_help(current_command, f)
45
46
  if setup.version
46
- d.puts "VERSION"
47
- d.indent(4) {
48
- d.put setup.version, :newlines => 2
49
- }
47
+ f.puts "VERSION"
48
+ f.indent(4) do |f|
49
+ f.puts setup.version, :newlines => 2
50
+ end
50
51
  end
51
52
  end
52
53
 
53
- def options_help(options, d)
54
+ def options_help(options, f)
54
55
  if options.count > 0
55
- d.puts "OPTIONS"
56
- d.indent(4) {
57
- d.table(:columns => 3, :newlines => 1) do |t|
56
+ f.puts "OPTIONS"
57
+ f.indent(4) do |f|
58
+ f.grid(:columns => 3) do |t|
58
59
  options.each do |option|
59
60
  t.row option.usage, '-', option.description
60
61
  option_conflicts_help(option, t)
@@ -62,7 +63,8 @@ module Escort
62
63
  option_validations_help(option, t)
63
64
  end
64
65
  end
65
- }
66
+ f.newline
67
+ end
66
68
  end
67
69
  end
68
70
 
@@ -80,20 +82,23 @@ module Escort
80
82
 
81
83
  def option_validations_help(option, t)
82
84
  if option.has_validations?
83
- t.row '', '', "- #{option.validations}"
85
+ option.validations.each do |validation|
86
+ t.row '', '', "- #{validation}"
87
+ end
84
88
  end
85
89
  end
86
90
 
87
- def commands_help(commands, d)
91
+ def commands_help(commands, f)
88
92
  if commands.count > 0
89
- d.puts "COMMANDS"
90
- d.indent(4) {
91
- d.table(:columns => 3, :newlines => 1) do |t|
93
+ f.puts "COMMANDS"
94
+ f.indent(4) do |f|
95
+ f.grid(:columns => 3) do |t|
92
96
  commands.each do |command|
93
97
  t.row command.name_with_aliases, '-', command.outline
94
98
  end
95
99
  end
96
- }
100
+ f.newline
101
+ end
97
102
  end
98
103
  end
99
104
  end