choosy 0.1.0 → 0.2.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 (45) hide show
  1. data/README.markdown +229 -221
  2. data/Rakefile +21 -3
  3. data/examples/bar.rb +44 -0
  4. data/examples/foo.rb +198 -0
  5. data/examples/superfoo.rb +125 -0
  6. data/lib/VERSION +1 -1
  7. data/lib/choosy/argument.rb +51 -0
  8. data/lib/choosy/base_command.rb +22 -7
  9. data/lib/choosy/command.rb +12 -4
  10. data/lib/choosy/dsl/argument_builder.rb +88 -0
  11. data/lib/choosy/dsl/base_command_builder.rb +71 -56
  12. data/lib/choosy/dsl/command_builder.rb +14 -2
  13. data/lib/choosy/dsl/option_builder.rb +43 -83
  14. data/lib/choosy/dsl/super_command_builder.rb +37 -9
  15. data/lib/choosy/option.rb +13 -11
  16. data/lib/choosy/parse_result.rb +8 -27
  17. data/lib/choosy/parser.rb +20 -16
  18. data/lib/choosy/printing/color.rb +39 -21
  19. data/lib/choosy/printing/erb_printer.rb +12 -3
  20. data/lib/choosy/printing/formatting_element.rb +17 -0
  21. data/lib/choosy/printing/help_printer.rb +204 -117
  22. data/lib/choosy/printing/terminal.rb +53 -0
  23. data/lib/choosy/super_command.rb +6 -6
  24. data/lib/choosy/super_parser.rb +26 -15
  25. data/lib/choosy/verifier.rb +61 -6
  26. data/spec/choosy/base_command_spec.rb +27 -2
  27. data/spec/choosy/command_spec.rb +31 -9
  28. data/spec/choosy/dsl/argument_builder_spec.rb +180 -0
  29. data/spec/choosy/dsl/base_command_builder_spec.rb +87 -44
  30. data/spec/choosy/dsl/commmand_builder_spec.rb +15 -4
  31. data/spec/choosy/dsl/option_builder_spec.rb +101 -191
  32. data/spec/choosy/dsl/super_command_builder_spec.rb +34 -9
  33. data/spec/choosy/parser_spec.rb +30 -8
  34. data/spec/choosy/printing/color_spec.rb +19 -5
  35. data/spec/choosy/printing/help_printer_spec.rb +152 -73
  36. data/spec/choosy/printing/terminal_spec.rb +27 -0
  37. data/spec/choosy/super_command_spec.rb +17 -17
  38. data/spec/choosy/super_parser_spec.rb +20 -10
  39. data/spec/choosy/verifier_spec.rb +137 -47
  40. data/spec/integration/command-A_spec.rb +6 -6
  41. data/spec/integration/command-B_spec.rb +45 -0
  42. data/spec/integration/supercommand-A_spec.rb +33 -27
  43. data/spec/integration/supercommand-B_spec.rb +32 -0
  44. data/spec/spec_helpers.rb +8 -5
  45. metadata +95 -54
@@ -4,30 +4,58 @@ require 'choosy/command'
4
4
 
5
5
  module Choosy::DSL
6
6
  class SuperCommandBuilder < BaseCommandBuilder
7
- def command(cmd)
7
+ HELP = :help
8
+ SUPER = :__SUPER_COMMAND__
9
+
10
+ def command(cmd, &block)
8
11
  subcommand = if cmd.is_a?(Choosy::Command)
9
12
  cmd
10
13
  else
11
14
  Choosy::Command.new(cmd)
12
15
  end
13
- yield subcommand.builder if block_given?
16
+
17
+ if block_given?
18
+ subcommand.builder.instance_eval(&block)
19
+ end
14
20
  finalize_subcommand(subcommand)
15
21
  end
16
22
 
23
+ def parsimonious
24
+ @command.parsimonious = true
25
+ end
26
+
27
+ def metaname(meta)
28
+ @command.metaname = meta
29
+ end
30
+
17
31
  def help(msg=nil)
18
32
  msg ||= "Show the info for a command, or this message"
