highline 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  Below is a complete listing of changes for each revision of HighLine.
4
4
 
5
+ == 1.0.2
6
+
7
+ * Removed old and broken help tests.
8
+ * Fixed test case typo found by David A. Black.
9
+ * Added ERb escapes processing to lists, for coloring list items. Color escapes
10
+ Do nod add to list element size.
11
+ * HighLine now throws EOFError when input is exhausted.
12
+
5
13
  == 1.0.1
6
14
 
7
15
  * Minor bug fix: Moved help initialization to before response building, so help
data/README CHANGED
@@ -28,4 +28,4 @@ See the INSTALL file for instructions.
28
28
  == Questions and/or Comments
29
29
 
30
30
  Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net] with
31
- any questions.
31
+ any questions.
data/Rakefile CHANGED
@@ -7,53 +7,54 @@ require "rubygems"
7
7
  task :default => [:test]
8
8
 
9
9
  Rake::TestTask.new do |test|
10
- test.libs << "test"
11
- test.test_files = [ "test/ts_all.rb" ]
12
- test.verbose = true
10
+ test.libs << "test"
11
+ test.test_files = [ "test/ts_all.rb" ]
12
+ test.verbose = true
13
13
  end
14
14
 
15
15
  Rake::RDocTask.new do |rdoc|
16
- rdoc.main = "README"
17
- rdoc.rdoc_files.include( "README", "INSTALL",
18
- "TODO", "CHANGELOG",
19
- "AUTHORS", "COPYING",
20
- "LICENSE", "lib/" )
21
- rdoc.rdoc_dir = "doc/html"
22
- rdoc.title = "HighLine Documentation"
16
+ rdoc.rdoc_files.include( "README", "INSTALL",
17
+ "TODO", "CHANGELOG",
18
+ "AUTHORS", "COPYING",
19
+ "LICENSE", "lib/" )
20
+ rdoc.main = "README"
21
+ rdoc.rdoc_dir = "doc/html"
22
+ rdoc.title = "HighLine Documentation"
23
23
  end
24
24
 
25
25
  desc "Upload current documentation to Rubyforge"
26
26
  task :upload_docs => [:rdoc] do
27
- sh "scp -r site/* " +
28
- "bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/"
29
- sh "scp -r doc/html/* " +
30
- "bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/doc/"
27
+ sh "scp -r site/* " +
28
+ "bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/"
29
+ sh "scp -r doc/html/* " +
30
+ "bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/doc/"
31
31
  end
32
32
 
33
33
  spec = Gem::Specification.new do |spec|
34
- spec.name = "highline"
35
- spec.version = "1.0.1"
36
- spec.platform = Gem::Platform::RUBY
37
- spec.summary = "HighLine is a high-level line oriented console interface."
38
- spec.files = Dir.glob("{examples,lib,test}/**/*.rb").
39
- delete_if { |item| item.include?("CVS") } +
40
- ["Rakefile", "setup.rb"]
41
- spec.test_suite_file = "test/ts_all.rb"
42
- spec.has_rdoc = true
43
- spec.extra_rdoc_files = %w{README INSTALL TODO CHANGELOG LICENSE}
44
- spec.rdoc_options << '--title' << 'HighLine Documentation' <<
45
- '--main' << 'README'
34
+ spec.name = "highline"
35
+ spec.version = "1.0.2"
36
+ spec.platform = Gem::Platform::RUBY
37
+ spec.summary = "HighLine is a high-level line oriented console interface."
38
+ spec.files = Dir.glob("{examples,lib,test}/**/*.rb").
39
+ delete_if { |item| item.include?("CVS") } +
40
+ ["Rakefile", "setup.rb"]
46
41
 
47
- ### Removed due to Windows' install problems ###
48
- # spec.add_dependency("termios", ">= 0.9.4")
42
+ spec.test_suite_file = "test/ts_all.rb"
43
+ spec.has_rdoc = true
44
+ spec.extra_rdoc_files = %w{README INSTALL TODO CHANGELOG LICENSE}
45
+ spec.rdoc_options << '--title' << 'HighLine Documentation' <<
46
+ '--main' << 'README'
49
47
 
50
- spec.require_path = 'lib'
51
- spec.autorequire = "highline"
52
- spec.author = "James Edward Gray II"
53
- spec.email = "james@grayproductions.net"
54
- spec.rubyforge_project = "highline"
55
- spec.homepage = "http://highline.rubyforge.org"
56
- spec.description = <<END_DESC
48
+ ### Removed due to Windows' install problems ###
49
+ # spec.add_dependency("termios", ">= 0.9.4")
50
+
51
+ spec.require_path = 'lib'
52
+ spec.autorequire = "highline"
53
+ spec.author = "James Edward Gray II"
54
+ spec.email = "james@grayproductions.net"
55
+ spec.rubyforge_project = "highline"
56
+ spec.homepage = "http://highline.rubyforge.org"
57
+ spec.description = <<END_DESC
57
58
  A "high-level line oriented" input/output library that grew out of my solution
58
59
  to Ruby Quiz #29. This library attempts to make standard console input and
59
60
  output robust and painless.
@@ -61,14 +62,14 @@ END_DESC
61
62
  end
62
63
 
63
64
  Rake::GemPackageTask.new(spec) do |pkg|
64
- pkg.need_zip = true
65
- pkg.need_tar = true
65
+ pkg.need_zip = true
66
+ pkg.need_tar = true
66
67
  end
67
68
 
68
69
  desc "Show library's code statistics"
69
70
  task :stats do
70
- require 'code_statistics'
71
- CodeStatistics.new( ["HighLine", "lib"],
72
- ["Functionals", "examples"],
73
- ["Units", "test"] ).to_s
71
+ require 'code_statistics'
72
+ CodeStatistics.new( ["HighLine", "lib"],
73
+ ["Functionals", "examples"],
74
+ ["Units", "test"] ).to_s
74
75
  end
data/TODO CHANGED
@@ -3,4 +3,4 @@
3
3
  The following is a list of planned expansions for HighLine, in no particular
4
4
  order.
5
5
 
6
- * This space for rent...
6
+ * Rent this space.
@@ -13,15 +13,15 @@ colors = %w{black red green yellow blue magenta cyan white}
13
13
 
14
14
  # Using color() with symbols.
15
15
  colors.each_with_index do |c, i|
