choosy 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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