19
- help = Choosy::Command.new :help do |help|
33
+ help_command = Choosy::Command.new HELP do |help|
20
34
  help.summary msg
21
35
 
22
- help.arguments do |args|
23
- if args.nil? || args.length == 0
24
- raise Choosy::HelpCalled.new(@command.name)
25
- else
26
- raise Choosy::HelpCalled.new(args[0].to_sym)
36
+ help.arguments do
37
+ count 0..1
38
+ validate do |args, options|
39
+ if args.nil?
40
+ raise Choosy::HelpCalled.new(SUPER)
41
+ elsif args.is_a?(Array)
42
+ if args.length == 0
43
+ raise Choosy::HelpCalled.new(SUPER)
44
+ else
45
+ raise Choosy::HelpCalled.new(args[0].to_sym)
46
+ end
47
+ else
48
+ raise Choosy::HelpCalled.new(args.to_sym)
49
+ end
27
50
  end
28
51
  end
29
52
  end
30
- finalize_subcommand(help)
53
+ finalize_subcommand(help_command)
54
+ end
55
+
56
+ def finalize!
57
+ super
58
+ @command.metaname ||= 'COMMAND'
31
59
  end
32
60
 
33
61
  private
@@ -1,22 +1,24 @@
1
+ require 'choosy/argument'
2
+
1
3
  module Choosy
2
- class Option
4
+ class Option < Argument
3
5
  attr_accessor :name, :description
4
- attr_accessor :short_flag, :long_flag, :flag_parameter
5
- attr_accessor :cast_to, :default_value
6
- attr_accessor :validation_step
7
- attr_accessor :arity
6
+ attr_accessor :short_flag, :long_flag
7
+ attr_accessor :default_value
8
8
  attr_accessor :dependent_options
9
+ attr_accessor :negation
9
10
 
10
11
  def initialize(name)
12
+ super()
11
13
  @name = name
12
- @required = false
13
14
  end
14
-
15
- def required=(req)
16
- @required = req
15
+
16
+ def negated?
17
+ !negation.nil?
17
18
  end
18
- def required?
19
- @required == true
19
+
20
+ def negated
21
+ @negated ||= long_flag.gsub(/^--/, "--#{negation}-")
20
22
  end
21
23
  end
22
24
  end
@@ -4,11 +4,11 @@ module Choosy
4
4
  class BaseParseResult
5
5
  attr_reader :command, :options, :unparsed
6
6
 
7
- def initialize(command)
7
+ def initialize(command, subresult)
8
8
  @command = command
9
9
  @options = {}
10
10
  @unparsed = []
11
- @verified = false
11
+ @subresult = subresult
12
12
  end
13
13
 
14
14
  def [](opt)
@@ -19,46 +19,27 @@ module Choosy
19
19
  @options[opt] = val
20
20
  end
21
21
 
22
- def verified?
23
- @verified
24
- end
25
-
26
- def verify!
27
- basic_verification
28
- end
29
-
30
- protected
31
- def basic_verification(&block)
32
- verifier = Verifier.new
33
- verifier.verify_options!(self)
34
- yield verifier if block_given?
35
- @verified = true
36
- self
22
+ def subresult?
23
+ @subresult
37
24
  end
38
25
  end
39
26
 
40
27
  class ParseResult < BaseParseResult
41
28
  attr_reader :args
42
29
 
43
- def initialize(command)
44
- super(command)
30
+ def initialize(command, subresult)
31
+ super(command, subresult)
45
32
  @args = []
46
33
  end
47
-
48
- def verify!
49
- return self if verified?
50
- basic_verification do |verifier|
51
- verifier.verify_arguments!(self)
52
- end
53
- end
54
34
  end
55
35
 
56
36
  class SuperParseResult < BaseParseResult
57
37
  attr_reader :subresults
58
38
 
59
39
  def initialize(command)
60
- super(command)
40
+ super(command, false)
61
41
  @subresults = []
62
42
  end
43
+
63
44
  end
64
45
  end
@@ -20,7 +20,7 @@ module Choosy
20
20
 