16
- say("This should be <%= color('#{c}', :#{c}) %>!")
17
- if i == 0
18
- say( "This should be " +
19
- "<%= color('white on #{c}', :white, :on_#{c}) %>!")
20
- else
21
- say( "This should be " +
22
- "<%= color( '#{colors[i - 1]} on #{c}',
23
- :#{colors[i - 1]}, :on_#{c} ) %>!")
24
- end
16
+ say("This should be <%= color('#{c}', :#{c}) %>!")
17
+ if i == 0
18
+ say( "This should be " +
19
+ "<%= color('white on #{c}', :white, :on_#{c}) %>!")
20
+ else
21
+ say( "This should be " +
22
+ "<%= color( '#{colors[i - 1]} on #{c}',
23
+ :#{colors[i - 1]}, :on_#{c} ) %>!")
24
+ end
25
25
  end
26
26
 
27
27
  # Using color with constants.
@@ -30,3 +30,9 @@ say("This should be <%= color('underlined', UNDERLINE) %>!")
30
30
 
31
31
  # Using constants only.
32
32
  say("This might even <%= BLINK %>blink<%= CLEAR %>!")
33
+
34
+ # It even works with list wrapping.
35
+ erb_digits = %w{Zero One Two Three Four} +
36
+ ["<%= color('Five', :blue) %%>"] +
37
+ %w{Six Seven Eight Nine}
38
+ say("<%= list(#{erb_digits.inspect}, :columns_down, 3) %>")
@@ -11,7 +11,7 @@ require "pp"
11
11
 
12
12
  grades = ask( "Enter test scores (or a blank line to quit):",
13
13
  lambda { |ans| ans =~ /^-?\d+$/ ? Integer(ans) : ans} ) do |q|
14
- q.gather = ""
14
+ q.gather = ""
15
15
  end
16
16
 
17
17
  say("Grades:")
@@ -12,64 +12,64 @@ require "yaml"
12
12
  contacts = [ ]
13
13
 
14
14
  class NameClass
15
- def self.parse( string )
16
- if string =~ /^\s*(\w+),\s*(\w+)\s*$/
17
- self.new($2, $1)
18
- else
19
- raise ArgumentError, "Invalid name format."
20
- end
21
- end
15
+ def self.parse( string )
16
+ if string =~ /^\s*(\w+),\s*(\w+)\s*$/
17
+ self.new($2, $1)
18
+ else
19
+ raise ArgumentError, "Invalid name format."
20
+ end
21
+ end
22
22
 
23
- def initialize(first, last)
24
- @first, @last = first, last
25
- end
26
-
27
- attr_reader :first, :last
23
+ def initialize(first, last)
24
+ @first, @last = first, last
25
+ end
26
+
27
+ attr_reader :first, :last
28
28
  end
29
29
 
30
30
  begin
31
- entry = Hash.new
32
-
33
- # basic output
34
- say("Enter a contact:")
31
+ entry = Hash.new
32
+
33
+ # basic output
34
+ say("Enter a contact:")
35
35
 
36
- # basic input
37
- entry[:name] = ask("Name? (last, first) ", NameClass) do |q|
38
- q.validate = /\A\w+, ?\w+\Z/
39
- end
40
- entry[:company] = ask("Company? ") { |q| q.default = "none" }
41
- entry[:address] = ask("Address? ")
42
- entry[:city] = ask("City? ")
43
- entry[:state] = ask("State? ") do |q|
44
- q.case = :up
45
- q.validate = /\A[A-Z]{2}\Z/
46
- end
47
- entry[:zip] = ask("Zip? ") do |q|
48
- q.validate = /\A\d{5}(?:-?\d{4})?\Z/
49
- end
50
- entry[:phone] = ask( "Phone? ",
51
- lambda { |p| p.delete("^0-9").
52
- sub(/\A(\d{3})/, '(\1) ').
53
- sub(/(\d{4})\Z/, '-\1') } ) do |q|
54
- q.validate = lambda { |p| p.delete("^0-9").length == 10 }
55
- q.responses[:not_valid] = "Enter a phone numer with area code."
56
- end
57
- entry[:age] = ask("Age? ", Integer) { |q| q.in = 0..105 }
58
- entry[:birthday] = ask("Birthday? ", Date)
59
- entry[:interests] = ask( "Interests? (comma separated list) ",
60
- lambda { |str| str.split(/,\s*/) } )
61
- entry[:description] = ask("Enter a description for this contact.") do |q|
62
- q.whitespace = :strip_and_collapse
63
- end
36
+ # basic input
37
+ entry[:name] = ask("Name? (last, first) ", NameClass) do |q|
38
+ q.validate = /\A\w+, ?\w+\Z/
39
+ end
40
+ entry[:company] = ask("Company? ") { |q| q.default = "none" }
41
+ entry[:address] = ask("Address? ")
42
+ entry[:city] = ask("City? ")
43
+ entry[:state] = ask("State? ") do |q|
44
+ q.case = :up
45
+ q.validate = /\A[A-Z]{2}\Z/
46
+ end
47
+ entry[:zip] = ask("Zip? ") do |q|
48
+ q.validate = /\A\d{5}(?:-?\d{4})?\Z/
49
+ end
50
+ entry[:phone] = ask( "Phone? ",
51
+ lambda { |p| p.delete("^0-9").
52
+ sub(/\A(\d{3})/, '(\1) ').
53
+ sub(/(\d{4})\Z/, '-\1') } ) do |q|
54
+ q.validate = lambda { |p| p.delete("^0-9").length == 10 }
55
+ q.responses[:not_valid] = "Enter a phone numer with area code."
56
+ end
57
+ entry[:age] = ask("Age? ", Integer) { |q| q.in = 0..105 }
58
+ entry[:birthday] = ask("Birthday? ", Date)
59
+ entry[:interests] = ask( "Interests? (comma separated list) ",
60
+ lambda { |str| str.split(/,\s*/) } )
61
+ entry[:description] = ask("Enter a description for this contact.") do |q|
62
+ q.whitespace = :strip_and_collapse
63
+ end
64
64
 
65
- contacts << entry
65
+ contacts << entry
66
66
  # shortcut for yes and no questions
67
67
  end while agree("Enter another contact? ", true)
68
68
 
69
69
  if agree("Save these contacts? ", true)
70
- file_name = ask("Enter a file name: ") do |q|
71
- q.validate = /\A\w+\Z/
72
- q.confirm = true
73
- end
74
- File.open("#{file_name}.yaml", "w") { |file| YAML.dump(contacts, file) }
70
+ file_name = ask("Enter a file name: ") do |q|
71
+ q.validate = /\A\w+\Z/
72
+ q.confirm = true
73
+ end
74
+ File.open("#{file_name}.yaml", "w") { |file| YAML.dump(contacts, file) }
75
75
  end
data/examples/menus.rb CHANGED
@@ -11,55 +11,55 @@ say(choices.map { |c| " #{c}\n" }.join)
11
11
 
12
12
  case ask("? ", choices)
13
13
  when "ruby"
14
- say("Good choice!")
14
+ say("Good choice!")
15
15
  else
16
- say("Not from around here, are you?")
16
+ say("Not from around here, are you?")
17
17
  end
18
18
 
19
19
  # The new and improved choose()...
20
20
  say("\nThis is the new mode (default)...")
21
21
  choose do |menu|
22
- menu.prompt = "Please choose your favorite programming language? "
22
+ menu.prompt = "Please choose your favorite programming language? "
23
23
 
24
- menu.choice :ruby do say("Good choice!") end
25
- menu.choices(:python, :perl) do say("Not from around here, are you?") end
24
+ menu.choice :ruby do say("Good choice!") end
25
+ menu.choices(:python, :perl) do say("Not from around here, are you?") end
26
26
  end
27
27
 
28
28
  say("\nThis is letter indexing...")
29
29
  choose do |menu|
30
- menu.index = :letter
31
- menu.index_suffix = ") "
30
+ menu.index = :letter
31
+ menu.index_suffix = ") "
32
32
 
33
- menu.prompt = "Please choose your favorite programming language? "
33
+ menu.prompt = "Please choose your favorite programming language? "
34
34
 
35
- menu.choice :ruby do say("Good choice!") end
36
- menu.choices(:python, :perl) do say("Not from around here, are you?") end
35
+ menu.choice :ruby do say("Good choice!") end
36
+ menu.choices(:python, :perl) do say("Not from around here, are you?") end
37
37
  end
38
38
 
39
39
  say("\nThis is with a different layout...")
40
40
  choose do |menu|
41
- menu.layout = :one_line
41
+ menu.layout = :one_line
42
42
 
43
- menu.header = "Languages"
44
- menu.prompt = "Favorite? "
43
+ menu.header = "Languages"
44
+ menu.prompt = "Favorite? "
45
45
 
46
- menu.choice :ruby do say("Good choice!") end
47
- menu.choices(:python, :perl) do say("Not from around here, are you?") end
46
+ menu.choice :ruby do say("Good choice!") end
47
+ menu.choices(:python, :perl) do say("Not from around here, are you?") end
48
48
  end
49
49
 
50
50
  say("\nYou can even build shells...")
51
51
  loop do
52
- choose do |menu|
53
- menu.layout = :menu_only
54
-
55
- menu.shell = true
56
-
57
- menu.choice(:load, "Load a file.") do |command, details|
58
- say("Loading file with options: #{details}...")
59
- end
60
- menu.choice(:save, "Save a file.") do |command, details|
61
- say("Saving file with options: #{details}...")
62
- end
63
- menu.choice(:quit, "Exit program.") { exit }
64
- end
52
+ choose do |menu|
53
+ menu.layout = :menu_only
54
+
55
+ menu.shell = true
56
+
57
+ menu.choice(:load, "Load a file.") do |command, details|
58
+ say("Loading file with options: #{details}...")
59
+ end
60
+ menu.choice(:save, "Save a file.") do |command, details|
61
+ say("Saving file with options: #{details}...")
62
+ end
63
+ menu.choice(:quit, "Exit program.") { exit }
64
+ end
65
65
  end
@@ -0,0 +1,22 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # trapping_eof.rb
4
+ #
5
+ # Created by James Edward Gray II on 2006-02-20.
6
+ # Copyright 2006 Gray Productions. All rights reserved.
7
+
8
+ require "rubygems"
9
+ require "highline/import"
10
+
11
+ loop do
12
+ begin
13
+ name = ask("What's your name?")
14
+ break if name == "exit"
15
+ puts "Hello, #{name}!"
16
+ rescue EOFError # HighLine throws this if @input.eof?
17
+ break
18
+ end
19
+ end
20
+
21
+ puts "Goodbye, dear friend."
22
+ exit
@@ -9,9 +9,9 @@ require "rubygems"
9
9
  require "highline/import"
10
10
 
11
11
  loop do
12
- cmd = ask("Enter command: ", %w{save load reset quit}) do |q|
13
- q.readline = true
14
- end
15
- say("Executing \"#{cmd}\"...")
16
- break if cmd == "quit"
12
+ cmd = ask("Enter command: ", %w{save load reset quit}) do |q|
13
+ q.readline = true
14
+ end
15
+ say("Executing \"#{cmd}\"...")
16
+ break if cmd == "quit"
17
17
  end
data/lib/highline.rb CHANGED
@@ -24,594 +24,621 @@ require "abbrev"
24
24
  # checking, convert types, etc.
25
25
  #
26
26
  class HighLine
27
- # An internal HighLine error. User code does not need to trap this.
28
- class QuestionError < StandardError
29
- # do nothing, just creating a unique error type
30
- end
27
+ # An internal HighLine error. User code does not need to trap this.
28
+ class QuestionError < StandardError
29
+ # do nothing, just creating a unique error type
30
+ end
31
31
 
32
- #
33
- # Embed in a String to clear all previous ANSI sequences. This *MUST* be
34
- # done before the program exits!
35
- #
36
- CLEAR = "\e[0m"
37
- # An alias for CLEAR.
38
- RESET = CLEAR
39
- # The start of an ANSI bold sequence.
40
- BOLD = "\e[1m"
41
- # The start of an ANSI dark sequence. (Terminal support uncommon.)
42
- DARK = "\e[2m"
43
- # The start of an ANSI underline sequence.
44
- UNDERLINE = "\e[4m"
45
- # An alias for UNDERLINE.
46
- UNDERSCORE = UNDERLINE
47
- # The start of an ANSI blink sequence. (Terminal support uncommon.)
48
- BLINK = "\e[5m"
49
- # The start of an ANSI reverse sequence.
50
- REVERSE = "\e[7m"
51
- # The start of an ANSI concealed sequence. (Terminal support uncommon.)
52
- CONCEALED = "\e[8m"
32
+ #
33
+ # Embed in a String to clear all previous ANSI sequences. This *MUST* be
34
+ # done before the program exits!
35
+ #
36
+ CLEAR = "\e[0m"
37
+ # An alias for CLEAR.
38
+ RESET = CLEAR
39
+ # The start of an ANSI bold sequence.
40
+ BOLD = "\e[1m"
41
+ # The start of an ANSI dark sequence. (Terminal support uncommon.)
42
+ DARK = "\e[2m"
43
+ # The start of an ANSI underline sequence.
44
+ UNDERLINE = "\e[4m"
45
+ # An alias for UNDERLINE.
46
+ UNDERSCORE = UNDERLINE
47
+ # The start of an ANSI blink sequence. (Terminal support uncommon.)
48
+ BLINK = "\e[5m"
49
+ # The start of an ANSI reverse sequence.
50
+ REVERSE = "\e[7m"
51
+ # The start of an ANSI concealed sequence. (Terminal support uncommon.)
52
+ CONCEALED = "\e[8m"
53
53
 
54
- # Set the terminal's foreground ANSI color to black.
55
- BLACK = "\e[30m"
56
- # Set the terminal's foreground ANSI color to red.
57
- RED = "\e[31m"
58
- # Set the terminal's foreground ANSI color to green.
59
- GREEN = "\e[32m"
60
- # Set the terminal's foreground ANSI color to yellow.
61
- YELLOW = "\e[33m"
62
- # Set the terminal's foreground ANSI color to blue.
63
- BLUE = "\e[34m"
64
- # Set the terminal's foreground ANSI color to magenta.
65
- MAGENTA = "\e[35m"
66
- # Set the terminal's foreground ANSI color to cyan.
67
- CYAN = "\e[36m"
68
- # Set the terminal's foreground ANSI color to white.
69
- WHITE = "\e[37m"
54
+ # Set the terminal's foreground ANSI color to black.
55
+ BLACK = "\e[30m"
56
+ # Set the terminal's foreground ANSI color to red.
57
+ RED = "\e[31m"
58
+ # Set the terminal's foreground ANSI color to green.
59
+ GREEN = "\e[32m"
60
+ # Set the terminal's foreground ANSI color to yellow.
61
+ YELLOW = "\e[33m"
62
+ # Set the terminal's foreground ANSI color to blue.
63
+ BLUE = "\e[34m"
64
+ # Set the terminal's foreground ANSI color to magenta.
65
+ MAGENTA = "\e[35m"
66
+ # Set the terminal's foreground ANSI color to cyan.
67
+ CYAN = "\e[36m"
68
+ # Set the terminal's foreground ANSI color to white.
69
+ WHITE = "\e[37m"
70
70
 
71
- # Set the terminal's background ANSI color to black.
72
- ON_BLACK = "\e[40m"
73
- # Set the terminal's background ANSI color to red.
74
- ON_RED = "\e[41m"
75
- # Set the terminal's background ANSI color to green.
76
- ON_GREEN = "\e[42m"
77
- # Set the terminal's background ANSI color to yellow.
78
- ON_YELLOW = "\e[43m"
79
- # Set the terminal's background ANSI color to blue.
80
- ON_BLUE = "\e[44m"
81
- # Set the terminal's background ANSI color to magenta.
82
- ON_MAGENTA = "\e[45m"
83
- # Set the terminal's background ANSI color to cyan.
84
- ON_CYAN = "\e[46m"
85
- # Set the terminal's background ANSI color to white.
86
- ON_WHITE = "\e[47m"
71
+ # Set the terminal's background ANSI color to black.
72
+ ON_BLACK = "\e[40m"
73
+ # Set the terminal's background ANSI color to red.
74
+ ON_RED = "\e[41m"
75
+ # Set the terminal's background ANSI color to green.
76
+ ON_GREEN = "\e[42m"
77
+ # Set the terminal's background ANSI color to yellow.
78
+ ON_YELLOW = "\e[43m"
79
+ # Set the terminal's background ANSI color to blue.
80
+ ON_BLUE = "\e[44m"
81
+ # Set the terminal's background ANSI color to magenta.
82
+ ON_MAGENTA = "\e[45m"
83
+ # Set the terminal's background ANSI color to cyan.
84
+ ON_CYAN = "\e[46m"
85
+ # Set the terminal's background ANSI color to white.
86
+ ON_WHITE = "\e[47m"
87
87
 
88
- #
89
- # Create an instance of HighLine, connected to the streams _input_
90
- # and _output_.
91
- #
92
- def initialize( input = $stdin, output = $stdout,
93
- wrap_at = nil, page_at = nil )
94
- @input = input
95
- @output = output
96
- @wrap_at = wrap_at
97
- @page_at = page_at
98
-
99
- @question = nil
100
- @answer = nil
101
- @menu = nil
102
- @header = nil
103
- @prompt = nil
104
- @gather = nil
105
- @answers = nil
106
- @key = nil
107
- end
108
-
109
- #
110
- # Set to an integer value to cause HighLine to wrap output lines at the
111
- # indicated character limit. When +nil+, the default, no wrapping occurs.
112
- #
113
- attr_accessor :wrap_at
114
- #
115
- # Set to an integer value to cause HighLine to page output lines over the
116
- # indicated line limit. When +nil+, the default, no paging occurs.
117
- #
118
- attr_accessor :page_at
119
-
120
- #
121
- # A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
122
- # answers ("y" and "n" are allowed) and returns +true+ or +false+
123
- # (+true+ for "yes"). If provided a +true+ value, _character_ will cause
124
- # HighLine to fetch a single character response.
125
- #
126
- def agree( yes_or_no_question, character = nil )
127
- ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
128
- q.validate = /\Ay(?:es)?|no?\Z/i
129
- q.responses[:not_valid] = 'Please enter "yes" or "no".'
130
- q.responses[:ask_on_error] = :question
131
- q.character = character
132
- end
133
- end
134
-
135
- #
136
- # This method is the primary interface for user input. Just provide a
137
- # _question_ to ask the user, the _answer_type_ you want returned, and
138
- # optionally a code block setting up details of how you want the question
139
- # handled. See HighLine.say() for details on the format of _question_, and
140
- # HighLine::Question for more information about _answer_type_ and what's
141
- # valid in the code block.
142
- #
143
- # If <tt>@question</tt> is set before ask() is called, parameters are
144
- # ignored and that object (must be a HighLine::Question) is used to drive
145
- # the process instead.
146
- #
147
- def ask(question, answer_type = String, &details) # :yields: question
148
- @question ||= Question.new(question, answer_type, &details)
149
-
150
- return gather if @question.gather
151
-
152
- # readline() needs to handle it's own output
153
- say(@question) unless @question.readline
154
- begin
155
- @answer = @question.answer_or_default(get_response)
156
- unless @question.valid_answer?(@answer)
157
- explain_error(:not_valid)
158
- raise QuestionError
159
- end
160
-
161
- @answer = @question.convert(@answer)
162
-
163
- if @question.in_range?(@answer)
164
- if @question.confirm
165
- # need to add a layer of scope to ask a question inside a
166
- # question, without destroying instance data
167
- context_change = self.class.new( @input, @output,
168
- @wrap_at, @page_at )
169
- if @question.confirm == true
170
- confirm_question = "Are you sure? "
171
- else
172
- # evaluate ERb under initial scope, so it will have
173
- # access to @question and @answer
174
- template = ERB.new(@question.confirm, nil, "%")
175
- confirm_question = template.result(binding)
176
- end
177
- unless context_change.agree(confirm_question)
178
- explain_error(nil)
179
- raise QuestionError
180
- end
181
- end
182
-
183
- @answer
184
- else
185
- explain_error(:not_in_range)
186
- raise QuestionError
187
- end
188
- rescue QuestionError
189
- retry
190
- rescue ArgumentError
191
- explain_error(:invalid_type)
192
- retry
193
- rescue Question::NoAutoCompleteMatch
194
- explain_error(:no_completion)
195
- retry
196
- rescue NameError
197
- raise if $!.is_a?(NoMethodError)
198
- explain_error(:ambiguous_completion)
199
- retry
200
- ensure
201
- @question = nil # Reset Question object.
202
- end
203
- end
88
+ #
89
+ # Create an instance of HighLine, connected to the streams _input_
90
+ # and _output_.
91
+ #
92
+ def initialize( input = $stdin, output = $stdout,
93
+ wrap_at = nil, page_at = nil )
94
+ @input = input
95
+ @output = output
96
+ @wrap_at = wrap_at
97
+ @page_at = page_at
98
+
99
+ @question = nil
100
+ @answer = nil
101
+ @menu = nil
102
+ @header = nil
103
+ @prompt = nil
104
+ @gather = nil
105
+ @answers = nil
106
+ @key = nil
107
+ end
108
+
109
+ #
110
+ # Set to an integer value to cause HighLine to wrap output lines at the
111
+ # indicated character limit. When +nil+, the default, no wrapping occurs.
112
+ #
113
+ attr_accessor :wrap_at
114
+ #
115
+ # Set to an integer value to cause HighLine to page output lines over the
116
+ # indicated line limit. When +nil+, the default, no paging occurs.
117
+ #
118
+ attr_accessor :page_at
119
+
120
+ #
121
+ # A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
122
+ # answers ("y" and "n" are allowed) and returns +true+ or +false+
123
+ # (+true+ for "yes"). If provided a +true+ value, _character_ will cause
124
+ # HighLine to fetch a single character response.
125
+ #
126
+ # Raises EOFError if input is exhausted.
127
+ #
128
+ def agree( yes_or_no_question, character = nil )
129
+ ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
130
+ q.validate = /\Ay(?:es)?|no?\Z/i
131
+ q.responses[:not_valid] = 'Please enter "yes" or "no".'
132
+ q.responses[:ask_on_error] = :question
133
+ q.character = character
134
+ end
135
+ end
136
+
137
+ #
138
+ # This method is the primary interface for user input. Just provide a
139
+ # _question_ to ask the user, the _answer_type_ you want returned, and
140
+ # optionally a code block setting up details of how you want the question
141
+ # handled. See HighLine.say() for details on the format of _question_, and
142
+ # HighLine::Question for more information about _answer_type_ and what's
143
+ # valid in the code block.
144
+ #
145
+ # If <tt>@question</tt> is set before ask() is called, parameters are
146
+ # ignored and that object (must be a HighLine::Question) is used to drive
147
+ # the process instead.
148
+ #
149
+ # Raises EOFError if input is exhausted.
150
+ #
151
+ def ask( question, answer_type = String, &details ) # :yields: question
152
+ @question ||= Question.new(question, answer_type, &details)
153
+
154
+ return gather if @question.gather
155
+
156
+ # readline() needs to handle it's own output
157
+ say(@question) unless @question.readline
158
+ begin
159
+ @answer = @question.answer_or_default(get_response)
160
+ unless @question.valid_answer?(@answer)
161
+ explain_error(:not_valid)
162
+ raise QuestionError
163
+ end
164
+
165
+ @answer = @question.convert(@answer)
166
+
167
+ if @question.in_range?(@answer)
168
+ if @question.confirm
169
+ # need to add a layer of scope to ask a question inside a
170
+ # question, without destroying instance data
171
+ context_change = self.class.new( @input, @output,
172
+ @wrap_at, @page_at )
173
+ if @question.confirm == true
174
+ confirm_question = "Are you sure? "
175
+ else
176
+ # evaluate ERb under initial scope, so it will have
177
+ # access to @question and @answer
178
+ template = ERB.new(@question.confirm, nil, "%")
179
+ confirm_question = template.result(binding)
180
+ end
181
+ unless context_change.agree(confirm_question)
182
+ explain_error(nil)
183
+ raise QuestionError
184
+ end
185
+ end
186
+
187
+ @answer
188
+ else
189
+ explain_error(:not_in_range)
190
+ raise QuestionError
191
+ end
192
+ rescue QuestionError
193
+ retry
194
+ rescue ArgumentError
195
+ explain_error(:invalid_type)
196
+ retry
197
+ rescue Question::NoAutoCompleteMatch
198
+ explain_error(:no_completion)
199
+ retry
200
+ rescue NameError
201
+ raise if $!.is_a?(NoMethodError)
202
+ explain_error(:ambiguous_completion)
203
+ retry
204
+ ensure
205
+ @question = nil # Reset Question object.
206
+ end
207
+ end
204
208
 
205
- #
206
- # This method is HighLine's menu handler. For simple usage, you can just
207
- # pass all the menu items you wish to display. At that point, choose() will
208
- # build and display a menu, walk the user through selection, and return
209
- # their choice amoung the provided items. You might use this in a case
210
- # statement for quick and dirty menus.
211
- #
212
- # However, choose() is capable of much more. If provided, a block will be
213
- # passed a HighLine::Menu object to configure. Using this method, you can
214
- # customize all the details of menu handling from index display, to building
215
- # a complete shell-like menuing system. See HighLine::Menu for all the
216
- # methods it responds to.
217
- #
218
- def choose( *items, &details )
219
- @menu = @question = Menu.new(&details)
220
- @menu.choices(*items) unless items.empty?
221
-
222
- # Set _answer_type_ so we can double as the Question for ask().
223
- @menu.answer_type = if @menu.shell
224
- lambda do |command| # shell-style selection
225
- first_word = command.split.first
209
+ #
210
+ # This method is HighLine's menu handler. For simple usage, you can just
211
+ # pass all the menu items you wish to display. At that point, choose() will
212
+ # build and display a menu, walk the user through selection, and return
213
+ # their choice amoung the provided items. You might use this in a case
214
+ # statement for quick and dirty menus.
215
+ #
216
+ # However, choose() is capable of much more. If provided, a block will be
217
+ # passed a HighLine::Menu object to configure. Using this method, you can
218
+ # customize all the details of menu handling from index display, to building
219
+ # a complete shell-like menuing system. See HighLine::Menu for all the
220
+ # methods it responds to.
221
+ #
222
+ # Raises EOFError if input is exhausted.
223
+ #
224
+ def choose( *items, &details )
225
+ @menu = @question = Menu.new(&details)
226
+ @menu.choices(*items) unless items.empty?
227
+
228
+ # Set _answer_type_ so we can double as the Question for ask().
229
+ @menu.answer_type = if @menu.shell
230
+ lambda do |command| # shell-style selection
231
+ first_word = command.split.first
226
232
 
227
- options = @menu.options
228
- options.extend(OptionParser::Completion)
229
- answer = options.complete(first_word)
233
+ options = @menu.options
234
+ options.extend(OptionParser::Completion)
235
+ answer = options.complete(first_word)
230
236
 
231
- if answer.nil?
232
- raise Question::NoAutoCompleteMatch
233
- end
237
+ if answer.nil?
238
+ raise Question::NoAutoCompleteMatch
239
+ end
234
240
 
235
- [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
236
- end
237
- else
238
- @menu.options # normal menu selection, by index or name
239
- end
240
-
241
- # Provide hooks for ERb layouts.
242
- @header = @menu.header
243
- @prompt = @menu.prompt
244
-
245
- if @menu.shell
246
- selected = ask("Ignored", @menu.answer_type)
247
- @menu.select(self, *selected)
248
- else
249
- selected = ask("Ignored", @menu.answer_type)
250
- @menu.select(self, selected)
251
- end
252
- end
241
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
242
+ end
243
+ else
244
+ @menu.options # normal menu selection, by index or name
245
+ end
246
+
247
+ # Provide hooks for ERb layouts.
248
+ @header = @menu.header
249
+ @prompt = @menu.prompt
250
+
251
+ if @menu.shell
252
+ selected = ask("Ignored", @menu.answer_type)
253
+ @menu.select(self, *selected)
254
+ else
255
+ selected = ask("Ignored", @menu.answer_type)
256
+ @menu.select(self, selected)
257
+ end
258
+ end
253
259
 
254
- #
255
- # This method provides easy access to ANSI color sequences, without the user
256
- # needing to remember to CLEAR at the end of each sequence. Just pass the
257
- # _string_ to color, followed by a list of _colors_ you would like it to be
258
- # affected by. The _colors_ can be HighLine class constants, or symbols
259
- # (:blue for BLUE, for example). A CLEAR will automatically be embedded to
260
- # the end of the returned String.
261
- #
262
- def color( string, *colors )
263
- colors.map! do |c|
264
- if c.is_a?(Symbol)
265
- self.class.const_get(c.to_s.upcase)
266
- else
267
- c
268
- end
269
- end
270
- "#{colors.join}#{string}#{CLEAR}"
271
- end
272
-
273
- #
274
- # This method is a utility for quickly and easily laying out lists. It can
275
- # be accessed within ERb replacements of any text that will be sent to the
276
- # user.
277
- #
278
- # The only required parameter is _items_, which should be the Array of items
279
- # to list. A specified _mode_ controls how that list is formed and _option_
280
- # has different effects, depending on the _mode_. Recognized modes are:
281
- #
282
- # <tt>:columns_across</tt>:: _items_ will be placed in columns, flowing
283
- # from left to right. If given, _option_ is the
284
- # number of columns to be used. When absent,
285
- # columns will be determined based on _wrap_at_
286
- # or a default of 80 characters.
287
- # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>, save
288
- # flow goes down.
289
- # <tt>:inline</tt>:: All _items_ are placed on a single line. The
290
- # last two _items_ are separated by _option_ or
291
- # a default of " or ". All other _items_ are
292
- # separated by ", ".
293
- # <tt>:rows</tt>:: The default mode. Each of the _items_ is
294
- # placed on it's own line. The _option_
295
- # parameter is ignored in this mode.
296
- #
297
- def list( items, mode = :rows, option = nil )
298
- items = items.to_ary
299
-
300
- case mode
301
- when :inline
302
- option = " or " if option.nil?
303
-
304
- case items.size
305
- when 0
306
- ""
307
- when 1
308
- items.first
309
- when 2
310
- "#{items.first}#{option}#{items.last}"
311
- else
312
- items[0..-2].join(", ") + "#{option}#{items.last}"
313
- end
314
- when :columns_across, :columns_down
315
- if option.nil?
316
- limit = @wrap_at || 80
317
- max_length = items.max { |a, b| a.length <=> b.length }.length
318
- option = (limit + 2) / (max_length + 2)
319
- end
260
+ #
261
+ # This method provides easy access to ANSI color sequences, without the user
262
+ # needing to remember to CLEAR at the end of each sequence. Just pass the
263
+ # _string_ to color, followed by a list of _colors_ you would like it to be
264
+ # affected by. The _colors_ can be HighLine class constants, or symbols
265
+ # (:blue for BLUE, for example). A CLEAR will automatically be embedded to
266
+ # the end of the returned String.
267
+ #
268
+ def color( string, *colors )
269
+ colors.map! do |c|
270
+ if c.is_a?(Symbol)
271
+ self.class.const_get(c.to_s.upcase)
272
+ else
273
+ c
274
+ end
275
+ end
276
+ "#{colors.join}#{string}#{CLEAR}"
277
+ end
278
+
279
+ #
280
+ # This method is a utility for quickly and easily laying out lists. It can
281
+ # be accessed within ERb replacements of any text that will be sent to the
282
+ # user.
283
+ #
284
+ # The only required parameter is _items_, which should be the Array of items
285
+ # to list. A specified _mode_ controls how that list is formed and _option_
286
+ # has different effects, depending on the _mode_. Recognized modes are:
287
+ #
288
+ # <tt>:columns_across</tt>:: _items_ will be placed in columns, flowing
289
+ # from left to right. If given, _option_ is the
290
+ # number of columns to be used. When absent,
291
+ # columns will be determined based on _wrap_at_
292
+ # or a default of 80 characters.
293
+ # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>, save
294
+ # flow goes down.
295
+ # <tt>:inline</tt>:: All _items_ are placed on a single line. The
296
+ # last two _items_ are separated by _option_ or
297
+ # a default of " or ". All other _items_ are
298
+ # separated by ", ".
299
+ # <tt>:rows</tt>:: The default mode. Each of the _items_ is
300
+ # placed on it's own line. The _option_
301
+ # parameter is ignored in this mode.
302
+ #
303
+ # Each member of the _items_ Array is passed through ERb and thus can contain
304
+ # their own expansions. Color escape expansions do not contribute to the
305
+ # final field width.
306
+ #
307
+ def list( items, mode = :rows, option = nil )
308
+ items = items.to_ary.map do |item|
309
+ ERB.new(item, nil, "%").result(binding)
310
+ end
311
+
312
+ case mode
313
+ when :inline
314
+ option = " or " if option.nil?
315
+
316
+ case items.size
317
+ when 0
318
+ ""
319
+ when 1
320
+ items.first
321
+ when 2
322
+ "#{items.first}#{option}#{items.last}"
323
+ else
324
+ items[0..-2].join(", ") + "#{option}#{items.last}"
325
+ end
326
+ when :columns_across, :columns_down
327
+ max_length = actual_length(
328
+ items.max { |a, b| actual_length(a) <=> actual_length(b) }
329
+ )
320
330
 
321
- max_length = items.max { |a, b| a.length <=> b.length }.length
322
- items = items.map { |item| "%-#{max_length}s" % item }
323
- row_count = (items.size / option.to_f).ceil
324
-
325
- if mode == :columns_across
326
- rows = Array.new(row_count) { Array.new }
327
- items.each_with_index do |item, index|
328
- rows[index / option] << item
329
- end
331
+ if option.nil?
332
+ limit = @wrap_at || 80
333
+ option = (limit + 2) / (max_length + 2)
334
+ end
330
335
 
331
- rows.map { |row| row.join(" ") + "\n" }.join
332
- else
333
- columns = Array.new(option) { Array.new }
334
- items.each_with_index do |item, index|
335
- columns[index / row_count] << item
336
- end
337
-
338
- list = ""
339
- columns.first.size.times do |index|
340
- list << columns.map { |column| column[index] }.
341
- compact.join(" ") + "\n"
342
- end
343
- list
344
- end
345
- else
346
- items.map { |i| "#{i}\n" }.join
347
- end
348
- end
349
-
350
- #
351
- # The basic output method for HighLine objects. If the provided _statement_
352
- # ends with a space or tab character, a newline will not be appended (output
353
- # will be flush()ed). All other cases are passed straight to Kernel.puts().
354
- #
355
- # The _statement_ parameter is processed as an ERb template, supporting
356
- # embedded Ruby code. The template is evaluated with a binding inside
357
- # the HighLine instance, providing easy access to the ANSI color constants
358
- # and the HighLine.color() method.
359
- #
360
- def say( statement )
361
- statement = statement.to_str
362
- return unless statement.length > 0
363
-
364
- template = ERB.new(statement, nil, "%")
365
- statement = template.result(binding)
366
-
367
- statement = wrap(statement) unless @wrap_at.nil?
368
- statement = page_print(statement) unless @page_at.nil?
369
-
370
- if statement[-1, 1] == " " or statement[-1, 1] == "\t"
371
- @output.print(statement)
372
- @output.flush
373
- else
374
- @output.puts(statement)
375
- end
376
- end
377
-
378
- private
379
-
380
- #
381
- # A helper method for sending the output stream and error and repeat
382
- # of the question.
383
- #
384
- def explain_error( error )
385
- say(@question.responses[error]) unless error.nil?
386
- if @question.responses[:ask_on_error] == :question
387
- say(@question)
388
- elsif @question.responses[:ask_on_error]
389
- say(@question.responses[:ask_on_error])
390
- end
391
- end
392
-
393
- #
394
- # Collects an Array/Hash full of answers as described in
395
- # HighLine::Question.gather().
396
- #
397
- def gather( )
398
- @gather = @question.gather
399
- @answers = [ ]
400
- original_question = @question
401
-
402
- @question.gather = false
403
-
404
- case @gather
405
- when Integer
406
- @answers << ask(@question)
407
- @gather -= 1
336
+ items = items.map do |item|
337
+ pad = max_length + (item.length - actual_length(item))
338
+ "%-#{pad}s" % item
339
+ end
340
+ row_count = (items.size / option.to_f).ceil
341
+
342
+ if mode == :columns_across
343
+ rows = Array.new(row_count) { Array.new }
344
+ items.each_with_index do |item, index|
345
+ rows[index / option] << item
346
+ end
408
347
 
409
- original_question.question = ""
410
- until @gather.zero?
411
- @question = original_question
412
- @answers << ask(@question)
413
- @gather -= 1
414
- end
415
- when String, Regexp
416
- @answers << ask(@question)
348
+ rows.map { |row| row.join(" ") + "\n" }.join
349
+ else
350
+ columns = Array.new(option) { Array.new }
351
+ items.each_with_index do |item, index|
352
+ columns[index / row_count] << item
353
+ end
354
+
355
+ list = ""
356
+ columns.first.size.times do |index|
357
+ list << columns.map { |column| column[index] }.
358
+ compact.join(" ") + "\n"
359
+ end
360
+ list
361
+ end
362
+ else
363
+ items.map { |i| "#{i}\n" }.join
364
+ end
365
+ end
366
+
367
+ #
368
+ # The basic output method for HighLine objects. If the provided _statement_
369
+ # ends with a space or tab character, a newline will not be appended (output
370
+ # will be flush()ed). All other cases are passed straight to Kernel.puts().
371
+ #
372
+ # The _statement_ parameter is processed as an ERb template, supporting
373
+ # embedded Ruby code. The template is evaluated with a binding inside
374
+ # the HighLine instance, providing easy access to the ANSI color constants
375
+ # and the HighLine.color() method.
376
+ #
377
+ def say( statement )
378
+ statement = statement.to_str
379
+ return unless statement.length > 0
380
+
381
+ template = ERB.new(statement, nil, "%")
382
+ statement = template.result(binding)
383
+
384
+ statement = wrap(statement) unless @wrap_at.nil?
385
+ statement = page_print(statement) unless @page_at.nil?
386
+
387
+ if statement[-1, 1] == " " or statement[-1, 1] == "\t"
388
+ @output.print(statement)
389
+ @output.flush
390
+ else
391
+ @output.puts(statement)
392
+ end
393
+ end
394
+
395
+ private
396
+
397
+ #
398
+ # A helper method for sending the output stream and error and repeat
399
+ # of the question.
400
+ #
401
+ def explain_error( error )
402
+ say(@question.responses[error]) unless error.nil?
403
+ if @question.responses[:ask_on_error] == :question
404
+ say(@question)
405
+ elsif @question.responses[:ask_on_error]
406
+ say(@question.responses[:ask_on_error])
407
+ end
408
+ end
409
+
410
+ #
411
+ # Collects an Array/Hash full of answers as described in
412
+ # HighLine::Question.gather().
413
+ #
414
+ # Raises EOFError if input is exhausted.
415
+ #
416
+ def gather( )
417
+ @gather = @question.gather
418
+ @answers = [ ]
419
+ original_question = @question
420
+
421
+ @question.gather = false
422
+
423
+ case @gather
424
+ when Integer
425
+ @answers << ask(@question)
426
+ @gather -= 1
417
427
 
418
- original_question.question = ""
419
- until (@gather.is_a?(String) and @answers.last.to_s == @gather) or
420
- (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
421
- @question = original_question
422
- @answers << ask(@question)
423
- end
424
-
425
- @answers.pop
426
- when Hash
427
- @answers = { }
428
- @gather.keys.sort.each do |key|
429
- @question = original_question
430
- @key = key
431
- @answers[key] = ask(@question)
432
- end
433
- end
434
-
435
- @answers
436
- end
437
-
438
- #
439
- # This section builds a character reading function to suit the proper
440
- # platform we're running on. Be warned: Here be dragons!
441
- #
442
- begin
443
- require "Win32API" # See if we're on Windows.
428
+ original_question.question = ""
429
+ until @gather.zero?
430
+ @question = original_question
431
+ @answers << ask(@question)
432
+ @gather -= 1
433
+ end
434
+ when String, Regexp
435
+ @answers << ask(@question)
444
436
 
445
- CHARACTER_MODE = "Win32API" # For Debugging purposes only.
437
+ original_question.question = ""
438
+ until (@gather.is_a?(String) and @answers.last.to_s == @gather) or
439
+ (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
440
+ @question = original_question
441
+ @answers << ask(@question)
442
+ end
443
+
444
+ @answers.pop
445
+ when Hash
446
+ @answers = { }
447
+ @gather.keys.sort.each do |key|
448
+ @question = original_question
449
+ @key = key
450
+ @answers[key] = ask(@question)
451
+ end
452
+ end
453
+
454
+ @answers
455
+ end
456
+
457
+ #
458
+ # This section builds a character reading function to suit the proper
459
+ # platform we're running on. Be warned: Here be dragons!
460
+ #
461
+ begin
462
+ require "Win32API" # See if we're on Windows.
446
463
 
447
- #
448
- # Windows savvy getc().
449
- #
450
- # *WARNING*: This method ignores <tt>@input</tt> and reads one
451
- # character from +STDIN+!
452
- #
453
- def get_character
454
- Win32API.new("crtdll", "_getch", [ ], "L").Call
455
- end
456
- rescue LoadError # If we're not on Windows try...
457
- begin
458
- require "termios" # Unix, first choice.
459
-
460
- CHARACTER_MODE = "termios" # For Debugging purposes only.
464
+ CHARACTER_MODE = "Win32API" # For Debugging purposes only.
465
+
466
+ #
467
+ # Windows savvy getc().
468
+ #
469
+ # *WARNING*: This method ignores <tt>@input</tt> and reads one
470
+ # character from +STDIN+!
471
+ #
472
+ def get_character
473
+ Win32API.new("crtdll", "_getch", [ ], "L").Call
474
+ end
475
+ rescue LoadError # If we're not on Windows try...
476
+ begin
477
+ require "termios" # Unix, first choice.
478
+
479
+ CHARACTER_MODE = "termios" # For Debugging purposes only.
461
480
 
462
- #
463
- # Unix savvy getc(). (First choice.)
464
- #
465
- # *WARNING*: This method requires the "termios" library!
466
- #
467
- def get_character
468
- old_settings = Termios.getattr(@input)
469
-
470
- new_settings = old_settings.dup
471
- new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
472
-
473
- begin
474
- Termios.setattr(@input, Termios::TCSANOW, new_settings)
475
- @input.getc
476
- ensure
477
- Termios.setattr(@input, Termios::TCSANOW, old_settings)
478
- end
479
- end
480
- rescue LoadError # If our first choice fails, default.
481
- CHARACTER_MODE = "stty" # For Debugging purposes only.
481
+ #
482
+ # Unix savvy getc(). (First choice.)
483
+ #
484
+ # *WARNING*: This method requires the "termios" library!
485
+ #
486
+ def get_character
487
+ old_settings = Termios.getattr(@input)
482
488
 
483
- #
484
- # Unix savvy getc(). (Second choice.)
485
- #
486
- # *WARNING*: This method requires the external "stty" program!
487
- #
488
- def get_character
489
- state = `stty -g`
490
-
491
- begin
492
- system "stty raw -echo cbreak"
493
- @input.getc
494
- ensure
495
- system "stty #{state}"
496
- end
497
- end
498
- end
489
+ new_settings = old_settings.dup
490
+ new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
491
+
492
+ begin
493
+ Termios.setattr(@input, Termios::TCSANOW, new_settings)
494
+ @input.getc
495
+ ensure
496
+ Termios.setattr(@input, Termios::TCSANOW, old_settings)
497
+ end
498
+ end
499
+ rescue LoadError # If our first choice fails, default.
500
+ CHARACTER_MODE = "stty" # For Debugging purposes only.
501
+
502
+ #
503
+ # Unix savvy getc(). (Second choice.)
504
+ #
505
+ # *WARNING*: This method requires the external "stty" program!
506
+ #
507
+ def get_character
508
+ state = `stty -g`
509
+
510
+ begin
511
+ system "stty raw -echo cbreak"
512
+ @input.getc
513
+ ensure
514
+ system "stty #{state}"
515
+ end
516
+ end
499
517
  end
518
+ end
500
519
 
501
- #
502
- # Read a line of input from the input stream and process whitespace as
503
- # requested by the Question object.
504
- #
505
- # If Question's _readline_ property is set, that library will be used to
506
- # fetch input. *WARNING*: This ignores the currently set input stream.
507
- #
508
- def get_line( )
509
- if @question.readline
510
- require "readline" # load only if needed
520
+ #
521
+ # Read a line of input from the input stream and process whitespace as
522
+ # requested by the Question object.
523
+ #
524
+ # If Question's _readline_ property is set, that library will be used to
525
+ # fetch input. *WARNING*: This ignores the currently set input stream.
526
+ #
527
+ def get_line( )
528
+ if @question.readline
529
+ require "readline" # load only if needed
511
530
 
512
- # capture say()'s work in a String to feed to readline()
513
- old_output = @output
514
- @output = StringIO.new
515
- say(@question)
516
- question = @output.string
517
- @output = old_output
518
-
519
- # prep auto-completion
520
- completions = @question.selection.abbrev
521
- Readline.completion_proc = lambda { |string| completions[string] }
522
-
523
- # work-around ugly readline() warnings
524
- old_verbose = $VERBOSE
525
- $VERBOSE = nil
526
- answer = @question.change_case(
527
- @question.remove_whitespace(
528
- Readline.readline(question, true) ) )
529
- $VERBOSE = old_verbose
531
+ # capture say()'s work in a String to feed to readline()
532
+ old_output = @output
533
+ @output = StringIO.new
534
+ say(@question)
535
+ question = @output.string
536
+ @output = old_output
537
+
538
+ # prep auto-completion
539
+ completions = @question.selection.abbrev
540
+ Readline.completion_proc = lambda { |string| completions[string] }
541
+
542
+ # work-around ugly readline() warnings
543
+ old_verbose = $VERBOSE
544
+ $VERBOSE = nil
545
+ answer = @question.change_case(
546
+ @question.remove_whitespace(
547
+ Readline.readline(question, true) ) )
548
+ $VERBOSE = old_verbose
530
549
 
531
- answer
532
- else
533
- @question.change_case(@question.remove_whitespace(@input.gets))
534
- end
535
- end
536
-
537
- #
538
- # Return a line or character of input, as requested for this question.
539
- # Character input will be returned as a single character String,
540
- # not an Integer.
541
- #
542
- def get_response( )
543
- if @question.character.nil?
544
- if @question.echo == true and @question.limit.nil?
545
- get_line
546
- else
547
- line = ""
548
- while character = get_character
549
- line << character.chr
550
- # looking for carriage return (decimal 13) or
551
- # newline (decimal 10) in raw input
552
- break if character == 13 or character == 10 or
553
- (@question.limit and line.size == @question.limit)
554
- @output.print(@question.echo) if @question.echo != false
555
- end
556
- say("\n")
557
- @question.change_case(@question.remove_whitespace(line))
558
- end
559
- elsif @question.character == :getc
560
- @question.change_case(@input.getc.chr)
561
- else
562
- response = get_character.chr
563
- echo = if @question.echo == true
564
- response
565
- elsif @question.echo != false
566
- @question.echo
567
- else
568
- ""
569
- end
570
- say("#{echo}\n")
571
- @question.change_case(response)
572
- end
573
- end
574
-
575
- #
576
- # Page print a series of at most _page_at_ lines for _output_. After each
577
- # page is printed, HighLine will pause until the user presses enter/return
578
- # then display the next page of data.
579
- #
580
- # Note that the final page of _output_ is *not* printed, but returned
581
- # instead. This is to support any special handling for the final sequence.
582
- #
583
- def page_print( output )
584
- lines = output.scan(/[^\n]*\n?/)
585
- while lines.size > @page_at
586
- @output.puts lines.slice!(0...@page_at).join
587
- @output.puts
588
- ask("-- press enter/return to continue -- ")
589
- @output.puts
590
- end
591
- return lines.join
592
- end
593
-
594
- #
595
- # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
596
- # newlines will not be affected by this process, but additional newlines
597
- # may be added.
598
- #
599
- def wrap( lines )
600
- wrapped = [ ]
601
- lines.each do |line|
602
- while line =~ /([^\n]{#{@wrap_at + 1},})/
603
- search = $1.dup
604
- replace = $1.dup
605
- if index = replace.rindex(" ", @wrap_at)
606
- replace[index, 1] = "\n"
607
- replace.sub!(/\n[ \t]+/, "\n")
608
- line.sub!(search, replace)
609
- else
610
- line[@wrap_at, 0] = "\n"
611
- end
612
- end
613
- wrapped << line
614
- end
615
- return wrapped.join
616
- end
550
+ answer
551
+ else
552
+ @question.change_case(@question.remove_whitespace(@input.gets))
553
+ end
554
+ end
555
+
556
+ #
557
+ # Return a line or character of input, as requested for this question.
558
+ # Character input will be returned as a single character String,
559
+ # not an Integer.
560
+ #
561
+ # Raises EOFError if input is exhausted.
562
+ #
563
+ def get_response( )
564
+ raise EOFError, "The input stream is exhausted." if @input.eof?
565
+
566
+ if @question.character.nil?
567
+ if @question.echo == true and @question.limit.nil?
568
+ get_line
569
+ else
570
+ line = ""
571
+ while character = get_character
572
+ line << character.chr
573
+ # looking for carriage return (decimal 13) or
574
+ # newline (decimal 10) in raw input
575
+ break if character == 13 or character == 10 or
576
+ (@question.limit and line.size == @question.limit)
577
+ @output.print(@question.echo) if @question.echo != false
578
+ end
579
+ say("\n")
580
+ @question.change_case(@question.remove_whitespace(line))
581
+ end
582
+ elsif @question.character == :getc
583
+ @question.change_case(@input.getc.chr)
584
+ else
585
+ response = get_character.chr
586
+ echo = if @question.echo == true
587
+ response
588
+ elsif @question.echo != false
589
+ @question.echo
590
+ else
591
+ ""
592
+ end
593
+ say("#{echo}\n")
594
+ @question.change_case(response)
595
+ end
596
+ end
597
+
598
+ #
599
+ # Page print a series of at most _page_at_ lines for _output_. After each
600
+ # page is printed, HighLine will pause until the user presses enter/return
601
+ # then display the next page of data.
602
+ #
603
+ # Note that the final page of _output_ is *not* printed, but returned
604
+ # instead. This is to support any special handling for the final sequence.
605
+ #
606
+ def page_print( output )
607
+ lines = output.scan(/[^\n]*\n?/)
608
+ while lines.size > @page_at
609
+ @output.puts lines.slice!(0...@page_at).join
610
+ @output.puts
611
+ ask("-- press enter/return to continue -- ")
612
+ @output.puts
613
+ end
614
+ return lines.join
615
+ end
616
+
617
+ #
618
+ # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
619
+ # newlines will not be affected by this process, but additional newlines
620
+ # may be added.
621
+ #
622
+ def wrap( lines )
623
+ wrapped = [ ]
624
+ lines.each do |line|
625
+ while line =~ /([^\n]{#{@wrap_at + 1},})/
626
+ search = $1.dup
627
+ replace = $1.dup
628
+ if index = replace.rindex(" ", @wrap_at)
629
+ replace[index, 1] = "\n"
630
+ replace.sub!(/\n[ \t]+/, "\n")
631
+ line.sub!(search, replace)
632
+ else
633
+ line[@wrap_at, 0] = "\n"
634
+ end
635
+ end
636
+ wrapped << line
637
+ end
638
+ return wrapped.join
639
+ end
640
+
641
+ def actual_length( string_with_escapes )
642
+ string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length
643
+ end
617
644
  end