highline 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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