21
21
  def parse!(argv, result=nil)
22
22
  index = 0
23
- result ||= ParseResult.new(@command)
23
+ result ||= ParseResult.new(@command, false)
24
24
 
25
25
  while index < argv.length
26
26
  case argv[index]
@@ -52,6 +52,9 @@ module Choosy
52
52
  def verify_option(option)
53
53
  verify_flag(option, option.short_flag)
54
54
  verify_flag(option, option.long_flag)
55
+ if option.negated?
56
+ verify_flag(option, option.negated)
57
+ end
55
58
  end
56
59
 
57
60
  def verify_flag(option, flag)
@@ -76,19 +79,23 @@ module Choosy
76
79
  end
77
80
  end
78
81
 
79
- if option.arity == Choosy::DSL::OptionBuilder::ZERO_ARITY
80
- parse_boolean_option(result, option, index, arg, current)
81
- elsif option.arity == Choosy::DSL::OptionBuilder::ONE_ARITY
82
+ if option.boolean?
83
+ parse_boolean_option(result, option, index, arg, current, flag)
84
+ elsif option.single?
82
85
  parse_single_option(result, option, index, argv, flag, arg)
83
86
  else # Vararg
84
87
  parse_multiple_option(result, option, index, argv, flag, arg)
85
88
  end
86
89
  end
87
90
 
88
- def parse_boolean_option(result, option, index, arg, current)
89
- raise Choosy::ParseError.new("Argument given to boolean flag: '#{current}'") if arg
91
+ def parse_boolean_option(result, option, index, arg, current, flag)
92
+ raise Choosy::ParseError.new("Argument given to boolean flag: '#{current}'") if arg
93
+ if option.negated? && flag == option.negated
94
+ result.options[option.name] = option.default_value
95
+ else
90
96
  result.options[option.name] = !option.default_value
91
- index + 1
97
+ end
98
+ index + 1
92
99
  end
93
100
 
94
101
  def parse_single_option(result, option, index, argv, flag, arg)
@@ -160,7 +167,7 @@ module Choosy
160
167
  return [nil, index] if index >= argv.length
161
168
 
162
169
  current = argv[index]
163
- return [nil, index] if current[0] == '-'
170
+ return [nil, index] if current =~ /^-/
164
171
  if @terminals.include? current
165
172
  result.unparsed.push(*argv[index, argv.length])
166
173
  return [nil, argv.length]
@@ -170,15 +177,12 @@ module Choosy
170
177
 
171
178
  def parse_rest(argv, index, result)
172
179
  index += 1
173
- while index < argv.length
174
- if lazy?
175
- result.unparsed << argv[index]
176
- else
177
- result.args << argv[index]
178
- end
179
- index += 1
180
+ if lazy?
181
+ result.unparsed.push(*argv[index, argv.length])
182
+ else
183
+ result.args.push(*argv[index, argv.length])
180
184
  end
181
- index
185
+ argv.length
182
186
  end
183
187
  end
184
188
  end
@@ -18,10 +18,13 @@ module Choosy::Printing
18
18
  EFFECTS = {
19
19
  :reset => 0,
20
20
  :bright => 1,
21
+ :bold => 1,
21
22
  :underline => 4,
22
23
  :blink => 5,
23
24
  :exchange => 7,
24
- :hide => 8
25
+ :hide => 8,
26
+ :primary => 10,
27
+ :normal => 22
25
28
  }
26
29
 
27
30
  FOREGROUND = 30
@@ -53,48 +56,63 @@ module Choosy::Printing
53
56
  EFFECTS.has_key?(effect.to_sym)
54
57
  end
55
58
 
59
+ def multiple(str, styles)
60
+ return str if styles.nil? || styles.empty? || disabled?
61
+
62
+ styles.each do |style|
63
+ if color?(style)
64
+ str = bedazzle(COLORS[style] + FOREGROUND, str)
65
+ elsif effect?(style)
66
+ str = bedazzle(EFFECTS[style], str)
67
+ end
68
+ end
69
+ str
70
+ end
71
+
56
72
  def respond_to?(method)
57
73
  color?(method) || effect?(method)
58
74
  end
59
75
 
60
76
  # Dynamically handle colors and effects
61
- def method_missing(method, *args, &block)
62
- str, offset = unpack_args(method, args)
63
- return str || "" if disabled?
77
+ def method_missing(method, *args, &block)
78
+ if disabled?
79
+ return args[0] || ""
80
+ end
64
81
 
65
82
  if color?(method)
66
- bedazzle(COLORS[method] + offset, str)
83
+ raise ArgumentError.new("too many arguments to Color##{method} (max 2)") if args.length > 2
84
+ offset = find_state(method, args[1])
85
+ bedazzle(COLORS[method] + offset, args[0])
67
86
  elsif effect?(method)
68
- bedazzle(EFFECTS[method], str)
87
+ raise ArgumentError.new("too many arguments to Color##{method} (max 1)") if args.length > 1
88
+ bedazzle(EFFECTS[method], args[0])
69
89
  else
70
90
  raise NoMethodError.new("undefined method '#{method}' for Color")
71
91
  end
72
92
  end
73
93
 
74
94
  private
75
- def unpack_args(method, args)
76
- case args.length
77
- when 0
78
- [nil, FOREGROUND]
79
- when 1
80
- [args[0], FOREGROUND]
81
- when 2
82
- case args[1]
83
- when :foreground then [args[0], FOREGROUND]
84
- when :background then [args[0], BACKGROUND]
85
- else raise ArgumentError.new("unrecognized state for Color##{method}, :foreground or :background only")
86
- end
95
+ def find_state(method, state)
96
+ case state
97
+ when nil
98
+ FOREGROUND
99
+ when :foreground
100
+ FOREGROUND
101
+ when :background
102
+ BACKGROUND
87
103
  else
88
- raise ArgumentError.new("too many arguments to Color##{method} (max 2)")
104
+ raise ArgumentError.new("unrecognized state for Color##{method}, :foreground or :background only")
89
105
  end
90
106
  end
91
107
 
92
108
  def bedazzle(number, str)
93
- prefix = "e#{number}[m"
109
+ prefix = "\e[#{number}m"
94
110
  if str.nil?
95
111
  prefix
112
+ elsif str =~ /\e\[0m$/
113
+ "#{prefix}#{str}"
96
114
  else
97
- "#{prefix}#{str}e0[m"
115
+ "#{prefix}#{str}\e[0m"
98
116
  end
99
117
  end
100
118
  end
@@ -4,8 +4,17 @@ require 'erb'
4
4
 
5
5
  module Choosy::Printing
6
6
  class ERBPrinter < HelpPrinter
7
- attr_reader :command
8
- attr_accessor :template
7
+ attr_reader :command, :template
8
+
9
+ def initialize(options)
10
+ super(options)
11
+ if options[:template].nil?
12
+ raise Choosy::ConfigurationError.new("no template file given to ERBPrinter")
13
+ elsif !File.exist?(options[:template])
14
+ raise Choosy::ConfigurationError.new("the template file doesn't exist: #{options[:template]}")
15
+ end
16
+ @template = options[:template]
17
+ end
9
18
 
10
19
  def print!(command)
11
20
  @command = command
@@ -13,7 +22,7 @@ module Choosy::Printing
13
22
  File.open(template, 'r') {|f| contents = f.read }
14
23
  erb = ERB.new contents
15
24
 
16
- erb.run(self)
25
+ erb.result(self)
17
26
  end
18
27
 
19
28
  def erb_binding
@@ -0,0 +1,17 @@
1
+ require 'choosy/errors'
2
+
3
+ module Choosy::Printing
4
+ class FormattingElement
5
+ attr_reader :value, :styles, :kind
6
+
7
+ def initialize(kind, value, styles)
8
+ @value = value
9
+ @kind = kind
10
+ @styles = styles
11
+ end
12
+
13
+ def header?
14
+ @kind == :header
15
+ end
16
+ end
17
+ end
@@ -1,174 +1,261 @@
1
1
  require 'choosy/errors'
2
- require 'choosy/printing/color'
2
+ require 'choosy/printing/terminal'
3
3
 
4
4
  module Choosy::Printing
5
5
  class HelpPrinter
6
- DEFAULT_LINE_COUNT = 25
7
- DEFAULT_COLUMN_COUNT = 80
6
+ include Terminal
8
7
 
9
- attr_reader :color
8
+ attr_reader :header_styles, :indent, :offset, :buffer, :usage
10
9
 
11
- def initialize
12
- @color = Color.new
13
- end
14
-
15
- def lines
16
- @lines ||= find_terminal_size('LINES', 'lines', 0) || DEFAULT_LINE_COUNT
17
- end
10
+ def initialize(options)
11
+ @indent = options[:indent] || ' '
12
+ @offset = options[:offset] || ' '
13
+ @header_styles = options[:header_styles] || [:bold, :blue]
14
+ @buffer = options[:buffer] || ""
15
+ @usage = options[:usage] || 'Usage:'
18
16
 
19
- def lines=(value)
20
- @lines = value
17
+ if options[:color] == false
18
+ color.disable!
19
+ end
20
+ if options[:max_width]
21
+ self.columns = options[:max_width]
22
+ end
21
23
  end
22
24
 
23
- def columns
24
- @columns ||= find_terminal_size('COLUMNS', 'cols', 1) || DEFAULT_COLUMN_COUNT
25
- end
25
+ def print!(command)
26
+ print_usage(command)
26
27
 
27
- def columns=(value)
28
- @columns = value
29
- end
28
+ cmd_indent, option_indent, prefixes = retrieve_formatting_info(command)
30
29
 
31
- def colored=(val)
32
- @color.disable! unless val
30
+ command.listing.each_with_index do |item, i|
31
+ case item
32
+ when Choosy::Option
33
+ print_option(item, prefixes[i], option_indent)
34
+ when Choosy::Command
35
+ print_command(item, prefixes[i], cmd_indent)
36
+ when Choosy::Printing::FormattingElement
37
+ print_element(item)
38
+ end
39
+ end
40
+
41
+ @buffer
33
42
  end
34
43
 
35
- def print!(command)
36
- print_usage(command)
37
- print_summary(command.summary) if command.summary
38
- print_description(command.description) if command.description
39
- command.listing.each do |l|
40
- if l.is_a?(String)
41
- print_separator(l)
42
- elsif l.is_a?(Choosy::Option)
43
- print_option(l)
44
- else
45
- print_command(l)
44
+ def print_usage(command)
45
+ print_header(@usage)
46
+ @buffer << ' '
47
+ @buffer << command.name.to_s
48
+ return if command.options.empty?
49
+
50
+ width = starting_width = 8 + command.name.to_s.length # So far
51
+ command.listing.each do |option|
52
+ if option.is_a?(Choosy::Option)
53
+ formatted = usage_option(option)
54
+ width += formatted.length
55
+ if width > columns
56
+ @buffer << "\n"
57
+ @buffer << ' ' * starting_width
58
+ @buffer << formatted
59
+ width = starting_width + formatted.length
60
+ else
61
+ @buffer << ' '
62
+ @buffer << formatted
63
+ width += 1
64
+ end
65
+ end
66
+ end
67
+
68
+ case command
69
+ when Choosy::Command
70
+ if command.arguments
71
+ @buffer << ' '
72
+ @buffer << command.arguments.metaname
46
73
  end
74
+ when Choosy::SuperCommand
75
+ @buffer << ' '
76
+ @buffer << command.metaname
47
77
  end
78
+
79
+ @buffer << "\n"
48
80
  end
49
81
 
50
- # FIXME: hideously ugly
51
- def print_usage(command)
52
- args = if command.respond_to?(:argument_validation) && command.argument_validation
53
- " [ARGS]"
54
- else
55
- ""
56
- end
57
- cmds = if command.respond_to?(:commands)
58
- " [COMMANDS]"
59
- else
60
- ""
61
- end
62
- options = if command.option_builders.length == 0
63
- ""
64
- else
65
- " [OPTIONS]"
66
- end
67
- $stdout << "USAGE: #{command.name}#{cmds}#{options}#{args}\n"
82
+ def print_header(str, styles=nil)
83
+ return if str.nil?
84
+ if styles && !styles.empty?
85
+ @buffer << color.multiple(str, styles)
86
+ else
87
+ @buffer << color.multiple(str, header_styles)
88
+ end
68
89
  end
69
90
 
70
- def print_summary(summary)
71
- write_lines(summary, ' ')
91
+ def print_element(element)
92
+ if element.header?
93
+ @buffer << "\n"
94
+ print_header(element.value, element.styles)
95
+ @buffer << "\n"
96
+ else
97
+ @buffer << "\n"
98
+ write_lines(element.value, indent, true)
99
+ end
72
100
  end
73
101
 
74
- def print_description(desc)
75
- print_separator("DESCRIPTION")
76
- write_lines(desc, " ")
102
+ def print_option(option, formatted_prefix, opt_indent)
103
+ write_prefix(formatted_prefix, opt_indent)
104
+ write_lines(option.description, opt_indent, false)
77
105
  end
78
106
 
79
- def print_separator(sep)
80
- $stdout << "\n#{sep}\n"
107
+ def print_command(command, formatted_prefix, cmd_indent)
108
+ write_prefix(formatted_prefix, cmd_indent)
109
+ write_lines(command.summary, cmd_indent, false)
81
110
  end
82
111
 
83
- def print_option(option)
84
- $stdout << " "
112
+ def usage_option(option, value=nil)
113
+ value ||= ""
114
+ value << "["
85
115
  if option.short_flag
86
- $stdout << option.short_flag
116
+ value << option.short_flag
87
117
  if option.long_flag
88
- $stdout << ", "
118
+ value << "|"
119
+ end
120
+ end
121
+ if option.long_flag
122
+ value << option.long_flag
123
+ end
124
+ if option.negated?
125
+ value << '|'
126
+ value << option.negated
127
+ end
128
+ if option.metaname
129
+ if option.arity.max > 1
130
+ value << ' '
131
+ value << option.metaname
132
+ else
133
+ value << '='
134
+ value << option.metaname
89
135
  end
90
136
  end
137
+ value << ']'
138
+ end
139
+
140
+ def regular_option(option, value=nil)
141
+ value ||= ""
142
+ if option.short_flag
143
+ value << option.short_flag
144
+ if option.long_flag
145
+ value << ', '
146
+ end
147
+ else
148
+ value << ' '
149
+ end
91
150
 
92
151
  if option.long_flag
93
- $stdout << option.long_flag
152
+ if option.negated?
153
+ value << '--['
154
+ value << option.negation
155
+ value << '-]'
156
+ value << option.long_flag.gsub(/^--/, '')
157
+ else
158
+ value << option.long_flag
159
+ end
94
160
  end
95
161
 
96
- if option.flag_parameter
97
- $stdout << " "
98
- $stdout << option.flag_parameter
162
+ if option.metaname
163
+ value << ' '
164
+ value << option.metaname
99
165
  end
166
+ value
167
+ end
168
+
169
+ protected
170
+ def retrieve_formatting_info(command)
171
+ cmdlen = 0
172
+ optionlen = 0
173
+ prefixes = []
100
174
 
101
- $stdout << "\n"
102
- write_lines(option.description, " ")
175
+ command.listing.each do |item|
176
+ case item
177
+ when Choosy::Option
178
+ opt = regular_option(item)
179
+ if opt.length > optionlen
180
+ optionlen = opt.length
181
+ end
182
+ prefixes << opt
183
+ when Choosy::Command
184
+ name = item.name.to_s
185
+ if name.length > cmdlen
186
+ cmdlen = name.length
187
+ end
188
+ prefixes << name
189
+ else
190
+ prefixes << nil
191
+ end
192
+ end
193
+
194
+ option_indent = ' ' * (optionlen + indent.length + offset.length)
195
+ cmd_indent = ' ' * (cmdlen + indent.length + offset.length)
196
+ [cmd_indent, option_indent, prefixes]
103
197
  end
104
198
 
105
- def print_command(command)
106
- write_lines("#{command.name}\t#{command.summary}", " ")
199
+ def write_prefix(prefix, after_indent)
200
+ len = after_indent.length - prefix.length - indent.length
201
+ @buffer << indent
202
+ @buffer << prefix
203
+ @buffer << ' ' * len
107
204
  end
108
205
 
109
- protected
110
- def write_lines(str, prefix)
206
+ def write_lines(str, prefix, indent_first)
111
207
  str.split("\n").each do |line|
112
208
  if line.length == 0
113
- $stdout << "\n"
209
+ @buffer << "\n"
114
210
  else
115
- wrap_long_lines(line, prefix)
211
+ index = 0
212
+
213
+ while index < line.length
214
+ index = write_line(line, prefix, index, indent_first)
215
+ indent_first = true
216
+ end
116
217
  end
117
218
  end
118
219
  end
119
220
 
120
- # FIXME: not exactly pretty, but it works, mostly
221
+ # Line:
222
+ # index char
223
+ # ----|---------------|--------------|--------------------|
224
+ # length? length?
225
+ #
226
+ # Printing:
227
+ # offset columns
228
+ # --|-------------------------------------------------|
229
+ #
121
230
  MAX_BACKTRACK = 25
122
- def wrap_long_lines(line, prefix)
123
- index = 0
124
- while index < line.length
125
- segment_size = line.length - index
126
- if segment_size >= columns
127
- i = columns + index - prefix.length
128
- while i > columns - MAX_BACKTRACK
129
- if line[i] == ' '
130
- indent_line(line[index, i - index], prefix)
131
- index = i + 1
132
- break
133
- else
134
- i -= 1
135
- end
136
- end
137
- else
138
- indent_line(line[index, line.length], prefix)
139
- index += segment_size
140
- end
231
+ def write_line(line, prefix, index, indent_first)
232
+ if indent_first
233
+ @buffer << prefix
141
234
  end
142
- end
143
235
 
144
- def indent_line(line, prefix)
145
- $stdout << prefix
146
- $stdout << line
147
- $stdout << "\n"
148
- end
236
+ max_line_length = columns - prefix.length # How much can we print?
237
+ char = index + max_line_length # Where are we within the line, looking at the max length
149
238
 
150
- private
151
- # https://github.com/cldwalker/hirb
152
- # modified from hirb
153
- def find_terminal_size(env_name, tput_name, stty_index)
154
- begin
155
- if ENV[env_name] =~ /^\d$/
156
- ENV[env_name].to_i
157
- elsif (RUBY_PLATFORM =~ /java/ || (!STDIN.tty? && ENV['TERM'])) && command_exists?('tput')
158
- `tput #{tput_name}`.to_i
159
- elsif STDIN.tty? && command_exists?('stty')
160
- `stty size`.scan(/\d+/).map { |s| s.to_i }[stty_index]
161
- else
162
- nil
239
+ if char > line.length # There's not a lot of line left to print
240
+ return write_rest(line, index)
241
+ end
242
+
243
+ char.downto(char - MAX_BACKTRACK) do |i| # Only go back a fixed line segment
244
+ if line[i, 1] == ' '
245
+ @buffer << line[index, i - index]
246
+ @buffer << "\n"
247
+ return i + 1
163
248
  end
164
- rescue
165
- nil
166
249
  end
250
+
251
+ # We didn't succeed in writing the line. so just bail and write the rest.
252
+ write_rest(line, index)
167
253
  end
168
254
 
169
- # directly from hirb
170
- def command_exists?(command)
171
- ENV['PATH'].split(File::PATH_SEPARATOR).any? {|d| File.exists? File.join(d, command) }
255
+ def write_rest(line, index)
256
+ @buffer << line[index, line.length]
257
+ @buffer << "\n"
258
+ line.length
172
259
  end
173
260
  end
174
261
  end